fix: pass active note content directly to AI context builder

In Tauri mode, allContent is {} (empty) until a note is explicitly
saved. The previous mergeTabContent fix enriched allContent with tab
content, but the indirection was fragile. This fix passes the active
tab's content directly to buildContextSnapshot as activeNoteContent,
which takes priority over allContent[path]. This ensures the AI always
receives the note body regardless of allContent state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-08 13:42:09 +01:00
parent 9c7f05fb7d
commit d37c75296c
4 changed files with 41 additions and 6 deletions

View File

@@ -16,6 +16,8 @@ interface AiPanelProps {
onVaultChanged?: () => void
vaultPath: string
activeEntry?: VaultEntry | null
/** Direct content of the active note from the editor tab. */
activeNoteContent?: string | null
entries?: VaultEntry[]
allContent?: Record<string, string>
openTabs?: VaultEntry[]
@@ -110,7 +112,7 @@ function MessageHistory({ messages, isActive, onOpenNote, onNavigateWikilink, ha
)
}
export function AiPanel({ onClose, onOpenNote, onFileCreated, onFileModified, onVaultChanged, vaultPath, activeEntry, entries, allContent, openTabs, noteList, noteListFilter }: AiPanelProps) {
export function AiPanel({ onClose, onOpenNote, onFileCreated, onFileModified, onVaultChanged, vaultPath, activeEntry, activeNoteContent, entries, allContent, openTabs, noteList, noteListFilter }: AiPanelProps) {
const [input, setInput] = useState('')
const [pendingRefs, setPendingRefs] = useState<NoteReference[]>([])
const inputRef = useRef<HTMLInputElement>(null)
@@ -122,17 +124,18 @@ export function AiPanel({ onClose, onOpenNote, onFileCreated, onFileModified, on
}, [activeEntry, entries])
const contextPrompt = useMemo(() => {
if (!activeEntry || !allContent || !entries) return undefined
if (!activeEntry || !entries) return undefined
return buildContextSnapshot({
activeEntry,
allContent,
allContent: allContent ?? {},
activeNoteContent: activeNoteContent ?? undefined,
openTabs,
noteList,
noteListFilter,
entries,
references: pendingRefs.length > 0 ? pendingRefs : undefined,
})
}, [activeEntry, allContent, openTabs, noteList, noteListFilter, entries, pendingRefs])
}, [activeEntry, activeNoteContent, allContent, openTabs, noteList, noteListFilter, entries, pendingRefs])
const fileCallbacks = useMemo<AgentFileCallbacks>(() => ({
onFileCreated,

View File

@@ -51,6 +51,7 @@ export function EditorRightPanel({
onVaultChanged={onVaultChanged}
vaultPath={vaultPath}
activeEntry={inspectorEntry}
activeNoteContent={inspectorContent}
entries={entries}
allContent={allContent}
openTabs={openTabs}

View File

@@ -331,6 +331,35 @@ describe('buildContextSnapshot', () => {
expect(json.noteList).toBeUndefined()
})
it('uses activeNoteContent for body when allContent is empty (Tauri mode)', () => {
const emptyAllContent: Record<string, string> = {}
const result = buildContextSnapshot({
activeEntry: active,
allContent: emptyAllContent,
entries,
activeNoteContent: '---\ntitle: Alpha\n---\n\n# Alpha\nProject content from tab.',
})
const json = JSON.parse(result.split('```json\n')[1].split('\n```')[0])
expect(json.activeNote.body).toContain('Project content from tab.')
})
it('prefers activeNoteContent over allContent when both present', () => {
const result = buildContextSnapshot({
activeEntry: active,
allContent,
entries,
activeNoteContent: 'Fresh editor content',
})
const json = JSON.parse(result.split('```json\n')[1].split('\n```')[0])
expect(json.activeNote.body).toBe('Fresh editor content')
})
it('falls back to allContent when activeNoteContent is undefined', () => {
const result = buildContextSnapshot({ activeEntry: active, allContent, entries })
const json = JSON.parse(result.split('```json\n')[1].split('\n```')[0])
expect(json.activeNote.body).toBe('# Alpha\nProject content.')
})
it('includes wikilink instruction in preamble', () => {
const result = buildContextSnapshot({ activeEntry: active, allContent, entries })
expect(result).toContain('[[Note Title]]')

View File

@@ -72,6 +72,8 @@ export interface NoteListItem {
export interface ContextSnapshotParams {
activeEntry: VaultEntry
allContent: Record<string, string>
/** Direct content of the active note from the editor tab (most reliable source). */
activeNoteContent?: string
openTabs?: VaultEntry[]
noteList?: NoteListItem[]
noteListFilter?: { type: string | null; query: string }
@@ -94,7 +96,7 @@ const MAX_NOTE_LIST_ITEMS = 100
/** Build a structured context snapshot as a system prompt for Claude. */
export function buildContextSnapshot(params: ContextSnapshotParams): string {
const { activeEntry, allContent, openTabs, noteList, noteListFilter, entries, references } = params
const { activeEntry, allContent, activeNoteContent, openTabs, noteList, noteListFilter, entries, references } = params
const snapshot: Record<string, unknown> = {
activeNote: {
@@ -102,7 +104,7 @@ export function buildContextSnapshot(params: ContextSnapshotParams): string {
title: activeEntry.title,
type: activeEntry.isA ?? 'Note',
frontmatter: entryFrontmatter(activeEntry),
body: allContent[activeEntry.path] ?? '',
body: activeNoteContent ?? allContent[activeEntry.path] ?? '',
},
}