agentgui 1.0.1031 → 1.0.1033

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/.gm/prd.yml CHANGED
@@ -2719,3 +2719,170 @@
2719
2719
  subject: Adversarially verify every landed fix live in the browser (0 console errors), push kit + app, watch CI green, mark complete only on real transition to COMPLETE.
2720
2720
  status: completed
2721
2721
  witness: 'Live agent-browser session against http://localhost:3010/gm/?token=... after kit rebuild+re-vendor+server restart: 0 console errors on load and after theme switch to dark; confirmed .btn-primary letter-spacing computed as ''normal'' (was var(--tr-caps)); confirmed .btn:focus-visible rule now reads ''outline: var(--focus-w) solid var(--focus-color)'' via live stylesheet query; confirmed pre .n color computed as rgb(148,99,0) (#946300 = --code-num, was --green-2''s 3.18:1-failing value); confirmed --amber root value #7C570F matches paper-theme override (drift fixed); confirmed ds-icon-btn-danger/btn-primary.danger hover rules both now brightness(0.92) (darkening, AA-safe) via live stylesheet query.'
2722
+ - id: run33-gui-ux-craft-workflow
2723
+ title: Run gui-ux-craft workflow (33rd sweep) for fresh usability/predictability/negative-space findings on top of 31st/32nd run fixes
2724
+ notes: user re-requested max usability/practicality/predictable/negative-space via wfgy-method critique; dispatch Workflow gui-ux-craft
2725
+ status: completed
2726
+ witness: 'Workflow wf_7f1a513c-2a3 completed: 33/34 agents succeeded, 21 confirmed findings, all implemented and resolved as individual PRD rows; 1 crashed lens (kit-dx) re-run separately.'
2727
+ - id: run33-wfgy-negative-space-audit
2728
+ title: Targeted live-browser negative-space/attention audit in parallel with workflow (screenshots+DOM measurement at 1280px and 420px)
2729
+ notes: per established pattern from 27th/29th/31st runs, catches regressions workflow fan-out misses
2730
+ status: completed
2731
+ witness: Dispatched parallel live-browser negative-space audit agent; found 2 real issues (health-chip contrast, history empty-state void) both fixed and resolved separately as run33-health-chip-connected-contrast and run33-history-empty-state-dead-void.
2732
+ - id: run33-kit-rebuild-revendor
2733
+ title: Rebuild kit (node scripts/build.mjs) and re-vendor dist into agentgui site/app/vendor if kit changes land
2734
+ notes: standing update flow
2735
+ status: completed
2736
+ witness: Kit rebuilt via node scripts/build.mjs multiple times across the fix batches (final rebuild at 12:2x with AgentListSkeleton addition); lint-glyphs/lint-null-children/lint-classes/lint-inline-styles all OK each time; dist/247420.{js,css} copied into agentgui site/app/vendor/anentrypoint-design/ as the final step.
2737
+ - id: run33-browser-witness-fixes
2738
+ title: Browser-witness every fix live (0 console errors) before VERIFY
2739
+ notes: standing rule, exec via browser dispatch
2740
+ status: completed
2741
+ witness: 'Browser-witnessed live at localhost:3012/gm/?token=... via agent-browser: health-chip connected color rgb(78,122,0) on tinted bg, history-empty-hints present, agentchat-cwd-text font-family mono, --focus-w token set, tone-success rule present in live stylesheet, 0 console errors across chat/history/settings tabs.'
2742
+ - id: run33-push-ci
2743
+ title: Push kit + agentgui changes, watch CI green on both repos
2744
+ notes: standing CONSOLIDATE requirement
2745
+ status: completed
2746
+ witness: 'gh run list confirms agentgui 3bac0fe: Auto-Declaudeify/Deploy GH Pages/Publish and Release/Test all success; design 4105bd1: Deploy GH Pages/Publish 247420 to npm both success. .ci-validated written with head_sha 3bac0fe6fe6579b7be34836391353a51af027e1e.'
2747
+ - id: run33-health-chip-connected-contrast
2748
+ title: Fix near-invisible connected health-chip text in Settings BACKEND panel
2749
+ notes: 'design/app-surfaces.css .ds-247420 .health-summary.health-ok .health-chip:first-child uses raw --accent (lime #B6FF1B) as text color on near-paper bg, measured rgb(182,255,27) vs sibling rgb(31,31,38) - give it an accent background + dark text per --accent-ink contrast pattern'
2750
+ status: completed
2751
+ witness: 'Browser-witnessed at localhost:3011/gm/ Settings tab: computed style of .health-chip:first-child ''connected'' text now rgb(78,122,0) on lime-tinted color(srgb .71 1 .11 /.16) bg, legible vs siblings rgb(31,31,38); before fix it was raw rgb(182,255,27) on near-invisible near-paper bg. Kit rebuilt (node scripts/build.mjs) and re-vendored into site/app/vendor/anentrypoint-design/.'
2752
+ - id: run33-history-empty-state-dead-void
2753
+ title: Fix large dead-space void in History empty state (~300px blank at 1280px, >50% viewport blank at 420px)
2754
+ notes: app.js History tab empty state vertically centers in full pane height leaving huge blank void below the message - collapse to natural height or surface useful content (shortcuts, recent-events preview)
2755
+ status: completed
2756
+ witness: 'Browser-witnessed at localhost:3011/gm/ History tab: .history-empty getBoundingClientRect height=353.8px (natural, top-aligned with new .history-empty-hints ShortcutList filling reclaimed space) vs prior fixed min-height:50vh centered void; 0 console errors.'
2757
+ - id: typography-rhythm-cwd-fact-font-family-inconsistent
2758
+ title: The 'working directory / cwd path' fact renders in mono in the Live dashboard but in the body sans font everywhere else
2759
+ notes: '[kit] In the kit (design/chat.css), align the composer and context-pane cwd renderings to the existing mono convention rather than stripping it from the dashboard: add `font-family: var(--ff-mono);` to `.agentchat-cwd-text` (chat.css:90 block) and to the context-pane cwd row selector `.ds-context .row .sub` at chat.css:474 (or a more targeted `.ds-context .row[data-cwd] .sub` if a cwd-specific hook exists) — matching `.ds-dash-cwd`, `.ds-session-agent`, and `.ds-dash-model`, all of which already use `--ff-mono` for this class of technical/path metadata.'
2760
+ status: completed
2761
+ witness: 'Browser-witnessed at localhost:3012/gm/ chat tab: getComputedStyle(.agentchat-cwd-text).fontFamily = ''ui-monospace, Menlo, Consolas, monospace'' (was inheriting sans body font before fix); context-pane cwd row scoped via new .ds-context-cwd-row wrapper.'
2762
+ - id: typography-rhythm-agent-name-font-family-inconsistent
2763
+ title: Agent name renders in mono in the session list row but in the sans display font on the Live dashboard card
2764
+ notes: '[kit] In design/chat.css:417, remove `font-family: var(--ff-mono);` from `.ds-session-agent`, leaving `font-size: var(--fs-tiny); color: var(--fg-3);` so it inherits the sans display font like `.ds-dash-agent`. No app or index.html changes needed.'
2765
+ status: completed
2766
+ witness: chat.css .ds-session-agent font-family:var(--ff-mono) declaration removed, now inherits sans display font matching .ds-dash-agent; confirmed via source diff in rebuilt chat.css.
2767
+ - id: typography-rhythm-chat-md-paragraph-margin-untokenized
2768
+ title: Chat markdown paragraph spacing is a hardcoded pixel value instead of a --space token
2769
+ notes: '[kit] In design/chat.css:814, change `.chat-msg-flat .chat-md p { margin: 8px 0; }` to `.chat-msg-flat .chat-md p { margin: var(--space-2) 0; }`.'
2770
+ status: completed
2771
+ witness: chat.css .chat-msg-flat .chat-md p margin changed from hardcoded 8px 0 to var(--space-2) 0; confirmed via source read of rebuilt chat.css.
2772
+ - id: typography-rhythm-panel-stack-margin-vs-files-stack-gap
2773
+ title: Stacked Panel sections (settings) use a 32px bottom-margin rhythm while the equivalent stacked-band rhythm on Files uses 16px, for the same 'sibling content blocks' relationship
2774
+ notes: '[kit] In design/app-shell.css, change .panel''s margin-bottom from var(--space-5) to var(--space-4) (24px) to sit closer to the .ds-files-stack rhythm without a large layout-affecting rewrite, since settings'' Panel list isn''t wrapped in a shared flex container (app.js just calls Panel(...) back-to-back) and switching to a wrapper+gap pattern would require added app.js markup changes beyond a pure kit CSS fix. Minimal one-line change: `.panel { margin: 0 0 var(--space-4); }`.'
2775
+ status: completed
2776
+ witness: app-shell.css .panel margin-bottom changed from var(--space-5) to var(--space-4), aligning settings panel rhythm with files-stack gap; confirmed via source read.
2777
+ - id: copy-tone-session-vs-conversation-vocab
2778
+ title: Same concept called 'session' on Live tab but 'conversation' on History tab
2779
+ notes: |
2780
+ [app] In site/app/js/app.js: (1) line 1766 change PageHeader title/lede on the Live tab from 'Live sessions'/'...agent sessions.' to 'Live conversations'/'Watch, open, and stop running agent conversations.'; (2) lines 1585-1590 in stopAllActive, make both branches say 'conversation(s)' consistently: change 1586 to `failed + ' conversation' + (failed === 1 ? '' : 's') + ' did not stop - try again'` and 1589 to `'stopped ' + okSids.length + ' of ' + sids.length + ' conversations'`; (3) line 1761 Alert title 'Some sessions did not stop' -> 'Some conversations did not stop'. Keep 'sid'/'session id' only as the internal technical identifier (e.g. variable names, activeSid prop) - do not rename those.</refinedFix>
2781
+ </invoke>
2782
+ status: completed
2783
+ witness: app.js Live tab PageHeader title/lede changed to 'Live conversations'/'Watch, open, and stop running agent conversations.'; stopAllActive messages and Alert title unified to 'conversation(s)' vocabulary; node --check app.js passed.
2784
+ - id: copy-tone-folders-cannot-be-dropped-duplicated
2785
+ title: Identical 'folders cannot be dropped' announce string duplicated verbatim at two call sites, one already slightly diverging as the two evolve independently
2786
+ notes: '[app] In site/app/js/app.js, inside the DropZone onDrop handler (around line 1476-1487), hoist the message to a single local const right before the `if (items)` check, e.g. `const FOLDER_DROP_MSG = ''folders cannot be dropped - use new folder to create one'';` and replace both `announce(''folders cannot be dropped - use new folder to create one'')` calls (lines 1483 and 1485) with `announce(FOLDER_DROP_MSG)`. No new module-level export needed since it''s only used within this one handler.'
2787
+ status: completed
2788
+ witness: app.js DropZone onDrop handler now uses single FOLDER_DROP_MSG const referenced at both announce() call sites instead of duplicated literal strings; node --check app.js passed.
2789
+ - id: copy-tone-jump-to-root-label-jargon
2790
+ title: '''Jump to an allowed root'' exposes internal server-confinement terminology (''allowed root'') as user-facing microcopy'
2791
+ notes: '[app] In site/app/js/app.js replace the phrase ''allowed root(s)'' with ''accessible folder(s)'' at all occurrences: line 880 (''That folder is outside the accessible folders, or access is denied.''), line 942 (''...or the target is outside the accessible folders.''), lines 1220/2161/2610 (''outside the accessible folders''), line 1443 aria-label (''Jump to an accessible folder''), and line 3226 (''accessible folders: none configured''). Keep wording consistent across all sites since they''re the same user-facing concept.'
2792
+ status: completed
2793
+ witness: app.js all 6 user-facing 'allowed root(s)' strings replaced with 'accessible folder(s)' (file-list error, fileMutationCopy 403, cwd-probe errors, RootsPicker aria-label, settings none-configured line); internal fsAllowRoots fn name and code comments left untouched per scope; node --check app.js passed.
2794
+ - id: perceived-performance-boot-render-gated-on-health-probe
2795
+ title: First real render is gated behind the health-probe network round-trip, so the app shell paints nothing until it resolves
2796
+ notes: '[app] In app.js init(), set `state.health = { status: ''unknown'' }` and call `render()` once synchronously before the `const r = await B.probeBackend(...)` line (i.e. as the first statements inside init(), ahead of the try block). Leave the existing try/catch + second render() (line 3620) unchanged so the health banner/agent list still update in place once the probe resolves.'
2797
+ status: completed
2798
+ witness: app.js init() now sets state.health={status:'unknown'} and calls render() synchronously before awaiting B.probeBackend(...); existing try/catch + post-resolve render() unchanged; node --check app.js passed.
2799
+ - id: perceived-performance-agents-list-spinner-not-skeleton
2800
+ title: Agent list uses a bare spinner+text loading state while Files/History/Sessions use shape-matched skeleton rows
2801
+ notes: '[kit+app] In design (kit): add an `AgentListSkeleton({rows = 5})` export near FileSkeleton/EventList''s skeleton, rendering `rows` Row-shaped ghost items (reuse `.ds-skel-icon`/`.ds-skel-title`/`.ds-skel-meta` placeholder spans already styled for FileSkeleton, wrapped to match Row''s visual footprint) with `aria-hidden=true`, plus a single `role=status aria-live=polite` text node like EventList''s `loadingText` pattern. In app.js: replace the `state.agentsLoading` branch at ~3355-3357 (`h(''div'', {class:''t-meta empty-state'', role:''status'', ''aria-live'':''polite''}, Spinner(...), ''loading agents…'')`) with `AgentListSkeleton({rows: 5})`, keeping the row-array key discipline (`key:''agload''` wrapper, all children keyed) already used elsewhere in this file.'
2802
+ status: completed
2803
+ witness: Added new AgentListSkeleton export in kit sessions.js (re-exported via components.js barrel), CSS in chat.css using existing ds-skel shimmer primitive; wired into app.js agentsLoading branch replacing bare Spinner+text; browser-witnessed rebuilt bundle contains AgentListSkeleton (grep count=1), 0 console errors live.
2804
+ - id: perceived-performance-upload-progress-no-percent-during-transfer
2805
+ title: Upload rows show 0% until the whole file finishes (fetch has no chunk progress) with no indeterminate-progress affordance
2806
+ notes: '[kit+app] Switch the in-flight (not-done, not-error) row to the kit''s indeterminate/marquee progress-bar state (if `UploadProgress` doesn''t have one yet, add it) instead of a static 0% fill, so the row visibly communicates ''working'' rather than ''stuck'' during the transfer.'
2807
+ status: completed
2808
+ witness: kit files.js UploadProgress gained indeterminate prop + ds-upload-marquee CSS animation (with prefers-reduced-motion fallback); app.js UploadProgress call site sets indeterminate:true for in-flight zero-percent rows; grep of rebuilt app-shell.css confirms 2 ds-upload-marquee occurrences; node --check app.js passed.
2809
+ - id: light-theme-contrast-accent-fill-hover-brightness
2810
+ title: Send button and rail primary-action hover use brightness(1.08) lightening on an already near-max-lightness accent fill in paper theme
2811
+ notes: '[kit] In app-shell.css, change both hover rules to darken via brightness(0.92) (consistent with the already-fixed ghost/danger pattern and with the existing `.send:active { filter: brightness(0.94); }` which already darkens): line ~2634 `.chat-composer .send:hover { filter: brightness(1.08); }` -> `filter: brightness(0.92);`, and line ~3743 `.ws-rail-action:hover { filter: brightness(1.08); }` -> `filter: brightness(0.92);`. This keeps the hover visible regardless of how saturated --accent is in the active theme/data-accent variant, and stays consistent with :active already darkening rather than lightening.'
2812
+ status: completed
2813
+ witness: app-shell.css .chat-composer .send:hover and .ws-rail-action:hover changed from brightness(1.08) lightening to brightness(0.92) darkening, consistent with already-fixed ghost/danger pattern; confirmed via source read of rebuilt app-shell.css.
2814
+ - id: light-theme-contrast-editor-primitives-hover-lighten-missed
2815
+ title: editor-primitives.css dialog-button hover states still use the lightening pattern the kit already fixed elsewhere
2816
+ notes: '[kit] In /config/workspace/design/editor-primitives.css replace lines 654 and 658: change `.ds-ep-dialog-btn.kind-primary:hover { filter: brightness(1.08); }` and `.ds-ep-dialog-btn.kind-danger:hover { filter: brightness(1.1); }` to `filter: brightness(0.92);` each, matching the darkening convention used elsewhere in the kit for AA compliance on light theme.'
2817
+ status: completed
2818
+ witness: editor-primitives.css .ds-ep-dialog-btn.kind-primary/.kind-danger:hover changed from brightness(1.08)/brightness(1.1) to brightness(0.92) each; confirmed via source read.
2819
+ - id: light-theme-contrast-community-green-2-text
2820
+ title: community.css server-back/add icon still colored with the AA-failing --green-2 token
2821
+ notes: '[kit] In community.css:134, change `color: var(--green-2);` to `color: var(--green);` in the shared `.cm-server-back, .cm-server-add` rule. This only visibly affects `.cm-server-add` (the only one of the two whose resting-state color isn''t already overridden downstream by the `.cm-server-back { color: var(--fg-2); }` rule at line 147), bringing it to ~5.2:1 on --bg-2, matching the token already used for the other 4 fixed instances. No other lines need changes (the hover rule at 149-153 sets background to green-2 with paper text, a different, already-passing contrast pair, and is out of scope).'
2822
+ status: completed
2823
+ witness: community.css .cm-server-back, .cm-server-add color changed from var(--green-2) to var(--green); confirmed via source read of rebuilt community.css.
2824
+ - id: light-theme-contrast-selection-tint-weak
2825
+ title: ::selection highlight is far less visible in paper theme than in ink theme because it blends the same light accent into an already-light background
2826
+ notes: '[kit] In app-shell.css, add a light-theme-specific override right after the base ::selection rule (paper theme is the default/root, so target it explicitly): `:root[data-theme="paper"] ::selection, :root:not([data-theme]) ::selection { background: color-mix(in oklab, var(--accent) 45%, var(--bg-2)); color: var(--fg); }` — or simpler, key off the existing light-theme selector pattern already used in colors_and_type.css (mirror however light vs ink is scoped there, e.g. `[data-theme="paper"]`/default vs `[data-theme="ink"]`) and raise the accent share to ~45-50% only in the light branch, leaving the ink theme''s existing 24% mix untouched.'
2827
+ status: completed
2828
+ witness: colors_and_type.css ::selection base rule raised to 46% accent mix for light/default theme; explicit 24%-preserving overrides added for [data-theme=ink/dark] and prefers-color-scheme dark auto block so dark theme selection is unaffected; confirmed via source diff.
2829
+ - id: light-theme-contrast-accent-tint-active-states
2830
+ title: --accent-tint active/selected-state background is tuned for dark theme and reads faint on paper across many shared components
2831
+ notes: '[kit] In colors_and_type.css, keep the dark/ink/auto-dark theme''s --accent-tint exactly as-is (re-declare the current raw formula explicitly inside the existing [data-theme="ink"],[data-theme="dark"] block at ~line 309-311, and the prefers-color-scheme dark auto block at ~350-352, so dark behavior is pinned and unaffected), then change only the :root (light/paper default) definition at line 93 from `color-mix(in oklab, var(--accent) 30%, var(--bg))` to blend the darker, already-AA-tuned --accent-ink (acid-deep, #4E7A00) instead of the raw light --accent, e.g. `--accent-tint: color-mix(in oklab, var(--accent-ink) 22%, var(--bg));`. This gives light theme a visibly tinted, higher-contrast selection wash (matching the darker tone already used for accent text) while leaving the dark-theme highlight, which already reads well, untouched. Also apply the same light-tuned value in the [data-theme'
2832
+ status: completed
2833
+ witness: colors_and_type.css :root --accent-tint now blends --accent-ink at 22% (was raw --accent 30%); prior dark-theme value explicitly restated in ink/dark and auto-dark blocks so dark behavior is pinned; [data-theme=paper] block also carries the light-tuned value; confirmed via source diff.
2834
+ - id: interaction-density-cwdbar-order-reversed
2835
+ title: Inline CwdBar save/cancel action order is reversed from ConfirmDialog's cancel/confirm order
2836
+ notes: '[kit] In design/src/components/agent-chat.js CwdBar editing branch (~line 124-125), swap the order to render Btn(cancel) first, then Btn(save, variant:''primary''), matching ConfirmDialog/PromptDialog''s cancel-then-primary order. No prop/behavior changes, just reordering the two Btn(...) calls in the h(''div'', ...) children list.'
2837
+ status: completed
2838
+ witness: agent-chat.js CwdBar editing branch reordered to cancel-then-save Btn order matching ConfirmDialog/PromptDialog convention; confirmed via source read.
2839
+ - id: interaction-density-focus-ring-hardcoded
2840
+ title: File-surface controls hardcode outline:2px/var(--accent-ink) instead of the --focus-w/--focus-color/--focus-offset tokens the button family already uses
2841
+ notes: '[kit] In /config/workspace/design/app-shell.css, for the 11 cited selectors (.ds-file-grid, .ds-file-sort-btn, .ds-file-open, .ds-file-check, .ds-file-selectall, .ds-density-btn, .ds-file-cell-open, .ds-upload-act, .ds-modal-input, .ds-segmented .ds-seg-btn, .ds-file-act), replace `outline: 2px solid var(--accent-ink)` with `outline: var(--focus-w) solid var(--focus-color)` and replace each rule''s hardcoded `outline-offset: Npx` with `outline-offset: var(--focus-offset)`, keeping each rule''s own border-radius override untouched. (Optionally extend the same mechanical replacement to the other now-discovered hardcoded instances at lines 33,250,286,328,1004,2640,3182,3192,3585,3679,3731,3744,3754,3827,3951,3976,3989,4324 in the same pass, since they''re the identical bug, but the 11 cited are sufficient to resolve this finding.) Then run `node scripts/build.mjs` and re-vendor per the kit upd'
2842
+ status: completed
2843
+ witness: 'app-shell.css: all 30 matching hardcoded outline:2px solid var(--accent-ink) focus rules replaced with var(--focus-w) solid var(--focus-color), positive outline-offset values replaced with var(--focus-offset) (negative inset-ring offsets deliberately preserved); browser-witnessed getPropertyValue(''--focus-w'') is set and non-empty on live page.'
2844
+ - id: interaction-density-chip-badge-tone-sprawl
2845
+ title: Two near-duplicate tone-* families (.chip and .ds-badge) each carry 3-4 synonymous tone names for the same visual, with no canonical name documented
2846
+ notes: '[kit] In app-shell.css, replace the per-class repeated declarations with grouped selectors per semantic, keeping all existing class names as accepted aliases (no API break) but writing the rule once: e.g. `.ds-badge.tone-green, .ds-badge.tone-live, .ds-badge.tone-success, .ds-badge.tone-ok, .chip.tone-green, .chip.tone-live, .chip.tone-success, .chip.tone-ok { background: var(--green-tint); color: var(--green-deep); }` and similarly one combined rule for `.tone-flame/.tone-error/.tone-miss` across both `.ds-badge` and `.chip`, plus one for the neutral/wip/disabled group and one for blue/sun/yellow/orange/mascot/purple where identical. Add a one-line comment above naming `tone-success`/`tone-error`/`tone-neutral` as canonical for new callers, with the rest listed as legacy aliases kept for back-compat.'
2847
+ status: completed
2848
+ witness: app-shell.css tone-success/tone-error/tone-neutral families consolidated into single grouped declarations across .chip and .ds-badge, all legacy class-name aliases preserved, canonical-name comment added; browser-witnessed live stylesheet query confirms a rule with selectorText including 'tone-success' exists (toneSuccessExists:true).
2849
+ - id: interaction-density-iconbtn-primary-hover-mismatch
2850
+ title: Icon-button primary/ghost hover uses full fg/bg invert while the equivalent text-button primary hover uses fg background + accent-ink text
2851
+ notes: |-
2852
+ [kit] In /config/workspace/design/app-shell.css, change line 502 from
2853
+ .ds-icon-btn-primary:hover { background: var(--fg); color: var(--bg); }
2854
+ to
2855
+ .ds-icon-btn-primary:hover { background: var(--fg); color: var(--accent-ink); }
2856
+ matching .btn-primary:hover's two-tone-flip color so primary-role hover reads identically across labeled and icon-only buttons.
2857
+ status: completed
2858
+ witness: app-shell.css .ds-icon-btn-primary:hover color changed from var(--bg) to var(--accent-ink), matching .btn-primary:hover two-tone-flip; confirmed via source read of rebuilt app-shell.css.
2859
+ - id: run33-kit-dx-lens-crashed-rerun
2860
+ title: hunt:kit-dx lens crashed mid-run (API connection error) in the 33rd gui-ux-craft workflow - unaudited scope, not a null result
2861
+ notes: 'per standing rule from 31st run: a crashed lens goes into confirming pass as its own row - re-run a targeted kit-dx audit'
2862
+ status: completed
2863
+ witness: 'Re-ran kit-dx audit via subagent: found 3 real doc gaps (voice.js/theme-toggle.js/data-density.js undocumented, SessionMeta undocumented, 6 utility exports undocumented). Filed as kit-dx-undocumented-component-files, kit-dx-sessionmeta-undocumented, kit-dx-utility-exports-undocumented.'
2864
+ - id: typography-rhythm-caps-letterspacing-drift
2865
+ title: All-caps micro-labels in the chat surface use three different letter-spacing values instead of the --tr-caps token
2866
+ notes: '[kit] In design/chat.css replace the hardcoded letter-spacing values with var(--tr-caps): line 449 `.03em`->`var(--tr-caps)`, line 777 `.03em`->`var(--tr-caps)`, line 869 `.04em`->`var(--tr-caps)`, line 901 `.04em`->`var(--tr-caps)`. No HTML/JS changes needed; rebuild kit dist and re-vendor per the standard update flow.'
2867
+ status: completed
2868
+ witness: chat.css lines 449/777/869/901 letter-spacing values replaced with var(--tr-caps); kit rebuilt, lint-glyphs/lint-null-children/lint-classes/lint-inline-styles all OK, re-vendored into agentgui.
2869
+ - id: typography-rhythm-chat-md-lineheight-oneoff
2870
+ title: Chat markdown body copy uses a bespoke line-height (1.65) that matches neither of the two body-text tokens
2871
+ notes: '[kit] In design/chat.css:813, change `.chat-msg-flat .chat-md { line-height: 1.65; }` to `.chat-msg-flat .chat-md { line-height: var(--lh-long); }`.'
2872
+ status: completed
2873
+ witness: chat.css .chat-msg-flat .chat-md line-height changed from 1.65 to var(--lh-long); confirmed via grep of rebuilt chat.css source.
2874
+ - id: kit-dx-undocumented-component-files
2875
+ title: voice.js, theme-toggle.js, data-density.js components have zero entries in COMPONENT_API.md
2876
+ notes: add prop-table sections for PttButton/VadMeter/WebcamPreview/VoiceSettingsModal/VoiceControls/AudioQueue, ThemeToggle, PhaseWalk/TreeNode/BarRow/StatTile/StatsGrid/SubGrid/SessionRow/DevRow/LiveLogEntry/LiveLog
2877
+ status: completed
2878
+ witness: git status --porcelain COMPONENT_API.md shows M (modified); added Voice Surfaces/ThemeToggle/Data-Density Components sections read from actual voice.js/theme-toggle.js/data-density.js signatures.
2879
+ - id: kit-dx-sessionmeta-undocumented
2880
+ title: sessions.js SessionMeta export undocumented while sibling SessionCard is documented
2881
+ notes: add SessionMeta prop-table entry adjacent to SessionCard in COMPONENT_API.md
2882
+ status: completed
2883
+ witness: SessionMeta prop-table entry added directly above SessionCard entry in Workspace Surfaces section of COMPONENT_API.md, verified via git diff showing new section text.
2884
+ - id: kit-dx-utility-exports-undocumented
2885
+ title: fmtDuration/fmtFileSize/safeUrl/makeThreadAutoScroll/injectCodeCopy/hasSelectionInside utility exports undocumented
2886
+ notes: add Utilities section to COMPONENT_API.md listing signature + one-line purpose for each, from chat.js/sessions.js/files.js
2887
+ status: completed
2888
+ witness: New Utilities section added to COMPONENT_API.md documenting fmtDuration/fmtFileSize/safeUrl/makeThreadAutoScroll/injectCodeCopy/hasSelectionInside with real signatures, placed before Accessibility Standards section.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1031",
3
+ "version": "1.0.1033",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -3,7 +3,7 @@ import * as B from './backend.js';
3
3
 
4
4
  installStyles().catch(() => {});
5
5
 
6
- const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, SearchInput, TextField, Select, Btn, Icon, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap } = C;
6
+ const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, SearchInput, TextField, Select, Btn, Icon, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton } = C;
7
7
 
8
8
  // One duration/bytes vocabulary across every surface: prefer the kit's shared
9
9
  // formatters (exported alongside the components), fall back to the local
@@ -877,7 +877,7 @@ async function loadDir(dirPath, { fromHash = false } = {}) {
877
877
  if (state.files._reqId !== myReq) return;
878
878
  // W9: translate HTTP status to plain, non-leaky copy.
879
879
  state.files.error = e.status === 403
880
- ? 'That folder is outside the allowed roots, or access is denied.'
880
+ ? 'That folder is outside the accessible folders, or access is denied.'
881
881
  : (e.status === 404 ? 'That folder no longer exists.' : (e.message || 'Could not list this directory.'));
882
882
  state.files.entries = [];
883
883
  }
@@ -939,7 +939,7 @@ function restoreFileFromHash(filePath) {
939
939
  // One dialog at a time lives in state.files.dialog: {kind, file, error, busy}.
940
940
  // Errors from the confined endpoints map to plain copy by HTTP status.
941
941
  function fileMutationCopy(e) {
942
- if (e.status === 403) return 'Permission denied, or the target is outside the allowed roots.';
942
+ if (e.status === 403) return 'Permission denied, or the target is outside the accessible folders.';
943
943
  if (e.status === 404) return 'That file no longer exists.';
944
944
  if (e.status === 409) return e.message || 'A file with that name already exists.';
945
945
  if (e.status === 413) return 'Too large (50MB upload cap).';
@@ -1217,7 +1217,7 @@ function fileDialog() {
1217
1217
  : (!st.dir ? 'that path is not a directory' : null);
1218
1218
  } catch (e) {
1219
1219
  if (state.files.dialog !== d) return;
1220
- d.error = e.status === 403 ? 'outside the allowed roots'
1220
+ d.error = e.status === 403 ? 'outside the accessible folders'
1221
1221
  : (e.status === 404 ? 'folder not found on the server' : null);
1222
1222
  }
1223
1223
  render();
@@ -1440,7 +1440,7 @@ function filesMain() {
1440
1440
  const rootsRow = roots.length > 1
1441
1441
  ? RootsPicker({
1442
1442
  roots: roots.map((r) => ({ id: r, label: truncate(projectLabel(r) || r, 16, 28) })),
1443
- selected: f.path, onSelect: (r) => loadDir(r), label: 'Jump to an allowed root',
1443
+ selected: f.path, onSelect: (r) => loadDir(r), label: 'Jump to an accessible folder',
1444
1444
  })
1445
1445
  : null;
1446
1446
  const targetCwd = f.path || (roots.length === 1 ? roots[0] : '');
@@ -1478,11 +1478,12 @@ function filesMain() {
1478
1478
  // Detect directory drops: browsers report an empty FileList for dirs.
1479
1479
  // Check DataTransferItems when available; fall back to empty files list.
1480
1480
  const items = ev && ev.dataTransfer && ev.dataTransfer.items;
1481
+ const FOLDER_DROP_MSG = 'folders cannot be dropped - use new folder to create one';
1481
1482
  if (items) {
1482
1483
  const hasDir = Array.from(items).some(it => { try { return it.webkitGetAsEntry && it.webkitGetAsEntry()?.isDirectory; } catch { return false; } });
1483
- if (hasDir) { announce('folders cannot be dropped - use new folder to create one'); return; }
1484
+ if (hasDir) { announce(FOLDER_DROP_MSG); return; }
1484
1485
  } else if (!files || !files.length) {
1485
- announce('folders cannot be dropped - use new folder to create one'); return;
1486
+ announce(FOLDER_DROP_MSG); return;
1486
1487
  }
1487
1488
  uploadFiles(files);
1488
1489
  },
@@ -1513,9 +1514,15 @@ function filesMain() {
1513
1514
  (f.uploads && f.uploads.length) ? h('div', { key: 'fup' }, UploadProgress({
1514
1515
  // Recovery affordances per row: 'replace' on a name collision (409),
1515
1516
  // dismiss on any error row (errors otherwise persist until the next batch).
1516
- items: f.uploads.map((it) => (it.error && it.status === 409 && !it._retrying)
1517
- ? { ...it, actions: [{ label: 'replace', onClick: () => retryUploadOverwrite(it) }] }
1518
- : it),
1517
+ items: f.uploads.map((it) => {
1518
+ // No chunk-level progress is available from fetch, so an in-flight
1519
+ // row with no percent yet gets the marquee affordance instead of a
1520
+ // static 0% fill that reads as stuck.
1521
+ const base = (!it.error && !it.done && !it.pct) ? { ...it, indeterminate: true } : it;
1522
+ return (base.error && base.status === 409 && !base._retrying)
1523
+ ? { ...base, actions: [{ label: 'replace', onClick: () => retryUploadOverwrite(base) }] }
1524
+ : base;
1525
+ }),
1519
1526
  onDismiss: (item, i) => {
1520
1527
  const src = (state.files.uploads || [])[i];
1521
1528
  if (src && src.error) dismissUpload(src);
@@ -1583,10 +1590,10 @@ async function stopAllActive(sessions) {
1583
1590
  const okSids = sids.filter((sid, i) => results[i].status === 'fulfilled');
1584
1591
  const failed = sids.length - okSids.length;
1585
1592
  state.live.bulkStopError = failed
1586
- ? failed + ' session' + (failed === 1 ? '' : 's') + ' did not stop - try again'
1593
+ ? failed + ' conversation' + (failed === 1 ? '' : 's') + ' did not stop - try again'
1587
1594
  : null;
1588
1595
  announce(failed
1589
- ? 'stopped ' + okSids.length + ' of ' + sids.length + ' sessions'
1596
+ ? 'stopped ' + okSids.length + ' of ' + sids.length + ' conversations'
1590
1597
  : 'stopped ' + plural(okSids.length, 'conversation'));
1591
1598
  await refreshActive();
1592
1599
  render();
@@ -1758,12 +1765,12 @@ function liveMain() {
1758
1765
  return [
1759
1766
  offlineBanner(),
1760
1767
  lv.bulkStopError
1761
- ? Alert({ key: 'bulkstoperr', kind: 'warn', title: 'Some sessions did not stop',
1768
+ ? Alert({ key: 'bulkstoperr', kind: 'warn', title: 'Some conversations did not stop',
1762
1769
  children: [
1763
1770
  h('span', { key: 'bsetxt' }, lv.bulkStopError + ' '),
1764
1771
  Btn({ key: 'bsedismiss', onClick: () => { state.live.bulkStopError = null; render(); }, children: 'dismiss' })] })
1765
1772
  : null,
1766
- PageHeader({ compact: true, dense: true, title: 'Live sessions', lede: 'Watch, open, and stop running agent sessions.' }),
1773
+ PageHeader({ compact: true, dense: true, title: 'Live conversations', lede: 'Watch, open, and stop running agent conversations.' }),
1767
1774
  SessionDashboard({
1768
1775
  sessions,
1769
1776
  offline,
@@ -2158,7 +2165,7 @@ function chatMain() {
2158
2165
  if (!st || st.ok === false) { state.cwdError = 'directory not found on the server: ' + path; render(); return; }
2159
2166
  if (!st.dir) { state.cwdError = 'that path is not a directory'; render(); return; }
2160
2167
  } catch (e) {
2161
- state.cwdError = e.status === 403 ? 'outside the allowed roots'
2168
+ state.cwdError = e.status === 403 ? 'outside the accessible folders'
2162
2169
  : (e.status === 404 ? 'directory not found on the server: ' + path
2163
2170
  : (e.status ? 'directory not found on the server: ' + path : 'could not validate the path - server unreachable'));
2164
2171
  render();
@@ -2607,7 +2614,7 @@ const debouncedCwdProbe = debounce(async () => {
2607
2614
  : (!st.dir ? 'that path is not a directory' : null);
2608
2615
  } catch (e) {
2609
2616
  if ((state.cwdDraft ?? '').trim() !== probed) return;
2610
- state.cwdError = e.status === 403 ? 'outside the allowed roots'
2617
+ state.cwdError = e.status === 403 ? 'outside the accessible folders'
2611
2618
  : (e.status === 404 ? 'folder not found on the server' : null);
2612
2619
  }
2613
2620
  state.cwdChecking = false;
@@ -2687,7 +2694,9 @@ function historyMain() {
2687
2694
  h('p', { key: 'gs', class: 'history-empty-sub' },
2688
2695
  count
2689
2696
  ? count + ' conversation' + (count === 1 ? '' : 's') + ' available · use the search box or press / to filter'
2690
- : 'Start a chat or run a local coding agent - its conversation will appear here live.')),
2697
+ : 'Start a chat or run a local coding agent - its conversation will appear here live.'),
2698
+ count ? h('div', { key: 'gh', class: 'history-empty-hints' },
2699
+ ShortcutList({ shortcuts: SHORTCUTS.slice(0, 4) })) : null),
2691
2700
  ].filter(Boolean);
2692
2701
  }
2693
2702
 
@@ -3221,7 +3230,7 @@ function serverPanel() {
3221
3230
  SessionMeta({ key: 'sfacts', items: facts }),
3222
3231
  roots.length
3223
3232
  ? SessionMeta({ key: 'sroots', items: roots.map((r, i) => ({ label: 'root ' + (i + 1), value: r, title: r, onCopy: () => copyText(r, 'root copied') })) })
3224
- : (hh.status && hh.status !== 'unknown' ? h('div', { key: 'snoroots', class: 't-meta' }, 'allowed roots: none configured') : null),
3233
+ : (hh.status && hh.status !== 'unknown' ? h('div', { key: 'snoroots', class: 't-meta' }, 'accessible folders: none configured') : null),
3225
3234
  ],
3226
3235
  });
3227
3236
  }
@@ -3353,8 +3362,7 @@ function agentsPanel() {
3353
3362
  // The empty array means one of three things; never let an in-flight load
3354
3363
  // read as a broken registry.
3355
3364
  : [state.agentsLoading
3356
- ? h('div', { key: 'agload', class: 't-meta empty-state', role: 'status', 'aria-live': 'polite' },
3357
- Spinner({ key: 'spin', size: 'sm' }), 'loading agents…')
3365
+ ? AgentListSkeleton({ rows: 5 })
3358
3366
  : (state.agentsError
3359
3367
  ? h('div', { key: 'agfail', class: 't-meta empty-state' },
3360
3368
  h('span', { key: 'agfailtxt' }, 'the agent list failed to load'),
@@ -3609,6 +3617,8 @@ async function recheckHealth() {
3609
3617
  }
3610
3618
 
3611
3619
  async function init() {
3620
+ state.health = { status: 'unknown' };
3621
+ render();
3612
3622
  try {
3613
3623
  const r = await B.probeBackend(state.backend);
3614
3624
  state.health = r.ok ? { status: 'ok', ...r.info } : { status: 'down', ...r };