diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index d5311a5..6585edf 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -147,6 +147,17 @@ async function createWindow(): Promise { attachWindowState(win, WINDOW_STATE_FILE); if (!app.isPackaged) { + // Auto-open DevTools in dev — the menu bar is stripped (Discord-style) + // so F12 / Ctrl+Shift+I have no chord; opening detached gives a + // separate inspector window for easy debugging. + win.webContents.openDevTools({ mode: 'detach' }); + // Forward renderer console messages to the main-process stdout so + // errors during local dev are visible in the terminal too (helps when + // the inspector isn't focused). + win.webContents.on('console-message', (_event, level, message, line, sourceId) => { + const tag = level === 3 ? 'error' : level === 2 ? 'warn' : level === 1 ? 'log' : 'info'; + console.log('[renderer ' + tag + ']', message, '(' + sourceId + ':' + line + ')'); + }); await win.loadURL(DEV_URL); } else { await win.loadFile(resolveRendererIndex()); diff --git a/apps/desktop/src/pages/ConversationPage.tsx b/apps/desktop/src/pages/ConversationPage.tsx index 2026ccb..63b11b7 100644 --- a/apps/desktop/src/pages/ConversationPage.tsx +++ b/apps/desktop/src/pages/ConversationPage.tsx @@ -434,6 +434,22 @@ export function ConversationPage() { return out; }, [messages, pending, displayCount]); + // Snapshot of the saved position for this conversation, captured once on + // mount. Used to derive the `initialTopMostItemIndex` we hand to the + // Virtuoso instance below — Virtuoso applies that index synchronously + // before its first paint, so re-entering a chat shows the saved row in + // one frame rather than a "starts at top, jumps" flicker. + // + // Declared HERE (above `initialTopMostIndex`) rather than further down + // because the useMemo that consumes it would otherwise hit a TDZ on + // first render — `const` refs aren't hoisted. + const savedPositionRef = useRef<{ topmostIndex: number; stickToBottom: boolean } | null>( + null, + ); + if (savedPositionRef.current === null && id) { + savedPositionRef.current = scrollPositions.get(id) ?? null; + } + // Initial scroll position for the freshly-mounted Virtuoso instance. // Default = bottom (newest message). If we have a saved position from a // previous visit to this chat AND the user wasn't sticking to the @@ -648,17 +664,8 @@ export function ConversationPage() { // The old useLayoutEffect that wrote `scrollTop = scrollHeight` is no // longer needed: Virtuoso owns scroll positioning now. - // Snapshot of the saved position for this conversation, captured once on - // mount. Used to derive the `initialTopMostItemIndex` we hand to the - // Virtuoso instance below — Virtuoso applies that index synchronously - // before its first paint, so re-entering a chat shows the saved row in - // one frame rather than a "starts at top, jumps" flicker. - const savedPositionRef = useRef<{ topmostIndex: number; stickToBottom: boolean } | null>( - null, - ); - if (savedPositionRef.current === null && id) { - savedPositionRef.current = scrollPositions.get(id) ?? null; - } + // (savedPositionRef declared earlier — see TDZ note above the + // initialTopMostIndex useMemo.) // Track whether the user is currently scrolled to the bottom. Virtuoso // calls this whenever the bottom-state changes; we feed it into