Dynamic workspace for periodic notes

Hi everyone,

I’m starting to explore the world of periodic notes, and I’d like to take my setup a step further.

I’m trying to build a workspace that automatically opens three notes side by side: the daily note, the monthly note, and the yearly note for the current date. This would help me stay on top of my monthly and annual goals as part of my daily workflow.

How can I set this up?
Is there a way to create a “dynamic” workspace that opens specific notes based on today’s date?

Thanks,
Gianluca

I do not use that plugin or Weekly/Monthly/Yearly notes. Is there no plugin to do this?

I guess certain mathematics are in order. I’d rely on AI to figure out how to make this work and then would employ javascript/typescript to provide the dynamic creation/refreshing of leaves.

I’d use a CodeScript Toolkit method: basically a non-plugin script that lives inside your Obsidian app. Whenever you’d open a daily note, your corresponding other W/M/Y notes with the existing filenames (or those to be created on the spot based on templates you have set up) would also be propagated in areas where you have set them in the .ts script.

So you would make one sample workspace that would suit your needs, maybe even show that workspace.json file content to AI to explain where you expect the other leaves to opened (I think leaves as well as sidebars have IDs in that json you could get AI to identify or you cannot).

This is the plugin to install:

Look up parts where they talk about in the thread how to load these scripts on startup on PC and mobile.
You need to set your folder to be used by the plugin and need a main.ts file.

So on PC, your main.ts would be:

import { invoke as periodicnotesOrganizer} from './Periodic-Notes-Organizer.ts';

export async function invoke(app: App): Promise<void> {
  await periodicnotesOrganizer(app);
}

You need to prompt AI to create this ‘Periodic-Notes-Organizer.ts’ for you.
Objective: you open a daily in a main tab group and other tab group items or sidebar elements will be refreshed with the corresponding other notes.

Show this sample script to AI so it knows the syntax of the .ts script that the plugin can interpret:

import { MarkdownView, Notice, App } from 'obsidian';

// ========================================================================
// CONFIGURATION
// ========================================================================
const PLAY_SOUND = true; // Set to false to disable audio alerts
const NOTICE_DURATION = 5000; // Duration in milliseconds
// ========================================================================

export async function invoke(app: App): Promise<void> {
    let lastEditorEl: HTMLElement | null = null;

    // Function to play system beep sound
    function playAlertSound() {
        if (!PLAY_SOUND) return;
        
        try {
            const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();
            
            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);
            
            oscillator.frequency.setValueAtTime(1000, audioContext.currentTime);
            oscillator.type = 'square';
            
            gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
            gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
            
            oscillator.start(audioContext.currentTime);
            oscillator.stop(audioContext.currentTime + 0.5);
            
        } catch (error) {
            console.log('[Tab Detector] Audio failed:', error);
        }
    }

    function attachToActiveEditor() {
        const activeView = app.workspace.getActiveViewOfType(MarkdownView);
        if (!activeView) return;
        const editor = activeView.editor;
        if (!editor) return;

        // Remove previous event listener if any
        if (lastEditorEl && (window as any)._tabDetectorHandler) {
            (window as any)._tabDetectorHandler();
            (window as any)._tabDetectorHandler = null;
        }

        let recentTabDetection = false;

        // Handler for keydown events - ONLY checks modifiers if Tab is pressed
        const handleKeyDown = (event: KeyboardEvent) => {
            // Early exit if not Tab key - no modifier checks needed!
            if (event.key !== 'Tab' && event.code !== 'Tab') return;
            
            // Only NOW do we check modifiers (Tab key was pressed)
            if (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) {
                // Tab with any modifier - ignore it
                return;
            }
            
            // Pure Tab key pressed with no modifiers
            if (!recentTabDetection) {
                new Notice('🚨 TAB KEY PRESSED! This will insert a tab character in your markdown!', NOTICE_DURATION);
                playAlertSound();
                
                recentTabDetection = true;
                setTimeout(() => {
                    recentTabDetection = false;
                }, 1000);
            }
        };

        const editorEl = activeView.containerEl.querySelector('.cm-editor');
        if (!editorEl) return;
        
        editorEl.addEventListener('keydown', handleKeyDown, true);
        
        const contentEl = editorEl.querySelector('.cm-content');
        if (contentEl) {
            contentEl.addEventListener('keydown', handleKeyDown, true);
        }
        
        // Store cleanup function
        (window as any)._tabDetectorHandler = () => {
            editorEl.removeEventListener('keydown', handleKeyDown, true);
            if (contentEl) {
                contentEl.removeEventListener('keydown', handleKeyDown, true);
            }
        };
        
        lastEditorEl = editorEl;
    }

    attachToActiveEditor();

    app.workspace.on('active-leaf-change', () => {
        setTimeout(() => attachToActiveEditor(), 100);
    });

    new Notice('🔍 Tab Character Detector is now ACTIVE', 3000);
}

And, as I said, after properly laying out your thoughts what you need, also show the workspace.json content (make sure you are in that workspace when you copy the contents of that file from .obsidian), cause AI can probably make use of it.

There may be easier ways to do this, but short of a dedicated plugin, this is what I’d do: a pseudo-plugin that listens for daily note openings in a particular workspace. You can start your prompt with this line as well.

I’d use ClaudeAI online rather than ChatGPT. In fact, I cannot recommend the latter.