diff --git a/apps/desktop/src/app/chat/index.tsx b/apps/desktop/src/app/chat/index.tsx
index 02f9186203..17d563cb37 100644
--- a/apps/desktop/src/app/chat/index.tsx
+++ b/apps/desktop/src/app/chat/index.tsx
@@ -697,9 +697,12 @@ const ChatViewContent = memo(function ChatViewContent({
// memo'd Thread/ThreadMessageList, and a fresh element per render would
// defeat their bail-out. Stats changes re-render inside SessionUsageFooter
// via its own useStore($sessions) subscription, not through these props.
+ // The footer needs a STORED id ($sessions rows key off it): activeSessionId
+ // is the runtime id, so fall back through the routed id for fresh resumes.
+ const footerSessionId = selectedSessionId || routedSessionId
const threadFooter = useMemo(
- () => (activeSessionId ? : undefined),
- [activeSessionId]
+ () => (footerSessionId ? : undefined),
+ [footerSessionId]
)
return (
diff --git a/apps/desktop/src/app/chat/session-usage-footer.test.tsx b/apps/desktop/src/app/chat/session-usage-footer.test.tsx
index 70f3ef0592..b95680d584 100644
--- a/apps/desktop/src/app/chat/session-usage-footer.test.tsx
+++ b/apps/desktop/src/app/chat/session-usage-footer.test.tsx
@@ -58,6 +58,16 @@ describe('SessionUsageFooter', () => {
expect(container.querySelector('[data-slot="session-usage-footer"]')).toBeNull()
})
+ it('matches a live lineage id to the stored row, like the header does', () => {
+ // Auto-compression rotates the session id: the view holds a chain tip while
+ // the list row still carries its own id plus the lineage ids.
+ setSessions([makeSessionInfo({ _lineage_ids: ['root', 'tip'], id: 'root', input_tokens: 2_000, output_tokens: 400 })])
+
+ const { container } = render()
+
+ expect(container.querySelector('[data-slot="session-usage-footer"]')?.textContent).toContain('2.4k tok')
+ })
+
it('re-renders when the sessions atom refreshes after a turn', () => {
setSessions([makeSessionInfo({ id: 's1', input_tokens: 1_000, output_tokens: 200 })])
diff --git a/apps/desktop/src/app/chat/session-usage-footer.tsx b/apps/desktop/src/app/chat/session-usage-footer.tsx
index 1543eb49bd..9fde23a6af 100644
--- a/apps/desktop/src/app/chat/session-usage-footer.tsx
+++ b/apps/desktop/src/app/chat/session-usage-footer.tsx
@@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react'
import { useI18n } from '@/i18n'
import { compactNumber } from '@/lib/format'
-import { $sessions } from '@/store/session'
+import { $sessions, sessionMatchesStoredId } from '@/store/session'
import { sessionCostUsd } from '@/store/sidebar-archive'
/** Cumulative token/cost totals for one session, rendered at the end of the
@@ -13,7 +13,9 @@ import { sessionCostUsd } from '@/store/sidebar-archive'
export function SessionUsageFooter({ sessionId }: { sessionId: string }) {
const { t } = useI18n()
const sessions = useStore($sessions)
- const session = sessions.find(candidate => candidate.id === sessionId)
+ // Match like ChatHeader does: a compression chain hands the live view a
+ // lineage id, not necessarily the row's own id.
+ const session = sessions.find(candidate => sessionMatchesStoredId(candidate, sessionId))
if (!session) {
return null