agentgui 1.0.958 → 1.0.959
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.
- package/.claude/workflows/gui-completion.js +88 -0
- package/AGENTS.md +18 -0
- package/PUNCHLIST-COMPLETION.md +266 -0
- package/lib/http-handler.js +196 -16
- package/package.json +1 -1
- package/scripts/validate-mutations.mjs +40 -0
- package/site/app/js/app.js +771 -104
- package/site/app/js/backend.js +61 -3
- package/site/app/vendor/anentrypoint-design/247420.css +110 -0
- package/site/app/vendor/anentrypoint-design/247420.js +12 -12
|
@@ -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 };
|
package/AGENTS.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# AgentGUI — Agent Notes
|
|
2
2
|
|
|
3
|
+
## GUI completion sweep (2026-06-10) — eighth maximum-effort run
|
|
4
|
+
|
|
5
|
+
Coverage run: hunts what the seven prior sweeps scoped out or never examined. New workflow `.claude/workflows/gui-completion.js` (6 coverage lenses: file-management-crud, routing-deeplinks, conversation-control, history-settings-depth, onboarding-recovery, crosscut-new-surface; hunt -> adversarial verify (kept-typography guard) -> plan). Ran 75 agents -> 46 confirmed gaps (`PUNCHLIST-COMPLETION.md`). ALL implemented.
|
|
6
|
+
|
|
7
|
+
**Files is a full manager now (the read-only scope cut is REVERSED).** Server `lib/http-handler.js`: shared `fsAllowRoots()` (one allowlist construction for list/file/download/mutations), `sanitizeEntryName()` (single path component; rejects separators, traversal, ADS colon, reserved Windows device names, trailing dot/space), `readBody()` (hard cap, 413), `isAllowRoot()` (roots are never mutation targets). New confined routes: `POST /api/rename` `{path,newName}`, `POST /api/delete` `{path,recursive}` (non-empty dir without recursive -> 409), `POST /api/mkdir` `{dir,name}`, `PUT /api/upload-file?dir&name[&overwrite=1]` (raw bytes, 50MB, no multipart dep), `GET /api/stat/<path>` (confined cwd validation). **CSRF guard on every POST/PUT/DELETE**: reject when Sec-Fetch-Site is cross-site AND body is a simple-form content type AND no Authorization header. `/health` gains `projectsDir` + `allowRoots`. All validated live by `scripts/validate-mutations.mjs` (17 checks: traversal/out-of-roots/root-refusal/reserved-names/ADS/CSRF/409 conflicts/round-trip) — run it after touching the mutation surface. App `filesMain`: rename/delete row actions -> kit `PromptDialog`/`ConfirmDialog` (destructive copy states recursion), `new folder` + `upload` toolbar buttons, `DropZone` wraps the grid (keyboard path = the upload button), `UploadProgress` per-file rows, one `loadDir` refresh per mutation, status->plain-copy map (403/404/409/413).
|
|
8
|
+
|
|
9
|
+
**Full hash routing (`HASH_KEYS = tab,sid,dir,file,q,project,section`).** `buildHash` reads all view state; `writeHash({push})`; user nav pushes, passive sync replaces; popstate diffs the full set (no more navTo side-effect clobber - `navTo(tab,{writeHash:false})` from popstate); chat+sid deep-links name `tab=chat` explicitly (bare sid historically meant history); Files dir/file round-trip (reload restores the dir + open preview, Back walks the tree - witnessed live); history q/project restore + re-search; settings `section=` scroll+focus.
|
|
10
|
+
|
|
11
|
+
**Conversation control.** `editAndResend` is two-step (`state.chat.confirmingEdit` banner) and **clears `resumeSid`** on truncation - never `--resume` a session whose tail diverged (retry of the last turn keeps it). Draft persists. Followups derive from the settled turn (tool error -> fix, code fence -> explain, file path -> open). `exportActions` (copy all / export md / export json) when messages exist; `transcriptToMarkdown`. Composer context appends `N turns`; cumulative cost feeds `ContextPane session:{turns,cost}`.
|
|
12
|
+
|
|
13
|
+
**Resilience.** `backend.js streamChat` holds a 12s grace timer on ws closed (reconnect re-subscribes and the turn continues); stream-error banner gains `retry`; `/health` re-probe on ws drop + 10s poll while not-ok + `reconnected` announce + agents refetch; `loadAgents()` with boot backoff + error banner; `errText` plain-language map (raw kept in title); cwd validated via `/api/stat` before save; ccsniff warmup copy escalates after 5s (`loadingText`); `installHint` (npx commands + recheck) when zero agents are available.
|
|
14
|
+
|
|
15
|
+
**History/settings depth.** Event rail finally honors tool_use=purple; `FilterPills` event-type filter (filtered BEFORE the eventsLimit slice); jump-to-first-error; copy-event-payload on expanded rows (kit `Row actions` slot, stopPropagation); per-session JSON export; `SessionMeta` header (cwd/duration/sid/events); search-hit `highlight` mark + title windowed around the first match + relative timestamps. Settings: `server` panel (version/uptime/ws/roots+copy/projectsDir), `keyboard` panel + ?-overlay fed by ONE `SHORTCUTS` array, export-chat + local-data size line; live sort/errorsOnly + files sort persist to localStorage (live selection Set deliberately NOT persisted - stale sids would arm stop-selected wrongly); two-step stop-all/stop-selected (`confirmingStop*` + 4s auto-reset).
|
|
16
|
+
|
|
17
|
+
**Kit additions** (`anentrypoint-design`): files.js `FileRow {actions,busy}` + permission-disabled rename/delete + no download on dirs; content.js `Row {highlight,actions}` + `Panel/PageHeader {id}` + `FilterPills`; sessions.js `SessionMeta` + dashboard `confirmingStopAll/Selected + onArmStop*` + `ConversationList {loadingText}`; agent-chat.js `installHint/exportActions/confirmEdit+onArmEdit`; context-pane.js `session {turns,cost}`. NOTE: `.ds-session-meta` was already taken (ConversationList row meta) - the strip is `.ds-session-meta-strip`.
|
|
18
|
+
|
|
19
|
+
**Witness.** localhost:3000/gm/ 0 console/page errors on chat/files/history/settings/live; files deep-link in+out+Back witnessed; FilterPills/SessionMeta/export/purple-rails(45) live; server+keyboard panels render; 420px live tab no h-scroll; decorative-glyph count 0. The stale scope-cut lines from the 7th run (read-only browser / upload not reusable / retry last-turn-only) are superseded by this section.
|
|
20
|
+
|
|
3
21
|
## GUI cohesion sweep (2026-06-10) — seventh maximum-effort run
|
|
4
22
|
|
|
5
23
|
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).
|