The /chat sidebar was two panels in a vertical 25/75 split — Live above, Sessions below — plus a directory
dropdown in the list header. Three problems that compounded: the Live panel was empty almost always, so a
quarter of the sidebar was permanently blank; the two panels had entirely disjoint indicators, so a
conversation with a turn running looked ordinary in the history; and the dropdown showed one directory at a
time, which turned "where was I working on that" into a search.
Now one panel, three tabs. Tree answers "what have I got, by project". Latest answers "what was I just
doing". Live answers "what is running". Same rows, same indicators, one place.
Tree is an accordion over /chat/pwds, each header carrying the full path from ~, the session count, and a
live count — plus New Chat here, since the tree only lists directories that already have history. A
COLLAPSED NODE ISSUES NO REQUEST: sessions are read by scanning transcripts off disk, and eager mounting
would be one scan per directory for rows nobody asked to see. `useClaudeSessions(cwd, {enabled})` is the
whole mechanism. The per-directory live count comes from the poll's own `cwd`, never from
`liveSessionTitle`, which reads a whole transcript and whose own comment warns against calling it from
anywhere hotter than a ten-second poll.
Latest needs a new server mode, `GET /chat/sessions?all=1` → `listAllClaudeSessions`. It loops directories
rather than scanning flat, because /clear chains are merged per group and a flat scan would let two
unrelated chains in different directories collapse into each other. Fetched only while the tab is showing.
Rows now carry the model, from the batched `getChatSessionModels` — one query per page, which is what that
query was added for.
THE MIGRATION IS THE DANGEROUS PART AND IT IS INVERTED. Every persisted layout still names `chat-live`, an
appType that no longer exists; `normalizeLayout` would collapse it onto the fallback and WRITE THAT BACK,
which is what made /offscale render three server pickers this morning (ed3806f9). The existing guard adopted
layouts LACKING the panel, so it had to flip to adopting layouts that HAVE it. defaultLayout.test.ts pins
both halves: the replacement must not contain the appType the guard tests for, or the effect fires on its
own output forever.
Deletes LiveSessions.tsx and PwdSelector.tsx — both replaced. Fixes the inherited bug in passing: the
"generating" Loader2 never had `animate-spin`, so the one indicator meaning "work is happening right now"
was a static icon.
View and expanded-node state go in localStorage, not the URL (a tab is a preference, and every session link
would carry it), not usePanelConfig (a NO-OP setter in a locked workspace, and /chat is locked), and not
useDashboardState (its PATCH only accepts layout-shaped objects). /chat/g/<path> still works and opens that
directory in the tree.
Untested against a running server: officer has not been restarted since these changes, and I am not
restarting it mid-conversation. `?all=1` and the model field are verified only by typecheck.
32 lines
1.4 KiB
TypeScript
32 lines
1.4 KiB
TypeScript
import type { LayoutNode } from 'officerdev';
|
|
|
|
/** Does this tree contain a panel running `appType`? */
|
|
export function hasAppType(node: LayoutNode, appType: string): boolean {
|
|
if (node.type === 'panel') return node.appType === appType;
|
|
return node.children.some((child) => hasAppType(child.node, appType));
|
|
}
|
|
|
|
/*
|
|
One sidebar panel, not two.
|
|
|
|
It was a vertical 25/75 split — `chat-live` above `chat-session-list` — on the reasoning that the two
|
|
answered different questions from different sources (the agent's in-memory map versus transcripts on
|
|
disk). True, and still true; what made it wrong was the arithmetic. Live sessions are rare, so the top
|
|
quarter of the sidebar was empty almost always, and the list that is used constantly got three quarters
|
|
of the space instead of all of it.
|
|
|
|
Both questions now live in one panel as tabs, which is the same information in the same place without
|
|
paying for the rarer one when it has nothing to say. `chat-live` no longer exists as a panel type — see
|
|
the migration guard in index.tsx, which exists because deleting a panel type from the code does not
|
|
delete it from anyone's saved layout.
|
|
*/
|
|
export const defaultLayout: LayoutNode = {
|
|
type: 'group',
|
|
id: 'chat-history-root',
|
|
direction: 'horizontal',
|
|
children: [
|
|
{ node: { type: 'panel', id: 'chat-list', appType: 'chat-session-list' }, size: 35 },
|
|
{ node: { type: 'panel', id: 'chat-detail', appType: 'chat-detail' }, size: 65 },
|
|
],
|
|
};
|