diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index f82a4899..a1a63811 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -3263,9 +3263,38 @@ function App() { return newSet }) - // Select the file to show it in the editor + // If tab already exists for this path (e.g. second call after transcription), + // force a content reload instead of creating a duplicate tab. + const existingTab = fileTabs.find(tab => tab.path === notePath) + if (existingTab) { + setActiveFileTabId(existingTab.id) + // Read fresh content from disk and update the editor + try { + const result = await window.ipc.invoke('workspace:readFile', { path: notePath, encoding: 'utf8' }) + const { raw: fm, body } = splitFrontmatter(result.data) + frontmatterByPathRef.current.set(notePath, fm) + setFileContent(body) + setEditorContent(body) + editorContentRef.current = body + editorPathRef.current = notePath + initialContentRef.current = body + initialContentByPathRef.current.set(notePath, body) + setEditorContentByPath(prev => ({ ...prev, [notePath]: body })) + editorContentByPathRef.current.set(notePath, body) + // Bump editor session to force TipTap to pick up the new content + setEditorSessionByTabId(prev => ({ + ...prev, + [existingTab.id]: (prev[existingTab.id] ?? 0) + 1, + })) + } catch { + // File read failed — ignore + } + return + } + + // First call — open the file in a tab navigateToFile(notePath) - }, [loadDirectory, navigateToFile]) + }, [loadDirectory, navigateToFile, fileTabs]) const ensureWikiFile = useCallback(async (wikiPath: string) => { const resolvedPath = toKnowledgePath(wikiPath) diff --git a/apps/x/apps/renderer/src/components/sidebar-content.tsx b/apps/x/apps/renderer/src/components/sidebar-content.tsx index 2ae699a9..40353183 100644 --- a/apps/x/apps/renderer/src/components/sidebar-content.tsx +++ b/apps/x/apps/renderer/src/components/sidebar-content.tsx @@ -608,6 +608,9 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => const notePathRef = React.useRef(null) const timestampRef = React.useRef(null) const relativePathRef = React.useRef(null) + // Keep a ref to always call the latest onNoteCreated (avoids stale closure in recorder.onstop) + const onNoteCreatedRef = React.useRef(onNoteCreated) + React.useEffect(() => { onNoteCreatedRef.current = onNoteCreated }, [onNoteCreated]) React.useEffect(() => { window.ipc.invoke('workspace:readFile', { @@ -642,11 +645,12 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => recursive: true, }) - const initialContent = `# Voice Memo - -**Type:** voice memo -**Recorded:** ${now.toLocaleString()} -**Path:** ${relativePath} + const initialContent = `--- +type: voice memo +recorded: "${now.toISOString()}" +path: ${relativePath} +--- +# Voice Memo ## Transcript @@ -659,7 +663,7 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => }) // Select the note so the user can see it - onNoteCreated?.(notePath) + onNoteCreatedRef.current?.(notePath) // Start actual recording const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) @@ -707,11 +711,12 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => const currentNotePath = notePathRef.current const currentRelativePath = relativePathRef.current if (currentNotePath && currentRelativePath) { - const transcribingContent = `# Voice Memo - -**Type:** voice memo -**Recorded:** ${new Date().toLocaleString()} -**Path:** ${currentRelativePath} + const transcribingContent = `--- +type: voice memo +recorded: "${new Date().toISOString()}" +path: ${currentRelativePath} +--- +# Voice Memo ## Transcript @@ -728,21 +733,23 @@ function VoiceNoteButton({ onNoteCreated }: { onNoteCreated?: (path: string) => const transcript = await transcribeWithDeepgram(blob) if (currentNotePath && currentRelativePath) { const finalContent = transcript - ? `# Voice Memo - -**Type:** voice memo -**Recorded:** ${new Date().toLocaleString()} -**Path:** ${currentRelativePath} + ? `--- +type: voice memo +recorded: "${new Date().toISOString()}" +path: ${currentRelativePath} +--- +# Voice Memo ## Transcript ${transcript} ` - : `# Voice Memo - -**Type:** voice memo -**Recorded:** ${new Date().toLocaleString()} -**Path:** ${currentRelativePath} + : `--- +type: voice memo +recorded: "${new Date().toISOString()}" +path: ${currentRelativePath} +--- +# Voice Memo ## Transcript @@ -755,7 +762,7 @@ ${transcript} }) // Re-select to trigger refresh - onNoteCreated?.(currentNotePath) + onNoteCreatedRef.current?.(currentNotePath) if (transcript) { toast('Voice note transcribed', 'success') diff --git a/apps/x/packages/core/src/knowledge/note_creation.ts b/apps/x/packages/core/src/knowledge/note_creation.ts index bec0dd21..adc1eafc 100644 --- a/apps/x/packages/core/src/knowledge/note_creation.ts +++ b/apps/x/packages/core/src/knowledge/note_creation.ts @@ -165,8 +165,8 @@ workspace-readFile({ path: "{source_file}" }) - Email signature **Voice memo indicators:** -- Has \`**Type:** voice memo\` field -- Has \`**Path:**\` field with path like \`Voice Memos/YYYY-MM-DD/...\` +- Has YAML frontmatter with \`type: voice memo\` +- Has frontmatter \`path:\` field like \`Voice Memos/YYYY-MM-DD/...\` - Has \`## Transcript\` section **Set processing mode:**