agentgui 1.0.958 → 1.0.960

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ export const meta = {
2
+ name: 'gui-completion',
3
+ description: 'Drive a maximum-effort COMPLETION sweep of the agentgui GUI: per-lens agents hunt every aspect the seven prior runs never covered (file management CRUD + upload, deep-link routing, mid-thread conversation control, history/settings surface depth, onboarding + error recovery, dynamic followups), an adversarial verifier confirms each gap against the live source (kept-typography guard), a synthesizer orders them into a kit-vs-app-vs-server build plan',
4
+ whenToUse: 'Run after the cohesion sweep (7th run) has landed. Distinct from all four prior workflows: gui-audit hunts JANK, gui-overhaul DESIGNS surfaces, gui-predictability-polish polishes primitives, gui-cohesion hunts the one-product feel. This one hunts COVERAGE - the deliberately scoped-out and never-examined aspects: the read-only Files cut (rename/delete/mkdir/upload), missing dir deep-links, last-turn-only retry, static-only followups, history/settings depth, first-run onboarding, and recovery flows. Reference bars: fsbrowse (full file manager), claude.ai/code, cowork, Claude Desktop. All visual/component work lands in c:\\dev\\anentrypoint-design; the app keeps wiring; new server endpoints must be confineToRoots-confined.',
5
+ phases: [
6
+ { title: 'Hunt', detail: 'one agent per coverage lens hunts the not-yet-covered aspects against the reference bar' },
7
+ { title: 'Verify', detail: 'adversarially confirm each gap is real, uncovered, and soundly fixable (kept-typography guard)' },
8
+ { title: 'Plan', detail: 'synthesize confirmed gaps into a dependency-ordered kit-vs-app-vs-server build plan' },
9
+ ],
10
+ };
11
+
12
+ // Shared grounding. Seven sweeps have shipped; this one hunts what they SKIPPED.
13
+ const GROUND =
14
+ ' GROUND IN REAL SOURCE (read, do not guess - seven polish/cohesion sweeps HAVE shipped; hunt what they SKIPPED): agentgui front-end c:\\dev\\agentgui\\site\\app\\js\\app.js + backend.js + index.html; kit c:\\dev\\anentrypoint-design\\src\\components\\{shell.js,sessions.js,agent-chat.js,chat.js,files.js,files-modals.js,context-pane.js,content.js} + {app-shell.css,chat.css}; server c:\\dev\\agentgui\\lib\\http-handler.js (confineToRoots helper) + lib\\routes-upload.js (legacy, conversationId-keyed) + server.js. The fsbrowse full file manager at c:\\dev\\fsbrowse (index.js checkPermissions/rename/delete/mkdir/upload + public/app.js hash routing/stepViewer) is the file-management bar. PUNCHLIST-COHESION.md + AGENTS.md record the documented scope cuts: file rename/delete (read-only), upload (legacy endpoint not reusable), retry last-turn-only, dir deep-links never landed, followups static-only.' +
15
+ ' POLICY: this is a fully-destructive run - scope cuts may be REVERSED, but every new mutation endpoint MUST be confineToRoots realpath-confined (403 out-of-roots + traversal + symlink-escape) and witnessed. ALL visual/component work lands in the KIT as defaults/props; app keeps state+callback wiring; NEVER new index.html !important overrides; kit tokens not hex; NO decorative glyphs (ASCII words + CSS .status-dot-disc) but KEEP middot/ellipsis/em-en-dash; every new surface needs a11y (roles/aria/focus/44x44/keyboard), responsive (420px), explicit empty/loading/error states, and plain-language error copy.';
16
+
17
+ const LENSES = [
18
+ { key: 'file-management-crud', prompt: 'COVERAGE LENS - FILE MANAGEMENT. The Files view is deliberately read-only (download only); fsbrowse is the bar: rename, delete (file + recursive dir with confirm), new-folder, move, upload (DropZone + progress), conflict handling (EEXIST), permission-aware disabling (read-only/no-access entries), optimistic refresh, undo-or-confirm semantics. Design the COMPLETE confined mutation surface: server endpoints (POST /api/rename, /api/delete, /api/mkdir, /api/upload multipart - all confineToRoots realpath-confined, filename sanitization, size caps, refuse root deletion + cross-root rename), backend.js client calls, kit dialog/affordance reuse (PromptDialog/ConfirmDialog/DropZone/UploadProgress already exist), app wiring in filesMain onAction. Name every gap + concrete fix with the kit-vs-app-vs-server split.' + GROUND },
19
+ { key: 'routing-deeplinks', prompt: 'COVERAGE LENS - ROUTING + DEEP LINKS + RELOAD STATEFULNESS. The hash scheme carries tab= and sid= only. Gaps to hunt: Files directory not in the hash (reload resets to root, Back never walks the tree - fsbrowse public/app.js pushes #/path on every cd); preview-open file not restorable; live-tab selected/filter state lost on reload; settings scroll/section anchors; whether EVERY surface restores meaningfully from a pasted URL. Design the extension of the EXISTING readHash/buildHash/writeHash + popstate scheme (dir=, file= params) without colliding with tab=/sid=. Name every reload/Back/deep-link behavior that diverges from a real desktop-class app.' + GROUND },
20
+ { key: 'conversation-control', prompt: 'COVERAGE LENS - CONVERSATION CONTROL DEPTH. Retry exists only on the LAST assistant turn and edit only on user turns; followup chips are static seeds. Hunt: mid-thread retry (fork from any assistant turn - slice + resend, with a confirm when later turns would drop), edit-and-resend any user turn, contextual followups derived from the last assistant content (code -> explain/run; error tool -> fix), copy-whole-conversation / export transcript (md or json), clear-conversation with confirm, a visible turn count / context-size affordance, and draft preservation across reload. Benchmark claude.ai/code + Claude Desktop conversation affordances. Name every gap with concrete kit-vs-app fixes that do NOT destroy persisted history silently.' + GROUND },
21
+ { key: 'history-settings-depth', prompt: 'COVERAGE LENS - HISTORY + SETTINGS SURFACE DEPTH. These two surfaces got the least attention across seven runs. History: event-type filter (tool/error/text), per-session export, copy event payload, jump-to-first-error, session metadata header (agent/model/cwd/duration/event count), search highlighting inside event bodies, relative+absolute timestamps. Settings: the agents panel shows health but offers no action (restart/reauth hint), no server info (version/uptime/roots/projects dir), no data management beyond clear-local (export chat, cache size), no keyboard-shortcut reference surface (shortcuts exist: g+c/h/s, n, /, ? - is ? wired to a help overlay? is it discoverable?). Benchmark Claude Desktop settings + claude.ai/code history. Name every gap.' + GROUND },
22
+ { key: 'onboarding-recovery', prompt: 'COVERAGE LENS - FIRST-RUN, ONBOARDING, ERROR RECOVERY. Never examined: what a brand-new user sees (no sessions, no agent picked, maybe no agents installed - is there a guided path from zero to first chat? does the empty state explain installing an agent via npx?), what happens when the backend dies mid-chat (is there a reconnect affordance + resume path, or a dead spinner?), WS reconnect UX (silent vs announced), SSE stream loss on history/live, partial-failure copy (agent spawn fails, model rejected, cwd missing - are errors actionable plain language with a retry?), and whether destructive actions (clear local data, stop-all) confirm. Benchmark Claude Desktop first-run + recovery. Name every gap with concrete fixes.' + GROUND },
23
+ { key: 'crosscut-new-surface', prompt: 'CROSS-CUTTING for the NEW surfaces this run adds (file mutations, upload, deep links, mid-thread fork). Security: every proposed mutation endpoint must be confineToRoots realpath-confined - enumerate the attack cases (.. traversal, symlink-escape, rename target outside roots, delete root itself, upload filename with path separators/reserved Windows names/ADS colon, oversize upload, MIME confusion) and the exact server checks; confirm the existing /api/list,file,image,download confinement pattern to copy. A11y: dialogs need focus-trap + Esc + aria; upload progress needs aria-live; drag-drop needs a keyboard path. Perf: upload progress should not re-render the whole app per chunk; dir refresh after mutation should be one fetch. Failure modes: EACCES/EEXIST/ENOENT per mutation -> plain copy. Name every requirement as a gap row.' + GROUND },
24
+ ];
25
+
26
+ const GAP_SCHEMA = {
27
+ type: 'object',
28
+ required: ['lens', 'gaps'],
29
+ properties: {
30
+ lens: { type: 'string' },
31
+ gaps: {
32
+ type: 'array',
33
+ items: {
34
+ type: 'object',
35
+ required: ['title', 'where', 'fixLocation', 'fix', 'severity'],
36
+ properties: {
37
+ title: { type: 'string', description: 'the uncovered aspect / gap vs the reference bar' },
38
+ where: { type: 'string', description: 'file:line or component the gap lives in (or "absent" for missing endpoints)' },
39
+ fixLocation: { type: 'string', enum: ['kit', 'app', 'server', 'kit+app', 'app+server', 'kit+app+server'], description: 'where the fix lands' },
40
+ fix: { type: 'string', description: 'the concrete change: endpoint/component/prop/css/wiring' },
41
+ benchmark: { type: 'string', description: 'which reference product (fsbrowse, claude.ai/code, cowork, Claude Desktop) sets the bar' },
42
+ severity: { type: 'string', enum: ['high', 'medium', 'low'] },
43
+ keptTypography: { type: 'boolean', description: 'true if this touches the middot/ellipsis/dash set - which must NOT be flagged as a glyph violation' },
44
+ },
45
+ },
46
+ },
47
+ },
48
+ };
49
+
50
+ const VERDICT_SCHEMA = {
51
+ type: 'object',
52
+ required: ['title', 'real', 'reason'],
53
+ properties: {
54
+ title: { type: 'string' },
55
+ real: { type: 'boolean', description: 'true if the gap is real, genuinely uncovered, and the fix is sound against the live source' },
56
+ reason: { type: 'string', description: 'why - cite the source you read' },
57
+ },
58
+ };
59
+
60
+ phase('Hunt');
61
+ const verified = await pipeline(
62
+ LENSES,
63
+ (l) => agent(l.prompt, { label: 'hunt:' + l.key, phase: 'Hunt', schema: GAP_SCHEMA }),
64
+ (report, l) => parallel((report?.gaps || []).map((g) => () =>
65
+ agent(
66
+ 'Adversarially verify this GUI coverage gap against the REAL source (read the cited files - do not trust the claim). '
67
+ + 'KEPT-TYPOGRAPHY GUARD: middot, ellipsis, em/en-dash are intentional product typography - flagging one of those as a decorative glyph is NOT a real gap (real=false). '
68
+ + 'A gap is real only if it genuinely exists in (or is genuinely absent from) the shipped source AND was not already fixed by a prior sweep AND the proposed fix is sound, confinement-safe for any server mutation, and lands in the right place (kit vs app vs server). '
69
+ + 'Reject vague gaps with no concrete source-grounded fix. '
70
+ + 'Gap: ' + JSON.stringify(g),
71
+ { label: 'verify:' + (g.title || '').slice(0, 32), phase: 'Verify', schema: VERDICT_SCHEMA }
72
+ ).then((v) => ({ ...g, lens: report.lens, verdict: v }))
73
+ ))
74
+ );
75
+
76
+ const confirmed = verified.flat().filter(Boolean).filter((g) => g.verdict && g.verdict.real);
77
+ log('confirmed ' + confirmed.length + ' real coverage gaps across ' + LENSES.length + ' lenses');
78
+
79
+ phase('Plan');
80
+ const plan = await agent(
81
+ 'Synthesize these adversarially-confirmed GUI coverage gaps into a dependency-ordered build plan for agentgui + the anentrypoint-design kit + the server. '
82
+ + 'Order: server endpoints first (they unblock client wiring), then kit components (each edited ONCE with all changes batched - FileGrid/FileRow/files-modals dialogs/AgentChat/ChatMessage/sessions), then app wiring, then the single build->vendor->witness step, with the security-validation checklist (traversal/symlink/oversize/reserved-name cases) attached to each mutation endpoint. '
83
+ + 'Call out which gaps share a component (batch), which are independent (parallelizable), which conflict, and the highest-leverage few. '
84
+ + 'Confirmed gaps: ' + JSON.stringify(confirmed),
85
+ { label: 'synthesize-plan', phase: 'Plan' }
86
+ );
87
+
88
+ return { confirmed, plan, lensCount: LENSES.length, confirmedCount: confirmed.length };
@@ -0,0 +1,88 @@
1
+ export const meta = {
2
+ name: 'gui-logic-predictability',
3
+ description: 'Drive a maximum-effort LOGIC + PREDICTABILITY sweep of the agentgui GUI: per-lens agents audit whether every element is presented and operates in the most logical, predictable way (interaction state machines, cross-surface consistency, information architecture, realtime truth, input ergonomics, scale robustness), an adversarial verifier confirms each finding against the live source (kept-typography guard), a synthesizer orders them into a kit-vs-app-vs-server build plan',
4
+ whenToUse: 'Run after the completion sweep (8th run) has landed. Distinct from all five prior workflows: gui-audit hunts JANK, gui-overhaul DESIGNS surfaces, gui-predictability-polish polishes primitives, gui-cohesion hunts one-product feel, gui-completion hunts scoped-out coverage. This one audits OPERATING LOGIC - whether each control behaves the way a user would predict (full lifecycle states, race/double-fire safety), whether the same fact reads the same on every surface, whether things live where a user would look, whether realtime counters agree across tabs, whether input ergonomics match a desktop-class chat agent, and whether the UI survives scale (huge transcripts, 1000-entry dirs, dozens of live sessions). Reference bars: claude.ai/code, cowork, Claude Desktop, fsbrowse. All visual/component work lands in c:\\dev\\anentrypoint-design; the app keeps wiring; server changes must be confineToRoots-confined.',
5
+ phases: [
6
+ { title: 'Hunt', detail: 'one agent per logic/predictability lens audits the shipped source against the reference bar' },
7
+ { title: 'Verify', detail: 'adversarially confirm each finding is real and soundly fixable (kept-typography guard)' },
8
+ { title: 'Plan', detail: 'synthesize confirmed findings into a dependency-ordered kit-vs-app-vs-server build plan' },
9
+ ],
10
+ };
11
+
12
+ // Shared grounding. Eight sweeps have shipped; this one audits whether what shipped OPERATES logically and predictably.
13
+ const GROUND =
14
+ ' GROUND IN REAL SOURCE (read, do not guess - eight sweeps HAVE shipped: jank audit, redesign, polish, cohesion, completion; the Files manager has full CRUD, hash routing carries tab/sid/dir/file/q/project/section, conversation has edit/retry/export/followups, resilience has ws-grace/health-reprobe/install-hints): agentgui front-end c:\\dev\\agentgui\\site\\app\\js\\app.js + backend.js + index.html; kit c:\\dev\\anentrypoint-design\\src\\components\\{shell.js,sessions.js,agent-chat.js,chat.js,files.js,files-modals.js,context-pane.js,content.js,forms.js} + {app-shell.css,chat.css}; server c:\\dev\\agentgui\\lib\\http-handler.js + lib\\ws-handlers-util.js + server.js. PUNCHLIST-COMPLETION.md + PUNCHLIST-COHESION.md + AGENTS.md record what already landed - do NOT re-report it; hunt what still operates illogically or unpredictably.' +
15
+ ' POLICY: fully-destructive run - reshape anything; ALL visual/component work lands in the KIT as defaults/props; app keeps state+callback wiring; NEVER new index.html !important overrides; kit tokens not hex; NO decorative glyphs (ASCII words + CSS .status-dot-disc) but KEEP middot/ellipsis/em-en-dash; any server mutation stays confineToRoots realpath-confined; every change needs a11y (roles/aria/focus/44x44/keyboard), responsive (420px), explicit empty/loading/error states, plain-language copy.';
16
+
17
+ const LENSES = [
18
+ { key: 'interaction-lifecycle', prompt: 'LOGIC LENS - INTERACTION STATE MACHINES. For EVERY actionable control in the GUI (send, stop, retry, edit-resend, followup chips, file rename/delete/mkdir/upload, download, stop-all/stop-selected, clear-data, export, copy buttons, theme toggle, drawer toggles, load-older), audit its FULL lifecycle: idle -> hover/focus -> pressed -> in-flight (busy/disabled? spinner? label change?) -> success feedback -> error recovery -> re-enabled. Hunt: double-click/double-submit races (can send fire twice? can delete fire while a delete is in flight? does retry during streaming corrupt state?), controls that stay enabled during an operation they invalidate, missing in-flight indication, success with no feedback, operations with no way to cancel, confirm steps that auto-reset at the wrong time, focus lost after an action completes (where does focus land after a dialog closes, a row deletes, a message sends?). Name every control whose lifecycle is incomplete or racy, with the concrete kit-vs-app fix.' + GROUND },
19
+ { key: 'cross-surface-consistency', prompt: 'PREDICTABILITY LENS - CROSS-SURFACE CONSISTENCY. The same fact must read the same everywhere. Audit: session identity (does the same session show the same title/label in chat rail, history rail, live dashboard, running panel?), timestamps (relative vs absolute, same format across chat/history/live/files?), cost/token figures (composer context line vs ContextPane vs history - same units, same rounding?), status vocabulary (connected/connecting/offline vs running/idle/stale vs healthy - one vocabulary per concept?), agent naming (id vs display name), cwd/project rendering (full path vs slug - consistent?), selection semantics (does clicking a session row do the same thing on chat tab vs history tab - and is that predictable?), icon/action placement (is copy always in the same corner? is destructive always rightmost?), empty-state copy tone, capitalization (sentence-case everywhere?). Name every divergence with which surface is canonical and the concrete fix.' + GROUND },
20
+ { key: 'information-architecture', prompt: 'LOGIC LENS - INFORMATION ARCHITECTURE + DISCOVERABILITY. Is everything where a user would look first? Audit: rail order (chat/history/files/live/settings - logical?), whether live-session management is discoverable from chat (a running chat is on the live tab - does chat say so?), whether settings panels are grouped logically (connection vs agents vs data vs keyboard vs server), whether the ?-overlay is discoverable without knowing ?, whether file actions are discoverable (hover-revealed actions on touch devices?), whether followup chips read as suggestions vs buttons, whether the composer context line communicates what will HAPPEN on send (which agent, which model, which cwd, resume vs fresh - is resume-vs-fresh visible ANYWHERE before sending?), whether history search scope is communicated (titles? bodies? all sessions?), whether export options are findable, whether the crumb communicates location. Hunt hidden functionality, mislabeled groups, and anything requiring tribal knowledge. Name every misplacement with the logical home and concrete fix.' + GROUND },
21
+ { key: 'realtime-truth', prompt: 'PREDICTABILITY LENS - REALTIME TRUTH + STATE SYNC. Counters and statuses must agree with reality and each other. Audit: the live dashboard tally vs history event counts vs chat turn count (can they disagree? when?), staleness derivation (45s idle -> stale: does it flap? does a slow tool wrongly read stale?), what happens when the SAME session is open on chat tab while events stream on live tab (do both update?), multi-browser-tab behavior (two GUI tabs open: do they fight over localStorage drafts/resumeSid? does one tab\'s stop kill the other\'s view?), ws reconnect vs SSE reconnect divergence (one recovers, one does not?), the 3s chat.active poll vs SSE push skew (running panel shows a session the dashboard does not?), cost accumulation across resume (does cumulative cost survive reload? double-count on retry?), event dedup on reconnect replay, clock skew in relative timestamps, and whether ANY surface can show stale data with no indication it is stale. Name every truth divergence with the concrete fix.' + GROUND },
22
+ { key: 'input-ergonomics', prompt: 'LOGIC LENS - INPUT ERGONOMICS + COMPOSER DEPTH. Benchmark the claude.ai/code / Claude Desktop composer. Audit: multiline behavior (Enter vs Shift+Enter - communicated?), composer autosize (grows with content? max height? scroll?), paste behavior (pasting a large block? pasting an image - handled or silently dropped?), drag-drop onto the composer (file path insertion? or lost?), IME composition safety (Enter during CJK composition must not send), undo history survival across draft restore, keyboard shortcut completeness (is there a shortcut to stop generation? to focus composer from anywhere? are they in the ? overlay?), Escape semantics (consistent close/cancel everywhere: dialogs, drawers, edit-mode, confirm-states?), Tab order across the shell (rail -> sessions -> main -> pane logical?), text selection inside streaming messages (does a render wipe selection?), form validation timing (cwd validation on blur vs save?), and search input ergonomics (clear button? Esc clears? result count announced?). Name every ergonomic gap with the concrete kit-vs-app fix.' + GROUND },
23
+ { key: 'scale-robustness', prompt: 'PREDICTABILITY LENS - SCALE + DEGENERATE STATES. The UI must stay logical at the extremes. Audit with the REAL rendering paths: a 500-message transcript (does the chat virtualize or render all? does jump-to-latest survive? does per-block code-copy injection rescan everything per token?), a 1000-entry directory (FILE_GRID_CAP=200 show-more: does sort/filter apply before or after the cap - is that predictable?), 20 simultaneous live sessions (dashboard sort stability while cards re-sort under the cursor mid-click?), a 100k-character single message (markdown render time? copy button on a giant block?), zero-state every tab (fresh install: chat/history/files/live/settings all have designed empty states - and do they explain the NEXT action?), an enormous event list in history (300-limit + load-older: predictable?), localStorage full/corrupt (restore path throws -> blank app or graceful?), offline boot (server up, network down for jsdelivr markdown stack - does chat degrade gracefully?), and extremely long session titles/cwd paths/agent names (truncation with title= everywhere?). Name every scale failure with the concrete fix.' + GROUND },
24
+ ];
25
+
26
+ const GAP_SCHEMA = {
27
+ type: 'object',
28
+ required: ['lens', 'gaps'],
29
+ properties: {
30
+ lens: { type: 'string' },
31
+ gaps: {
32
+ type: 'array',
33
+ items: {
34
+ type: 'object',
35
+ required: ['title', 'where', 'fixLocation', 'fix', 'severity'],
36
+ properties: {
37
+ title: { type: 'string', description: 'the illogical/unpredictable behavior' },
38
+ where: { type: 'string', description: 'file:line or component (or "absent")' },
39
+ fixLocation: { type: 'string', enum: ['kit', 'app', 'server', 'kit+app', 'app+server', 'kit+app+server'], description: 'where the fix lands' },
40
+ fix: { type: 'string', description: 'the concrete change: component/prop/css/wiring/endpoint' },
41
+ benchmark: { type: 'string', description: 'which reference product sets the bar' },
42
+ severity: { type: 'string', enum: ['high', 'medium', 'low'] },
43
+ keptTypography: { type: 'boolean', description: 'true if this touches the middot/ellipsis/dash set - which must NOT be flagged as a glyph violation' },
44
+ },
45
+ },
46
+ },
47
+ },
48
+ };
49
+
50
+ const VERDICT_SCHEMA = {
51
+ type: 'object',
52
+ required: ['title', 'real', 'reason'],
53
+ properties: {
54
+ title: { type: 'string' },
55
+ real: { type: 'boolean', description: 'true if the behavior is genuinely illogical/unpredictable in the shipped source and the fix is sound' },
56
+ reason: { type: 'string', description: 'why - cite the source you read' },
57
+ },
58
+ };
59
+
60
+ phase('Hunt');
61
+ const verified = await pipeline(
62
+ LENSES,
63
+ (l) => agent(l.prompt, { label: 'hunt:' + l.key, phase: 'Hunt', schema: GAP_SCHEMA }),
64
+ (report, l) => parallel((report?.gaps || []).map((g) => () =>
65
+ agent(
66
+ 'Adversarially verify this GUI logic/predictability finding against the REAL source (read the cited files - do not trust the claim). '
67
+ + 'KEPT-TYPOGRAPHY GUARD: middot, ellipsis, em/en-dash are intentional product typography - flagging one of those as a decorative glyph is NOT a real finding (real=false). '
68
+ + 'A finding is real only if the behavior genuinely exists in the shipped source AND was not already fixed by a prior sweep (check PUNCHLIST-COMPLETION.md / PUNCHLIST-COHESION.md / AGENTS.md) AND the proposed fix is sound, lands in the right place (kit vs app vs server), and any server mutation stays confinement-safe. '
69
+ + 'Reject vague findings with no concrete source-grounded fix. '
70
+ + 'Finding: ' + JSON.stringify(g),
71
+ { label: 'verify:' + (g.title || '').slice(0, 32), phase: 'Verify', schema: VERDICT_SCHEMA }
72
+ ).then((v) => ({ ...g, lens: report.lens, verdict: v }))
73
+ ))
74
+ );
75
+
76
+ const confirmed = verified.flat().filter(Boolean).filter((g) => g.verdict && g.verdict.real);
77
+ log('confirmed ' + confirmed.length + ' real logic/predictability findings across ' + LENSES.length + ' lenses');
78
+
79
+ phase('Plan');
80
+ const plan = await agent(
81
+ 'Synthesize these adversarially-confirmed GUI logic/predictability findings into a dependency-ordered build plan for agentgui + the anentrypoint-design kit + the server. '
82
+ + 'Order: server changes first, then kit components (each edited ONCE with all changes batched), then app wiring, then the single build->vendor->witness step. '
83
+ + 'Call out which findings share a component (batch), which are independent (parallelizable), which conflict, and the highest-leverage few. '
84
+ + 'Confirmed findings: ' + JSON.stringify(confirmed),
85
+ { label: 'synthesize-plan', phase: 'Plan' }
86
+ );
87
+
88
+ return { confirmed, plan, lensCount: LENSES.length, confirmedCount: confirmed.length };
package/AGENTS.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # AgentGUI — Agent Notes
2
2
 
3
+ ## GUI logic+predictability sweep (2026-06-10) — ninth maximum-effort run
4
+
5
+ Audits whether everything OPERATES logically and predictably (not coverage, not cohesion). New workflow `.claude/workflows/gui-logic-predictability.js` (6 lenses: interaction-lifecycle, cross-surface-consistency, information-architecture, realtime-truth, input-ergonomics, scale-robustness; hunt -> adversarial verify (kept-typography guard) -> plan). 78 agents -> 67 confirmed findings (`PUNCHLIST-LOGIC.md`). ALL implemented across kit/app/server.
6
+
7
+ **Interaction lifecycles.** Kit `ConfirmDialog`/`PromptDialog` gain `error` (inside `.ds-modal-body`, role=alert) + `busy`/`busyLabel` (disables actions AND Escape/backdrop close via `data-busy`); Backdrop's Escape listener is **document-level** (focus can sit outside the dialog) and focus-restore fires only on genuine close (queueMicrotask remount guard) - the app's global Escape ladder ALSO closes `state.files.dialog` as belt-and-braces (a witness caught Escape dead with focus on H1). Per-session stop: `state.live.stopping` Set + kit `session.stopping` ('stopping...', disabled); bulk stops await `Promise.allSettled`, announce 'stopped N of M', partial failures keep their selection. Upload: one drain loop, concurrent drops append, 409 rows get `replace` (overwrite=1) + dismiss (kit `UploadProgress {actions,onDismiss}`). new-chat/backend-change/stop confirms are arm-only with bound values + 4s reset; chips never clobber a typed draft.
8
+
9
+ **Realtime truth.** Dashboard joins on `realSid = claudeSessionId || sessionId` - server `chat.active` rows now carry `claudeSessionId`; `streaming_cancelled` is broadcast (an aborted turn never also completes; backend maps it to `{type:'cancelled'}`, app marks the turn 'stopped'); a 60s per-session terminal-frame buffer replays on re-subscribe (`replayedTerminal`) so a turn finishing during a ws drop cannot hang busy; `chat_active_changed` hints (started/ended/cancelled) trigger immediate refreshActive. Error status derives from recency (not cumulative count - a session is not 'error' forever); stale derivation honors any session's running tool (per-sid `toolRunning` off SSE); SSE counter increments dedup by `ev.i` maxI; counters take max(index,tally) and never move backwards; clock-skew clamps to 'just now'; external (non-owned) sessions render as read-only dashboard cards; sorts compare real timestamps with sid tiebreaker; tally pruned 24h/200-LRU. persistChat: 500ms debounce, 100-msg cap, >4KB tool payloads trimmed (`truncatedForStorage`), quota failure banners once, cross-tab `storage` listener with ts guard ('updated in another tab' banner instead of clobber).
10
+
11
+ **Input ergonomics.** IME guards (composer, PromptDialog, SearchInput - `isComposing || keyCode===229` never sends). Escape ladder: shortcuts overlay > file dialog > confirmingEdit > new-chat arm > stop-arms > stop generation (and Esc in composer cancels while busy). Window-level dragover/drop preventDefault outside `.ds-dropzone` (a dropped file never navigates the page). Composer: `onPasteFiles`/`onDropFiles` kit props (image paste with no handler -> polite transient note), `.chat-composer-hint` 'Enter to send · Shift+Enter for a new line', context `bits` may carry per-bit `onClick` (cwd is its own target; agent/model inert). `Mod+Shift+L` focuses composer from anywhere (the one chord BEFORE the modifier early-return). cwd validates on blur (debounced `/api/stat` -> kit `cwdError`/`cwdChecking`). Streaming re-render pauses while a selection is held inside the thread (kit `hasSelectionInside` + autoscroll pause).
12
+
13
+ **Scale.** `AgentChat {shownMessages,onShowEarlier}` windowed thread (`MESSAGE_CAP=100`, keyed 'show N earlier turns' row); >20k streaming part renders a 4k tail window ('streaming · N KB so far'); history events sliced to last 5000 in-app (server adds a `?limit=N[&before=]` slicing shim in server.js - ccsniff itself has no limit support, do NOT edit node_modules); markdown loader failure no longer latches degraded (30s retry, cache never stores escaped fallback).
14
+
15
+ **Consistency/IA.** ONE `fmtDuration` (kit sessions.js) + ONE `fmtBytes` (= fmtFileSize, `0 B` not em-dash) kit-wide with app fallbacks; SessionCard `title` shares the rails' title expression; `session.external` + `session.elapsedMs`; STREAM_WORD `offline`; coarse-pointer always-visible file row actions; idle status never reads 'resuming...' (plain 'continues session <sid8>' in composer context); `/` focuses the files/live filter; running panel links 'open in live'; settings reordered backend -> server -> agents -> appearance -> keyboard -> data; crumb says 'settings' and Files crumb carries the parent trail.
16
+
17
+ **Witness.** localhost:3000/gm/ 0 console/page errors across chat/files/live/history/settings; composer hint renders; rename dialog opens + Escape closes + focus restores; 64-row files grid + crumb trail; 420px chat+live no h-scroll; decorative-glyph count 0 (kept middot/ellipsis/dash). Joint risk to re-check after parallel agent runs: prop-name contracts (this run caught app `onPasteImage` vs kit `onPasteFiles`, and backend missing the new `streaming_cancelled` type).
18
+
19
+ ## GUI completion sweep (2026-06-10) — eighth maximum-effort run
20
+
21
+ Coverage run reversing the prior scope cuts. Workflow `.claude/workflows/gui-completion.js` (6 lenses) -> 46 gaps (`PUNCHLIST-COMPLETION.md`), ALL implemented. Load-bearing remnants: Files is a FULL manager (confined `POST /api/rename,/api/delete,/api/mkdir`, `PUT /api/upload-file`, `GET /api/stat` via `fsAllowRoots()`/`sanitizeEntryName()`; CSRF guard on POST/PUT/DELETE; **run `scripts/validate-mutations.mjs` (17 checks) after touching the mutation surface**). Full hash routing `HASH_KEYS=[tab,sid,dir,file,q,project,section]` (popstate diffs the full set; `navTo(tab,{writeHash:false})`). `editAndResend` clears `resumeSid` on truncation (never --resume a diverged tail). `.ds-session-meta` was taken - the strip is `.ds-session-meta-strip`. One `SHORTCUTS` array feeds the ?-overlay + settings. Live selection Set deliberately NOT persisted (stale sids would arm stop-selected wrongly). Full detail in rs-learn (recall "agentgui GUI completion sweep").
22
+
3
23
  ## GUI cohesion sweep (2026-06-10) — seventh maximum-effort run
4
24
 
5
25
  The GUI reads as ONE polished chat-agent product (claude.ai/code / cowork / Claude-Desktop), not a kit of parts. New workflow deliverable `.claude/workflows/gui-cohesion.js` — 6 cohesion *lenses* (chat-agent-feel, fsbrowse-file-view, simultaneous-live-sessions, shell-navigation, visual-system, crosscut-perf-a11y-security) benchmark each surface against the real reference products, an adversarial verifier confirms each gap (kept-typography guard), a synthesizer orders them. Distinct from `gui-audit` (jank), `gui-overhaul` (new surfaces), `gui-predictability-polish` (primitive edges) — this one hunts the **product-cohesion/feel gap**. Ran 61 agents -> 40 confirmed gaps (`PUNCHLIST-COHESION.md`).
@@ -0,0 +1,266 @@
1
+ # PUNCHLIST-COMPLETION.md
2
+
3
+ agentgui GUI completion sweep (workflow gui-completion.js, run wf_783c6bb0-eb5): 75 agents, 6 coverage lenses -> 46 adversarially-confirmed gaps (kept-typography guard). Hunts what the seven prior sweeps deliberately scoped out or never examined: file management CRUD + upload, deep-link routing, mid-thread conversation control, history/settings depth, onboarding + error recovery. ALL phases implemented + witnessed this run (see AGENTS.md 8th-run section).
4
+
5
+ ## Confirmed gaps (46) + synthesized plan
6
+
7
+ 1. [high | kit+app | coverage-file-management] Kit FileRow rename/delete buttons render today but the app ignores them - dead affordances, and they are not permission-disabled
8
+ fix: Kit: FileRow disables rename/delete (disabled attr + aria-disabled + title 'read-only') when readOnly||noAccess, and hides download for type==='dir' (the app already no-ops it - app.js:800 guards file.type, leaving a dead button on dir rows); optionally accept an actions:['download','rename','delete'] prop so a host without mutation endpoints renders no dead controls. App: extend onAction to open
9
+ verdict: Verified against live source. Kit C:\dev\anentrypoint-design\src\components\files.js:80-84: when onAction is passed, FileRow unconditionally renders download/rename/delete buttons - none honor the noA
10
+
11
+ 2. [medium | app | coverage-file-management] DropZone + UploadProgress exist in the kit but are never wired - no upload surface at all
12
+ fix: In filesMain wrap the FileGrid body in DropZone({dragover:state.files.dragover, onDrop, onDragOver, onDragLeave, onPick}) gated on the current dir being writable (root permissions from /api/list); onDrop/onPick call B.uploadFiles and feed state.files.uploads into UploadProgress({items:[{name,pct,status,error}]}); on EEXIST conflict show a ConfirmDialog 'replace N existing files?' before retrying w
13
+ verdict: Verified against live source. Kit exports DropZone (C:\dev\anentrypoint-design\src\components\files.js:237) and UploadProgress (:253) exactly as cited, and grep over C:\dev\agentgui\site\app\js\app.js
14
+
15
+ 3. [medium | app | coverage-file-management] No new-folder affordance in the toolbar
16
+ fix: Add Btn({children:'new folder'}) to the FileToolbar right slot (disabled with a 'read-only folder' title when the current dir lacks write permission), opening PromptDialog({title:'new folder', placeholder:'folder name'}) -> B.mkdirPath -> on ok loadDir(f.path) and set the new row active; on 409 surface the dialog inline error. 44px coarse-pointer floor already comes from the kit's pointer:coarse r
17
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\app.js:826-831 — the FileToolbar right slot contains only the 'use as chat cwd' Btn; no new-folder control exists anywhere in app.js (grep for
18
+
19
+ 4. [medium | app | coverage-file-management] No post-mutation refresh/undo semantics: preview pane and chat-cwd can dangle on rename/delete
20
+ fix: After any successful mutation: loadDir(f.path) (sort/filter/shown preserved since they live in state, NOT reset); if state.files.preview.path equals the deleted path close the preview (and the <900px FileViewer), if renamed repoint preview.path/name; announce() the result ('deleted report.txt', 'renamed to notes.md') for SR users; since there is no undo, delete ALWAYS goes through ConfirmDialog({d
21
+ verdict: The working tree is mid-sweep: lib/http-handler.js now ships confined POST /api/rename, /api/delete, /api/mkdir (comment at ~379-388: 'The Files surface is a real manager (fsbrowse-grade)'), and site/
22
+
23
+ 5. [low | app | coverage-file-management] PUNCHLIST/AGENTS scope-cut records will contradict the shipped mutation surface
24
+ fix: When the reversal lands, update both documents: rename/delete/mkdir/upload are now shipped confined endpoints (witnessed 403 out-of-roots/traversal/symlink-escape, 409 EEXIST, root-deletion refusal); remove the 'read-only browser, no mutation endpoints' and 'legacy upload not reusable' lines so the next sweep does not re-cut them.
25
+ verdict: Verified against live source: C:\dev\agentgui\lib\http-handler.js now ships confined mutation endpoints — POST /api/rename (lines ~388-407, confineToRoots + isAllowRoot refusal + sanitizeEntryName + 4
26
+
27
+ 6. [high | app | routing-deeplinks-reload-statefulness] Files directory is not in the hash: reload resets to root and Back exits the Files tab instead of walking up the tree
28
+ fix: Extend buildHash/readHash with a `dir=` param (encodeURIComponent full path - safe with Windows backslashes and & since params are &-joined key=value pairs, no collision with tab=/sid=). loadDir(dirPath) calls writeHash with {push:true} after a successful load; popstate handler diffs readHash().dir against state.files.path and calls loadDir(dir, {fromPop:true}) (a flag that suppresses the re-push)
29
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. readHash (lines 35-43) and buildHash (44-49) carry only sid=/tab= — no dir param. loadDir (610-628) updates state.files.path but never calls writeH
30
+
31
+ 7. [high | app | routing-deeplinks-reload-statefulness] Open file preview is not restorable from a URL - no `file=` param, openPreview only mutates state
32
+ fix: Add `file=` hash param written by openPreview (push:true so Back closes the preview - a real desktop-class dismissal) and cleared by closePreview via writeHash replaceState. Boot/popstate: after the dir load resolves, find the entry by name in state.files.entries and openPreview(it); if absent or permissions==='EACCES', show the existing Alert('Cannot preview file') copy instead of silently droppi
33
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. The URL hash machinery (readHash line 35, buildHash line 44, writeHash line 50) only carries `tab=` and `sid=` - no file param exists anywhere. ope
34
+
35
+ 8. [high | app | routing-deeplinks-reload-statefulness] popstate handler only understands tab/sid - any new param (and even the current tab=files entry path) re-runs full navTo side effects and cannot diff dir/file
36
+ fix: Rewrite the popstate handler to read the full param set {tab,sid,dir,file} and apply minimal diffs: tab change -> navTo without writeHash (add a writeHash:false option to navTo so popstate never re-pushes/clobbers the entry it just popped), dir change -> loadDir(fromPop), file change -> openPreview/closePreview. Today navTo(tab) inside popstate calls writeHash (line 233) which replaceState-rewrite
37
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. The popstate handler (lines 2033-2038) destructures only {sid,tab} from readHash() (lines 38-43, which parse only sid/tab), and buildHash (lines 44
38
+
39
+ 9. [medium | app | routing-deeplinks-reload-statefulness] Live dashboard sort/filter/errors-only/selection are lost on reload and unreachable by deep link
40
+ fix: Persist state.live.sort + errorsOnly to localStorage('agentgui.live') on change and hydrate at boot (preferences, not navigation - localStorage is the right tier); optionally carry the text filter as a shareable `q=` hash param on the live tab. Selection (Set of sids) should deliberately NOT persist - stale sids after reload would arm stop-selected against the wrong sessions; document that as the
41
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. state.live is initialized at line 28 as { es, connected, lastEventTs, error, eventCount, reconnects } with no sort/filter/errorsOnly hydration, and
42
+
43
+ 10. [medium | app | routing-deeplinks-reload-statefulness] History search query and project filter are not in the URL - a found-events deep link cannot be shared and reload drops the search
44
+ fix: Add `q=` (and `project=`) params written via replaceState from the debounced search path (replaceState, not push - per-keystroke pushes would pollute Back); on boot with tab=history&q=, set state.searchQ and invoke debouncedSearch after refreshHistory. Clear both params in the existing 'clear search' Btn (1635) and refreshHistory's projectFilter reset (1899)
45
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. Routing is hash-based: readHash() (lines 35-43) parses only `sid` and `tab`, and buildHash() (44-49) writes only those two — `state.searchQ` (22) a
46
+
47
+ 11. [medium | app | routing-deeplinks-reload-statefulness] Tab switches use replaceState, so Back never walks tab visits - history->files->Back leaves the app (or pops a stale sid entry) instead of returning to history
48
+ fix: Make user-initiated navTo push (push:true) and keep replaceState only for programmatic/initial syncs (boot, popstate, stale-sid clear at 1878). Also stop nulling sid on tab departure: keep sid in the hash whenever state.selectedSid is set so returning to history via a pasted/Back URL restores the selected session rather than the empty state
49
+ verdict: Confirmed in C:\dev\agentgui\site\app\js\app.js. writeHash (lines 50-57) defaults push=false and uses history.replaceState; only loadSession (line 1924) passes push:true. navTo (line 233) calls writeH
50
+
51
+ 12. [low | app | routing-deeplinks-reload-statefulness] sid deep link force-routes to history, discarding an explicit tab= in the same URL; chat-surface restore-by-sid is impossible
52
+ fix: Honor tab= when both are present (tab=chat&sid=... selects the session in the chat rail / restores via resumeSid where it matches a persisted transcript; tab=history&sid keeps current behavior). At minimum, route on initialTab||'history' instead of hardcoding history, and have popstate apply the same rule (line 2035 has the identical hardcode: `state.tab = 'history'`)
53
+ verdict: Verified in C:\dev\agentgui\site\app\js\app.js. readHash() (lines 35-43) parses BOTH sid and tab from the hash, but the boot block (1991-1998) does `if (initialSid) { navTo('history'); ... loadSession
54
+
55
+ 13. [low | app | routing-deeplinks-reload-statefulness] Files sort/sortDir/filter reset on every reload - the fsbrowse-grade browser forgets its view configuration
56
+ fix: Persist files.sort/sortDir to localStorage('agentgui.files') following the existing ds.ws.* pattern and hydrate at boot; the transient text filter stays ephemeral by design (matching the live-tab decision). No hash params needed - these are preferences, not locations
57
+ verdict: Verified in C:\dev\agentgui\site\app\js\app.js: line 32 initializes state.files with hardcoded `sort: 'name', sortDir: 'asc', filter: ''` (no lsGet hydration), and the onSort handler at lines 784-787
58
+
59
+ 14. [low | kit+app | routing-deeplinks-reload-statefulness] Settings has no section anchors or scroll restoration - a deep link to the agents-status or health panel is impossible
60
+ fix: Kit: PageHeader/section primitives accept an `id` prop rendered on the section element. App: optional `section=` hash param on tab=settings; on boot/popstate, scrollIntoView + focus (tabindex=-1, reusing the data-prog-focus suppression pattern at lines 248-251) the matching section. Low effort, completes the every-surface-restores-meaningfully bar
61
+ verdict: Verified against live source. app.js settingsMain() (lines 1726-1780) renders four kit Panels (backend, appearance, agentsPanel, preferencesPanel) with no ids/anchors; the hash parser (app.js lines 36
62
+
63
+ 15. [high | app | conversation-control-depth] Retry/edit truncate the LOCAL transcript but keep resumeSid, so the server-side claude session still contains the dropped turns - the 'forked' conversation silently resumes with the old history
64
+ fix: In retryLastTurn and editAndResend, when turns are dropped, either clear state.chat.resumeSid (start a fresh server session seeded by the sliced transcript) or record a fork point; never --resume a session whose tail no longer matches the visible thread. Persist via persistChat so reload stays consistent.
65
+ verdict: Verified against live source. site/app/js/app.js: retryLastTurn (lines 1251-1269) and editAndResend (1272-1281) slice state.chat.messages and persistChat() but never touch state.chat.resumeSid; sendCh
66
+
67
+ 16. [medium | kit+app | conversation-control-depth] Edit-and-resend silently destroys every later turn with no confirm - a destructive slice behind a one-click 'edit' action
68
+ fix: Kit: ChatMessage edit action gains an optional confirm affordance (or AgentChat surfaces a banner 'editing will remove N later turns - continue / cancel'). App: gate the slice behind state.confirmingEdit (same pattern as confirmingNewChat); only persistChat after confirm.
69
+ verdict: Verified against live source. site/app/js/app.js:1272-1281 editAndResend does `state.chat.messages = state.chat.messages.slice(0, idx); ... persistChat();` immediately on click — the truncation is per
70
+
71
+ 17. [medium | app | conversation-control-depth] Followup chips are a hardcoded static array, not derived from the last assistant content
72
+ fix: Derive followups from the settled last assistant turn: if parts contain a code fence/CodeNode -> 'Explain this code'/'Run it'; if any tool part has status:'error' -> 'Fix that error'; if a file path appears -> 'Open <file>'; fall back to generic seeds. Kit AgentChat already accepts followups/onFollowupClick - no kit change needed.
73
+ verdict: Confirmed against live source. C:\dev\agentgui\site\app\js\app.js line 1115 ships exactly `followups: state.chat.messages.length ? ['Explain that in more detail', 'Show me the diff', 'Run the tests']
74
+
75
+ 18. [medium | kit+app | conversation-control-depth] No copy-whole-conversation / export transcript (md or json) - only per-message copy exists
76
+ fix: Kit: AgentChat header/controls row gains optional exportActions (text-labeled buttons 'copy all' / 'export md' / 'export json', 44px targets, aria-labels). App: transcriptToMarkdown()/JSON over state.chat.messages (roles, times, tool names+labels), copy via clipboard + announce(), download via Blob URL - read-only, no server endpoint needed.
77
+ verdict: Verified against live source. Kit C:\dev\anentrypoint-design\src\components\agent-chat.js has no export/copy-all/transcript affordance anywhere (only match for "export" is the module's `export functio
78
+
79
+ 19. [medium | app | conversation-control-depth] Draft is lost on reload - persistChat saves messages/resumeSid/agent/model but never state.chat.draft
80
+ fix: Include draft in the CHAT_KEY payload (write debounced from the existing onDraft handler at app.js:1131, which already fires per keystroke) and restore it in restoreChat into state.chat.draft so the composer refills after reload.
81
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. persistChat (lines 1203-1211) serializes only {messages, resumeSid, agent, model} into CHAT_KEY and restoreChat (1212-1228) restores only those plu
82
+
83
+ 20. [low | kit+app | conversation-control-depth] No visible turn-count / context-size affordance in the chat surface - ContextPane shows only LAST-turn usage and the composer context line is agent · model · cwd only
84
+ fix: Kit: ContextPane accepts session:{turns, cumulativeTokens} rendered as a 'conversation' Row group; AgentChat composerContext accepts an extra segment. App: append 'N turns' (count of user messages) to composerContext using the kept middot separator, and accumulate usage across turns into state.chat for the pane.
85
+ verdict: Verified against live source. In C:\dev\agentgui\site\app\js\app.js: composerContext (line ~1110) builds bits from only [agentName, model, cwd] - no turn count; and the 'result' handler (line ~1329) O
86
+
87
+ 21. [high | kit+app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] History event list has no event-type filter (tool / error / text / role)
88
+ fix: Add a segmented filter (kit Select or pill role=group, 44px targets) above EventList: all / text / tool_use / errors. Kit gets the control as an EventList `filters`/`onFilter` prop or a reusable FilterPills component in content.js; app filters state.events before slicing to eventsLimit and shows an explicit '0 events match filter' empty state with a clear-filter button.
89
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js lines 1404-1481 (historyMain selected-session branch): the only event controls are the 'expand shown'/'collapse shown' toggle and the 'load N older'
90
+
91
+ 22. [high | app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] tool_use events never get the purple rail the code's own comment promises — semantic regression
92
+ fix: `const rail = e.isError ? 'flame' : (e.type === 'tool_use' ? 'purple' : 'green')` so the GUI-wide rail semantics (green=normal, purple=tool, flame=error) actually hold in the event list; witness a session with tool events.
93
+ verdict: Read C:\dev\agentgui\site\app\js\app.js lines 1455-1478: the comment at 1465-1467 explicitly states 'flame = error, purple = tool_use, green = normal turn', yet line 1468 is `const rail = e.isError ?
94
+
95
+ 23. [medium | app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] No jump-to-first-error despite session rows advertising an error count
96
+ fix: Add a 'jump to first error (N)' Btn to the history actions when sess.errors > 0: findIndex(e => e.isError), widen eventsLimit to include it, then reuse the existing scrollIntoView + .event-flash path (app.js:1940-1953). Hide when 0 errors.
97
+ verdict: Verified against live C:\dev\agentgui\site\app\js\app.js. The history detail actions block (lines 1402-1405) contains only 'open in chat' and 'copy sid' Btns — no jump-to-error control. Session rows d
98
+
99
+ 24. [medium | kit+app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] No copy-event-payload action on an expanded event row
100
+ fix: Kit Row (content.js) gains an optional `actions` slot rendered when expanded (reveal-on-focus/hover button, aria-label 'copy event', keyboard reachable, must not trigger the row onClick — stopPropagation); app passes a copy callback writing the `full` string via the existing clipboard fallback (copySid pattern) with a 'copied' toast in a role=status region.
101
+ verdict: Verified against live source. In C:\dev\agentgui\site\app\js\app.js (~1446-1481) expanded event rows build the `full` string (text + JSON.stringify(toolInput)) and render it as the Row title, but the
102
+
103
+ 25. [medium | app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] No per-session export (download events as JSON/markdown)
104
+ fix: Add an 'export' Btn beside 'copy sid': client-side Blob download of the already-loaded /v1/history/sessions/:sid/events payload (JSON, filename `<projectLabel>-<sid>.json`), no new server endpoint needed; disable with plain copy while events are still loading.
105
+ verdict: Verified in app.js historyMain: the actions row (lines 1402-1405) renders only 'open in chat' and 'copy sid' Btns — no export exists anywhere in app.js. backend.js getSessionEvents (lines 135-139) alr
106
+
107
+ 26. [medium | kit+app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Session metadata header omits agent/model/full cwd/duration/sid
108
+ fix: Render a metadata strip (kit: a SessionMeta row in sessions.js or PageHeader `meta` prop — middot-separated definition list, role=group) showing full cwd, duration (first->last event ts), sid (truncated, title=full), and agent/model when ccsniff carries them; app derives duration from state.events once loaded. Responsive wrap at 420px.
109
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js lines 1391-1405: the selected-session lede is exactly `(projectLabel(sess.project) || sess.cwd || '?') + ' · ' + events + ' · ' + turns + ' · ' + fm
110
+
111
+ 27. [medium | kit+app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Search hits flash the row but the matched query is never highlighted inside the event body
112
+ fix: Kit Row accepts `highlight` (string) and wraps case-insensitive matches in title/expanded body with a token-styled <mark class="ds-hl"> (background via kit token, not hex); app threads state.searchQ as highlight when arriving via a search hit, and prefers slicing the title window AROUND the first match rather than chars 0-220 (a deep match currently shows an irrelevant prefix).
113
+ verdict: Verified against live source. App: C:\dev\agentgui\site\app\js\app.js loadSession (lines ~1944-1965) only scrolls + adds .event-flash to the matched row; the event row builder (line 1474) renders `tex
114
+
115
+ 28. [medium | app+server | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Settings has no server-info panel: uptime, ws clients, allowed roots, Claude projects dir are all fetched or fetchable but never shown
116
+ fix: Server: extend /health (or add /api/server-info) with `projectsDir` (CLAUDE_PROJECTS_DIR resolution) and the /api/list allowRoots list — read-only strings, no path traversal surface. App: a 'server' Panel in settingsMain listing version · uptime (humanized) · ws clients · allowed roots · projects dir, with copy buttons for the paths; loading/error states with plain copy when /health is partial.
117
+ verdict: Verified against live source. lib/http-handler.js:200 — /health already returns uptime (process.uptime()), wsClients, memory, queueSizes, acp, db; /api/list (line 296) already returns the allowRoots l
118
+
119
+ 29. [medium | app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Data management is clear-local only: no chat-transcript export and no stored-data size readout
120
+ fix: In preferencesPanel add 'export chat' (Blob download of localStorage['agentgui.chat'] as JSON, disabled with 'no saved chat' copy when absent) and a per-key size line ('local data: N KB across 5 keys') computed from the agentgui.* keys, so the destructive clear has an escape hatch and a magnitude.
121
+ verdict: Read C:\dev\agentgui\site\app\js\app.js lines 1791-1824: preferencesPanel() renders only version/keyboard/cwd lines and the clearLocalData confirm flow (Alert with clear/cancel). clearLocalData() at 1
122
+
123
+ 30. [medium | kit+app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Keyboard-shortcut reference is a one-line Alert and the settings copy of it is STALE (missing files/live tabs)
124
+ fix: Kit: a ShortcutList component (content.js + css) rendering rows of <kbd>-styled key chips + plain descriptions (role=list, kit tokens, 420px wrap). App: ONE shortcuts definition array consumed by both the ?-overlay and a 'keyboard' settings panel so the two surfaces can never drift again; fix the stale c/h/s copy now. Keep the status-bar 'press ? for shortcuts' discoverability line.
125
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\app.js:1812 (preferencesPanel) literally says 'keyboard: g then c/h/s - switch tabs ...' while the keydown handler (lines 2080-2086) supports
126
+
127
+ 31. [low | app | coverage: history + settings surface depth (benchmarked against claude.ai/code history and Claude Desktop settings)] Search-hit rows lack the absolute event timestamp and matched-event position context
128
+ fix: Append fmtRelTime(r.ts) (title=absolute) to the search-hit Row sub so results are temporally scannable like the session list; r.ts is already carried for focusEventTs.
129
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js. The historySide search-hit Row (line 1590) builds sub as `(r.project||'?') + ' · ' + (r.role||'?') + (r.tool ? ' · '+r.tool : '')` — no timestamp —
130
+
131
+ 32. [high | kit+app | first-run, onboarding, error recovery] No guided zero-to-first-chat path when no agents are installed
132
+ fix: AgentChat gains an `installHint` prop rendered in the empty state and in the not-installed banner: when every agent has available===false, show plain copy 'No agent CLIs found on the server' plus the concrete install command for each npxInstallable agent (e.g. 'npx @anthropic-ai/claude-code') and a 'recheck agents' Btn wired to a new onRecheckAgents callback (app re-runs B.listAgents). Today the e
133
+ verdict: Verified against live source. Kit c:\dev\anentrypoint-design\src\components\agent-chat.js:245-257: emptyState renders only 'Choose an agent to begin' / 'Start a conversation with <name>' plus optional
134
+
135
+ 33. [high | app | first-run, onboarding, error recovery] agents.list failure at boot is silent and never retried - picker stays empty forever
136
+ fix: On listAgents rejection set state.agentsError, render an Alert banner on the chat tab ('Couldn\'t load agents from the server - retry') with a retry Btn re-running the agents block of init(), plus 2-3 automatic backoff retries (the ccsniff 30-90s warmup makes a first-request failure likely). Currently the failure leaves state.agents=[] and the Select shows the bare '— agent —' placeholder with zer
137
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\app.js init() line 1989 calls `state.agents = await B.listAgents(state.backend)` and its catch (line 1998) is exactly `console.warn('agents fe
138
+
139
+ 34. [high | app | first-run, onboarding, error recovery] Backend health is probed exactly once at init - mid-session server death never flips the offline banner, and recovery never re-inits
140
+ fix: Re-probe /health when onWsStatus reports closed/error (and on a slow interval while status!=='ok'); on recovery flip health back to ok, re-run the agents/models fetch, and announce 'reconnected' via the aria-live announcer. Today if the server dies after boot, health.status stays 'ok' (banner never shows - only the crumb quietly says 'ws reconnecting'), and if it was down at boot and comes back, a
141
+ verdict: Verified against C:\dev\agentgui\site\app\js\app.js and backend.js. B.probeBackend (a GET /health, backend.js:42) is called exactly once, inside init() at app.js:1982; state.health.status is never wri
142
+
143
+ 35. [high | app | first-run, onboarding, error recovery] WS drop mid-stream kills the turn instantly even though the socket auto-reconnects and re-subscribes - no reattach, error banner offers only dismiss
144
+ fix: In streamChat, on 'closed' start a grace timer (~10-15s) instead of finishing immediately; if onWsStatus reports 'open' before it fires, the existing conversation.subscribe re-attach resumes events for the still-running server session and the turn continues. If it does expire, the Stream error banner should add a 'retry' Btn wired to the existing retryLastTurn() beside dismiss. Today a 1-second bl
145
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\backend.js line 365: `const onWs = (s) => { if ((s === 'closed' || s === 'error') && !done) { errored = errored || 'connection lost during str
146
+
147
+ 36. [medium | kit+app | first-run, onboarding, error recovery] stop all / stop selected fire immediately with no confirmation
148
+ fix: Match the established two-step pattern: SessionDashboard accepts confirmingStop state (or manages a local first-click 'stop N sessions? press again to confirm' label swap on the danger Btn, auto-reset after ~4s), so one mis-click cannot kill every running agent. App threads state.live.confirmingStop like confirmingNewChat.
149
+ verdict: Verified in live source. Kit c:\dev\anentrypoint-design\src\components\sessions.js lines 196-198: the danger Btns call onStopSelected([...selSet]) / onStopAll(sessions) directly on first click with no
150
+
151
+ 37. [medium | app | first-run, onboarding, error recovery] Raw transport strings surface as user-facing error copy
152
+ fix: Extend errText() (app.js:1023 area) into a mapping layer: 'ws closed'/'connection lost during stream' -> 'Lost connection to the server while the agent was responding - retry the last message'; 'no sessionId from server' -> 'The server couldn\'t start the agent - check the agent is installed and try again'; HTTP 'sessions: 503' -> 'History is still indexing - try again in a moment'. Keep the raw s
153
+ verdict: Verified against live source. backend.js:227 rejects pending RPCs with `new Error('ws closed')`, backend.js:340 yields `{type:'error', error:'no sessionId from server'}`, backend.js:130 throws `new Er
154
+
155
+ 38. [medium | app+server | first-run, onboarding, error recovery] No cwd existence validation before spawn - a typo'd directory fails as an opaque streaming_error
156
+ fix: Add a WS method or GET /api/stat that fs.statSync's the proposed cwd (existence + directory + readable; no root-confinement needed since cwd is a spawn dir not a file read, but reuse plain 403/404 copy), called from onCwdSave; on failure set state.cwdError to 'directory not found on the server: <path>' instead of letting the next send die mid-spawn with a raw runner error. Pair with the existing c
157
+ verdict: Verified against live source. app.js onCwdSave (lines 1149-1163) validates only path SHAPE via regex /^([\\/]|[A-Za-z]:[\/\\])/ - absolute-path syntax, no existence/dir check - then immediately persis
158
+
159
+ 39. [low | app | first-run, onboarding, error recovery] First-run ccsniff warmup (30-90s JSONL walk) shows a bare 'loading…' spinner with no explanation
160
+ fix: Escalate the loading copy on a timer: after ~5s of an unresolved first history/sessions fetch, swap the spinner label to 'indexing your Claude history - the first load can take a minute…' (role=status, aria-live polite already present). One state flag + setTimeout in refreshHistory/loadSession; no new components.
161
+ verdict: Verified against live source. AGENTS.md documents the 30-90s first-request loadOnce JSONL walk on /v1/history/*. Grepping C:\dev\agentgui\site\app\js\app.js: the only loading copy is generic — line 68
162
+
163
+ 40. [medium | app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Files dir deep-links never landed - hash routing covers tab/sid only, not the browsed path
164
+ fix: Extend the hash codec to carry `path=<encodeURIComponent(dir)>` (and optionally `file=` for an open preview) when tab=files; loadDir() pushes history.pushState on navigation, popstate handler (app.js:2033) restores tab+path+preview. Server already 403/404s bad paths so a stale/foreign deep-link degrades to the existing plain-copy error (app.js:622). Back/forward must step the dir history, not just
165
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\app.js readHash() (lines 35-43) parses only sid=/tab=, buildHash() (44-49) emits only tab+sid, and the popstate handler (2033-2038) restores o
166
+
167
+ 41. [medium | kit+app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Drag-drop upload needs a keyboard path and must not be the only entry
168
+ fix: Kit DropZone over the FileGrid: dragenter/over/leave/drop with a visible drop ring (tokens, no glyphs), PLUS a real <input type=file multiple> behind a 44x44 'upload' button in the files toolbar so keyboard/AT users have an equivalent path; the drop target itself is not focusable UI, the button is the accessible affordance. 420px: button stays in the toolbar row, no h-scroll.
169
+ verdict: Verified against live source. The user-facing gap is real: C:\dev\agentgui\site\app\js\app.js contains zero references to DropZone/UploadProgress/uploadFile/onPick (grep: no matches), so the shipped f
170
+
171
+ 42. [medium | app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Per-mutation failure copy: EACCES/EEXIST/ENOENT must map to plain language in the app, like listDir 403 already does
172
+ fix: Extend backend.js with renameFile/deleteFile/mkdir/upload helpers that surface {status, error}; app maps 403->'no permission to change this file', 409->'a file with that name already exists', 404->'that file no longer exists - the folder was refreshed', 413->'file too large (50MB max)'; on 404 auto-loadDir() once. Error renders inline near the toolbar (role=status), not a silent console line.
173
+ verdict: Verified against live source. The backend.js half of the proposed fix is ALREADY done: C:\dev\agentgui\site\app\js\backend.js:96-122 ships renameEntry/deleteEntry/makeDir (mutateJSON, POST) and upload
174
+
175
+ 43. [low | app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Dir refresh after any mutation must be exactly one /api/list fetch, with optimistic row state
176
+ fix: All mutation success paths call a single loadDir(state.files.path); during flight the affected FileRow shows a busy/disabled state (kit prop) rather than skeletoning the whole grid; concurrent-mutation guard: serialize via a state.files.mutating flag so two deletes don't double-fetch. If the preview pane shows the deleted/renamed file, close or repoint it in the same pass.
177
+ verdict: Verified against live source. The mutation stack is mid-landing in the working tree: lib/http-handler.js (uncommitted) now ships POST /api/rename (line 388), /api/delete (411), /api/mkdir (436), PUT /
178
+
179
+ 44. [low | kit+app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Mutations must respect the per-entry permissions tag the server already reports
180
+ fix: FileRow rename/delete actions render disabled (aria-disabled, no tab-stop per the kit Row disabled contract) when 'write' is absent from permissions or permissions==='EACCES', with a title 'read-only' - do not offer a mutation the server will 403, but keep the server check authoritative (UI gating is UX, not security).
181
+ verdict: Verified against live source. Server: C:\dev\agentgui\lib\http-handler.js /api/list computes per-entry permissions (['read','write'] | [] | 'EACCES', lines ~278-292) and mutation endpoints /api/rename
182
+
183
+ 45. [high | server | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] CSRF surface widens with mutation endpoints when PASSWORD is unset (wildcard ACAO + no auth)
184
+ fix: New mutation routes (rename/delete/mkdir/upload) must require Content-Type: application/json (or multipart with a custom header) and reject simple-form content types, so a cross-site form POST cannot drive deletes on a passwordless localhost server; alternatively check Origin/Sec-Fetch-Site is same-origin for state-changing methods. Read-only routes keep current behavior.
185
+ verdict: Verified against C:\dev\agentgui\lib\http-handler.js. Mutation routes exist and mutate the filesystem: POST /api/rename (388), POST /api/delete (411, fs.rmSync/unlinkSync), POST /api/mkdir (436), PUT
186
+
187
+ 46. [low | app | crosscut-perf-a11y-security (new surfaces: file mutations, upload, deep links, mid-thread fork)] Followups remain static-only - no model-derived suggestions after a turn
188
+ fix: Derive followups from the settled turn cheaply client-side (e.g. from tool names used / files touched: 'open <file>', 'run it', 'explain the error') rather than a second model call; pass through the existing followups prop - kit unchanged. Low value vs mutation work; keep last.
189
+ verdict: Verified against live source. C:\dev\agentgui\site\app\js\app.js line 1115 passes a hardcoded array — `followups: state.chat.messages.length ? ['Explain that in more detail', 'Show me the diff', 'Run
190
+
191
+ ===PLAN===
192
+ ## AgentGUI Build Plan — coverage-gap synthesis (40 confirmed gaps, dependency-ordered)
193
+
194
+ **Pre-flight conflict to resolve first.** Gap "FileRow dead affordances" verdict says *keep rename/delete unrendered* (scope cut), but five later verdicts confirm the mutation endpoints (`/api/rename,delete,mkdir,upload-file` + `backend.js renameEntry/deleteEntry/makeDir/uploadFile`) now SHIP uncommitted in the working tree. **Resolution: the scope cut is superseded — wire mutations fully (Phase C2) and treat the "keep unrendered" clause as stale.** The kit `actions` prop is still built (hosts without mutations need it), but agentgui passes the full set. Also note: verdicts cite naming drift — real exports are `B.makeDir` (not `mkdirPath`), `B.uploadFile` per-file (not `uploadFiles`), `renameEntry/deleteEntry` (not `renameFile/deleteFile`); 409 status is the EEXIST signal.
195
+
196
+ ---
197
+
198
+ ### Phase A — Server (`lib/http-handler.js`) — unblocks everything; commit the uncommitted mutation routes WITH these
199
+
200
+ **A1 (HIGH, highest-leverage security): CSRF guard on state-changing methods.** When `PASSWORD` unset, ACAO:* + advertised POST/PUT/DELETE lets a cross-site page drive deletes on localhost. For POST/PUT/DELETE only: require same-origin `Origin`/`Sec-Fetch-Site` (or reject simple-form content types; require `Content-Type: application/json`). GET routes unchanged.
201
+
202
+ **A2: `GET /api/stat`** (cwd existence validation) — `fs.statSync` existence+isDirectory+readable, **routed through `confineToRoots`** (an unconfined stat is a filesystem oracle; the gap's "no confinement needed" claim was rejected by its own verdict). Plain 403/404 copy.
203
+
204
+ **A3: `/health` extension** — add `projectsDir` (CLAUDE_PROJECTS_DIR resolution) + `allowRoots` list. Read-only strings, no path params.
205
+
206
+ **Security-validation checklist — run against EACH mutation endpoint (`/api/rename`, `/api/delete`, `/api/mkdir`, `PUT /api/upload-file`) before Phase D witness:**
207
+ - [ ] Traversal: `..` segments in dir and in name → 403 (lexical + `sanitizeEntryName`)
208
+ - [ ] Symlink escape: symlink inside root pointing outside → `confineToRoots` realpath 403 (fail closed)
209
+ - [ ] Out-of-roots absolute path → 403
210
+ - [ ] Oversize: upload >50MB → 413
211
+ - [ ] Reserved/invalid names: `CON`, `NUL`, `aux.txt`, trailing dot/space, `:` ADS on Windows → rejected by sanitize
212
+ - [ ] Root-deletion refusal: delete/rename of an allow-root itself → 403
213
+ - [ ] EEXIST: rename/mkdir/upload onto existing → 409 (upload `overwrite=1` path works)
214
+ - [ ] CSRF: cross-origin form POST and JSON fetch → rejected per A1
215
+ - [ ] EACCES/EPERM → 403 with no path leakage in body
216
+
217
+ ---
218
+
219
+ ### Phase B — Kit (`c:\dev\anentrypoint-design`) — each file edited ONCE, all changes batched; parallelizable across files
220
+
221
+ **B1 `src/components/files.js` (FileGrid/FileRow) — batch of 4 gaps:** `actions:[...]` prop (host declares which controls render); disable rename/delete when `readOnly||noAccess` (disabled + aria-disabled + title "read-only"); hide download on `type==='dir'`; per-row `busy` state for in-flight mutations. DropZone/UploadProgress need **no changes** (verdict: keyboard `onPick` path already exists).
222
+
223
+ **B2 `src/components/content.js` (Row/Panel/Section/PageHeader) — batch of 3 gaps:** Row `actions` slot rendered when expanded (sibling of the role=button shell, NOT nested inside it — ARIA violation otherwise; stopPropagation); Row `highlight` string → `<mark class="ds-hl">` (token bg) in title/body; `id` prop on Panel/Section/PageHeader for settings anchors. Plus segmented FilterPills (or reuse Select) for the history event filter.
224
+
225
+ **B3 `src/components/sessions.js` — batch of 3 gaps:** SessionMeta strip (middot-separated dl, role=group, wraps at 420px); SessionDashboard two-step confirm on stop-all/stop-selected (first-click label swap "stop N? press again", ~4s auto-reset); ConversationList `loadingText` prop (warmup copy).
226
+
227
+ **B4 `src/components/agent-chat.js` + `chat.js` (ChatMessage) — batch of 4 gaps:** `installHint` prop + `onRecheckAgents` in empty state/unavailable banner; `exportActions` slot in controls row (text-labeled, 44px); composerContext extra segment ("N turns"); optional edit-confirm affordance ("editing removes N later turns").
228
+
229
+ **B5 `src/components/context-pane.js`:** render the documented-but-dead `session` prop — conversation block (turns, cumulative cost; verdict: emphasize turn count over redundant token sum).
230
+
231
+ **B6 `src/components/interaction-primitives.js`:** extend existing `ShortcutHelpDialog`/`ShortcutHint` (do NOT add a new ShortcutList — verdict found these already ship) for the grouped shortcuts surface.
232
+
233
+ **files-modals.js: no edit needed** — ConfirmDialog/PromptDialog/FilePreviewPane already exist; FileViewer/FilePreviewPane render from props (deep-link restore is app-only).
234
+
235
+ ---
236
+
237
+ ### Phase C — App wiring (`site/app/js/app.js` + `backend.js`) — C1 is a hard prerequisite for C-routing; C2–C6 parallelizable after their kit/server deps
238
+
239
+ **C1 (HIGH, highest-leverage structural): popstate/hash refactor — must land BEFORE any new param.** Rewrite popstate to diff the full `{tab,sid,dir,file,q,project,section}` set; add `writeHash:false` option to `navTo` so popstate never replaceState-clobbers the entry it popped (root cause); user-initiated navTo pushes (`push:true`), programmatic syncs replace; stop nulling sid on tab departure; honor `tab=` when sid present (boot 1991 + popstate 2035 hardcodes → `initialTab||'history'`; `tab=chat&sid` reuses `resumeInChat`). Then add params: `dir=` (pushState per loadDir, `fromPop` flag, fall-back-to-root on 403/404), `file=` (**carry full path + loadDir(dirname) first** per verdict — name-only fails for nested files; push so Back closes preview), `q=`/`project=` (replaceState from debounced search; clear in clear-btn + refreshHistory reset), `section=` on settings (scrollIntoView + tabindex=-1 focus). Note: the two files-deep-link gaps (`dir=` vs `path=`) are the SAME gap reported twice — implement once.
240
+
241
+ **C2 Files mutations (depends A1 checklist + B1):** wire rename PromptDialog / delete `ConfirmDialog({destructive:true})` / new-folder toolbar Btn (`B.makeDir`) / DropZone+UploadProgress (`B.uploadFile` per file, 409 → replace confirm, writability-gated); post-mutation: exactly ONE `loadDir(state.files.path)` (sort/filter/shown survive — they live in state), `state.files.mutating` serialization, per-row busy, close/repoint preview on delete/rename, `announce()` results; status→copy map (403/404/409/413, auto-loadDir once on 404, role=status inline). The "drag-drop keyboard path" gap is app-only (kit done). Persist `files.sort/sortDir` to `localStorage('agentgui.files')`; filter stays ephemeral.
242
+
243
+ **C3 Chat conversation control (independent; B4/B5 for two items):** **clear `resumeSid` on editAndResend truncation** (HIGH — never `--resume` a session whose tail diverges; verdict: retryLastTurn may keep resume, branch-style; "seed from sliced transcript" is NOT possible — server takes last message only); gate edit behind `state.confirmingEdit`, persistChat only after confirm; persist `draft` (debounced from onDraft; drop the empty-messages early-return when draft non-empty); derive followups from last turn (code fence/tool error/file path); transcriptToMarkdown/JSON + copy-all/export via Blob; "N turns" composer segment + cumulative usage into ContextPane.
244
+
245
+ **C4 Resilience (independent, HIGH cluster):** `backend.js` streamChat grace timer (~10-15s) on ws closed before finish — existing re-subscribe resumes the still-running server turn; add "retry" Btn (wired to existing retryLastTurn) to the Stream error banner; re-probe `/health` on ws closed/error + slow interval while not-ok, re-fetch agents/models on recovery, announce "reconnected"; `state.agentsError` banner + backoff retries on listAgents failure; `errText()` mapping layer (raw string in title attr; map `sessions: <n>` generically); cwd validation via `/api/stat` (A2) feeding the existing cwdError banner; installHint/recheck wiring (B4); 5s-escalated warmup copy ("indexing your Claude history…") via loadingText (B3) + app spinners.
246
+
247
+ **C5 History (depends B2/B3):** **one-liner rail fix** `e.isError ? 'flame' : (e.type==='tool_use' ? 'purple' : 'green')` — trivial, HIGH, do regardless; event-type filter (filter BEFORE eventsLimit slice; compute shownKeys/codes off filtered array; zero-match empty state); jump-to-first-error (reuse loadSession widen+flash path); copy-event-payload via Row actions slot; per-session export (Blob of loaded events, gate on eventsLoaded); SessionMeta header (cwd/duration/sid, agent/model when present); thread searchQ as `highlight` + slice title window AROUND first match; append `fmtRelTime(r.ts)` to search-hit sub (sub is a plain string — drop the title=absolute hover or make it a VElement).
248
+
249
+ **C6 Settings + Live (independent):** server-info Panel (version/uptime/ws clients/roots/projectsDir, copy buttons); export-chat Blob + "local data: N KB across 5 keys"; ONE shortcuts definition array feeding both ?-overlay (ShortcutHelpDialog) and settings panel — fixes the stale `g c/h/s` copy NOW; persist `live.sort/errorsOnly` to `localStorage('agentgui.live')`, **selection Set deliberately NOT persisted** (stale-sid stop hazard — document as scope line); confirmingStop threading (B3).
250
+
251
+ **C7 Docs (last, with the commit):** update AGENTS.md "Scope cuts" + PUNCHLIST-COHESION.md — rename/delete/mkdir/upload are shipped confined endpoints; remove "read-only browser" / "legacy upload not reusable" lines so the next sweep doesn't re-cut; record the live-selection and files-filter ephemerality decisions.
252
+
253
+ ---
254
+
255
+ ### Phase D — Single build → vendor → witness
256
+
257
+ `node c:\dev\anentrypoint-design\scripts\build.mjs` → copy `dist/247420.{js,css}` → `site/app/vendor/anentrypoint-design/` → `bun server.js` → witness `localhost:3000/gm/`: 0 console errors; **app.js `C` destructure lists every new kit component** (FilterPills/SessionMeta/DropZone/UploadProgress/PromptDialog/ConfirmDialog/ShortcutHelpDialog — the 7th run's `Icon is not defined` lesson); run the full Phase-A security checklist against all four mutation endpoints; verify Back walks tab→dir→preview; reload restores dir/file/q/draft/sort; rename/delete/upload round-trip with single loadDir; tool_use rows purple; 420px no h-scroll; lint-tokens+lint-glyphs pass. Push kit (rebase+rebuild if remote advanced), then agentgui.
258
+
259
+ ---
260
+
261
+ ### Summary matrix
262
+
263
+ - **Batched (same component):** B1 files.js x4, B2 content.js x3+filter, B3 sessions.js x3, B4 agent-chat x4; C1 absorbs all 7 routing gaps; C2 absorbs all 6 mutation gaps.
264
+ - **Parallelizable:** A1/A2/A3; all B files; C2–C6 (after C1 + their deps).
265
+ - **Conflicts:** FileRow keep-unrendered vs full mutation wiring (resolved: wire); `dir=` vs `path=` duplicate gap (implement once); new ShortcutList vs existing ShortcutHelpDialog (reuse existing).
266
+ - **Highest-leverage five:** A1 CSRF guard; C1 popstate refactor (unblocks 7 gaps); C2 mutation wiring (closes 9 gaps incl. stale docs); C4 stream grace + resumeSid fork fix (HIGH correctness pair); C5 purple-rail one-liner (cheapest HIGH).