agentgui 1.0.972 → 1.0.974

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.
Files changed (43) hide show
  1. package/AGENTS.md +15 -26
  2. package/lib/http-handler.js +16 -4
  3. package/package.json +1 -1
  4. package/site/app/vendor/anentrypoint-design/247420.css +66 -22
  5. package/site/app/vendor/anentrypoint-design/247420.js +11 -11
  6. package/site/theme.mjs +2 -2
  7. package/scripts/harvest-fixtures.mjs +0 -219
  8. package/site/app/vendor/cdn/dompurify.js +0 -9
  9. package/site/app/vendor/cdn/fonts/1291de6d401a.woff2 +0 -0
  10. package/site/app/vendor/cdn/fonts/1ba89a87e0b8.woff2 +0 -0
  11. package/site/app/vendor/cdn/fonts/3644d51c507b.woff2 +0 -0
  12. package/site/app/vendor/cdn/fonts/4b91d2650dc2.woff2 +0 -0
  13. package/site/app/vendor/cdn/fonts/530d036ba64a.woff2 +0 -0
  14. package/site/app/vendor/cdn/fonts/570a2bdd8f8b.woff2 +0 -0
  15. package/site/app/vendor/cdn/fonts/5dd6d880fee9.woff2 +0 -0
  16. package/site/app/vendor/cdn/fonts/62de9143afe3.woff2 +0 -0
  17. package/site/app/vendor/cdn/fonts/64884efa2f11.woff2 +0 -0
  18. package/site/app/vendor/cdn/fonts/68cd7063be2e.woff2 +0 -0
  19. package/site/app/vendor/cdn/fonts/6c252abcf99b.woff2 +0 -0
  20. package/site/app/vendor/cdn/fonts/71e69e06516a.woff2 +0 -0
  21. package/site/app/vendor/cdn/fonts/9ea68c62083f.woff2 +0 -0
  22. package/site/app/vendor/cdn/fonts/c010f9b7d6b2.woff2 +0 -0
  23. package/site/app/vendor/cdn/fonts/d69723fc74be.woff2 +0 -0
  24. package/site/app/vendor/cdn/fonts/fonts.css +0 -459
  25. package/site/app/vendor/cdn/marked.js +0 -8
  26. package/site/app/vendor/cdn/prismjs/components/prism-bash.min.js +0 -1
  27. package/site/app/vendor/cdn/prismjs/components/prism-clike.min.js +0 -1
  28. package/site/app/vendor/cdn/prismjs/components/prism-core.min.js +0 -1
  29. package/site/app/vendor/cdn/prismjs/components/prism-css.min.js +0 -1
  30. package/site/app/vendor/cdn/prismjs/components/prism-diff.min.js +0 -1
  31. package/site/app/vendor/cdn/prismjs/components/prism-go.min.js +0 -1
  32. package/site/app/vendor/cdn/prismjs/components/prism-javascript.min.js +0 -1
  33. package/site/app/vendor/cdn/prismjs/components/prism-json.min.js +0 -1
  34. package/site/app/vendor/cdn/prismjs/components/prism-jsx.min.js +0 -1
  35. package/site/app/vendor/cdn/prismjs/components/prism-markdown.min.js +0 -1
  36. package/site/app/vendor/cdn/prismjs/components/prism-markup.min.js +0 -1
  37. package/site/app/vendor/cdn/prismjs/components/prism-python.min.js +0 -1
  38. package/site/app/vendor/cdn/prismjs/components/prism-rust.min.js +0 -1
  39. package/site/app/vendor/cdn/prismjs/components/prism-sql.min.js +0 -1
  40. package/site/app/vendor/cdn/prismjs/components/prism-toml.min.js +0 -1
  41. package/site/app/vendor/cdn/prismjs/components/prism-tsx.min.js +0 -1
  42. package/site/app/vendor/cdn/prismjs/components/prism-typescript.min.js +0 -1
  43. package/site/app/vendor/cdn/prismjs/components/prism-yaml.min.js +0 -1
package/AGENTS.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # AgentGUI — Agent Notes
2
2
 
3
+ ## Design-maturity sweep + dead-code + secret-hardening (2026-06-18) — sixteenth run
4
+
5
+ Same mandate ("fix every aspect, all design lives in ../design, fan out subagents, track with a workflow"). One tracking Workflow **`gui-design-16` (run `wf_6479308b-24f`, 12 lenses chat-thread/composer-input/files-browser/sessions-dashboard/shell-chrome/tokens-theme/a11y-motion/history-settings/marketing-site/glyph-residue/dead-code/server-security): 54 agents -> hunt -> adversarial verify -> 40 confirmed findings, ~2.7M tokens.** Applied via a clean per-file subagent fan-out (one agent per physical file, zero edit conflicts).
6
+
7
+ **Kit fixes (ALL in `/config/workspace/design`):** chat.js ToolCallNode now gates the `args` section on real args (`hasArgs`) so result-only/arg-less cards no longer show `args {}` + composer `<textarea>` binds the `disabled` prop; agent-chat.js cwd input emits `aria-busy` while checking; content.js EventList now forwards `detail/actions/highlight/meta` to Row (expanded history events were dropping toolInput detail + copy + search-highlight) + TextField gained a full invalid/error state (`error`/`aria-invalid`/`aria-describedby`/`.ds-field-error` role=alert); interaction-primitives.js ShortcutList splits combos into discrete `.ds-kbd` caps (`.ds-kbd-caps`/`.ds-kbd-sep` + CSS) instead of one wide cap; sessions.js ConversationList rail filter migrated from bespoke `.ds-session-search` to the shared `SearchInput`/`.ds-search-input` (one filter control across rail + dashboard); shell.js WsResizer adds `aria-valuetext "<n> pixels"`; voice.js `×`->`Icon('x')`, community.js `⋯`->`'more'` (glyph residue). chat.css: cwd `.is-checking` hint tone + `aria-busy` field cue + cwd-btn `6px`->`var(--r-1)`, breakdown idle disc moved off the connecting-amber to canonical `--stale` double-inset, breakdown running disc shape-ring, `.ds-dash-clear` hover/focus-visible, `.chat-tool-copy:focus-visible`, errors-toggle AA fix, ds-session-search base block removed. app-shell.css: composer + WorkspaceShell (`.ws-rail/.ws-pane/.ws-sessions/.ws-scrim`) reduced-motion guards, `.ds-file-row.is-locked/.is-restricted` + `.ds-file-perm-tag` chip, `.ws-drawer-toggle` 44px coarse floor, `.ds-density-btn.active`/`.ds-filter-pill.active` AA contrast (`color:var(--fg)`), ShortcutList legend rules. editor-primitives.css `#000` dock shadow->`var(--shadow-3)` + checkbox `#fff` fallback dropped. app-surfaces.css `@media print` re-asserts paper-tuned signal tokens. community.css `:focus-visible` for ThreadPanel/Forum/Page + forum-search. marketing.css NEW `.site-footer` family (theme.mjs footers migrated off the in-app `.app-status` strip that suppressed content <=1100px) + `.site-cli` baseline+card-chrome + token paddings + `100dvh` embed.
8
+
9
+ **Server secret-hardening (`lib/http-handler.js`, the high-sev cluster):** the module-level `SECRET_RE` (already blocking `/api/file`+`/api/download`) is now ALSO applied to `/api/upload-file`, `/api/mkdir`, `/api/rename` (403 on a secret/dotfile target name — was an overwrite-a-secret hole) and `/api/list` (filters secret-named dirents from the listing — was enumerating `.env`/`.pem` the preview/download routes block); `/api/download` Content-Disposition is now RFC-5987 encoded (ASCII `filename=` fallback + `filename*=UTF-8''<pct>`) so a CJK/emoji name no longer throws `ERR_INVALID_CHAR`. `validate-mutations.mjs` 26/26 PASS, no regression.
10
+
11
+ **Dead-code removal:** agentgui `site/app/vendor/cdn/` (36 dead files — marked/dompurify/prismjs/fonts, zero refs; markdown stack fetches jsDelivr at runtime), `scripts/harvest-fixtures.mjs` (zero refs), and the 3 `node scripts/copy-vendor.js` lines in `.github/workflows/build-platforms.yml` (script deleted in the 15th run — CI was broken); kit `scripts/bundle-markdown.mjs` (orphaned, superseded by src/markdown.js).
12
+
13
+ **Witnessed** (localhost:3009/gm/?token, PASSWORD=`123,slam,123,slam`, fresh server + re-vendored dist, via gm `browser` verb): readyState complete, `ds-247420` dark body `rgb(19,19,24)`, 3 resizers, hScroll 0, rail `ds-search-input`=1 / bespoke `ds-session-search`=0 (consolidation landed), `ds-file-perm-tag`/`site-footer`/`ds-kbd-caps` rules all resolve, **0 console errors**. Kit build all 4 lints PASS. `test.js` 10 pass/0 fail. Re-vendored `dist/247420.{css,js}`.
14
+
3
15
  ## Design-maturity sweep + dead-code + server-hardening (2026-06-18) — fifteenth run
4
16
 
5
17
  Mandate: "fix every aspect, update ../design, all design content lives there, fan out subagents, track with a workflow." Two tracking Workflows ran. **`gui-design-15` (run `wf_7ba315a1-9a9`, 8 lenses composer-input/chat-thread/files/sessions/shell/tokens-theme/a11y-motion/glyph-residue): 59 agents -> 42 findings -> 35 confirmed -> 33 applied across 9 kit files** + 2 deferred fixes implemented after (shell pane-toggle relocated into `.ws-crumb` as a `ws-desktop-toggle`; files density picker made icon-led with 3 NEW shell.js icons `rows`/`rows-tight`/`grid` + `DENSITY_ICONS` + `.ds-density-btn` 28px square). **`gui-design-15b` (run `wf_5b2861b2-717`, 3 lenses history-settings/server-boundary/security): 25 agents -> 22 findings -> 18 confirmed**, all landed. Total ~95 agents, ~4M tokens.
@@ -14,15 +26,11 @@ Mandate: "fix every aspect, update ../design, all design content lives there, fa
14
26
 
15
27
  ## Design-maturity sweep + marketing-site consolidation (2026-06-18) — fourteenth run
16
28
 
17
- Continues the 13th run's "all design lives in the kit" mandate to the LAST residue surface + a kit design-maturity sweep. **`site/theme.mjs` (the flatspace marketing/landing renderer, NOT the SPA) was the remaining residue**: it carried an inline `<style>` with hardcoded hex (`#FBF6EB`/`#1F1B16`), ~12 inline `style=` props, deprecated `C.Btn({primary})`, and two `↗` arrow glyphs. ALL migrated: a new kit **`marketing.css`** `.site-*` family (`.site-panel`/`.site-hero`/`.site-hero-h`/`.site-hero-body`/`.site-chip-row`/`.site-cta-row`/`.site-cli`+`.cli`/`.prompt`/`.cmd`/`.site-embed`, pre-scoped `.ds-247420`, wired into `build.mjs` cssParts + `lint-glyphs` `SCAN_ROOT_FILES`); kit `Panel`/`Heading` gained a **`class` prop** (they only accepted `style`) so consumers attach classes instead of inline styles; `Btn({primary})` -> `Btn({variant:'primary'})`; `↗` -> `->`. theme.mjs head now render-blocking-`<link>`s the kit `247420.css` (it loaded the kit via JS importmap only, so the inline `<style>` was its pre-JS FOUC guard) — the kit already owns the base surface (`app-surfaces.css:27` `.ds-247420 body { background:var(--bg); color:var(--fg); font-family:var(--ff-display) }`). flatspace is NOT a dep here; theme.mjs is a pure render fn witnessed by loading its `renderHtml()` output. **The marketing site tracks unpkg@latest (importmap+CSS link); the SPA ships the pinned vendored dist — two intentional load strategies.**
18
-
19
- **Workflow `gui-design-14` (via the Workflow tool, run `wf_50751bd9-a43`, 7 lenses: chat-thread/files/sessions/shell-chrome/tokens-theme/a11y-motion/marketing-residue): 46 agents -> 39 findings -> 28 adversarially confirmed** (`PUNCHLIST-DESIGN-14.md`; 2 lens connection-failures: tokens-theme hunt + marketing-residue verify). ALL landed in the kit. **chat:** ThinkingNode base CSS (`.chat-thinking`/`-text`/`-dots` had ZERO base rules outside `.agentchat-working`), markdown `table`/`th`/`td`/`hr` (were unstyled -> bare cells), dead `.chat-tick` inline-code class given a rule, composer toolbar unified on 32px/`--r-1` (was 40px-round vs 36px-square), flat `.chat-md` rhythm+inset, streaming `.chat-stream-pre` persistent chrome, tool/code `:has()` measure-breakout. **files:** FileSkeleton container was a single 48px shimmer bar collapsing all rows (now inner `.ds-skel`-only), filter folded into one `.ds-file-controls` toolbar row, per-type cell icon tints, dead `data-columns` card-mode removed (+ `FILE_GRID_MIN_COL`/`columns` param), `.ds-file-check` dead font/color decls dropped + `.ds-check-box` rest border -> `--fg-3`. **sessions:** stale disc own tone (was sharing `--amber` with connecting) + connecting now a hollow ring, dead duplicate `.ds-dash-status.is-running` removed + running unified on `--accent` across disc/breakdown/card, `.seg.is-idle` weight 600, reduced-motion live-disc static-ring fallback, mixed-tick thickened 1.5px->2px, card cost as emphasized `.ds-dash-stat-cost`, conv-list unread -> hollow ring (shape-distinct from running). **shell:** resizers hidden past their staging breakpoints (`@media` 1480/1100/900 — were dead handles dragging vars into a fixed drawer), coarse-pointer 16px hit target, `WS_RESIZE_CLAMP` reconciled to the CSS `clamp()` floors/ceilings, localStorage committed on pointerup (not per-move), separator `aria-valuemin/max/now`. **a11y:** voice `vx-*` added to `community.css` reduced-motion guard, status discs given non-colour SHAPE channels, `.ds-input-bare` `:focus`->`:focus-visible`+ring. **Status-disc shape rule:** live=solid+pulse(or static ring under reduced-motion), error=solid+halo, connecting=hollow ring, stale=muted solid — four channels, not colour-only. Witnessed live (localhost:3009/gm/?token, PASSWORD=`123,slam,123,slam`): 0 console/page errors, dark body surface painted by kit, 3 resizers, no h-scroll, migrated classes resolve. Kit pushed `3704876`; rebased onto a concurrent CI `v0.0.211` bump.
29
+ `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").
20
30
 
21
31
  ## Design-content consolidation — ALL design lives in the kit now (2026-06-18) — thirteenth run
22
32
 
23
- The mandate: every design decision must live in the kit (`../design` = `/config/workspace/design`), none in the agentgui app. **`site/app/index.html` no longer carries an inline `<style>` block** — the ~240-line block (28 classes: `.pill`/`.cwd-bar`/`.resume-banner`/`.health-chip`/`.settings-grid`/`.history-empty`/`.boot-splash`/`.chat-controls`/`.status-dot`/`.ds-event-list` overrides + `event-flash` keyframe + scrollbar theming + focus rings + responsive + print) moved to a NEW kit file **`../design/app-surfaces.css`** (wired into `scripts/build.mjs` CSS_FILES, scoped `.ds-247420`, reads kit tokens directly with NO `--agentgui-*` fallbacks — those local color vars are deleted). `app.js` carries **zero inline `style=` props** (the 6 margin literals -> `.agentgui-field-mb`/`.agentgui-field-my` kit utilities; the `mainStyle` layout string -> `.agentgui-main`/`.agentgui-main-chat` kit rules). **Rule going forward: no design content in agentgui — new surface styling is a kit CSS rule, never an inline `<style>` or `style=`.** `app-surfaces.css` selectors are written pre-scoped `.ds-247420 ...` so the build's selector-prefixer (handles `:root`/`html`/`body`, compounds `[attr]`/`*`) leaves them untouched. Because `247420.css` is a render-blocking `<link>` in head and `<html class="ds-247420">` is static, there is NO FOUC from moving the base/boot-splash styling into the kit.
24
-
25
- **Workflow `.claude/workflows/gui-design-consolidation.js`** (residue-first 13th run, 6 lenses: design-residue/chat-thread/files-live/shell-chrome/tokens-theme/a11y-motion) ran 44 agents -> 19 confirmed findings (`PUNCHLIST-DESIGN.md`), ALL landed in the kit: composer send-row `.chat-composer-toolbar` (was unstyled), role-monotonic chat hover tints, tool-card `done` success tone, base-`Chat` `.chat-empty-suggestion` styling, error-card flame inset rail, `.ds-check-box` CSS-drawn multi-select checkbox (replaced the `[x]/[ ]` bracket text in files.js+sessions.js with a bordered box that fills on `.is-marked`/`[aria-checked]` — border-drawn tick, NOT a glyph), thin 26px status strip, status-item ellipsis, `.ws-crumb` gutter align, fluid `clamp()` shell width tokens, `:focus`->`:focus-visible` (no ring on mouse click), `--warn`/`--sky` dark-theme pairs, unified `--focus-*` tokens, reduced-motion skeleton static-fill, color-blind-safe rail SHAPE differentiation + sr-only status word in `Row()`. Amber/purple hardcoded `var(--token, #hex)` fallbacks stripped (the dark-hex could wrongly paint the light theme). **`lint-glyphs` was extended** with `SCAN_ROOT_FILES` (app-shell/chat/colors_and_type/community/community-app/editor-primitives/app-surfaces.css) — the root bundled CSS previously escaped the glyph lint, which is how 39 box-drawing comment dividers in app-shell.css slipped in (now converted to ASCII). Witnessed live (localhost:3009/gm/, fresh bundle): 0 console/page errors, migrated classes resolve from the kit bundle (`.pill` cursor:pointer/radius 999px, `.ds-check-box` 15px, cwd mono), no h-scroll.
33
+ 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").
26
34
 
27
35
  ## CRITICAL — `authedFetch` must NOT set `Authorization: Bearer` behind an nginx Basic-Auth proxy (2026-06-18)
28
36
 
@@ -30,26 +38,7 @@ Witnessed on the boxone.off.l-inc.co.za/gm deploy: the page HTML/JS/CSS loaded (
30
38
 
31
39
  ## UX-craft sweep + empirical-witness-first (2026-06-11) — twelfth run
32
40
 
33
- Two-track run: EMPIRICAL baseline witnessing of the live page found 18 shipped janks BEFORE theorizing, then a fresh hunt workflow `.claude/workflows/gui-ux-craft.js` (6 lenses the 11 prior sweeps never used: typography-rhythm, copy-tone, perceived-performance, kit-DX, light-theme-contrast, interaction-density; 46 agents -> 40 confirmed, `PUNCHLIST-UX.md`). ALL 58 implemented. Server on this env runs `PORT=3009` (3000 owned by another app).
34
-
35
- **Load-bearing shipped-bug discoveries (the live page as debugger).**
36
- (1) **The live tab's "1 running over No-live-sessions" contradiction was a webjsx applyDiff CRASH** (reading 'key'), reproduced via `window.__agentgui.render()` in page.evaluate: SessionDashboard swapped its UNKEYED empty-state body for keyed group children, so the diff died mid-apply leaving the header updated and the body stale. Fix: ONE stable keyed `.ds-dash-groups` body across empty/grouped/flat (the ConversationList stable-keyed-body rule) + `Btn` now passes `key` through (it silently DROPPED it before) + no stop control when empty + kit `emptyAction` CTA prop. (2) **The mobile drawers were dead in production**: base `.ws-drawer-toggle/.ws-scrim { display:none }` sat AFTER the media blocks - same specificity, later source order wins regardless of media. CSS-order class of bug; base-hidden rules must PRECEDE their media reveals. (3) **The composer cwd bit was an invisible dead button**: app passes `{label}`, kit read only `.text` - the prop-name-contract failure mode AGENTS.md already warned about; ChatComposer now normalizes text||label and drops empty bits BEFORE separators (kills the dangling middot too). (4) **The 11th run's multi-select checkbox was hover-hidden/clipped in the shipped bundle**: a stale duplicate `.ds-file-check` absolute-overlay block later in app-shell.css clobbered the real in-flow rules. (5) `lint-null-children.mjs` (new build gate) immediately caught **14 latent crash sites in freddie.js**.
37
-
38
- **Layout architecture: staged column yield.** `.ws-shell` now yields columns to the CONTENT in stages: pane track -> right overlay drawer at <=1480px, sessions track -> left drawer at <=1100px, rail -> fixed 60px icon strip below 900px (labels hidden UNCONDITIONALLY - a 60px rail showing clipped 'age'/'Ne' text was the witnessed 420px regression). The chat thread at 1304px went from ~245px (crushed by a 380px context pane) to 776px. Drawer width is a literal (not --ws-pane-w which is 0 when collapsed); `.ws-pane-open.ws-pane-collapsed` un-hides pane children via `display: revert`.
39
-
40
- **Content-first surfaces.** `PageHeader` gained `dense` (one row: small h1 + one-line ellipsized lede; 24px instead of ~180px) - all five tabs use it. DropZone with children is a passive wrapper (dashed overlay ONLY during drag; `ds-dropzone--wrap`). Files: ONE breadcrumb (top crumb says just 'files'; in-page BreadcrumbPath navigates), compact ghost toolbar (`.ds-file-toolbar .btn` 32px/r-1), compact right-aligned filter, `.ds-files-stack` 16px rhythm wrapper in app. FileGrid skeleton only on COLD load; refresh dims in place (`is-refreshing`). History rail flame derives from error DENSITY (`sessionErrorDense`: errors>=3 AND >15% of events - tool errors are routine, every real session had 1-2%) and title==project rows drop the duplicate sub.
41
-
42
- **Theme/token work.** `--flame`/`--amber` are now theme-tuned pairs (light #C53E00/#8A6512 pass AA on paper; dark keeps #FF5A1F/#D9A93A - they were dark-tuned only, 2.9:1/2.0:1 on paper); `--amber` was referenced 8x but NEVER DEFINED (hardcoded fallbacks). Five `--code-*` syntax-token aliases (light AA, dark brights) feed both Prism palettes + static pre. Destructive ACTIONS are warn; flame is exclusively error STATUS. `ThemeToggle` compact says 'theme: auto|light|dark' (ink/paper codenames only in title) + CSS half-disc for icon-only strips.
43
-
44
- **Control craft.** One 32px/r-1 metric for agentchat-controls, dash-header, file toolbar, BulkBar (quiet warn-outline destructive; arm state goes loud); two compact-button tiers consolidated at chat.css END (later-file wins by order - chat.css loads after app-shell.css in the bundle); focus rings unified on inset accent; `.ds-select` ellipsizes + `:focus-visible`; cancel-before-destructive in app banners (matches ConfirmDialog); history subagent toggle is the kit Checkbox.
45
-
46
- **Perceived perf.** index.html: 3x modulepreload + jsdelivr preconnect + static boot-splash in #app. AgentChat warms the markdown stack on mount; MdNode paints raw text synchronously while the loader is in flight (streamed tokens were invisible on cold start). EventList gained a loading skeleton (history events pane); agents panel has a loading/error/empty tri-state (`state.agentsLoading`).
47
-
48
- **Server.** `POST /api/move` (dual realpath confinement, root/self-nesting refusal, 409-unless-overwrite, never overwrites a dir, EXDEV mapped) + `backend.js moveEntry` + BulkBar 'move selected' -> bulk-move PromptDialog -> `runBulkMove` (upfront dest stat, allSettled, failures stay marked). `validate-mutations.mjs` now 26 checks (9 move cases) - run it after touching the mutation surface.
49
-
50
- **Kit DX.** COMPONENT_API.md gained a 'Workspace Surfaces' section documenting every flagship contract (AgentChat incl. the onPasteFiles-not-onPasteImage trap, WorkspaceShell stableFrame/mainFlush, SessionDashboard, FileGrid selection/density, BulkBar, DropZone wrapper semantics) + an honest CSS-taxonomy section. FileGrid accepts canonical `selected`/`onToggleSelect` (marked/onMark stay as aliases). `Icon`/`iconMarkup` accept the props-object form. 22 deprecated `Btn primary:true` call sites -> `variant:'primary'`; AgentChat `who` -> `role`. Build now runs FOUR lints: tokens, glyphs, null-children, classes (class-prefix taxonomy with a frozen bare-name allowlist).
51
-
52
- **Witness caveats (this env).** The spool out/ dir keeps STALE responses from prior sessions - a Read racing the watcher's write returns old bytes; re-read when content doesn't match your probe. plugkit browser sessions expire between dispatches (a fresh session needs a full goto). The first git_status after heavy file churn can report stat-cache false positives (empty git_diff resolves them).
41
+ 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.
53
42
 
54
43
  ## Files command surface + PASSWORD-boot fix (2026-06-11) — eleventh run
55
44
 
@@ -37,6 +37,12 @@ export function confineToRoots(inputPath, allowRoots) {
37
37
  return { ok: true, realPath };
38
38
  }
39
39
 
40
+ // Secret-bearing basenames that must never be readable through ANY confined
41
+ // raw-bytes route (preview or download), even when they sit inside an allowed
42
+ // root: dotfiles, env/key/cert material, and credential stores. One const so
43
+ // /api/file and /api/download can never drift apart.
44
+ export const SECRET_RE = /(^\.|\.(env|pem|key|crt|p12|pfx)$|secret|credential|\.npmrc$|\.netrc$)/i;
45
+
40
46
  // The allowlist the Files surface operates within: server cwd + Claude
41
47
  // projects dir, widened via FS_ROOTS (path-separated). One construction so
42
48
  // /api/list,file,download and the mutation routes can never drift apart.
@@ -403,7 +409,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
403
409
  for (const [t, exts] of Object.entries(EXT_TYPE)) if (exts.includes(ext)) return t;
404
410
  return 'other';
405
411
  };
406
- const dirents = fs.readdirSync(normalizedPath, { withFileTypes: true });
412
+ const dirents = fs.readdirSync(normalizedPath, { withFileTypes: true }).filter((d) => !SECRET_RE.test(d.name));
407
413
  const entries = dirents.map((d) => {
408
414
  const full = path.join(normalizedPath, d.name);
409
415
  let size = null, modified = null, permissions;
@@ -449,7 +455,6 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
449
455
  // material, and credential stores must never be readable through the
450
456
  // Files preview even when they sit inside an allowed root.
451
457
  const base = path.basename(normalizedPath);
452
- const SECRET_RE = /(^\.|\.(env|pem|key|crt|p12|pfx)$|secret|credential|\.npmrc$|\.netrc$)/i;
453
458
  if (SECRET_RE.test(base)) { res.writeHead(403); res.end('Forbidden'); return; }
454
459
  // Only known text/code extensions (images go through /api/image). An
455
460
  // unknown/binary extension is rejected, never served as octet-stream.
@@ -493,15 +498,19 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
493
498
  const conf = confineToRoots(decodedPath, allowRoots);
494
499
  if (!conf.ok) { res.writeHead(conf.reason === 'not found' ? 404 : 403); res.end('Forbidden'); return; }
495
500
  const normalizedPath = conf.realPath;
501
+ // Same secret-name block as /api/file: a download must not exfiltrate
502
+ // .env/.pem/.key/credential material just because it streams bytes.
503
+ if (SECRET_RE.test(path.basename(normalizedPath))) { res.writeHead(403); res.end('Forbidden'); return; }
496
504
  try {
497
505
  const st = fs.statSync(normalizedPath);
498
506
  if (!st.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
499
507
  const MAX = 50 * 1024 * 1024; // 50MB cap so a download can't exhaust memory
500
508
  if (st.size > MAX) { res.writeHead(413); res.end('File too large to download'); return; }
501
- const name = path.basename(normalizedPath).replace(/["\r\n]/g, '');
509
+ const base = path.basename(normalizedPath);
510
+ const asciiName = base.replace(/[\\"\r\n]/g, '').replace(/[^\x20-\x7e]/g, '_');
502
511
  res.writeHead(200, {
503
512
  'Content-Type': 'application/octet-stream',
504
- 'Content-Disposition': 'attachment; filename="' + name + '"',
513
+ 'Content-Disposition': 'attachment; filename="' + asciiName + '"; filename*=UTF-8\'\'' + encodeURIComponent(base),
505
514
  'Content-Length': String(st.size),
506
515
  'Cache-Control': 'no-cache',
507
516
  });
@@ -533,6 +542,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
533
542
  if (isAllowRoot(conf.realPath, allowRoots)) { sendJSON(req, res, 403, { error: 'forbidden: cannot rename an allowed root' }); return; }
534
543
  const newName = sanitizeEntryName(body.newName);
535
544
  if (!newName) { sendJSON(req, res, 400, { error: 'invalid name' }); return; }
545
+ if (SECRET_RE.test(newName)) { sendJSON(req, res, 403, { error: 'forbidden: secret/dotfile name' }); return; }
536
546
  const target = path.join(path.dirname(conf.realPath), newName);
537
547
  // The target stays in the same (already-confined) directory by
538
548
  // construction, but re-check anyway so the invariant is local.
@@ -622,6 +632,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
622
632
  if (!conf.ok) { sendJSON(req, res, conf.reason === 'not found' ? 404 : 403, { error: 'forbidden: ' + conf.reason }); return; }
623
633
  const name = sanitizeEntryName(body.name);
624
634
  if (!name) { sendJSON(req, res, 400, { error: 'invalid name' }); return; }
635
+ if (SECRET_RE.test(name)) { sendJSON(req, res, 403, { error: 'forbidden: secret/dotfile name' }); return; }
625
636
  const target = path.join(conf.realPath, name);
626
637
  if (fs.existsSync(target)) { sendJSON(req, res, 409, { error: 'a file with that name already exists' }); return; }
627
638
  try { fs.mkdirSync(target); sendJSON(req, res, 200, { ok: true, path: target }); }
@@ -641,6 +652,7 @@ export function createHttpHandler({ BASE_URL, expressApp, queries, sendJSON, ser
641
652
  if (!conf.ok) { sendJSON(req, res, conf.reason === 'not found' ? 404 : 403, { error: 'forbidden: ' + conf.reason }); return; }
642
653
  const name = sanitizeEntryName(qs.get('name'));
643
654
  if (!name) { sendJSON(req, res, 400, { error: 'invalid name' }); return; }
655
+ if (SECRET_RE.test(name)) { sendJSON(req, res, 403, { error: 'forbidden: secret/dotfile name' }); return; }
644
656
  const target = path.join(conf.realPath, name);
645
657
  if (fs.existsSync(target) && qs.get('overwrite') !== '1') { sendJSON(req, res, 409, { error: 'a file with that name already exists' }); return; }
646
658
  let buf;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.972",
3
+ "version": "1.0.974",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -1809,7 +1809,7 @@
1809
1809
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1810
1810
  }
1811
1811
  .ds-247420 .ds-density-btn:hover { color: var(--fg); }
1812
- .ds-247420 .ds-density-btn.active { background: var(--accent-tint); color: var(--accent); }
1812
+ .ds-247420 .ds-density-btn.active { background: var(--accent-tint); color: var(--fg); }
1813
1813
  .ds-247420 .ds-density-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
1814
1814
 
1815
1815
  /* Compact density — tighter rows for long listings. */
@@ -1834,6 +1834,10 @@
1834
1834
  .ds-247420 .ds-file-cell.active { border-color: var(--accent); background: var(--accent-tint); }
1835
1835
  .ds-247420 .ds-file-cell.is-marked { border-color: var(--accent); background: var(--accent-tint); }
1836
1836
  .ds-247420 .ds-file-cell.is-locked { opacity: 0.6; }
1837
+ .ds-247420 .ds-file-row.is-locked { opacity: 0.6; }
1838
+ .ds-247420 .ds-file-row.is-restricted .title { color: var(--fg-2); }
1839
+ .ds-247420 .ds-file-perm-tag { font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-3); padding: 1px 6px; border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1); white-space: nowrap; }
1840
+ .ds-247420 .ds-file-perm-tag.is-noaccess { color: var(--flame); border-color: color-mix(in srgb, var(--flame) 40%, transparent); }
1837
1841
  .ds-247420 .ds-file-cell-open {
1838
1842
  display: flex; flex-direction: column; align-items: stretch; gap: var(--space-1);
1839
1843
  width: 100%; padding: var(--space-1); margin: 0;
@@ -2230,6 +2234,10 @@
2230
2234
  border-radius: var(--r-2); font-size: var(--fs-xs);
2231
2235
  }
2232
2236
  .ds-247420 .ds-shortcut-row { display: flex; align-items: center; gap: var(--space-2); }
2237
+ .ds-247420 .ds-shortcuts-hint .ds-kbd-label { flex: 1 1 auto; opacity: .85; }
2238
+ .ds-247420 .ds-shortcuts-hint .ds-kbd { white-space: normal; max-width: 100%; }
2239
+ .ds-247420 .ds-kbd-caps { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
2240
+ .ds-247420 .ds-kbd-sep { color: var(--fg-3); font-size: var(--fs-micro); padding: 0 2px; }
2233
2241
  .ds-247420 .ds-kbd {
2234
2242
  display: inline-block; min-width: 0;
2235
2243
  padding: 2px 7px; border-radius: 6px;
@@ -2702,6 +2710,12 @@
2702
2710
  .ds-247420 .chat-composer .send:disabled {
2703
2711
  background: var(--bg-3); color: var(--fg-3); cursor: not-allowed; transform: none;
2704
2712
  }
2713
+ @media (prefers-reduced-motion: reduce) {
2714
+ .ds-247420 .chat-composer,
2715
+ .ds-247420 .chat-composer textarea,
2716
+ .ds-247420 .chat-composer .send,
2717
+ .ds-247420 .composer-btn { transition: none; }
2718
+ }
2705
2719
 
2706
2720
  .ds-247420 .aicat-portrait { display: inline-flex; align-items: center; gap: 10px; padding: 4px 0; }
2707
2721
  .ds-247420 .aicat-face {
@@ -3694,6 +3708,7 @@
3694
3708
  .ds-247420 .ws-pane-collapsed .ws-pane > * { display: none; }
3695
3709
  @media (pointer: coarse) {
3696
3710
  .ds-247420 .ws-rail-toggle { width: 44px; height: 44px; }
3711
+ .ds-247420 .ws-drawer-toggle { width: 44px; height: 44px; }
3697
3712
  }
3698
3713
 
3699
3714
  /* Drawer toggles and the scrim are hidden by default and revealed by the staged
@@ -3819,6 +3834,11 @@
3819
3834
  .ds-247420 .ws-drawer-toggle:hover { background: var(--bg-2); color: var(--fg); }
3820
3835
  .ds-247420 .ws-drawer-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
3821
3836
 
3837
+ @media (prefers-reduced-motion: reduce) {
3838
+ .ds-247420 .ws-shell, .ds-247420 .ws-rail, .ds-247420 .ws-pane, .ds-247420 .ws-sessions, .ds-247420 .ws-scrim { transition: none; }
3839
+ .ds-247420 .ws-resizer::after { transition: none; }
3840
+ }
3841
+
3822
3842
  /* ============================================================
3823
3843
  Row title highlight, expanded-row actions, filter pills.
3824
3844
  ============================================================ */
@@ -3849,7 +3869,7 @@
3849
3869
  transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
3850
3870
  }
3851
3871
  .ds-247420 .ds-filter-pill:hover { background: var(--bg-3); color: var(--fg); }
3852
- .ds-247420 .ds-filter-pill.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
3872
+ .ds-247420 .ds-filter-pill.active { background: var(--accent-tint); color: var(--fg); border-color: var(--accent); }
3853
3873
  .ds-247420 .ds-filter-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
3854
3874
 
3855
3875
  /* Touch floor for the new small controls. */
@@ -4907,6 +4927,16 @@
4907
4927
  border-radius: var(--r-1);
4908
4928
  }
4909
4929
 
4930
+ /* ThreadPanel / Forum / Page interactive elements */
4931
+ .ds-247420 .cm-tp-item:focus-visible,
4932
+ .ds-247420 .cm-forum-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--r-1); }
4933
+ .ds-247420 .cm-tp-new:focus-visible,
4934
+ .ds-247420 .cm-tp-close:focus-visible,
4935
+ .ds-247420 .cm-forum-new:focus-visible,
4936
+ .ds-247420 .cm-forum-sort:focus-visible,
4937
+ .ds-247420 .cm-page-edit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-1); }
4938
+ .ds-247420 .cm-forum-search:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
4939
+
4910
4940
  /* ---------- mobile header ---------- */
4911
4941
  .ds-247420 .cm-mobile-header {
4912
4942
  display: flex;
@@ -5487,7 +5517,7 @@
5487
5517
  background: none;
5488
5518
  border: 1px solid var(--rule);
5489
5519
  color: inherit;
5490
- border-radius: 6px;
5520
+ border-radius: var(--r-1);
5491
5521
  padding: 2px 8px;
5492
5522
  cursor: pointer;
5493
5523
  font: inherit;
@@ -5506,6 +5536,7 @@
5506
5536
  }
5507
5537
  .ds-247420 .agentchat-cwd-input:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }
5508
5538
  .ds-247420 .agentchat-cwd-input[aria-invalid='true'] { border-color: var(--flame); box-shadow: inset 0 0 0 var(--bw-hair) var(--flame); }
5539
+ .ds-247420 .agentchat-cwd-input[aria-busy='true'] { border-color: var(--fg-3); box-shadow: inset 0 0 0 var(--bw-hair) var(--fg-3); }
5509
5540
 
5510
5541
  /* head + thread */
5511
5542
  .ds-247420 .agentchat-head {
@@ -5756,18 +5787,14 @@
5756
5787
  }
5757
5788
  .ds-247420 .ds-session-new > span { display: none; }
5758
5789
  .ds-247420 .ds-session-new:hover { background: var(--bg-2); color: var(--fg); }
5759
- .ds-247420 .ds-session-search { order: 1; flex: 1 1 auto; min-width: 0; }
5790
+ /* Rail filter uses the shared .ds-search-input primitive (provides bg/border/
5791
+ radius/focus-ring); only the rail layout + touch floor live here. */
5792
+ .ds-247420 .ds-session-head .ds-search-input { order: 1; flex: 1 1 auto; min-width: 0; }
5760
5793
  .ds-247420 .ds-session-new:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
5761
- .ds-247420 .ds-session-search {
5762
- width: 100%; padding: var(--space-1) var(--space-3); min-height: 36px;
5763
- background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); color: var(--fg);
5764
- border-radius: var(--r-1); font-family: var(--ff-body); font-size: var(--fs-sm);
5765
- }
5766
- .ds-247420 .ds-session-search:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
5767
5794
  /* Touch floor (must FOLLOW the base rules - same specificity, order decides). */
5768
5795
  @media (pointer: coarse) {
5769
5796
  .ds-247420 .ds-session-new { width: 44px; min-height: 44px; }
5770
- .ds-247420 .ds-session-search { min-height: 44px; }
5797
+ .ds-247420 .ds-session-head .ds-search-input { min-height: 44px; }
5771
5798
  }
5772
5799
  .ds-247420 .ds-session-list, .ds-247420 .ds-session-groups { flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-2); }
5773
5800
  /* Grouped rows (Today/Yesterday/...) lay out like the flat list; the section
@@ -5946,7 +5973,7 @@
5946
5973
  border-radius: var(--r-1); background: var(--bg-2); color: var(--fg-2);
5947
5974
  cursor: pointer; font-family: var(--ff-body); font-size: var(--fs-tiny);
5948
5975
  }
5949
- .ds-247420 .ds-dash-errors-toggle.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
5976
+ .ds-247420 .ds-dash-errors-toggle.active { background: var(--accent-tint); color: var(--fg); border-color: var(--accent); }
5950
5977
  .ds-247420 .ds-dash-errors-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
5951
5978
 
5952
5979
  /* --- C3: per-card select checkbox. --- */
@@ -6114,6 +6141,7 @@
6114
6141
  /* Inline cwd validation line (checking / error) under the cwd input. */
6115
6142
  .ds-247420 .agentchat-cwd-hint { font-size: var(--fs-tiny); color: var(--fg-3); }
6116
6143
  .ds-247420 .agentchat-cwd-hint.is-error { color: var(--flame); }
6144
+ .ds-247420 .agentchat-cwd-hint.is-checking { color: var(--fg-2); font-style: italic; }
6117
6145
 
6118
6146
  /* Dashboard: shared session title heading (same string as the rails). */
6119
6147
  .ds-247420 .ds-dash-title {
@@ -6213,6 +6241,7 @@
6213
6241
  }
6214
6242
  .ds-247420 .chat-msg .chat-tool .chat-tool-head::-webkit-details-marker { display: none; }
6215
6243
  .ds-247420 .chat-msg .chat-tool .chat-tool-head:hover { background: var(--bg-2); }
6244
+ .ds-247420 .chat-msg .chat-tool .chat-tool-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
6216
6245
  .ds-247420 .chat-tool-icon { display: inline-flex; color: var(--fg-3); }
6217
6246
  .ds-247420 .chat-tool-name { font-family: var(--ff-mono); font-weight: 600; color: var(--fg); }
6218
6247
  .ds-247420 .chat-tool-label { color: var(--fg-3); font-size: var(--fs-tiny); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
@@ -6233,6 +6262,7 @@
6233
6262
  .ds-247420 .chat-tool-section-label { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); }
6234
6263
  .ds-247420 .chat-tool-copy { position: static; opacity: 0; }
6235
6264
  .ds-247420 .chat-tool-section:hover .chat-tool-copy, .ds-247420 .chat-tool-section:focus-within .chat-tool-copy { opacity: 1; }
6265
+ .ds-247420 .chat-tool-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
6236
6266
  .ds-247420 .chat-tool-pre { margin: 0; padding: var(--space-2); background: var(--bg-2); border-radius: var(--r-1); font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1.45; overflow-x: auto; max-height: 320px; overflow-y: auto; }
6237
6267
  .ds-247420 .chat-tool-pre.is-error { color: var(--flame); }
6238
6268
  .ds-247420 .chat-tool-pre.chat-tool-empty { color: var(--fg-3); }
@@ -6288,15 +6318,17 @@
6288
6318
  .ds-247420 .ds-dash-breakdown .seg { display: inline-flex; align-items: center; gap: var(--space-1); font-weight: 600; }
6289
6319
  .ds-247420 .ds-dash-breakdown .seg::before { content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none; }
6290
6320
  .ds-247420 .ds-dash-breakdown .seg.is-running { color: var(--accent); }
6291
- .ds-247420 .ds-dash-breakdown .seg.is-running::before { background: var(--accent); }
6321
+ .ds-247420 .ds-dash-breakdown .seg.is-running::before { background: var(--accent); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--accent) 30%, transparent); }
6292
6322
  .ds-247420 .ds-dash-breakdown .seg.is-error { color: var(--flame); }
6293
6323
  .ds-247420 .ds-dash-breakdown .seg.is-error::before { background: var(--flame); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--flame) 38%, transparent); }
6294
- .ds-247420 .ds-dash-breakdown .seg.is-idle { color: var(--amber); }
6295
- .ds-247420 .ds-dash-breakdown .seg.is-idle::before { background: transparent; box-shadow: inset 0 0 0 2px var(--amber); }
6324
+ .ds-247420 .ds-dash-breakdown .seg.is-idle { color: var(--stale); }
6325
+ .ds-247420 .ds-dash-breakdown .seg.is-idle::before { background: transparent; box-shadow: inset 0 0 0 1px var(--stale), inset 0 0 0 3px var(--bg); }
6296
6326
  .ds-247420 .ds-dash-stream-disc { display: inline-flex; align-items: center; gap: var(--space-1); }
6297
6327
  .ds-247420 .ds-dash-selectall { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--fs-tiny); color: var(--fg-2); cursor: pointer; background: none; border: none; padding: var(--space-1); }
6298
6328
  .ds-247420 .ds-dash-selectall:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
6299
6329
  .ds-247420 .ds-dash-clear { background: none; border: none; color: var(--fg-3); cursor: pointer; font-size: var(--fs-tiny); text-decoration: underline dotted; padding: var(--space-1); }
6330
+ .ds-247420 .ds-dash-clear:hover { color: var(--fg); }
6331
+ .ds-247420 .ds-dash-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: var(--r-1); }
6300
6332
 
6301
6333
  /* Conversation-rail loading skeleton (cold ccsniff walk). */
6302
6334
  .ds-247420 .ds-session-row-skeleton { display: flex; flex-direction: column; gap: 6px; padding: var(--space-2) var(--space-3); }
@@ -7504,7 +7536,7 @@
7504
7536
  .ds-247420 .ds-input-check:checked { background: var(--accent); border-color: var(--accent); }
7505
7537
  .ds-247420 .ds-input-check:checked::after {
7506
7538
  content: ''; position: absolute; left: 4px; top: 1px;
7507
- width: 4px; height: 8px; border: solid var(--accent-fg, #fff);
7539
+ width: 4px; height: 8px; border: solid var(--accent-fg);
7508
7540
  border-width: 0 2px 2px 0; transform: rotate(45deg);
7509
7541
  }
7510
7542
  .ds-247420 .ds-input-check:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@@ -7546,7 +7578,7 @@
7546
7578
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
7547
7579
  border: 1px solid var(--rule);
7548
7580
  border-radius: var(--r-2, 8px);
7549
- box-shadow: 0 6px 24px color-mix(in oklab, #000 28%, transparent);
7581
+ box-shadow: var(--shadow-3);
7550
7582
  color: var(--panel-text);
7551
7583
  overflow: hidden;
7552
7584
  }
@@ -7913,6 +7945,10 @@
7913
7945
  }
7914
7946
 
7915
7947
  @media print {
7948
+ /* Re-assert the paper-tuned signal tokens so token-derived foregrounds
7949
+ (status discs, error text, tool cards) print with light-theme colours
7950
+ rather than the dark-theme-derived values inherited under auto-dark. */
7951
+ .ds-247420 { --flame:#C53E00; --amber:#8A6512; --warn:#E0241A; --sky:#3A6EFF; --bg:var(--paper); --fg:var(--ink); }
7916
7952
  .ds-247420 #app { min-height: auto; display: block; height: auto; }
7917
7953
  .ds-247420 .skip-link, .ds-247420 .status-dot, .ds-247420 .history-actions, .ds-247420 .chat-composer { display: none !important; }
7918
7954
  .ds-247420 .app, .ds-247420 .app-main, .ds-247420 .panel, .ds-247420 .chat, .ds-247420 .chat-thread {
@@ -7930,20 +7966,28 @@
7930
7966
  .ds-247420 .site-panel { margin: var(--space-2); }
7931
7967
 
7932
7968
  /* Hero block */
7933
- .ds-247420 .site-hero { padding: 24px 22px; }
7969
+ .ds-247420 .site-hero { padding: var(--space-4); }
7934
7970
  .ds-247420 .site-hero-h { margin: 0 0 var(--space-2); }
7935
7971
  .ds-247420 .site-hero-body { margin: var(--space-2) 0 var(--space-3); color: var(--fg-2); max-width: 64ch; }
7936
7972
  .ds-247420 .site-chip-row { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 var(--space-3); }
7937
7973
  .ds-247420 .site-cta-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }
7938
7974
 
7939
7975
  /* Quickstart CLI block — these .cli/.prompt/.cmd nodes were previously unstyled. */
7940
- .ds-247420 .site-cli { padding: 16px 22px; }
7941
- .ds-247420 .site-cli .cli { display: flex; gap: var(--space-2); font-family: var(--ff-mono); font-size: var(--fs-sm); padding: 2px 0; }
7976
+ .ds-247420 .site-cli { padding: var(--space-3); background: var(--bg); border: var(--bw-hair, 1px) solid var(--rule); border-radius: var(--r-1); }
7977
+ .ds-247420 .site-cli .cli { display: flex; align-items: baseline; gap: var(--space-2); font-family: var(--ff-mono); font-size: var(--fs-sm); padding: 2px 0; }
7942
7978
  .ds-247420 .site-cli .prompt { color: var(--fg-3); user-select: none; flex: 0 0 auto; }
7943
7979
  .ds-247420 .site-cli .cmd { color: var(--fg); white-space: pre-wrap; word-break: break-word; }
7944
7980
 
7945
- /* Embedded legacy doc (iframe wrapper) */
7946
- .ds-247420 .site-embed { width: 100%; height: calc(100vh - 180px); min-height: 520px; border: 0; border-radius: var(--r-1); background: var(--bg-2); display: block; }
7981
+ /* Embedded legacy doc (iframe wrapper). 180px = the page chrome (topbar +
7982
+ * crumb + footer) sitting above the embed; dvh keeps it mobile-safe. */
7983
+ .ds-247420 .site-embed { width: 100%; height: calc(100dvh - 180px); min-height: 320px; border: 0; border-radius: var(--r-1); background: var(--bg-2); display: block; }
7984
+
7985
+ /* Marketing footer family (the in-app .app-status strip suppresses content
7986
+ * below 1100px; the site footer always shows its credits row). */
7987
+ .ds-247420 .site-footer { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); width: 100%; padding: var(--space-3) var(--space-4); font-family: var(--ff-body); font-size: var(--fs-sm); line-height: 1.4; color: var(--fg-3); border-top: 1px solid var(--rule); }
7988
+ .ds-247420 .site-footer .item { color: inherit; }
7989
+ .ds-247420 .site-footer .item:first-of-type { color: var(--accent); }
7990
+ .ds-247420 .site-footer .spread { flex: 1; }
7947
7991
 
7948
7992
  /* spoint/loading-screen.css */
7949
7993
  /* Loading-screen kit styles. Scoped under .ds-247420 at build time.