agentgui 1.0.1043 → 1.0.1045

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.
@@ -1,266 +0,0 @@
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).
@@ -1,33 +0,0 @@
1
- # PUNCHLIST-DENSITY — 11th run: screen-real-estate + density + SDK-as-application
2
-
3
- Workflow `.claude/workflows/gui-screen-realestate.js` (6 lenses: space-efficiency, viewport-utilization, information-density, adaptive-layout, application-chrome, finish-pending). 50 agents -> 17 confirmed gaps. Verdict: the shell spends ~848px (rail 232 + sessions 296 + pane 320) of fixed chrome before content, the chat thread pins left dumping a one-sided gutter on wide screens, surfaces are airy (not Claude-Code-dense), nothing resizes, and webpage-tells remain (pill buttons, 42px status footer).
4
-
5
- ## Highest-leverage
6
- 1. Shell column resize handles (G2) — up to ~250px reclaimable
7
- 2. Sessions desktop collapse toggle (G3) — full 296px reclaim
8
- 3. Files density-thumbnail grid (G11) — ~4x files per viewport
9
- 4. Files multi-select + bulk ops (G9) — N dialogs -> one batch pass
10
-
11
- ## Phase 0 — server
12
- - S1 `lib/http-handler.js`: `POST /api/move-confined {path,destDir}` mirroring /api/rename guard (confineToRoots both, refuse allow-root src, 409 on exists, EACCES/EPERM->403 ENOENT->404, global CSRF covers it)
13
- - S2 `scripts/validate-mutations.mjs`: add move case
14
- - S3 `backend.js`: `moveConfined(base,filePath,destDir)` stub
15
-
16
- ## Phase 1 — kit CSS
17
- - `app-shell.css` (one edit): G8 fluid clamps `--ws-rail-w clamp(200,16vw,260)` / sessions `clamp(260,22vw,360)` / pane `clamp(300,24vw,420)`; G2 `.ws-resizer`; G3 `.ws-sessions-collapsed{--ws-sessions-w:0}`; G6 laptop-band pane->drawer at 1100px (transform drawer, not display:none); G13 status bar `--app-status-h:24px`; G14 `.btn*` radius `--r-1` padding `8px 14px` min-height 32 (44 coarse floor kept); G15 `.panel{box-shadow:none}`
18
- - `chat.css` (one edit): G1/G5 `.chat-msg-flat{max-width:var(--measure);margin-inline:auto}`; G7 thread spacing via `calc(token*var(--density))`; G4/G8 `.ds-dash-grid` minmax(240) gap space-2 padding space-3; G10-tier `@media(min-width:1500){minmax(220) gap space-2}`; G12 `@media(min-width:1600){.agentchat-thread{--measure:84ch}}`; G15 `.ds-dash-card`+`.ds-context .panel` border `--bg-3`->`--rule`; `.ds-file-thumb`
19
- - `colors_and_type.css`: confirm `--density` exists (it does: compact .75 / comfortable 1 / spacious 1.35) — no edit
20
-
21
- ## Phase 2 — kit components
22
- - `shell.js`: G2 resizers (role=separator, ArrowLeft/Right 16px, persist ds.ws.w.<col>) + G3 sessions toggle (extend toggleWs map, persist ds.ws.sessions)
23
- - `files.js`: G9 FileRow `{selectable,marked,onToggleMark}` native .ds-file-check + FileGrid `{marked,onSelectAll,onClearSelection}` + BulkBar export + Ctrl/Cmd+A/Escape; G10 FileRow `{draggable,onDragStartFile,onDropOnDir}` + BulkBar move…; G11 `{view,onView}` segmented + image thumb swap in grid mode
24
-
25
- ## Phase 3 — app
26
- - app.js: persisted widths seed; state.files.marked Set + shift-range over _sorted + bulk-delete loop; state.files.view persisted + columns/thumbSrc; onDropOnDir -> B.moveConfined
27
- - backend.js: moveConfined
28
-
29
- ## Phase 4 — build/vendor/witness/push
30
- build.mjs -> vendor -> witness localhost/gm/ (0 errors, thread centered, resizer+persist, sessions-collapse, status 24px, rounded buttons, grid thumbnails, multi-select+bulk+drag, no h-scroll 420/1024/2560, glyph 0) -> validate-mutations 18 -> push kit+app.
31
-
32
- ## Dedupes / conflicts
33
- G1==G5; G2-dash==G4==G8-dash (one metric); G12-pane folds into G8 clamp. Conflict: G2 inline-prop drag overrides G8 clamp base (inline > stylesheet, desired) — widen JS clamps to cover ceilings. G9/G10/G11 CSS already ships as dead CSS (only .ds-file-thumb is new).
@@ -1,43 +0,0 @@
1
- # PUNCHLIST-DESIGN-14 (14th run — design-maturity sweep)
2
-
3
- Workflow `gui-design-14` (run wf_50751bd9-a43): 46 agents, 39 findings, 28 adversarially confirmed. All fixes land in the kit (`/config/workspace/design`), never the agentgui app. ASCII-only; kept typography (middot/ellipsis/dash) preserved.
4
-
5
- ## chat (chat.css / app-shell.css / src/components/chat.js)
6
- 1. [high] ThinkingNode `.chat-thinking/.chat-thinking-text/.chat-thinking-dots` have ZERO base CSS (only `.agentchat-working .chat-thinking-dots` exists) -> add base thinking rules to chat.css reusing `agentchat-dot-bounce` + reduced-motion guard.
7
- 2. [high] `.chat-md table/th/td/hr` unstyled -> markdown tables collapse to bare cells, `---` vanishes. Add to the `.chat-bubble.chat-md` block in app-shell.css.
8
- 3. [med] `.chat-tick` (inline backtick code, chat.js:47) is a dead class -> add a real rule in chat.css.
9
- 4. [med] Composer toolbar geometry mismatch: 40px round `.composer-btn` vs 36px square `.send` -> unify on 32px/`--r-1` in chat.css (later source order wins).
10
- 5. [med] Flat-layout `.chat-msg-flat .chat-md` cramped against role label + flush `.them` tint -> add flat rhythm/inset in chat.css.
11
- 6. [low] Streaming `.chat-stream-pre` drops the lang tab + copy chrome the settled block gets -> add minimal persistent chrome.
12
- 7. [low] Tool-card pre overflows inside the centered `--measure` column -> let tool/code break out wider in flat layout.
13
-
14
- ## files (app-shell.css / src/components/files.js)
15
- 8. [high] `FileSkeleton` markup/CSS disagree: `.ds-file-skeleton` container styled as a single 48px shimmer bar collapsing all rows + double-shimmer -> drop the container height/gradient/anim block + dead `.ds-file-grid-loading`.
16
- 9. [med] filter + sort/select-all/density are two stacked strips with conflicting alignment -> fold filter into one `.ds-file-controls` row.
17
- 10. [med] thumbnail tiles: no per-type icon tint on cells + 4:3 box wastes space for non-image -> add `.ds-file-cell[data-file-type]` tints, denser non-image media.
18
- 11. [med] `data-columns` card-mode is a dead half-wired third layout (flex rows in a grid) not exposed by density -> remove the dead path.
19
- 12. [low] `.ds-file-check` carries dead font/color decls (bracket text gone) + at-rest box too faint -> drop dead decls, strengthen `.ds-check-box` rest border.
20
-
21
- ## sessions (chat.css / src/components/sessions.js)
22
- 13. [med] stale and connecting discs share `var(--amber)` + neither animates -> give stale its own tone/ring.
23
- 14. [med] duplicate `.ds-dash-status.is-running` (green@400 then accent@475) -> delete dead rule, unify running tone across disc/breakdown/card.
24
- 15. [low] `.seg.is-idle` lighter weight than running/error siblings -> add `font-weight:600`.
25
- 16. [low] heartbeat live disc loses its cue under reduced-motion -> static concentric ring fallback.
26
- 17. [low] select-all mixed tick is a 1.5px sub-pixel bar -> thicken to ~2px crisp.
27
- 18. [med] card `.ds-dash-stat` crams elapsed/counter/tok/cost into one mono middot run -> emphasize cost, dim unit suffixes.
28
- 19. [low] conv-list running vs unread are same-color same-shape discs -> differentiate by shape/tone.
29
-
30
- ## shell (app-shell.css / src/components/shell.js)
31
- 20. [high] resizers render past their breakpoints (dead handles dragging vars that no longer affect a fixed drawer) -> media `display:none` matching the 1480/1100/900 staging.
32
- 21. [med] JS `WS_RESIZE_CLAMP` bounds fall below/above the CSS `clamp()` floors/ceilings -> derive from CSS clamp min/max.
33
- 22. [med] resize writes localStorage every pointermove + separator lacks `aria-valuenow/min/max` -> commit on pointerup, add aria-value*.
34
- 23. [low] resizer 8px hit target, no coarse-pointer floor -> `@media (pointer:coarse)` widen/hide.
35
-
36
- ## a11y / motion
37
- 24. [low] voice `vx-*` surfaces omitted from community.css reduced-motion block (infinite `vx-pulse`) -> add to the guard list.
38
- 25. [med] status discs differentiate by COLOR only (no shape channel like the rail tones) -> add non-color channel + disambiguate stale/connecting (overlaps 13).
39
- 26. [low] `.ds-input-bare:focus` (not `:focus-visible`) kills outline -> switch + `--focus-ring-inset`.
40
- 27. [low] conv-list indicators color-only for sighted users (overlaps 19).
41
-
42
- ## marketing site (site/theme.mjs -> kit)
43
- 28. [high] theme.mjs saturated with design content: inline `<style>`+hex, ~12 inline `style=`, deprecated `Btn({primary})`, `↗` glyphs -> new kit `marketing.css` `.site-*` family + base-surface rule + render-blocking CSS link; theme.mjs carries zero design CSS.
@@ -1,37 +0,0 @@
1
- # PUNCHLIST — 15th design-maturity + hardening sweep (2026-06-18)
2
-
3
- Two tracking Workflows (Workflow tool):
4
- - `gui-design-15` (`wf_7ba315a1-9a9`): 8 lenses, 59 agents, 42 findings -> 35 confirmed -> 33 applied in kit + 2 deferred-then-implemented.
5
- - `gui-design-15b` (`wf_5b2861b2-717`): 3 lenses (history-settings/server-boundary/security), 25 agents, 22 findings -> 18 confirmed, all landed.
6
-
7
- ## Kit design (../design) — all confirmed findings landed
8
- - composer-input: cwd-input focus-visible/aria-invalid, `.send.cancel` neutral tone, radius/border tokens, error state, composer-btn size de-dup.
9
- - chat-thread: code-card chrome, flat-user inline code, structured-turn band suppression, ToolCallNode copy buttons.
10
- - files: filtered-empty keeps controls mounted, perm-tag chip, folder-open empty icon, emptyAction CTA, skeleton 12 rows, roving-radiogroup keyboard nav, icon-led density picker (3 new icons).
11
- - sessions: shape-distinct status discs (live/error/connecting/stale), breakdown disc channel, canonical `--stale`, cost-separator emphasis, is-new.is-error compound.
12
- - shell: collapsed-track resizer hide, coarse 44px toggle floor, WS_RESIZE_CLAMP ceilings raised, pane-toggle relocated into crumb.
13
- - tokens-theme: auto-dark `--danger` parity, `--ink-3-dark`/`--paper-3-dark` anchors, paper-island `--warn`/`--sky`, print `--paper`/`--ink`, `--on-color` fills, health-chip/cwd-bar tokens.
14
- - a11y-motion: `.ds-seg-btn` + editor-primitives `:focus-visible`, 4 reduced-motion opt-in guards.
15
- - history-settings: NEW `ShortcutList` export, Row `detail` prop + `.ds-row-detail`, `.empty-state--inline`, `.panel-body` owl rhythm.
16
-
17
- ## App (agentgui) wiring
18
- - app.js: ShortcutList in overlay + keyboard panel, search highlight, empty-state--inline x3, expanded-event detail prop, smart-quote -> ASCII.
19
-
20
- ## Dead code removed
21
- - static/ (webjsx.js, xstate.umd.min.js), scripts/build-rippleui.mjs, scripts/copy-vendor.js; copy-vendor dropped from postinstall.
22
-
23
- ## Server hardening (lib/)
24
- - terminal RCE fail-closed guard (HTTP + WS) + drop client shell/env.
25
- - /api/file+/api/list root confinement gated on PASSWORD/FS_ALLOW_CWD + secret-file block + TEXT_EXTS trim.
26
- - CSRF: JSON-only + Origin==Host (dropped octet-stream/empty-CT bypass).
27
- - constant-time SHA-256 token compare (http-handler + ws-setup).
28
- - chat.sendMessage cwd confined via confineToRoots realPath.
29
- - cookie Secure (conditional) + Max-Age; rate-limit TRUST_PROXY + failed-auth penalty.
30
- - health endpoint omits fs paths under health-exemption.
31
- - CSP script-src nonce (unsafe-inline dropped; importmap nonced).
32
- - agents.models wired through getModelsForAgent.
33
-
34
- ## Witness
35
- - browser-4 (localhost:3009/gm/?token): 0 console errors, dark kit surface, 3 resizers, no h-scroll, ShortcutList 11 rows in overlay+settings, CSP nonce did not break boot.
36
- - validate-mutations.mjs 26/26 PASS on live hardened server.
37
- - kit build 4 lints pass; test.js 10 pass/0 fail.