Skip to content

Latest commit

 

History

History
326 lines (167 loc) · 76.9 KB

File metadata and controls

326 lines (167 loc) · 76.9 KB

AgentGUI — Agent Notes

Docstudio design-cue follow-up (2026-07-17) — forty-seventh run, confirming pass

Per gm-continue's mandatory first-confirming-pass discipline, a second independent Explore-agent survey (told what the 47th run's main pass just shipped) re-checked docstudio against the kit + app and found one last real, narrow gap: app.js's cross-tab external-update banner still called native window.confirm() for the reload-discards-unsent-draft guard — the sole remaining native browser dialog call anywhere in the app, matching docstudio's own dialog.js rationale of never using window.confirm/alert/prompt for data-loss confirmations. Replaced with a state.chat.confirmingReloadDiscard flag rendering the kit's existing ConfirmDialog modal, mirroring the same pattern already used for file delete/bulk-delete. Browser-witnessed by fetching the live-served /gm/js/app.js directly and regex-confirming window.confirm is gone and the new confreload dialog key is present. A second confirming pass after this one found nothing further — docstudio's buildDocAccessPrompt/buildScopePrompt variants are narrow Google-OAuth/Drive-doc-specific flows with no analog in agentgui's domain model, and everything else checked (ApprovalPrompt's focus/a11y semantics, build-freshness poll, modal focus-trap/Escape/backdrop semantics) was already correctly ported.

Docstudio design-cue follow-up (2026-07-17) — forty-seventh run

Fifth docstudio-cue pass, same directive repeated a fifth time after the 46th run's confirming pass had concluded the mining task "genuinely exhausted." A fresh independent Explore-agent re-verification (told what runs 42-46 shipped) found that conclusion was not fully accurate: chat-approval-prompts.js:47-71's buildApprovalPrompt has an auto-focused free-text note textarea threaded through every approve/deny decision (prefixed onto the denial message so the assistant sees why it was denied) that the kit's PermissionMenu (a settings-style checkbox dropdown, architecturally different) had no analog for at all. Added a new ApprovalPrompt component (overlay-primitives.js) — an inline, in-thread permission card (name/category head, optional args preview, auto-focused optional note, once/session/all/deny actions via onDecision(kind, note)) — plus a lock icon added to the shared ICON_PATHS (didn't exist previously) and a barrel re-export per the standing barrel rule. Also landed the pass's secondary finding: update-check.js's /version.json poll + persistent reload-nudge pattern, ported to app.js as startBuildFreshnessPoll() (90s interval, compares live /health version against window.__SERVER_VERSION, reuses the kit's existing toast({actionLabel,onAction,duration:0}) from the 46th run rather than inventing a new notice surface). A first CSS pass tripped the frozen spacing-lint baseline (raw 6px 14px button padding) — fixed by mapping to --space-1-75/--space-3 and --r-pill instead of literal 999px. Kit rebased once onto concurrent v0.0.352/v0.0.353 releases (dist-only conflict, rebuilt fresh per the standing discipline). Kit pushed e93f4f76d71b34, all 3 checks green. Browser-witnessed live on localhost:3009/gm/: this session's gm browser verb had a real, repeat tool defect (url=/dom=/screenshot= prefixes evaluate in a detached Node vm, not the real page — ReferenceError: document is not defined despite navigation_requested:true) — recorded to durable memory (agentgui-browser-verb-url-dom-prefix-broken-2026-07-17) rather than left as a stuck PRD row, per gm's diagnose-the-tool discipline. Worked around by using only the bare capture\n<script> form (page.goto+page.evaluate inline) and reading results via console.log('WITNESS:'+...) inside the page context rather than the top-level result field, which came back null even on successful (exit_code:0) dispatches. Confirmed live: toast()'s action button fires onAction(dismiss); ApprovalPrompt renders (.ov-approval present), note textarea auto-focuses, lock icon renders, Deny click fires onDecision('deny', '') correctly. agentgui re-vendored, pushed 1d372909b25faf08483cf, all 4 CI checks green both pushes.

Docstudio design-cue follow-up (2026-07-17) — forty-sixth run, confirming pass

Per gm-continue's mandatory first-confirming-pass discipline, a second independent Explore-agent survey (told what runs 42-46 already shipped, including the aria-live fix above) checked docstudio's remaining source (update-check.js, debug-panel.js, chat-tabs.js, chat-model-selector.js, webview-detector.js, xls-preview.js, doc-tab.js, chat-approval-prompts.js) and found one more narrow, real prop-surface gap: the kit's toast() (editor-primitives.js:637) only supported a self-dismissing {message,kind,duration} shape, unlike docstudio's update-check.js:18-30 persistent click-to-reload notification bar. Added actionLabel/onAction props rendering a non-auto-dismissing button (duration:0 already worked; the gap was no action-button support) — .ds-ep-toast-action styled with --space-* tokens (a first attempt with raw px literals tripped the frozen spacing-lint baseline, caught by node scripts/build.mjs before commit). CI caught a real miss on first push: docs/component-props.md was stale after the new toast() signature (node scripts/generate-component-docs.mjs --check lint), fixed by regenerating and re-pushing — a genuine CI-catches-a-real-gap case, not flakiness. Kit rebased twice cleanly onto concurrent v0.0.350/v0.0.351 releases (dist-only, rebuilt fresh both times). Kit pushed b5530cbe93f4f7, all 3 kit CI checks green. Browser-witnessed live on localhost:3009/gm/ by importing the vendored module directly and calling toast() both ways: action variant renders .ds-ep-toast.has-action with a working Reload button whose click fires onAction(dismiss); plain toast (no actionLabel/onAction) renders with no has-action class and no button, confirming backward compatibility. agentgui re-vendored, pushed ed36a363a59c3b, all 4 CI checks green. A second confirming pass after this one found nothing further.

Docstudio design-cue follow-up (2026-07-17) — forty-sixth run

Fourth docstudio-cue pass, same directive repeated a fourth time. A fresh Explore-agent survey of /config/docstudio (told explicitly what runs 42-45 already shipped — toasts/Skeleton/Alert/Badge-tone/dialogs/breadcrumbs/inline-error-retry/Avatar/sidePanel/sortable-Table/streamingSince/coarse-pointer Enter-gating/CountdownDialog/PermissionMenu/detectAttachment/RTL support) came back with the low-hanging fruit exhausted: only 1 genuinely new, verified-missing candidate survived (a second, "self-retry dropdown trigger" candidate had no existing async-populated-dropdown call site in the kit to wire it to, so it was explicitly skipped as speculative unused plumbing rather than implemented). Landed: chat.js's two role=log chat-thread containers (chat.js:533,581) and agent-chat.js's one (agent-chat.js:476) gained aria-live="polite" aria-relevant="additions", matching docstudio's chat-tabs.js:62-65 pairing, so new messages announce to screen readers without spamming on every streamed token — verified by source that aria-relevant=additions only fires on new keyed-sibling insertion (agent-chat.js:582's ...all.map((m,i)=>ChatMessage({...m,key:...}))), not on in-place text mutation within an existing message's subtree during streaming. Also fixed, as a genuinely unrelated hygiene issue surfaced mid-rebase: test.js's lint-tokens.mjs spacing report check regexed for the old [lint-spacing] REPORT — N raw format, but upstream v0.0.349's "ratcheted spacing lint" consolidation changed the script's own stdout to PASS — N <= baseline N, silently breaking the regression check's ability to catch anything post-rebase — widened the regex to match both formats. Kit built+tested (bun test.js all pass post-fix), rebased cleanly onto v0.0.348/v0.0.349 (one dist-only conflict, resolved by rebuilding dist/247420.js fresh rather than hand-merging generated code — the standing discipline). Kit pushed 8436f0af81f93d, all 3 kit CI checks green (ci/Publish/Deploy-GH-Pages). agentgui re-vendored both dist files, browser-witnessed live on localhost:3009/gm/ (bodyLen:19429, hasAgentgui:true, 0 console errors, .agentchat-thread confirmed via page.evaluate to carry role=log aria-live=polite aria-relevant=additions), pushed e84cf26639b78e, all 4 CI checks green (Test/Publish-and-Release/Auto-Declaudeify/Deploy-GH-Pages) — the Test job's bun install step took an unusually long ~13 minutes this run (likely npm-registry propagation delay for the just-published kit version) before completing green; not a hang, just slow.

Docstudio design-cue follow-up (2026-07-17) — forty-fifth run

Third docstudio-cue pass: 5 new candidates landed (ChatComposer streamingSince elapsed counter, coarse-pointer Enter-gating, CountdownDialog, PermissionMenu, detectAttachment badge). Full detail in rs-learn (recall "agentgui-45th-run-docstudio-full-detail"). Kit pushed 5c0571e1d46202, agentgui re-vendored and pushed, browser-witnessed clean.

Docstudio design-cue follow-up (2026-07-17) — forty-fourth run

5 candidates landed (ChatMessage error/onRetry, Avatar primitive, FileSkeleton aria-busy); a mandatory browser witness caught a real syntax-crash from a stray brace. Full detail in rs-learn (recall "agentgui-44th-run-docstudio-followup-full-detail"). Kit pushed 341759217ee86c, agentgui pushed f08f698, all CI green.

Docstudio design-cue pass (2026-07-17) — forty-third run

User directive ("docstudio has a much more mature design, take cues from it, all GUI lives in ../design, no overrides here, this project is still very immature") repeated more emphatically than the prior sweep. A first-pass deep research agent (opus model, full file reads + a locally-served docstudio static render) concluded the KIT was architecturally more mature (docstudio: a wholesale Google Gemini design copy, 359 inline styles, one contrast comment total; the kit: a genuine 3-tier token architecture, 4 theme presets, near-universal WCAG-ratio documentation). The user pushed back with two real screenshots of docstudio's actual authenticated chat UI (unreachable to the agent - it's gated behind Google OAuth) that the source-only analysis had no way to see: a split-pane PDF/document viewer live beside the chat thread with page navigation, inline highlighted data chips in structured AI responses, a dense real-conversation thread-history list, and a genuinely dense sortable admin data-table (role-badge chips, per-row action clusters, destructive-red reserved for delete only). Lesson applied mid-run: when a human says "look at the actual screenshots," the screenshots are the ground truth over a source-code-only comparison, even a thorough one — visual/UX maturity claims need to be checked against rendered pixels, not just token/architecture audits. Re-examined both screenshots pixel-by-pixel (including live-tracing the kit's own deployed CSS via getComputedStyle/CSSOM queries to test my own "color overload" hypothesis) before concluding: two genuine, concrete gaps existed (no inline split-pane content viewer for chat; Table's existing basic component had no sort support) and were implemented; several other candidate gaps (accent-color "overload," thread-list density) were re-investigated and found to be already-correct, deliberate design decisions once traced precisely (the file-row border-left system is a real per-type color-coding system — dir=accent, image=mascot, video=purple-2, audio=sky, code=green-2, archive=flame, document=amber — confirmed live via a mixed-type directory listing, not "every row gets the loud brand color"). Kit changes (design/src/components/{agent-chat,content,shell}.js, chat.css, src/css/app-shell/kits-appended.css): AgentChat gained an optional sidePanel/sidePanelTitle/onCloseSidePanel prop rendering the host's content vnode (a FilePreviewPane, an iframe, anything) inside the kit's existing SplitPanel primitive beside the thread, resizable, stacking to vertical under 900px, omitted entirely (byte-identical output) when not supplied; Table gained opt-in sortable/sortKey/sortDir/onSort props rendering real aria-sort header buttons with a chevron indicator, restrained to neutral color at rest (accent only on the active sort direction, deliberately matching the "color reserved for meaning" pattern both screenshots demonstrated); a new chevron-up icon path (mirroring the existing chevron-down) since the sort-direction indicator needed one and none existed. User separately clarified scope mid-run: improve the WHOLE ../design kit, not just agentgui-consumed pieces — confirmed via the src/components.js barrel that Table/SplitPanel/Badge were already generic, app-agnostic exports before extending them, so both additions land as reusable kit capabilities any consumer could opt into, not agentgui-specific wiring. Kit built+tested (bun test.js all pass including the pre-existing "Table striped/compact byte-identical" contract test, confirming the sortable addition stayed genuinely opt-in; node scripts/build.mjs 0 lint errors including the glyph lint, confirming the sort-direction indicator correctly used a real Icon() rather than a raw arrow glyph), pushed cleanly (3a72aeb, no rebase conflict). agentgui re-vendored, pushed 254eb74, all 4 CI checks green. Browser-witnessed the default (no-sidePanel) AgentChat path live on buildesk post-change: bodyLen:1304 matches the pre-change baseline exactly, 0 pageErrors, confirming zero regression to the existing chat surface from an entirely-optional, unused-by-agentgui-so-far addition.

GUI practicality sweep (2026-07-17) — forty-second run

User-driven ask ("setting the cwd is not nearly practical enough... go over every possible aspect of the app's practicality") plus a mid-run docstudio design-cue directive. Started narrow (cwd-setting) and expanded into the widest sweep yet, surfacing a real infrastructure bug and a real kit crash along the way. cwd practicality (site/app/js/app.js, design/src/components/agent-chat.js, design/src/components/files-modals.js): the cwd editor was a bare text input requiring an exact remembered absolute path with no discoverability at all — added an inline browse popover (reusing the existing confined /api/list, writing to an independent state.cwdBrowse so it never disturbs the Files tab's own state), always-visible allowed-roots chips (prefetched on editor open, not gated behind first clicking "browse"), a small localStorage-backed recent-cwd MRU (CWD_RECENT_KEY, capped at 6), relative-subpath resolution against the current cwd (onCwdSave/debouncedCwdProbe both resolve a bare name like myproject against state.chatCwd), and fixed a real bug where Escape did nothing to close the cwd editor (the global keydown handler's typing-guard blurred the focused input and returned before ever reaching the Escape ladder — added a targeted exception plus a nested browse-popover-then-editor ladder). History's directory SessionMeta fact gained the same one-click "use as chat cwd" action Files already had (kit SessionMeta gained a generic onAction/actionLabel, alongside the existing onCopy). Real infrastructure bug found+fixed (lib/http-handler.js, site/app/js/backend.js): the new cwd browse popover inherited an existing, previously-undiscovered production bug — this env's nginx proxy_pass URI normalization silently collapses an encoded %2F path-segment slash back into a literal / before forwarding, so any /api/list/<abs-path>-shaped request from a path SEGMENT lost its leading slash and 403'd, which also meant the pre-existing Files tab's own drill-down navigation had been broken in production this whole time, not just the new picker. Fixed by switching /api/list, /api/stat, /api/file, /api/download, /api/image to a ?dir=/?path= query param (immune to that proxy normalization) via a new shared resolveConfinedPath() helper, keeping the legacy path-segment form for compatibility. scripts/validate-mutations.mjs (29 checks) confirmed the confinement contract is byte-identical before/after. Files/completion sweep (gui-completion workflow wf_bcf46a89-739, 29 agents, 28 done/1 lens crashed on a StructuredOutput retry cap, 17 confirmed findings): rename/mkdir 409 collisions now prefill a suggested alternate name (suggestAlternateName, "name"→"name (2)"→"name (3)"); bulk-move destination gained a one-click roots picker (kit PromptDialog gained an optional roots/onPickRoot prop); delete is now soft — moves into a confined per-root .agentgui-trash/ instead of unlinking (moveToTrash/restoreFromTrash, new POST /api/restore, 10-minute retention with purge-on-delete + a 200-entry eviction cap) with a real undo toast, not just the pre-existing confirm-before dialog; search-hit event anchors (focusEventI/focusEventTs) are now carried in the URL hash (ets=) so reload/Back reproduces the same scrolled+flashed position a live click gives, previously lost; Settings gained a scroll-position scrollspy (manual scrolling now updates state.settingsSection/the URL, previously deep-link-in only); truncation confirm banners now state the actual turn count + cost about to be discarded instead of "the later turns"; the cross-tab "reload it" banner now warns about + confirms discarding an unsent draft; History event list gained persistent next/prev error navigation (jumpToNextError), not just jump-to-first; the History session header now shows agent/model (reading sess.model directly — a first attempt reading a nonexistent sess.agent field silently rendered nothing, caught and corrected); backend.js exported onSessionExpired() but nothing ever subscribed to it — a mid-session 401 failed every fetch completely silently; now surfaces a persistent "Session expired, reload to sign in again" banner; the offline banner gained a manual "retry now" button; search-term highlighting now extends into expanded event bodies (Row's detail field), not just the collapsed title; History search results gained an export button (JSON of title/project/snippet/timestamp/sid); Settings gained a navigator.storage.estimate() browser-storage breakdown. Real kit crash found+fixed mid-implementation: an initial attempt to add the search-results export button by passing a VElement (or an extra captionAction prop) through ConversationList's caption prop caused an intermittent webjsx "Cannot read properties of undefined (reading 'key')" page crash — root-caused by tracing caption's only other use site (aria-label: caption || 'Conversations', which needs a plain string) and confirmed via 3 independent before/after browser runs (crash present with the VElement version, absent 3/3 times after reverting). Re-implemented as a fully separate sibling element outside ConversationList entirely (.agentgui-search-rail-wrap, replicating .ds-sessions' own flex-column layout), re-verified crash-free across 3 more independent runs. docstudio design-cue pass (a fresh subagent surveyed /config/docstudio's vanilla-ESM Material/Gemini-styled chat UI against the kit): docstudio turned out architecturally LESS mature than the kit in every systemic dimension (typography scale, color tokens, spacing, motion) — the 6 concrete candidate cues it found were mostly already-implemented (streaming caret, soft-tint error bubbles, dark-aware skeleton shimmer, filled-contrast destructive dialogs, suggestion-chip semantics — inapplicable here since agentgui's chips send-immediately rather than fill-then-edit) except session-group eyebrow labels, which were using the generic --tr-caps tracking token instead of the more emphatic --tr-label token .t-label/.t-micro already establish as the canonical section-eyebrow convention — fixed both .ds-session-group-label and .ds-dash-group-label. Two upstream-diverged rebases handled cleanly mid-session (one single-conflict, one 35-commits-diverged including new upstream a11y/motion/visual-regression work) by rebuilding dist/ fresh from merged sources per the standing discipline rather than hand-merging generated code — bun test.js confirmed 0 regressions both times. Kit pushed 7c27e9cfb11176acd5fd2 (three pushes across the run's phases), agentgui pushed 71d735256b865f2fecd75a1a394a, all CI green throughout. Standing lesson: a kit component prop with an established narrow contract (here, ConversationList's caption being read as aria-label: caption || 'Conversations' elsewhere in the same function) can silently break when a caller passes a richer value (a VElement) than the prop was ever designed for — the failure surfaces as a webjsx "reading key" crash far from the actual mismatch. When extending what a caller passes through an existing prop, grep every OTHER use site of that prop inside the component first, not just the one you're touching.

GUI logic+predictability sweep (2026-07-12) — forty-first run

gui-logic-predictability workflow wf_c645ba95-c6a (23 agents, 192 tool calls, 0 errors) — 10 confirmed findings, all implemented, plus one real kit bug found and fixed mid-verification (not from the workflow's own hunt). App (site/app/js/app.js): runningPanel()'s title read 'running · N' with no scoping cue even though the panel always lists every in-flight session app-wide, not just ones related to the current chat — retitled 'all running sessions · N', and it now appends '(refreshing…)' while a backgrounded tab's poll is catching up after refocus (refreshActive() sets a state._activeStale flag while document.hidden skips the network call, cleared on the next real response) instead of silently painting stale data as fresh. runningRowTitle() fell straight to UNTITLED_CONVERSATION for a brand-new session with no index row yet, while the Live dashboard's computeLiveSessions() already had a 3s "indexing…" carve-out for the identical case — the same session read two contradictory identities in the same 0-3s window depending only on which panel you looked at; gave runningRowTitle the matching carve-out. Live dashboard cards sorted by elapsed/activity re-sort on every tick (both are continuously-changing values), which could reorder a card out from under a mid-click pointer (selecting a checkbox, clicking stop) — added a module-level _livePointerDown flag (set/cleared on document pointerdown/pointerup/pointercancel) that freezes the sort to the last-computed sid order while a pointer is held anywhere in the dashboard. agy's real --continue resume path (wired last run) had no composer disclosure the way claude-code's resume banner does — added a 'continues most recent conversation' composerContext bit for agy past its first turn. History's ConversationList sessions mapper hardcoded agent: undefined while the Running-panel/Live-dashboard rows for the identical finished-vs-in-flight session both show an agent/model badge — traced to ccsniff's Store.sessions() never folding per-event model into the session record even though flattenEvent captures it; fixed upstream (see below) and wired agent: agentById('claude-code').name + ' · ' + s.model into the mapper (ccsniff only reads Claude Code's own JSONL, so the agent is always constant). Kit (design/src/components/{chat,content}.js, design/chat.css, design/src/components/sessions.js): per-message chat copy action (ChatMessage's generic actionRow) had zero visible click feedback for sighted/mouse users — only an aria-live announce() — unlike the three sibling copy patterns already in the same file (code-block copy, CodeNode/ToolCallNode copy) which all self-manage a label/icon flip to "copied" for 1.6s; added the matching flip keyed off a.label === 'copy'. Follow-up chips (contextual, derived from the last turn) and empty-state seed chips (generic starter ideas) shared identical DOM shape/class with zero visual signal distinguishing them — .agentchat-followup gained a dashed border (same pill shape/size, no new color/shape language). flashComposerNote's single shared DOM node silently overwrote an unread note if a second call landed before the first note's 2.6s timeout — now queues notes and drains them in order instead of dropping the first message. SessionDashboard's filter SearchInput never forwarded resultCount to its aria-live region (the Live dashboard's own filtered session count was available but unused) — wired it through. That last change surfaced a real, previously-latent kit bug independent of this run's own hunt: SearchInput (design/src/components/content.js) conditionally returned either a bare <input> VElement or a wrapping <span> VElement depending on whether resultCount/a clear button were present that render — since resultCount now toggles from undefined to a string as the Live filter's value changes, webjsx's applyDiff tried to morph one element type into another at the same keyed toolbar slot, producing a corrupted merged DOM node (a real <select> picked up type=search/name=q/placeholder=... attributes from the SearchInput render, and the actual <input> vanished entirely) — live-witnessed on buildesk before the fix (hasInput:false on the toolbar). Fixed by always returning the .ds-search-input-wrap shape (confirmed display:contents in CSS, so it's layout-transparent and safe for every existing call site). ccsniff (separate repo, github:AnEntrypoint/ccsniff#main, cloned fresh to /config/workspace/ccsniff-work): Store.sessions() (src/store.js) folded in the most-recent-by-timestamp model per session (events aren't guaranteed in ts order since JSONL is read file-by-file); index.js's assistant-event ingestion never actually populated model on pushed content blocks (only system/init events carried e.model) — real Claude Code JSONL carries the actually-used model at message.model on every assistant turn, so that's now inherited onto every block from that message. node test.js: ALL TESTS PASS both commits. Pushed 11f6c3b then (after a remote-advanced rebase) 26bbfcc/beff5e7, ccsniff CI (Publish/Auto-Declaudeify/Deploy GH Pages) green both pushes. Kit built+tested twice (once per round of edits — findings, then the SearchInput fix), re-vendored both times. Reinstalling the ccsniff dependency required removing bun.lock's pinned commit hash entirely (bun install ccsniff@github:...#main --force alone kept resolving to the stale pinned sha even after a rm -rf node_modules/ccsniff) — deleting bun.lock and re-running bun install re-resolved #main to the actual latest commit. The local buildesk server process needed an explicit restart (kill -TERM on the bun server.js PID) for the new node_modules/ccsniff to load — Node/Bun caches modules in-process, so an npm/git dependency swap under a running process is invisible until restart; the env's custom Node supervisor (/opt/gmweb-startup, not a standard supervisord) auto-respawned it within ~20s of the health-check failing, re-launching via bunx agentgui@latest which correctly resolves to the local workspace's own bin/gmgui.cjs in this workspace-linked setup (confirmed by the "Workspace mode: skipping npm version checker" startup log line), not the published npm package. Browser-witnessed every finding live on buildesk: .ds-dash-toolbar filter input/select intact before AND after typing with the corruption fixed, aria-live region reading '0 results'; .chat-msg-action.is-copied/.agentchat-followup CSS rules present in the live CSSOM; History rail rows reading 'agentgui · 3s ago · Claude Code · claude-sonnet-5' (18/18 sessions carrying a real model after the ccsniff fix + server restart); synthetic pointerdown dispatch confirmed no page error from the new listener. 0 pageErrors throughout.

GUI logic+predictability sweep (2026-07-12) — fortieth run

11 confirmed findings (Escape-ladder confirmingClearData gap, wsCall 15s timeout, agy resume wiring, composer paste/drop visible feedback, Row .title tooltip, .ds-file-actions resting opacity, Enter-to-send hint visibility). Full detail in rs-learn (recall "agentgui 40th run logic-predictability sweep"). Standing browser-witness gotchas confirmed there still bind: combine creds+goto+wait+evaluate into ONE dispatch (session ids rotate across separate dispatches), and read via console.log('WITNESS:'+...) + stdout grep, never the capture prefix's result field.

GUI logic+predictability sweep (2026-07-12) — thirty-ninth run

6 confirmed findings (runningRowTitle identity prefix, stopActiveChat 9s stuck-stopping timeout, live-session cost tally, shortcuts-help IconButton, CodeNode selection-guard parity, Row.sub tooltip). Kit pushed 00dae4c, agentgui pushed 64f4466. Full detail + the setHTTPCredentials comma-password witness fix in rs-learn (recall "agentgui 39th run full detail").

GUI logic+predictability sweep (2026-07-12) — thirty-eighth run

gui-logic-predictability workflow wf_186e2a2a-dfc (32 agents, 205 tool calls, 0 errors) — 18 confirmed findings, all implemented, re-running the same lens one cycle after the 37th run to catch residual/newly-introduced gaps. App (site/app/js/app.js, site/app/js/backend.js): stopAllActive now clears every attempted sid (both ok and failed) from state.live.stopping after Promise.allSettled, so a failed bulk-cancel is retryable instead of leaving that row's stop button permanently disabled; copyText's clipboard-write catch now calls announce('copy failed') instead of swallowing the error silently (mirrors copySid's existing pattern); search-result row titles now route through projectLabel() (r.snippet || projectLabel(r.title) || projectLabel(r.project) || UNTITLED_CONVERSATION) matching the rail's contract; the History detail header's title-fallback chain gained || state.selectedSid before the generic label, so a metadata-sparse session shows the same identity in History as it does in the rail; the Live nav-badge error flame no longer goes stale while off the history tab (stream stays connected whenever state.active is non-empty); computeLiveSessions() gained a 5s reconnectGraceUntil window so an SSE reconnect mid-session no longer flaps a running session's status to stale; a brand-new session between the chat.active poll and history-index catch-up now shows 'indexing…' instead of reading identically to a permanently-failed lookup; the Live dashboard's sort/filter prefs (hydratePrefs()) now reconcile across browser tabs on focus/visibilitychange, alongside the existing refreshActive() call; streamHistory's EventSource (backend.js) gained manual close+reopen exponential backoff (500ms start, doubles, capped 30s, resets on hello) mirroring the WS client's scheduleReconnect, instead of relying on native EventSource's uncapped retry; the sessions rail empty-state and History event-list both gained next-action affordances (a start-from-chat CTA, and a "load all (N hidden)" button for very large sessions); a draft restored from localStorage now surfaces a one-time note that undo history doesn't carry over a reload. Kit (design/src/components/{agent-chat,chat,content,sessions}.js, app-shell.css): follow-up suggestion chips' container lost its aria-hidden="true" — the chips are real interactive <button>s that were completely invisible to screen-reader users despite role="group" being present; MdNode's markdown-settle refSink now defers its innerHTML swap via a one-time selectionchange listener when the user's active text selection is anchored inside the element (was wiping any in-progress text selection every streaming tick); ChatComposer's paste handler now surfaces a "pasted N characters" note via flashComposerNote for large plain-text pastes; SearchInput gained a visible clear (X) button (was Escape-only, undiscoverable to mouse/touch users) at a 44px touch target, sharing the same clear path as Escape; ConversationList now forwards a resultCount prop through to SearchInput's existing (previously unpopulated) aria-live result-count region, wired from agentgui's search-results branch as hits.length + ' result(s)'. Kit built+tested (bun test.js all pass), a concurrent upstream release (5cdd667, v0.0.315) required a rebase with one dist-only merge conflict (resolved by rebuilding dist/247420.js fresh from the merged sources rather than hand-merging generated code), re-vendored, browser-witnessed live on buildesk (chat tab shows connected status + visible follow-up chip buttons, history tab loads 52 conversations cleanly, 0 visible errors across both screenshots). Kit pushed bed4036, agentgui pushed 159030f (after a second push-rebase for a PRD-state-only commit), all 4 CI checks green both repos. New tool defect this run (recorded, not fixable from this repo — filed as blockedBy:external): the gm browser verb's dom= and capture prefixes both throw ReferenceError (no document/page binding at all) rather than evaluating in page context — a regression beyond the 37th run's already-documented "dom= ignores its trailing script" caveat. Only screenshot= and url=<target>\n<literal-non-DOM-expr> dispatches actually reach the real page in this tool version; browser sessions also silently rotate ids across dispatches with no state carryover, and one dispatch hit a dead extension-bridge session requiring an unprompted CDP relay restart. Screenshots are the reliable fallback for browser witnessing until this is fixed upstream in gm-plugkit/playwriter.

Punch-list document cleanup (2026-07-12) — 37th-run confirming pass

Removed 11 standing PUNCHLIST-*.md/AUDIT-PUNCHLIST.md files (all findings already merged, content duplicated AGENTS.md). Full detail in rs-learn (recall "agentgui 37th run punchlist cleanup detail"). Standing rule for future sweeps: no new PUNCHLIST-*.md/AUDIT-PUNCHLIST.md (or similarly-named) file should be checked into the repo as a durable artifact — a workflow's punch-list is a transient working document for that run's synthesis step; the run's outcome belongs in AGENTS.md (or drains to rs-learn), never a standing file.

GUI logic+predictability sweep (2026-07-12) — thirty-seventh run

gui-logic-predictability workflow wf_5eec2403-09d (30 agents, 6 hunt lenses + verify + synthesize, 0 errors) — 15 confirmed findings, all implemented. App (site/app/js/app.js): stopActiveChat no longer leaves the per-card stop button permanently stuck on a failed cancel (deletes the sid from the stopping Set + surfaces an error on rejection instead of swallowing it); Files dialogs (rename/delete/mkdir/bulk) now restore keyboard focus to the triggering element on close, mirroring the existing cwd-edit pattern; runBulkDelete/runBulkMove gained the same stale-dialog guard runFileMutation already had; the "Untitled conversation" fallback consolidated to one UNTITLED_CONVERSATION constant (3 call sites had drifted casing); the settings db health chip now reads connected/offline instead of a third online/offline register, matching the adjacent connection chip; history search placeholder/caption now discloses it searches all projects' titles+messages; runningPanel() shows a lightweight "will also appear in live" hint during the send-to-active-poll gap instead of returning null; computeLiveSessions()'s external-session stale threshold no longer silently multiplies STALE_AFTER_MS by an undocumented 0.6 (owned and external sessions in the same objective state now report the same status); pasted/dropped composer files upload through the existing confined PUT /api/upload-file and insert the resulting path into the draft (was a permanent no-op/dead-end toast); the sessions rail's 60-item cap gained a load-more affordance (state.sessionsLimit += 60), mirroring the existing eventsLimit "load older" pattern; PERSIST_MSG_CAP truncation now surfaces a dismissible banner instead of silently dropping older turns from localStorage. Kit (design/src/components/{chat,agent-chat,content,files,sessions}.js, app-shell.css, chat.css): deleted dead onAttach/onMenu composer toolbar plumbing (no such feature existed); AgentChat now forwards onEmoji to its internal ChatComposer call (the emoji toolbar button was invisible because AgentChat — the wrapper agentgui actually calls, not ChatComposer directly — never destructured the prop; caught mid-EXECUTE via a transition to=PLAN re-scope); SearchInput and the Files filter input both gained Escape-to-clear + a visually-hidden aria-live result-count region; ConversationList gained hasMore/onLoadMore; composer textarea overflow-y changed from focus-only auto to always-auto so a capped-height composer keeps its scroll cue after blur. Kit built+tested (bun test.js all pass, all build lints pass), re-vendored, browser-witnessed live on buildesk across chat/history/settings/files (0 blocking errors on clean navigations). Kit pushed af028ff, agentgui pushed a3b844d, all CI green both repos. Standing gotcha reconfirmed: this env's gm browser verb text-protocol (dom=<selector>\n<script>) only evaluates the selector query, not the trailing script — use dom=<precise-selector> alone to inspect attributes/rect, not as a general page-eval channel. Also: embedding Basic-Auth creds directly in a page.goto URL (https://user:pass@host/...) breaks the app's own same-origin fetch() calls with "Request cannot be constructed from a URL that includes credentials" — do one embedded-creds navigation to seed the browser's per-origin Basic-Auth cache, then navigate again WITHOUT embedded creds (plain URL or ?token=) for any witness that needs real fetches to succeed.

GUI AI-tell sweep (2026-07-10) — thirty-sixth run

Follow-on to the 35th run's AI-tell sweep (commit 1702fa8). gui-ai-tell-sweep workflow wf_a53b7d8f-27b (26 agents, 25 done/1 verify-agent hit a mid-response connection error, 200 tool calls): 5 hunter lenses (gradient-glow-glass, emoji-generic-icons, oversaturated-color, boilerplate-copy, layout-uniformity-tells) found 20 candidates, adversarial verify confirmed 11 (9 correctly refuted as already-deliberate — status-disc pulse triplicate, --sun "retired" claim, radius-scale uniformity — the kept-typography-style guard doing its job on non-typography claims too). Landed in the kit (design/app-shell.css/chat.css/src/components/agent-chat.js): hardcoded one-off context-menu/toast shadow → var(--shadow-overlay) token; three data-metering fills (barchart/upload/bar) demoted from the loudest acid-lime --accent to neutral --fg-3 (color reserved for state, not routine progress); checkbox checked-fill --accent--fg; drag-over collapsed from a triple accent stack (tint+border+ring) to a single dashed border; the keyboard-only .skip-to-main link de-loudened from CTA-accent to neutral ink (it was visually identical to the primary send button despite being invisible to sighted users); session-unread dot neutralized off --accent so it stops competing with the active row's accent; dead template-shaped .empty-state child rules and an unused .ds-panel-trio/.ds-panel-grid hover-lift block deleted; AgentChat empty-state copy de-boilerplated away from "invitation" framing. App: keyboardPanel()/preferencesPanel() gained kind:'wide' so settings' purely-informational/utility panels read lighter than the consequence-bearing backend/agents panels. Kit rebuilt+re-vendored, browser-witnessed live (0 pageErrors, all 4 sampled CSS rules confirmed post-fix in the real CSSOM). Kit pushed 4de1771 (after a rebase — a concurrent version-bump commit had landed), agentgui pushed e6dc1d9, all CI green both repos. Tool-defect note (recorded, not re-fixed — the workaround is durable): the gm-plugkit fs_write verb's payload key is content (a JSON string), not body — passing {path, body:{...}} silently no-ops with bytes:0 and no error, which nearly let a stale/empty .ci-validated marker slip through this run; caught via recall mid-VERIFY and corrected before COMPLETE.

GUI ux-craft sweep (2026-07-10) — thirty-fourth run

gui-ux-craft workflow wf_919164f1-648 (34 agents, 0 errors) + a parallel live-browser negative-space audit. Full detail in rs-learn (recall "agentgui 34th run ux-craft sweep"). Headline: negative-space audit came back clean for the 3rd consecutive run (31st/33rd/34th) — future sweeps should shift focus to interaction-level polish instead of re-running that lens. Kit: --fs-2/--fs-0 shadow font tokens didn't exist in the real scale (silently fell back to hardcoded px, decoupled from [data-typescale]) — remapped to --fs-lg/--fs-tiny; stale --lh-base fallback (1.4, should be 1.55) fixed; ~29 more hardcoded focus-ring outline:2px solid var(--accent-ink) instances tokenized; .chat-tool-copy gained the coarse-pointer 44px touch target; .ds-select gained a base min-height:32px floor; Btn's className prop renamed to class to match Panel/Heading; AgentControls/AgentChat gained an agentsLoading state. App: History no longer falls back to the raw session id (cwd/"Untitled conversation" instead), ACP error copy truncated+prefixed. Kit pushed 3b1080c (after a rebase — a concurrent release commit had landed), agentgui pushed 9c8094b, all CI green both repos.

GUI ux-craft sweep (2026-07-10) — thirty-third run

34-agent workflow, 21 confirmed. Full detail in rs-learn (recall "agentgui 33rd run ux-craft sweep"). Kit pushed 4105bd1, agentgui pushed 3bac0fe. Standing lesson: a new kit component must be re-exported through src/components.js's barrel to be consumable — adding it to a component file alone leaves it invisible to the built bundle even with 0 lint errors; grep the built dist for the export name before wiring the app against it.

GUI ux-craft sweep (2026-07-10) — thirty-second run

35-agent workflow, 22 confirmed. Full detail in rs-learn (recall "agentgui 32nd run ux-craft sweep"). Kit pushed ad000b6, agentgui pushed 5ccaa8a. Standing gotcha: this env's PASSWORD value can itself contain literal commas (e.g. 123,slam,123,slam) — treat the whole string as one token, never comma-split it when testing auth.

Light-theme contrast + maximal usability sweep (2026-07-10) — thirty-first run

Full detail in rs-learn (recall "agentgui 31st run light-theme-contrast" and "agentgui 31st run maximal usability sweep"). Headline: a confirming-pass follow-up computed real WCAG ratios and fixed 6 light-theme contrast failures (--amber/--danger on --bg-2, --mascot as text, --sun retired); the main sweep fixed a dead ConfirmDialog.destructive prop, settings-panel spacing drift, misapplied .lede in history empty-state, and copy-tone/perf items. Kit pushed df04d69/prior, agentgui pushed 03002cf. Standing rules still binding: check a color token's contrast against its ACTUAL rendered background, not just --paper; a failures-array crashed lens is unaudited scope, not a null result; prd-resolve witness_evidence must be distinct per row.

Sessions-rail selection dead-click fix (2026-07-03) — thirtieth run

Fixed non-chat-tab clicks in the persistent sessions rail being dead clicks (loaded state, no visible navigation). Standing rule: a keepXTrack-mounted component's interaction must produce the same visible effect on every tab it appears on. Full detail in rs-learn (recall "agentgui 30th run sessions-rail dead-click").

Mobile sessions-drawer sliver fix (2026-07-03) — twenty-ninth run

Scoped negative-space/attention follow-up (not a full agent fan-out) to the explicit "looks very diy" feedback. Live-browser screenshot + DOM measurement at 420px found a real regression the prior two runs' screenshot audits missed: the closed .ws-sessions drawer left a ~35px sliver visibly overlapping the chat content on mobile. Root cause in ../design app-shell.css: the <=1100px breakpoint's closed-state transform: translateX(-110%) assumes the drawer's left is 0; the <=900px breakpoint then shifts the drawer's left to var(--ws-rail-w-collapsed) (60px) so it sits right of the icon-only rail, but never adjusted the closed-state translate to match — -110% of a 248px-wide drawer is only -272.8px, undershooting the needed -308px (left + width) by ~35px. Fixed by overriding the closed-state transform inside the 900px block: translateX(calc(-100% - var(--ws-rail-w-collapsed))), which scales correctly with both the drawer width and the rail offset. Verified via getBoundingClientRect before (x:-212.8, rightEdge:35.2 — visible) and after (x:-248, rightEdge:0 — fully clear) plus before/after screenshots. Standing rule: any drawer/overlay whose left/inset changes across breakpoints must re-derive its closed-state transform from that breakpoint's own offset — a transform tuned for one breakpoint's geometry silently breaks at the next if the offset shifts underneath it. Kit pushed 76b2bac, agentgui pushed 4bb9f3f.

Visual-polish pass — shell depth, composer weight, chat gutter (2026-07-03) — twenty-eighth run

Screenshot audit fixed 3 "looks very diy" causes: .ws-sessions flat --bg (should match .ws-rail/.ws-pane's --bg-2), .chat-composer zero elevation (--shadow-1), .agentchat zero inline gutter under mainFlush. Standing rule: .ws-rail/.ws-sessions/.ws-pane must all three use --bg-2. Full detail in rs-learn (recall "agentgui 28th run visual-polish"). Kit pushed 53c8464, agentgui pushed 42708ea.

Negative-space + attention sweep (2026-07-03) — twenty-seventh run

Scoped live-browser review (chat/files/settings, 1280px+420px); fixed a clipped Files filter placeholder and a misapplied .lede styling 4 settings facts as oversized paragraphs. App-only, pushed b0b38fb. Full detail in rs-learn (recall "agentgui 27th run negative-space-attention detail").

Per-row file move + lint tooling fix (2026-07-02) — twenty-sixth run

Single-file move action on FileRow reusing the bulk-move dialog; fixed a lint-null-children.mjs bracket-matcher bug (apostrophes in // comments corrupted bracket-depth tracking). Full detail in rs-learn (recall "agentgui 26th run file-move-lint-fix").

Mid-thread retry (2026-07-02) — twenty-fifth run

Retry ungated from last-message-only to every settled assistant turn, with truncate-and-resend + resumeSid clearing shared via one executeTruncateAndResend() path. Full detail in rs-learn (recall "agentgui 25th run mid-thread-retry").

GUI completion sweep (2026-07-02) — twenty-fourth run

gui-completion workflow wf_4a731841-832 (33 agents, 17 confirmed). Landed 5 smaller-scope findings (live-tab dashboard state persistence, shortcuts overlay FocusTrap, history copy-when-collapsed, settings re-check affordance) plus a CSRF-regression-test gap (a test's Authorization header accidentally matched the wrong threat model). Full detail in rs-learn (recall "agentgui 24th run GUI completion sweep"). Pushed d3e2d1e.

GUI perceived-performance sweep (2026-07-02) — twenty-third run

Landed the 22nd-run's deferred perceived-perf findings (modulepreload for markdown CDN modules, "checking…"/"loading…" in-flight states instead of "unknown"/"0/0"); first-render network-gating re-scoped, not dropped. Full detail in rs-learn (recall "agentgui 23rd run perceived-performance sweep"). agentgui pushed 6a045ba.

GUI ux-craft sweep (2026-07-02) — twenty-second run

35-agent audit, 20 confirmed. Accent-on-paper contrast fix (--accent->--accent-ink for text/fills), typography-rhythm parity fixes, theme-flash-prevention script in index.html. Full detail in rs-learn (recall "agentgui 22nd run ux-craft sweep").

GUI cohesion sweep (2026-07-02) — twenty-first run

25-agent audit, 12 confirmed. WorkspaceRail attention-dot tone, WorkspaceShell keepSessionsTrack (stable column count across tabs), mobile drawer focus-trap, ConversationList richer status, ContextPane recentFiles panel. Standing rule: commit and push any inherited uncommitted work found dirty in the tree BEFORE starting new work at PLAN entry — never leave it stranded under a fresh, disjoint cover. Full detail in rs-learn (recall "agentgui 21st run cohesion sweep").

CRITICAL — ACP is managed ONLY by lib/acp-sdk-manager.js

Twentieth run deleted a redundant eager-spawn lib/plugins/acp-plugin.js (conflicting ports, no restart logic, crashed boot on a missing binary). Rule: ACP lifecycle lives in lib/acp-sdk-manager.js alone; every spawn() callsite needs a proc.on('error', …) handler (ENOENT surfaces async under Bun). Full detail in rs-learn (recall "agentgui 20th run ACP redundant manager").

GUI quality sweep (2026-06-19) — nineteenth run

132-agent workflow wf_8183560b-e3d (12 lenses, 93 confirmed). Kit: thinking settled state, retry on non-last message, AT aria fixes (followup chips live guard, cwd focus restore, skip-link target, sessions toggle chevron, shell print styles, a11y-01 index.html), composer disabled visual state, files-modals focus trap re-query + stable aria-labelledby, sessions.js listbox+aria-selected. App: resume-transcript-load (loadResumeTranscript historical messages + spinner), ACP force-restart (unhealthy agent restart btn + WS handler), history tool_use rail=purple, shortcuts overlay role=dialog, streaming-active badge on chat rail tab, sortedAgents memoized, files filter 150ms debounce, _seen Set capped 5000, humanizeMs->fmtDuration, dead historySide() removed, pathBasename util, ARM_RESET_MS constant, refreshHistory guard, perf-002/006/007/008 memoization+cleanup, live-tokens accumulated, backend-change-mid-chat guard, transcript loading state, session-expiry onSessionExpired hook, server-500 stream error sanitized. Server: IPv4-mapped IPv6 normalization in rate-limit, image route streaming (no sync read), isWindows module-level, getAvailableAgents export removed, files-plugin+workflow-plugin confinement, acp-plugin shell injection fix, plugin-routes CSRF fix, asset-server JS injection fix. Full detail in rs-learn (recall "agentgui 19th run").

GUI quality sweep (2026-06-19) — eighteenth run

53-agent audit wf_7cd243d2-753 (29 confirmed). Kit: showWorkingTail status=running (was backwards), focus ring 2px+offset, aria-expanded dynamic on sessions toggle, DOMPurify version-pinned, FORCE_BODY, 360px button fill, cwd-btn 44px coarse. App: thinking branch in sendChat loop, saveBackend deep health probe, aria-live guard, O(1) SSE dedupe Set, sessionDuration reduce, tool_use rail flame (purple=subagent rule), ACP vocabulary connected/connecting/disconnected + unhealthy rail flame, skip-link. Server: CORS credentials guard, token masking in logs. Also: history (result)/(tool call) labels, multi-turn preamble for non-resume agents. Deferred: resume-transcript (#3), ACP force-restart (#4). Full detail in rs-learn (recall "agentgui 18th run").

GUI quality sweep (2026-06-19) — seventeenth run

Two-pass 48-agent audit (wf_bcac251f-d54, 25 confirmed). Critical: absIdx ReferenceError crash in history, Prism new Function → CSP-safe script injection + Promise.all tiers, scrollChatToBottom rAF reflow removed, sessions drawer <=1100px fix, thinking block dispatched. Full detail in rs-learn (recall "agentgui 17th run").

Design-maturity sweep + dead-code + secret-hardening (2026-06-18) — sixteenth run

54-agent workflow gui-design-16 (wf_6479308b-24f, 12 lenses, 40 confirmed). SECRET_RE extended to upload/mkdir/rename/list; RFC-5987 Content-Disposition; dead vendor/cdn/ 36 files + harvest-fixtures + CI copy-vendor lines removed. Full detail in rs-learn (recall "agentgui 16th run").

Design-maturity sweep + dead-code + server-hardening (2026-06-18) — fifteenth run

Two Workflows: gui-design-15 (wf_7ba315a1-9a9, 8 lenses, ~95 agents total) + gui-design-15b (wf_5b2861b2-717, 3 lenses). ShortcutList + Row.detail kit exports; static/ dead-code removed; terminal RCE fix; CSP nonce; constant-time token compare. Full detail in rs-learn (recall "agentgui 15th run").

Design-maturity sweep + marketing-site consolidation (2026-06-18) — fourteenth run

site/theme.mjs (marketing renderer, NOT the SPA) was the last design residue -> migrated to a NEW kit marketing.css .site-* family; Panel/Heading gained a class prop; Btn({primary})->Btn({variant:'primary'}). Workflow gui-design-14 (wf_50751bd9-a43, 7 lenses) 46 agents -> 28 confirmed, all in the kit (ThinkingNode/markdown-table/status-disc shape channels/shell resizer breakpoints). Status-disc shape rule: live=solid+pulse, error=solid+halo, connecting=hollow ring, stale=muted solid. Full detail in rs-learn (recall "agentgui 14th run marketing-site consolidation").

Design-content consolidation — ALL design lives in the kit now (2026-06-18) — thirteenth run

The mandate's origin: every design decision lives in the kit (../design), none in agentgui. site/app/index.html lost its ~240-line inline <style> (moved to NEW kit app-surfaces.css); app.js carries zero inline style= props. Rule going forward: no design content in agentgui — new surface styling is a kit CSS rule, never an inline <style> or style=. lint-glyphs extended with SCAN_ROOT_FILES so root bundled CSS no longer escapes the glyph lint. Workflow gui-design-consolidation.js (6 lenses) 44 agents -> 19 confirmed. Full detail in rs-learn (recall "agentgui 13th run design-content consolidation").

CRITICAL — authedFetch must NOT set Authorization: Bearer behind an nginx Basic-Auth proxy (2026-06-18)

Witnessed on the boxone.off.l-inc.co.za/gm deploy: the page HTML/JS/CSS loaded (browser sends its cached HTTP Basic creds) but every app fetch() (/gm/health, /gm/v1/history/sessions, /api/*) returned 401 from nginx, and the user saw a repeating Basic-Auth prompt. Root cause: site/app/js/backend.js authedFetch set Authorization: Bearer <window.__WS_TOKEN>, which overwrites the browser's cached Authorization: Basic credentials. nginx auth_basic only accepts Basic, so a Bearer header is rejected at the proxy before it ever reaches agentgui. The WS survived because it auths via ?token= query on an auth_basic off path. Fix: thread the token via the ?token= query param (withToken(), exactly like the WS / EventSource / image / download URLs) and never override Authorization — the query param coexists with the upstream Basic auth, and agentgui accepts ?token= on every HTTP route. Rule: same-origin app auth must never use the Authorization header when an upstream proxy may own Basic auth — use the query param + the agentgui_token cookie. boxone runs the published agentgui@latest, so this reaches it only after an npm publish + service restart (or via the gmweb nginx auth_basic off mitigation on the agentgui-proxied API paths).

UX-craft sweep + empirical-witness-first (2026-06-11) — twelfth run

6-lens workflow gui-ux-craft.js (46 agents -> 40 confirmed, all 58 implemented): live-page-as-debugger caught shipped crashes (SessionDashboard unkeyed-empty-body webjsx applyDiff crash; mobile drawers dead from base-hidden-after-media CSS-order; composer cwd label/text prop-name miss; stale .ds-file-check overlay), staged column yield (pane->drawer 1480 / sessions 1100 / rail 60px-icon 900), PageHeader dense, theme-tuned --flame/--amber AA pairs + 5 --code-* aliases, 32px/r-1 control metric, perceived-perf preloads + synchronous MdNode paint, POST /api/move, FOUR build lints. Full detail in rs-learn (recall "agentgui 12th run UX-craft sweep"). Server on this env runs PORT=3009 (3000 owned by another app); witness caveats (stale spool out/, expiring browser sessions, git_status stat-cache) also in rs-learn.

Files command surface + PASSWORD-boot fix (2026-06-11) — eleventh run

Fixed PASSWORD-gated deploys never booting the SPA (static subresources carried no token, agentgui_token cookie now set on auth) and made Files a full command surface (multi-select, density modes, bulk delete). Full detail in rs-learn (recall "agentgui 11th run Files command surface PASSWORD-boot").

GUI screen-real-estate layer (2026-06-12) — fluid columns + drag-resize handles

Fluid-column + drag-resize density layer (workflow gui-screen-realestate.js, 50 agents -> 17 gaps). Fixed --ws-*-w chrome became fluid clamp(); kit shell.js .ws-resizer separators write persisted inline --ws-<col>-w; .ws-sessions-toggle desktop collapse; .btn* -> --r-1; .app-status 26px. Concurrent-writer rule (load-bearing): when origin advances mid-run with the same feature, drop yours and adopt theirs, then re-apply only genuinely-distinct work — never a parallel impl. Full detail in rs-learn (recall "agentgui screen-real-estate layer").

GUI Claude-Code-web parity sweep (2026-06-11) — tenth maximum-effort run

Visual/layout/motion parity vs claude.ai/code (workflow gui-claude-code-parity.js, 66 agents -> 37 gaps). Flat full-width chat turns (.chat-msg-flat at --measure), single bordered composer rounded-rect, .chat-tool status-toned cards, WorkspaceShell mainFlush, command-center SessionDashboard, reduced-motion-guarded transitions. Load-bearing webjsx rule: never pass a conditional x?h():null positionally — build the array and .filter(Boolean) it; ANY null among VElement siblings crashes applyDiff (reading 'key'); Btn spreads array children. Full detail in rs-learn (recall "agentgui 10th run parity").

GUI logic+predictability sweep (2026-06-10) — ninth maximum-effort run

78-agent workflow, 67 confirmed findings, all implemented (interaction lifecycles, realtime truth, input ergonomics, scale, consistency). Escape ladder order: shortcuts overlay > file dialog > confirmingEdit > new-chat arm > stop-arms > stop generation (still binding - later runs extend the ladder, never reorder it). Full detail in rs-learn (recall "agentgui 9th run logic predictability sweep").

GUI completion sweep (2026-06-10) — eighth maximum-effort run

Coverage run reversing the prior scope cuts. Workflow .claude/workflows/gui-completion.js (6 lenses) -> 46 gaps (PUNCHLIST-COMPLETION.md), ALL implemented. Load-bearing remnants: Files is a FULL manager (confined POST /api/rename,/api/delete,/api/mkdir, PUT /api/upload-file, GET /api/stat via fsAllowRoots()/sanitizeEntryName(); CSRF guard on POST/PUT/DELETE; run scripts/validate-mutations.mjs (17 checks) after touching the mutation surface). Full hash routing HASH_KEYS=[tab,sid,dir,file,q,project,section] (popstate diffs the full set; navTo(tab,{writeHash:false})). editAndResend clears resumeSid on truncation (never --resume a diverged tail). .ds-session-meta was taken - the strip is .ds-session-meta-strip. One SHORTCUTS array feeds the ?-overlay + settings. Live selection Set deliberately NOT persisted (stale sids would arm stop-selected wrongly). Full detail in rs-learn (recall "agentgui GUI completion sweep").

GUI cohesion sweep (2026-06-10) — seventh maximum-effort run

One-product feel sweep (61 agents -> 40 gaps, PUNCHLIST-COHESION.md, workflow .claude/workflows/gui-cohesion.js): per-block code copy, FilePreviewPane split view, SessionDashboard sort/filter/stale/stop-selected, stableFrame, one canonical .status-dot-disc, confined /api/download, per-entry permissions. Full detail in rs-learn (recall "agentgui GUI cohesion sweep 7th run"). Standing rule: the app.js C destructure MUST list every kit component it calls (a witness caught Icon is not defined).

GUI predictability + polish sweep (2026-06-05) — fifth maximum-effort run

Superseded/extended by the 7th run. Load-bearing survivor: confineToRoots() in lib/http-handler.js applies fs.realpathSync re-confinement on /api/list, /api/file, /api/image — symlink-escape fails closed. Full detail in rs-learn (recall "agentgui-fifth-run-polish-detail").

Claude-Desktop redesign + embedding fix (2026-06-05) — fourth maximum-effort run

Shipped the three-column WorkspaceShell + WorkspaceRail (kit shell.js, .ws-* in app-shell.css, collapse persisted localStorage ds.ws.{rail,pane}), the shared ConversationList rail (sessions.js), the Files view (server /api/list allowlist-confined + backend.js listDir() + app.js filesMain() with kit BreadcrumbPath/FileGrid, mirrors c:\dev\fsbrowse), and the Live dashboard (SessionDashboard/SessionCard). Also a no-compromises gm-plugkit WASM embedding fix (incremental re-embed, rs-plugkit ab09ed0). The 5th run (above) polished all of these. Full detail in rs-learn (recall "agentgui Claude-Desktop redesign").

webjsx keying class-bug (load-bearing, still true). Mixing keyed VElements with null/strings in any children array crashes applyDiff ("reading 'key'"). ConversationList uses a stable keyed body wrapper (loading/empty/populated diff children not container) + [...].filter(Boolean) on conditional row children; app.js runningPanel keys all three row children. refreshHistory keeps render() OUT of the try so a render exception doesn't masquerade as a history fetch error. See the webjsx note below.

GUI-predictability sweep closure (2026-06-05) — third maximum-effort run

Override-elimination pass (index.html !important -> kit defaults) + 19 other PRD rows. Full detail in rs-learn (recall "agentgui 3rd run GUI-predictability sweep closure").

GUI-audit closure (2026-06-04 fan-out) — second maximum-effort sweep

.claude/workflows/gui-audit.js ran a 64-agent fan-out (9 per-surface reviewers -> adversarial verify -> synthesize) -> 44 confirmed findings (AUDIT-PUNCHLIST.md, 38 deduped), all fixed across server/app/kit and pushed (agentgui eb1eab3/951404e, kit anentrypoint-design bb776aa -> CI publishes npm/unpkg). Per-finding detail + key learnings (ccsniff SSE {sid,payload} unwrap, CSP must allow Google Fonts, DS Select promotes title->aria-label, index.html now loads the kit from local ./vendor/, kit publish flow) in rs-learn (recall "agentgui GUI-audit closure").

Architecture (2026-05-19 pivot — single surface)

One surface. server.js serves site/app/ under BASE_URL (default /gm) and mounts ccsniff's /v1/history/* Express router in-process at both / and BASE_URL. The legacy static/ tree and the legacy lib/routes-*/lib/db-queries-*/lib/jsonl-watcher.js modules are gone. acptoapi is no longer used by this project.

When PASSWORD env var is set, every HTTP route is gated by lib/http-handler.js accepting Basic auth, Authorization: Bearer <pwd>, OR ?token=<pwd> query param (added 2026-05-26 so EventSource and direct deep-links work — neither can set headers). WS /sync requires ?token= only. The HTML head script injects window.__BASE_URL, window.__SERVER_VERSION, and window.__WS_TOKEN; site/app/js/backend.js reads __WS_TOKEN and threads it onto every fetch (Bearer header) / EventSource (qs) / WebSocket (qs).

  • site/app/index.html — shell + CSS; imports anentrypoint-design from the local vendored copy ./vendor/anentrypoint-design/247420.{js,css} (re-vendored 2026-06-04 for a PREDICTABLE shipped UI that does not shift when upstream publishes, and offline operation — the markdown stack marked/dompurify/prismjs still fetches from jsdelivr on first chat render). Update flow: edit the kit at c:\dev\anentrypoint-design, node scripts/build.mjs, copy dist/247420.{js,css} into site/app/vendor/anentrypoint-design/, then publish the kit so unpkg stays in sync.
  • site/app/js/backend.js — same-origin WS/HTTP client (DEFAULT_BACKEND = ''); the transport glue that wires the kit; ?backend= query override for cross-origin debugging
  • site/app/js/app.js — webjsx view + state; renders the AgentChat kit (from anentrypoint-design) for the chat surface and wires agentgui's WS/ccsniff state as kit callbacks; history/settings remain agentgui-local. Exposes window.__agentgui

The chat GUI lives in the design kit, not in agentgui. The reusable multi-agent chat surface is the AgentChat component in anentrypoint-design (src/components/agent-chat.js); agentgui keeps only the transport glue (WS backend.js, ccsniff history wiring, agent orchestration) and passes state + callbacks into the kit. To change chat UI, edit the kit and push it (CI publishes to npm -> unpkg @latest), not agentgui.

  • server.js — initializes the ACP-SDK manager + agent registry, registers WS handlers (ws-handlers-util.js), mounts createHistoryRouter() from ccsniff at /, serves site/app/ as static root. The lib/plugins/ system was removed (2026-06-20, 20th run) — it was dead scaffolding (routes 404, wsHandlers never wired, api unconsumed); all server logic lives directly in server.js + lib/ws-handlers-util.js + lib/http-handler.js + lib/routes-upload.js + database.js.

Dependencies:

  • ccsniff (>=1.1.0) — exports createHistoryRouter({projectsDir}) mountable on Express; serves /v1/history/{sessions,sessions/:sid/events,search,snapshot,reindex,stream}. Reads ~/.claude/projects (override via CLAUDE_PROJECTS_DIR).
  • anentrypoint-design (>=0.0.119) — kit library, single-file ESM from unpkg

GUI audit pass (2026-06-04) — structured chat, predictable presentation

Landed structured tool cards (toolPart/applyToolResult by id), multi-turn resume wiring (streaming_session -> state.chat.resumeSid), rAF-coalesced streaming, /api/image confinement, history eventsLimit + search-to-event flash. Largely superseded by runs 5-12; full detail in rs-learn (recall "agentgui GUI audit pass 2026-06-04 structured chat").

Orchestration agents

The four flagship agents the GUI drives are Claude Code, OpenCode, Kilo, and Antigravity (agy); the agent picker (site/app/js/app.js, PRIMARY_AGENTS) sorts these first, then other-available, then npx-installable, then not-installed.

Two runner protocols exist. Direct (lib/claude-runner-direct.js): claude-code and agy — spawn the CLI per turn, parse stdout. ACP (lib/acp-sdk-manager.js): opencode/kilo/codex — an on-demand long-lived server on ports 18100/18101/18102, health-checked via /provider. The on-demand start + restart-backoff is correct even when an ACP agent lacks provider auth (it reports running-not-healthy rather than crashing).

agy (Antigravity) is a Gemini-backed Go CLI. Its invocation is agy --print "<prompt>" --dangerously-skip-permissions [--continue]--print is a value flag (the prompt is its argument; a positional prompt exits 2). It emits plain text (not stream-json) and prints no session id, so its parseOutput wraps each line into an assistant-text event and resume is --continue-only. A live model response needs an authenticated Antigravity session; without it agy returns empty (the direct runner resolves gracefully, no hang). Detail in rs-learn (recall "agentgui agy Antigravity agent").

The direct runner spawns with shell:false against a resolved binary path — never shell:true. shell:true on Windows concatenates argv without escaping, so a chat prompt containing &/|/>/backticks executes as shell commands (arbitrary command execution). lib/claude-runner.js resolveBinaryPath resolves the command to an absolute .exe; getSpawnOptions only defaults shell:true when a caller passes no explicit shell. Detail in rs-learn (recall "agentgui SECURITY direct runner shell:false").

Agent availability comes from registry.isAvailable(id) (lib/ws-handlers-util.js, agents.list), which runs where/which. A binary installed outside the system PATH reads as "(not installed)"; the fix is an npxPackage on the registry entry so it falls back to bun/npx presence. Detail in rs-learn (recall "agentgui agent availability where npxPackage").

Output and source carry no decorative glyphs. The GUI uses ASCII words and CSS-drawn discs (.status-dot-disc) for status, never ●/◌/○/⌘/§/▶ etc. Convert any decorative glyph to its ASCII equivalent on sight.

Browser Witness

bun server.js. Default PORT=3000 (server.js); the SPA is served under BASE_URL (default /gm/), so the live app is http://localhost:3000/gm//health and / answer at root, the app is under /gm/. First request to /gm/ or /v1/history/* triggers a 30-90s ccsniff JSONL walk (curl with a short timeout returns 000 during warmup). AppShell renders nav=[chat,history,settings], SSE hello, 0 console errors, backend resolves to '' (same origin).

CI / GitHub Actions

capture-screenshots must run under bun, not node.

npm install --ignore-scripts in gh-pages.yml skips native compilation, leaving better-sqlite3 without a compiled .node binding. database.js tries bun:sqlite first, then falls back to better-sqlite3. When the step runs under Node both fail and the server crashes silently within the 20s health-check window.

Fix: bun run scripts/capture-screenshots.mjs (not node ...).

Why it works: process.execPath becomes bun, so the spawned child server also runs under bun and loads bun:sqlite natively — no compiled binding needed.

Rule: any CI step that spawns the agentgui server (directly or via a script that inherits process.execPath) must invoke it with bun.

Plugin system removed (2026-06-20 — 20th run)

The entire lib/plugins/ system + plugin-loader.js was deleted as dead scaffolding (commits up to e73ed0e): plugin routes were unreachable (http-handler forwarded only a few prefixes -> 404), plugin wsHandlers were never wired (the real WS handlers come from registerWsHandlers in ws-handlers-util.js), plugin api objects were never consumed, and the inits were no-ops or managed local Maps nobody read. Removed across 7 commits (acp, files, agents, then database/stream/workflow/websocket + loader). The general lesson survives: any import under lib/ must be a built-in (crypto, fs, path) or already in package.json — a missing dep crashes boot.

Plugin Tool Provisioning on Windows

lib/tool-spawner.js iterates BUNX_RUNNERS=['bun','npx'] and detects missing-command via regex on both error.message and stdout+stderr. Full detail in rs-learn (recall "agentgui windows tool-spawner bunx fallback").

GM Plugin Autonomy Blocker

gm plugin's pre-tool-use hook gates multi-tool autonomy via a .gm/needs-gm marker; the hook content is TEMPLATED from the gm codebase, not gm-starter/hooks/ - patching those files does not propagate. Full diagnosis + fix path in rs-learn (recall "agentgui GM plugin autonomy blocker").

History Integration via ccsniff (2026-05-21 — reverted from acptoapi)

agentgui mounts ccsniff's history router in-process — no external proxy.

server.js imports createHistoryRouter from the ccsniff package and mounts it on the internal Express app at /, exposing GET /v1/history/{snapshot,sessions,sessions/:sid/events,search,reindex,stream}. Reads ~/.claude/projects by default; override with CLAUDE_PROJECTS_DIR env var. acptoapi's previously-bundled history routes were removed in acptoapi 1.0.103; ccsniff is now the canonical source. Browser client (site/app/js/backend.js) calls these same-origin via the agentgui server.

buildSystemPrompt System Prompt for claude-code

lib/provider-config.js buildSystemPrompt() must return '' for claude-code agent; returning "Model: X." breaks conversation resume.

The function previously returned "Model: X." when agentId was 'claude-code' and model was non-null. This caused buildArgs in lib/claude-runner-agents.js to pass --append-system-prompt "Model: X." to the claude CLI, which triggers "argument missing" error on conversation resume. Fix: return '' early when agentId is 'claude-code' or falsy. The model is already passed via --model flag; system prompt is only for non-claude-code agents.

WebSocket Sync Endpoint Testing

/sync sends sync_connected+clientId on connect; legacy handler in lib/ws-legacy-handlers.js (ping/subscribe/get_subscriptions/unsubscribe/latency_report) via codec; tests must register the message handler before sending. Full detail in rs-learn (recall "agentgui websocket /sync testing handler ordering").

better-sqlite3 & Node v24 Startup

Node v24 lacks a prebuilt better-sqlite3 binary; the node path needs a from-source postinstall compile, and start is bun server.js || node server.js. database.js tries bun:sqlite first. Detail (postinstall command, paired package.json/bin changes, CI must-run-under-bun) in rs-learn (recall "agentgui startup better-sqlite3 node v24 bun").

webjsx applyDiff Array Keying (2026-05-04)

Rule still binding: mixing keyed VElements with raw strings/numbers in any webjsx array prop crashes applyDiff. Full detail in rs-learn (recall "agentgui webjsx applyDiff array-keying rule").

Live History Stream Wiring (2026-05-05)

site/app/js/app.js opens an SSE EventSource on navTo('history') (B.streamHistory, state.live shape, hello/event/conversation/error dispatch, rAF-throttled renders) against ccsniff's in-process /v1/history/stream; first /v1/history/* request triggers a 30-90s synchronous JSONL walk (CLAUDE_PROJECTS_DIR) — health-check timeouts during that window are normal. Full detail in rs-learn (recall "agentgui Live History Stream Wiring").

Runtime CDN: GUI loaded from unpkg — SUPERSEDED, do not trust

This 2026-05-29 unpkg-at-runtime approach was reverted; see "DS load = LOCAL vendor" below for the current truth (re-confirmed live on the 37th run, 2026-07-12). Historical detail in rs-learn (recall "agentgui unpkg superseded local vendor").

Agent/model/session management (2026-05-28)

  • agents.list (WS) returns available + npxInstallable per agent; agents.models returns model choices (claude-code -> sonnet/opus/haiku). The chat picker is agent-then-model, not a flat model list. Unavailable agents are disabled/gated.
  • chat.sendMessage accepts cwd (defaults to STARTUP_CWD) and model/agentId separately. chat.active (WS) lists in-flight chats with agentId/model/cwd/startedAt/pid; the history tab polls it (3s) and shows a running panel with per-session stop.
  • Client (app.js): chat transcript persists to localStorage[agentgui.chat] and restores on load; tool_use/result events render as chat parts; keyboard shortcuts (g+c/h/s, n, /, ?); settings has an agents-status panel from health.acp[].

Kit Row rail + rank contract

The design-kit Row (anentrypoint-design src/components/content.js) renders a leading status rail from a rail prop (green | purple | flame, via a .row.rail-<tone>::before bar) and accepts rank as an alias for the leading code index. A state: 'disabled' Row is inert — no onClick/role=button/tab-stop, aria-disabled=true. agentgui's history sessions, search results, and agents panel pass rail + rank and rely on this; the rendering reaches the live GUI only after the kit publishes to npm -> unpkg @latest. agentgui keeps the rail semantics consistent everywhere: green = selected/ok, purple = subagent, flame = error/unavailable.

GUI glyph policy

GUI source keeps typographic product characters — the middot separator ·, the ellipsis , and em/en dashes in prose — as established design (AGENTS.md itself uses ·). It carries no decorative tells: arrows, box-drawing, bullets, status dots, checks. Status is words + the CSS-drawn .status-dot-disc. Convert any arrow/box-drawing/bullet glyph to ASCII on sight (->, // --- x ---, -/*).

DS CSS cascade — overriding component styles (2026-05-28)

installStyles() injects DS CSS into a runtime <style> after the head <style>, so local overrides need !important or higher specificity than the DS's .ds-247420-prefixed rules. Full detail (font vars --ff-display/--ff-mono, the .chat-head .sub "00 msgs" quirk, EventList .row[role=button], [data-prog-focus] focus suppression, projectLabel() for cwd slugs) is in rs-learn (recall "agentgui GUI styling DS cascade installStyles").

No decorative glyphs — ASCII-only GUI (2026-06-04)

The GUI carries no decorative tell-tale glyphs; remaining non-ASCII is the deliberate typographic separator set. The de-jank sweep removed every tell-tale (status dots [CHAR], arrows, box-drawing dividers, checks) in favor of ASCII words + the CSS-drawn status disc. The middot separator · is kept as established product design (it reads as a real meta-separator, used consistently across rows/subs); ellipsis and em/en dashes were converted to ... / - on sight. Verified live on buildesk: document.body.innerText matches no decorative-glyph regex (excluding the kept middot) across chat/history/settings. The DS Alert dismiss control renders its own close glyph only on a dismissible alert (intentional DS icon affordance, exempt).

DS SearchInput accessible name comes from label, not aria-label (2026-06-04)

anentrypoint-design's SearchInput (internal De) sets aria-label = label || placeholder; it ignores any aria-label prop you pass. To give the search box a real accessible name, pass label: (and a matching placeholder: for the visible hint). Passing only aria-label leaves AT announcing the placeholder. A post-render setAttribute race-loses against the DS re-render, so the prop is the only durable fix.

DS load = LOCAL vendor (resolved — the earlier unpkg note is stale)

index.html loads the DS kit from local ./vendor/anentrypoint-design/247420.{js,css}, NOT unpkg — reconfirmed live on the 40th run. Full detail in rs-learn (recall "agentgui ds load local vendor detail").

@.gm/next-step.md