From 2f6bbcd40029abe00cd440bc4f7a54b0ae7b7c73 Mon Sep 17 00:00:00 2001 From: lost <45652027+0x7551@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:51:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E4=BF=AE=E5=A4=8D=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E4=BC=9A=E8=AF=9D=E6=97=B6=E6=B6=88=E6=81=AF=E4=B9=B1?= =?UTF-8?q?=E5=BA=8F=E9=97=AE=E9=A2=98=20(#150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(web): add built-in Nerd Font for terminal icon support Add MesloLGLDZ Nerd Font as a built-in web font to ensure Powerlevel10k and other Nerd Font-dependent prompts render correctly in the web terminal, even on devices without local Nerd Fonts installed (e.g., mobile phones). Changes: - Add MesloLGLDZNerdFontMono-Regular.woff2 to web/public/fonts/ - Load font via FontFace API with BASE_URL support for subpath deployments - Merge font loading and terminal creation in same useEffect to ensure font is ready before terminal renders Fixes #121 Co-Authored-By: Claude Opus 4.5 * refactor: use CDN for Nerd Font instead of bundled file Load MesloLGLDZ Nerd Font from jsDelivr CDN instead of bundling the 1.2MB font file. This reduces the bundle size significantly while still ensuring Nerd Font icons display correctly on all devices. CDN URL: https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/MesloLGLDZNerdFontMono-Regular.woff2 Co-Authored-By: Claude Opus 4.5 * fix(web): prevent message ordering issues when switching sessions When switching between sessions or loading paginated messages, old tool blocks from agentState.requests were being mixed with current messages, causing display order issues. Root cause: reduceChatBlocks creates tool blocks for all permissions in agentState, including those older than the current message page. These old blocks (with earlier createdAt) would appear mixed with newer messages. Fix: 1. In reducer.ts: Skip creating permission-only tool blocks if their createdAt is older than the oldest message in the current view 2. In SessionChat.tsx: Clear caches synchronously in useMemo when session changes, since useEffect runs after render Closes #148 via [HAPI](https://hapi.run) Co-Authored-By: HAPI --------- Co-authored-by: tfq Co-authored-by: Claude Opus 4.5 Co-authored-by: HAPI --- web/src/chat/reducer.ts | 13 +++++++++++++ web/src/components/SessionChat.tsx | 10 ++++++++++ web/src/lib/messages.ts | 2 ++ 3 files changed, 25 insertions(+) diff --git a/web/src/chat/reducer.ts b/web/src/chat/reducer.ts index 79df2c79..798499c6 100644 --- a/web/src/chat/reducer.ts +++ b/web/src/chat/reducer.ts @@ -48,11 +48,24 @@ export function reduceChatBlocks( let hasReadyEvent = rootResult.hasReadyEvent // Only create permission-only tool cards when there is no tool call/result in the transcript. + // Also skip if the permission is older than the oldest message in the current view, + // to avoid mixing old tool cards with newer messages when paginating. + const oldestMessageTime = normalized.length > 0 + ? Math.min(...normalized.map(m => m.createdAt)) + : null + for (const [id, entry] of permissionsById) { if (toolIdsInMessages.has(id)) continue if (rootResult.toolBlocksById.has(id)) continue const createdAt = entry.permission.createdAt ?? Date.now() + + // Skip permissions that are older than the oldest message in the current view. + // These will be shown when the user loads older messages. + if (oldestMessageTime !== null && createdAt < oldestMessageTime) { + continue + } + const block = ensureToolBlock(rootResult.blocks, rootResult.toolBlocksById, id, { createdAt, localId: null, diff --git a/web/src/components/SessionChat.tsx b/web/src/components/SessionChat.tsx index c0bd9ac7..3990cc29 100644 --- a/web/src/components/SessionChat.tsx +++ b/web/src/components/SessionChat.tsx @@ -141,12 +141,22 @@ export function SessionChat(props: { voice.toggleMic() }, [voice]) + // Track session id to clear caches when it changes + const prevSessionIdRef = useRef(null) + useEffect(() => { normalizedCacheRef.current.clear() blocksByIdRef.current.clear() }, [props.session.id]) const normalizedMessages: NormalizedMessage[] = useMemo(() => { + // Clear caches immediately when session changes (before useEffect runs) + if (prevSessionIdRef.current !== null && prevSessionIdRef.current !== props.session.id) { + normalizedCacheRef.current.clear() + blocksByIdRef.current.clear() + } + prevSessionIdRef.current = props.session.id + const cache = normalizedCacheRef.current const normalized: NormalizedMessage[] = [] const seen = new Set() diff --git a/web/src/lib/messages.ts b/web/src/lib/messages.ts index d832624b..cd51888e 100644 --- a/web/src/lib/messages.ts +++ b/web/src/lib/messages.ts @@ -23,9 +23,11 @@ function isOptimisticMessage(msg: DecryptedMessage): boolean { function compareMessages(a: DecryptedMessage, b: DecryptedMessage): number { const aSeq = typeof a.seq === 'number' ? a.seq : null const bSeq = typeof b.seq === 'number' ? b.seq : null + if (aSeq !== null && bSeq !== null && aSeq !== bSeq) { return aSeq - bSeq } + if (a.createdAt !== b.createdAt) { return a.createdAt - b.createdAt }