import { app, BrowserWindow } from 'electron' import assert from 'node:assert/strict' import { randomUUID } from 'node:crypto' import { writeFileSync } from 'node:fs' import { join } from 'node:path' import { DatabaseSync } from 'node:sqlite' import { registerIpcHandlers } from '../../src/main/ipc' import { AssistantDatabase } from '../../src/main/assistant/assistant-database' import { ApplicationSettingsStore } from '../../src/main/application-settings-store' import { defaultRuntimeSettings } from '../../src/shared/contracts' const directory = process.env.GB_CAPTURE_DIRECTORY! app.setPath('userData', join(directory, 'profile')) app.whenReady().then(async () => { const win = new BrowserWindow({ show: false, width: 1280, height: 900, webPreferences: { sandbox: true, contextIsolation: true, nodeIntegration: false, backgroundThrottling: false, preload: join(directory, 'preload.cjs') } }) const path = join(directory, 'assistant.sqlite') const database = new AssistantDatabase(path) database.initialize(directory) const project = database.listProjects()[0]! const header = { id: randomUUID(), title: 'Capture discussion', projectId: project.id, updatedAt: Date.now() } const messages = Array.from({ length: 502 }, (_, index) => ({ id: randomUUID(), role: index % 2 ? 'assistant' as const : 'user' as const, content: `CAPTURE_HISTORY_${index}_END`, state: 'complete' as const, createdAt: 1775000000000 + index })) database.saveLocalConversations([{ header, messages }]) const existing = database.createMagicNote({ title: 'Collected answers' }) database.createMagicNoteEntry({ noteId: existing.id, content: { version: 1, ops: [{ insert: 'Keep existing entry\n' }] }, plainText: 'Keep existing entry' }) const applicationSettings = new ApplicationSettingsStore(join(directory, 'application.json')) await applicationSettings.update({ magicNotesEnabled: true, magicNoteCommentMode: 'after-save-manual', checkUpdatesOnStartup: false }) const settings = { ...defaultRuntimeSettings, workspacePath: directory, modelProfiles: [], embeddingConnections: [] } // Unrelated App services are test environment dependencies. All capture-related handlers are production. const dispose = registerIpcHandlers(win, { capability: 'text', getStatus: async () => ({ available: true, name: 'Capture test', capability: 'text' }) } as never, 'CommandOrControl+Shift+Space', { getResolvedSettings: async () => settings, getPublicSettings: async () => settings } as never, {} as never, { clear() {}, cancelImport() {}, getDraft: () => [] } as never, { snapshot: () => ({ libraries: [], sources: [], documents: [], entities: [], relations: [], evidence: [], tasks: [] }), database: { externalStore: { listBindings: () => [] } }, external: { listInstances: () => [] } } as never, database, { clear() {} } as never, {} as never, async () => {}, undefined, undefined, undefined, undefined, applicationSettings, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, { getPending: async () => undefined } as never) win.webContents.on('console-message', event => { if (event.level === 'error') console.error(event.message) }) const run = (script: string): Promise => win.webContents.executeJavaScript(script, true) async function wait(script: string, label: string): Promise { for (let i = 0; i < 400; i++) { if (await run(script)) return await new Promise(resolve => setTimeout(resolve, 50)) } throw new Error(`Timed out: ${label}`) } function button(name: string, root = 'document'): string { return `[...${root}.querySelectorAll('button,[role="menuitem"]')].find(e => (e.getAttribute('aria-label') || e.textContent.trim()) === ${JSON.stringify(name)} && e.getClientRects().length)` } async function click(name: string, root?: string): Promise { const expression = button(name, root) await wait(`Boolean(${expression})`, name) await run(`${expression}.click()`) } async function key(keyCode: string): Promise { win.webContents.sendInputEvent({ type: 'keyDown', keyCode }) if (keyCode === 'Enter') win.webContents.sendInputEvent({ type: 'char', keyCode: '\r' }) win.webContents.sendInputEvent({ type: 'keyUp', keyCode }) await new Promise(resolve => setTimeout(resolve, 80)) } const panel = 'document.querySelector(".magic-note-panel")' const evidence: Record = {} let closed = false try { await win.loadURL(process.env.GB_CAPTURE_URL!) await wait('document.body.innerText.includes("CAPTURE_HISTORY_501_END")', 'App persisted history') assert.equal(await run('document.body.innerText.includes("CAPTURE_HISTORY_0_END")'), false, 'old history must initially be folded') // The last assistant reply is rendered by the real ChatTimeline. await run(`[...document.querySelectorAll('button')].filter(e => e.getAttribute('aria-label') === 'Add to note' || e.title === 'Add to note').at(-1).click()`) await wait('Boolean(document.querySelector("#compact-note-text"))', 'capture preview') assert.equal(await run('(document.querySelector("#compact-note-text")).value'), messages[501]!.content) assert.equal(await run(`${button('Add to note', panel)}.disabled`), true) await wait(`${panel}.innerText.includes('Collected answers')`, 'existing target') await run(`${panel}.querySelector('.magic-note-panel__note').click()`) await click('Add to note', panel) await wait(`${panel}.innerText.includes('Keep existing entry') && !document.querySelector('#compact-note-text')`, 'saved existing entry') const saved = database.getMagicNote(existing.id) assert.equal(saved.entries.length, 2) const captured = saved.entries.find(entry => entry.source)! assert.equal(captured.plainText, messages[501]!.content) assert.deepEqual(captured.content, { version: 1, ops: [{ insert: `${messages[501]!.content}\n` }] }) assert.equal(captured.source?.kind, 'message') assert.equal(captured.source?.conversationId, header.id) assert.equal(captured.source?.projectId, project.id) assert.deepEqual(captured.source?.messageIds, [messages[501]!.id]) assert.equal(captured.source?.conversationTitle, header.title) assert.equal(captured.source?.projectName, project.name) evidence.existingSaved = true await click('Open in full workspace', `document.getElementById('compact-note-entry-${captured.id}')`) await wait(`Boolean(document.getElementById('magic-note-entry-${captured.id}'))`, 'full workspace exact entry') evidence.workspaceOpened = true await click('View source message', `document.getElementById('magic-note-entry-${captured.id}')`) await wait('document.activeElement?.textContent?.includes("CAPTURE_HISTORY_501_END")', 'source message focus') evidence.sourceOpened = true await click('Conversation actions') await click('Save conversation to note') await wait('Boolean(document.querySelector("#compact-note-text"))', 'full history preview') const expected = messages.map(message => `## ${message.role === 'user' ? 'User' : 'Assistant'}\n\n${message.content}`).join('\n\n') assert.equal(await run('document.querySelector("#compact-note-text").value'), expected) evidence.fullHistoryCount = messages.length await click('New note', panel) await wait('document.activeElement?.id === "compact-note-title"', 'new title focus') assert.equal(await run('document.querySelector("#compact-note-title").value'), header.title) await click('Add to note', panel) await wait(`!document.querySelector('#compact-note-text') && ${panel}.innerText.includes('CAPTURE_HISTORY_0_END')`, 'saved full history') const newNote = database.listMagicNotes().find(note => note.id !== existing.id)! let detail = database.getMagicNote(newNote.id) assert.equal(detail.entries.length, 1) assert.equal(detail.title, header.title) assert.equal(detail.entries[0]!.plainText, expected) assert.deepEqual(detail.entries[0]!.content, { version: 1, ops: [{ insert: `${expected}\n` }] }) assert.equal(detail.entries[0]!.source?.kind, 'conversation') assert.equal(detail.entries[0]!.source?.conversationId, header.id) assert.equal(detail.entries[0]!.source?.projectName, project.name) assert.deepEqual(detail.entries[0]!.source?.messageIds, messages.map(message => message.id)) evidence.newSaved = true // Electron resize and the production sidebar transition settle asynchronously. win.setSize(760, 800) await wait(`${panel}.getBoundingClientRect().width > 100`, 'narrow panel layout after resize') await run(`document.querySelector('#compact-note-append').focus()`) await key('Escape') await wait('document.activeElement?.id === "compact-note-search"', 'Escape returns to list and search') await key('Tab') assert.equal(await run('document.activeElement?.classList.contains("magic-note-panel__note")'), true) await key('Enter') await wait('Boolean(document.querySelector("#compact-note-append"))', 'keyboard opens note') const bounds = await run<{ width: number; overflow: number }>(`(() => { const p=${panel};return {width:p.getBoundingClientRect().width,overflow:p.scrollWidth-p.clientWidth} })()`) assert.ok(bounds.width > 0 && bounds.width <= 480, JSON.stringify(bounds)) assert.ok(bounds.overflow <= 1, JSON.stringify(bounds)) await run(`document.querySelector('#compact-note-append').focus()`) await win.webContents.insertText('Keyboard append') await wait(`${button('Add to note', panel)}.disabled === false`, 'keyboard draft ready') await key('Tab') await key('Tab') assert.equal(await run('document.activeElement?.textContent.trim()'), 'Add to note') await key('Enter') await wait(`${panel}.innerText.includes('Keyboard append') && document.querySelector('#compact-note-append').value === ''`, 'keyboard save') detail = database.getMagicNote(newNote.id) assert.equal(detail.entries.length, 2) assert.ok(detail.entries.some(entry => entry.plainText === 'Keyboard append' && !entry.source)) evidence.keyboard = true evidence.narrowPanel = true evidence.panelWidth = bounds.width await run('window.goodbuddy.magicNotes.get(' + JSON.stringify(newNote.id) + ')') await dispose() database.close() closed = true const reopened = new AssistantDatabase(path) reopened.initialize(directory) try { assert.deepEqual(reopened.getMagicNote(newNote.id), detail) assert.deepEqual(reopened.getMagicNote(existing.id), saved) reopened.deleteLocalConversation(header.id) assert.deepEqual(reopened.getMagicNote(newNote.id), detail) evidence.sqliteReopened = true evidence.sourceSurvivesDeletion = true } finally { reopened.close() } const sql = new DatabaseSync(path) try { const rows = sql.prepare('SELECT source_json FROM magic_note_entries WHERE source_json IS NOT NULL').all() assert.equal(rows.length, 2) assert.ok(rows.some(row => JSON.parse(String(row.source_json)).messageIds.length === 502)) evidence.sqliteSourceRows = rows.length } finally { sql.close() } evidence.modelAttempts = (globalThis as typeof globalThis & { captureModelAttempts?: number }).captureModelAttempts ?? 0 } catch (error) { evidence.error = error instanceof Error ? error.stack : String(error) evidence.layout = await run(`(() => { let e=${panel}; const rows=[]; while(e) {rows.push({tag:e.tagName,classes:e.className,width:e.getBoundingClientRect().width,style:e.getAttribute('style')});e=e.parentElement} return rows })()`) evidence.body = await run('document.body.innerText') if (!closed) { await dispose() database.close() } } finally { writeFileSync(join(directory, 'result.json'), JSON.stringify(evidence)) win.destroy() } app.exit(0) }).catch(error => { console.error(error); app.exit(1) })