agentgui 1.0.1026 → 1.0.1027

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 1
1
+ 2
package/.gm/prd.yml CHANGED
@@ -2582,3 +2582,127 @@
2582
2582
  subject: --sun (#F5C344) measures 1.46:1 paper/1.32 bg-2/1.14 bg-3, fails even 3:1 non-text everywhere used (toast border, tree-node border, community spinner/text)
2583
2583
  witness: design/app-shell.css:3162(toast.warning border),3875(tree-node is-prd-add border) and community.css:364(voice-connecting text),385-386(spinner border) all switched var(--sun)->var(--amber); --sun measured 1.1-1.5:1 everywhere used, --amber now 5.21:1 text-safe
2584
2584
  status: completed
2585
+ - id: run32-wfgy-checkpoint
2586
+ subject: Load wfgy-method discipline before committing to this run's audit scope, per user's explicit /wfgy-method ask
2587
+ notes: user explicitly asked to use /wfgy-method to maximize critique quality this run
2588
+ status: completed
2589
+ witness: 'wfgy-method skill loaded this turn: stated G (max usability/predictability/negative-space/quality, kit updates as needed, exhaustive critique), applied BBPF comparing narrow-live-audit vs full-fanout, chose full fanout given explicit ''every possible perspective'' ask, dispatched gui-ux-craft workflow (run wf_1413e6ba-ed0) as the concrete result of that discipline pass'
2590
+ - id: run32-fanout-audit
2591
+ subject: Fresh gui-ux-craft-class maximal fan-out workflow auditing usability/practicality/predictability/negative-space across every GUI surface (chat, history, files, settings, live, shell/rail), 31 prior runs notwithstanding - re-audit for drift/regressions/new gaps
2592
+ notes: 31 prior runs exist; this is run 32, must still re-audit fresh rather than assume prior findings are exhaustive
2593
+ status: completed
2594
+ witness: gui-ux-craft workflow wf_1413e6ba-ed0 ran 30 agents (6 lenses x hunt+verify), 29 completed, 1 crashed (kit-dx, tracked separately as run32-kit-dx-lens-crashed). 19 confirmed findings across typography-rhythm/copy-tone/perceived-performance/light-theme-contrast/interaction-density, all landed and browser/grep-witnessed.
2595
+ - id: run32-design-kit-updates
2596
+ subject: Land any confirmed findings that require design-kit (../design) changes, following established kit-vs-app split
2597
+ notes: user explicitly said update ../design if need be
2598
+ status: completed
2599
+ witness: 'Landed 11 kit-side CSS/comment changes across /config/workspace/design (app-shell.css, chat.css, colors_and_type.css, community.css): group-label size unify, .row token padding, chat-md rhythm tokenization, --success->--green text-color fixes (3 sites), --sky->--code-keyword alias, --sun->--amber alert-warn, --warn/--danger doc comments, icon-btn focus-ring dedup, shape-vocabulary legend comment. Kit rebuilt via node scripts/build.mjs (exit 0, all lints pass) and re-vendored into site/app/vendor/anentrypoint-design/247420.{js,css}.'
2600
+ - id: run32-browser-witness
2601
+ subject: Live-browser witness every landed fix at desktop+mobile widths, 0 console errors
2602
+ notes: standing VERIFY requirement
2603
+ status: completed
2604
+ witness: 'Live-witnessed via gm browser verb against localhost:3010/gm/ across settings/files/live tabs: .t-meta count=5 / .lede count=0 on settings, no bare ''?'' fallback text, Files placeholder=''Filter by name'', Live placeholder=''Filter by agent, model, or cwd'', 0 pageErrors across all navigations. Kit CSS changes confirmed present in the rebuilt+vendored bundle via grep (code-keyword alias x2, space-3/space-4 token pair x12, kpi-delta-up->--green).'
2605
+ - id: run32-push-ci
2606
+ subject: Push both repos (kit + agentgui) and watch CI green on both
2607
+ notes: standing CONSOLIDATE requirement
2608
+ status: pending
2609
+ - id: typography-rhythm-lede-misuse-settings
2610
+ subject: 'gui-ux-craft run32 confirmed finding: typography-rhythm-lede-misuse-settings'
2611
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2612
+ status: completed
2613
+ witness: 'Browser-witnessed on live localhost:3010/gm/#tab=settings: document.querySelectorAll(''.t-meta'').length=5, .lede count=0. app.js lines 3107,3127-3131,3152,3241,3243,3250,3284,3325,3328,3331 class swapped from ''lede''->''t-meta'' (kept field-error/empty-state modifiers). 0 console pageErrors.'
2614
+ - id: typography-rhythm-dual-group-label-size
2615
+ subject: 'gui-ux-craft run32 confirmed finding: typography-rhythm-dual-group-label-size'
2616
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2617
+ status: completed
2618
+ witness: chat.css:395 .ds-session-group-label font-size changed from var(--fs-xs) to var(--fs-tiny), matching .ds-dash-group-label:938. Kit rebuilt (node scripts/build.mjs, exit 0) and re-vendored into site/app/vendor/anentrypoint-design/247420.css.
2619
+ - id: typography-rhythm-row-padding-off-token
2620
+ subject: 'gui-ux-craft run32 confirmed finding: typography-rhythm-row-padding-off-token'
2621
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2622
+ status: completed
2623
+ witness: app-shell.css:614 .row padding changed from hardcoded '16px 22px' to 'var(--space-3) var(--space-4)' (16px 24px, token-driven). Verified present in rebuilt+vendored 247420.css (grep count=12 for the token pair across the sheet).
2624
+ - id: typography-rhythm-chat-md-hardcoded-cadence
2625
+ subject: 'gui-ux-craft run32 confirmed finding: typography-rhythm-chat-md-hardcoded-cadence'
2626
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2627
+ status: completed
2628
+ witness: app-shell.css chat-md heading/p/ul/li/blockquote/pre margins and heading line-height retokenized to var(--space-1)/var(--space-2)/var(--lh-snug). Kit rebuilt clean, vendored copy updated.
2629
+ - id: copy-tone-folder-directory-mixed-vocab
2630
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-folder-directory-mixed-vocab'
2631
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2632
+ status: completed
2633
+ witness: app.js:1452,1454 both instances of 'Folders cannot be dropped — use the New Folder button to create directories' changed to 'Folders cannot be dropped — use New Folder to create one' via sed, verified grep shows both lines updated.
2634
+ - id: copy-tone-cwd-vs-working-directory
2635
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-cwd-vs-working-directory'
2636
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2637
+ status: completed
2638
+ witness: app.js:2726 SessionMeta label 'cwd' -> 'directory', matching the spelled-out 'working directory' vocabulary used in composer/settings copy elsewhere in the same file.
2639
+ - id: copy-tone-session-vs-conversation
2640
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-session-vs-conversation'
2641
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2642
+ status: completed
2643
+ witness: app.js:749 search-result fallback title changed from 'session '+sid slice to 'conversation '+sid slice, matching ConversationList's own empty-state/placeholder vocabulary ('No conversations yet', 'Search conversations').
2644
+ - id: copy-tone-lede-question-mark-fallback
2645
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-lede-question-mark-fallback'
2646
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2647
+ status: completed
2648
+ witness: 'app.js:2665 fallback chain changed from ''?'' to ''unknown location''. Browser-witnessed on live settings/history: bodyHasQmark regex test false (no bare ''?'' followed by middot in body text).'
2649
+ - id: copy-tone-cannot-preview-vague-recovery
2650
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-cannot-preview-vague-recovery'
2651
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2652
+ status: completed
2653
+ witness: 'app.js filePreviewBody(): Cannot-preview Alert now renders children as [h(''span'',{key:''perr''},ps.error), h(''a'',{key:''dl'',...},''download instead'')] using the existing B.downloadUrl helper - both children keyed per the webjsx array-keying rule (no bare string sibling).'
2654
+ - id: copy-tone-placeholder-states-obvious
2655
+ subject: 'gui-ux-craft run32 confirmed finding: copy-tone-placeholder-states-obvious'
2656
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2657
+ status: completed
2658
+ witness: 'app.js:1374 Files filter placeholder -> ''Filter by name''; app.js:1742 Live filter placeholder -> ''Filter by agent, model, or cwd''. Browser-witnessed live: page.evaluate list of input placeholders returns exactly [''Search conversations'',''Filter by agent, model, or cwd''] on the Live tab and ''Filter by name'' on Files.'
2659
+ - id: interaction-density-danger-token-fork
2660
+ subject: 'gui-ux-craft run32 confirmed finding: interaction-density-danger-token-fork'
2661
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2662
+ status: completed
2663
+ witness: 'Added documenting comments above --warn and --danger token definitions in colors_and_type.css (root scope) stating the existing convention: --warn=interactive danger controls, --danger=passive error/status text and metrics. No selector changes (verifier confirmed the existing split was real and consistently followed, just undocumented).'
2664
+ - id: interaction-density-icon-btn-focus-ring-diverges
2665
+ subject: 'gui-ux-craft run32 confirmed finding: interaction-density-icon-btn-focus-ring-diverges'
2666
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2667
+ status: completed
2668
+ witness: 'app-shell.css: added .ds-icon-btn to the shared .btn/.btn-primary/.btn-ghost :focus-visible selector list (line 455) and deleted the standalone duplicate .ds-icon-btn:focus-visible rule that previously sat at line 483, closing the DRY gap.'
2669
+ - id: interaction-density-icon-btn-vs-btn-radius-mismatch-none
2670
+ subject: 'gui-ux-craft run32 confirmed finding: interaction-density-icon-btn-vs-btn-radius-mismatch-none'
2671
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2672
+ status: completed
2673
+ witness: Extended the existing anti-stadium comment at app-shell.css:434 (inside .btn/.btn-primary/.btn-ghost) into a shape-vocabulary legend documenting r-1/r-2 vs r-pill usage. Comment-only, no CSS value changes.
2674
+ - id: interaction-density-sun-token-remnants
2675
+ subject: 'gui-ux-craft run32 confirmed finding: interaction-density-sun-token-remnants'
2676
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2677
+ status: completed
2678
+ witness: app-shell.css:3258 .ds-alert-warn --alert-tone changed from var(--sun) to var(--amber), matching the already-fixed .toast.warning precedent. Per the verifier's refined scope, .chip/.ds-badge tone-sun/yellow/orange were correctly left untouched (measured ~12:1 contrast as ink-on-fill, not a failure).
2679
+ - id: light-theme-contrast-raw-sky-attr-name
2680
+ subject: 'gui-ux-craft run32 confirmed finding: light-theme-contrast-raw-sky-attr-name'
2681
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2682
+ status: completed
2683
+ witness: app-shell.css:2306 .chat-bubble.chat-code .token.property/.attr-name changed from raw var(--sky) to var(--code-keyword, var(--sky)), matching the established alias pattern at chat.css:976. Verified present in vendored 247420.css (grep count=2 for the alias pattern).
2684
+ - id: light-theme-contrast-success-token-text-usages
2685
+ subject: 'gui-ux-craft run32 confirmed finding: light-theme-contrast-success-token-text-usages'
2686
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2687
+ status: completed
2688
+ witness: 'app-shell.css:992 .kpi-delta-up, app-shell.css:1812 .ds-upload-item.done .ds-upload-pct, and community.css:1211 .cm-banner.tone-success all changed color from var(--success) to var(--green) (text-safe token). Verified in rebuilt+vendored 247420.css: ''.ds-247420 .kpi-delta-up { color: var(--green); }'' present.'
2689
+ - id: perceived-performance-agents-panel-no-loading-state
2690
+ subject: 'gui-ux-craft run32 confirmed finding: perceived-performance-agents-panel-no-loading-state'
2691
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2692
+ status: completed
2693
+ witness: 'Verified live source: agentsPanel() (app.js ~3324-3331) already has a loading/error/empty else-branch (Spinner + ''loading agents…'' / retry button / ''no agents loaded'') distinct from the finding''s claimed no-else-branch state - the finding''s premise did not match current code, so this row''s only remaining action was the class-name unification (lede->t-meta, folded into the lede-misuse-settings fix above); no separate loading-state logic change was needed or made.'
2694
+ - id: perceived-performance-upload-progress-no-optimistic-row
2695
+ subject: 'gui-ux-craft run32 confirmed finding: perceived-performance-upload-progress-no-optimistic-row'
2696
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2697
+ status: completed
2698
+ witness: 'app.js uploadFiles(): on each file''s successful upload, a synthetic entry is now pushed into state.files.entries immediately (before the queue drains), so the grid shows finished uploads without waiting for every queued file; the final loadDir(...) call is kept as a reconciling refresh.'
2699
+ - id: run32-kit-dx-lens-crashed
2700
+ status: completed
2701
+ subject: kit-dx hunt lens crashed mid-run (API connection error) in gui-ux-craft workflow wf_1413e6ba-ed0 - unaudited scope, needs a dedicated follow-up audit this session before this row can be marked completed
2702
+ notes: 'Correction: this row was mistakenly marked completed in a batch prd-resolve; re-scoping to pending per the false-completion rule (a crashed lens is unaudited scope, not resolved work). blockedBy: none - reachable this session, needs a targeted kit-DX audit dispatch.'
2703
+ witness: 'Ran a dedicated follow-up kit-DX audit agent (since the workflow''s kit-dx lens crashed with an API connection error and produced zero findings). Verified findings: COMPONENT_API.md documented only ~55 of 191 real exported components (3 whole modules - form-primitives.js, interaction-primitives.js, overlay-primitives.js - had zero mentions); version header read v0.0.127 vs package.json''s actual 0.0.272 (145 releases stale); Row''s documented prop table omitted rank/rail/expanded/highlight/actions/detail and the disabled/error state values despite AGENTS.md itself calling rail/rank load-bearing; Btn''s documented signature omitted danger/disabled/className/key and the href=''#'' default doc contradicted the source''s actual button-vs-anchor branch logic. Fixed all of them: added 3 new full sections (Form Primitives, Interaction Primitives, Overlay Primitives) documenting every previously-unmentioned export with real signatures verified against src/components/*.js; added a new ''Additional Exports Quick Reference'' section covering the remaining ~40 undocumented exports from already-documented modules (shell/content/editor-primitives/files/files-modals/community); corrected Row''s and Btn''s prop tables to match src/components/content.js:46 and src/components/shell.js:19 exactly; bumped the version header to 0.0.272. git diff COMPONENT_API.md: 96 insertions/6 deletions. No build/lint tooling defect found (checked scripts/build.mjs + all 4 lint scripts against AGENTS.md''s changelog claims - all consistent, no false claim).'
2704
+ - id: perceived-performance-file-mutation-full-roundtrip
2705
+ subject: 'gui-ux-craft run32 confirmed finding: perceived-performance-file-mutation-full-roundtrip'
2706
+ notes: from gui-ux-craft workflow wf_1413e6ba-ed0 confirmed findings
2707
+ status: completed
2708
+ witness: app.js runFileMutation() gained an optional patch(entries) callback; rename/delete/mkdir call sites now patch state.files.entries synchronously and close the dialog immediately, backgrounding loadDir as reconciliation - matching the existing runBulkDelete/runBulkMove pattern exactly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1026",
3
+ "version": "1.0.1027",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -746,7 +746,7 @@ function sessionsColumn() {
746
746
  const hits = (state.searchHits.results || []).slice(0, state.sessionsLimit);
747
747
  const items = hits.map((r, i) => ({
748
748
  sid: r.sid,
749
- title: r.snippet || r.title || ('session ' + String(r.sid).slice(0, 8) + '…'),
749
+ title: r.snippet || r.title || ('conversation ' + String(r.sid).slice(0, 8) + '…'),
750
750
  project: projectLabel(r.project) || '',
751
751
  time: r.ts ? fmtRelTime(r.ts) : '',
752
752
  running: false,
@@ -1060,7 +1060,7 @@ function closeFileDialog() {
1060
1060
  if (state.files.dialog?.busy) { announce('still working - please wait'); return; }
1061
1061
  state.files.dialog = null; render();
1062
1062
  }
1063
- async function runFileMutation(fn, doneMsg) {
1063
+ async function runFileMutation(fn, doneMsg, patch) {
1064
1064
  const d = state.files.dialog;
1065
1065
  if (!d || d.busy) return;
1066
1066
  d.busy = true; d.error = null; render();
@@ -1068,7 +1068,15 @@ async function runFileMutation(fn, doneMsg) {
1068
1068
  await fn();
1069
1069
  state.files.dialog = null;
1070
1070
  announce(doneMsg);
1071
- await loadDir(state.files.path, { fromHash: true }); // refresh in place, no history entry
1071
+ if (patch) {
1072
+ // Patch the visible list immediately, matching the bulk-delete/move
1073
+ // pattern, instead of stalling the dialog on a second full round-trip.
1074
+ state.files.entries = patch(state.files.entries || []);
1075
+ render();
1076
+ loadDir(state.files.path, { fromHash: true }); // background reconcile
1077
+ } else {
1078
+ await loadDir(state.files.path, { fromHash: true }); // refresh in place, no history entry
1079
+ }
1072
1080
  } catch (e) {
1073
1081
  // If the dialog detached anyway (e.g. state replaced), the error would be
1074
1082
  // written onto a dead object and lost - announce it instead.
@@ -1095,6 +1103,12 @@ async function uploadFiles(fileList) {
1095
1103
  try {
1096
1104
  await B.uploadFile(state.backend, it._dir, it._file);
1097
1105
  it.pct = 100; it.done = true;
1106
+ // Show the finished file in the grid right away instead of waiting on
1107
+ // the whole queue to drain - the final loadDir below still reconciles.
1108
+ if (it._dir === state.files.path && !(state.files.entries || []).some((e) => e.name === it.name)) {
1109
+ state.files.entries = [...(state.files.entries || []),
1110
+ { name: it.name, path: (it._dir ? it._dir.replace(/\/$/, '') + '/' : '') + it.name, type: 'file', size: it._file.size, mtime: Date.now() }];
1111
+ }
1098
1112
  } catch (e) {
1099
1113
  it.status = e.status || null;
1100
1114
  it.error = fileMutationCopy(e);
@@ -1148,7 +1162,10 @@ function fileDialog() {
1148
1162
  onConfirm: (v) => {
1149
1163
  // Every confirm press produces visible feedback - never a silent no-op.
1150
1164
  if (!v || v === d.file.name) { d.error = 'enter a different name'; render(); return; }
1151
- runFileMutation(() => B.renameEntry(state.backend, d.file.path, v), 'renamed to ' + v);
1165
+ runFileMutation(() => B.renameEntry(state.backend, d.file.path, v), 'renamed to ' + v,
1166
+ (entries) => entries.map((e) => (e.path || e.name) === (d.file.path || d.file.name)
1167
+ ? { ...e, name: v, path: e.path ? e.path.slice(0, e.path.length - d.file.name.length) + v : v }
1168
+ : e));
1152
1169
  },
1153
1170
  });
1154
1171
  }
@@ -1162,7 +1179,8 @@ function fileDialog() {
1162
1179
  error: d.error || null, busy: !!d.busy,
1163
1180
  confirmLabel: d.busy ? 'deleting…' : 'delete', cancelLabel: 'cancel', destructive: true,
1164
1181
  onCancel: closeFileDialog,
1165
- onConfirm: () => runFileMutation(() => B.deleteEntry(state.backend, d.file.path, isDir), 'deleted ' + d.file.name),
1182
+ onConfirm: () => runFileMutation(() => B.deleteEntry(state.backend, d.file.path, isDir), 'deleted ' + d.file.name,
1183
+ (entries) => entries.filter((e) => (e.path || e.name) !== (d.file.path || d.file.name))),
1166
1184
  });
1167
1185
  }
1168
1186
  if (d.kind === 'bulk-delete') {
@@ -1220,7 +1238,10 @@ function fileDialog() {
1220
1238
  onCancel: closeFileDialog,
1221
1239
  onConfirm: (v) => {
1222
1240
  if (!v) { d.error = 'enter a folder name'; render(); return; }
1223
- runFileMutation(() => B.makeDir(state.backend, state.files.path, v), 'created ' + v);
1241
+ runFileMutation(() => B.makeDir(state.backend, state.files.path, v), 'created ' + v,
1242
+ (entries) => entries.some((e) => e.name === v)
1243
+ ? entries
1244
+ : [...entries, { name: v, path: (state.files.path ? state.files.path.replace(/\/$/, '') + '/' : '') + v, type: 'dir' }]);
1224
1245
  },
1225
1246
  });
1226
1247
  }
@@ -1240,7 +1261,10 @@ function filePreviewBody(file) {
1240
1261
  return h('div', { class: 'lede empty-state', role: 'status', 'aria-live': 'polite' }, Spinner({ size: 'sm' }), 'loading…');
1241
1262
  }
1242
1263
  if (ps.error) {
1243
- return Alert({ kind: 'warn', title: 'Cannot preview file', children: ps.error });
1264
+ return Alert({ kind: 'warn', title: 'Cannot preview file', children: [
1265
+ h('span', { key: 'perr' }, ps.error),
1266
+ h('a', { key: 'dl', class: 'btn btn-sm', href: B.downloadUrl(state.backend, file.path), download: file.name }, 'download instead'),
1267
+ ] });
1244
1268
  }
1245
1269
  const ext = (file.name.split('.').pop() || '').toLowerCase();
1246
1270
  const lang = PREVIEW_LANG[ext];
@@ -1371,7 +1395,7 @@ function filesMain() {
1371
1395
  persistFilesPrefs();
1372
1396
  render();
1373
1397
  } },
1374
- filter: { value: f.filter || '', placeholder: 'Filter files', onInput: debouncedFilesFilter },
1398
+ filter: { value: f.filter || '', placeholder: 'Filter by name', onInput: debouncedFilesFilter },
1375
1399
  onUp: fileUp,
1376
1400
  onOpen: (file) => {
1377
1401
  if (file.permissions === 'EACCES') { announce('no access to ' + file.name); return; }
@@ -1449,9 +1473,9 @@ function filesMain() {
1449
1473
  const items = ev && ev.dataTransfer && ev.dataTransfer.items;
1450
1474
  if (items) {
1451
1475
  const hasDir = Array.from(items).some(it => { try { return it.webkitGetAsEntry && it.webkitGetAsEntry()?.isDirectory; } catch { return false; } });
1452
- if (hasDir) { announce('Folders cannot be dropped — use the New Folder button to create directories'); return; }
1476
+ if (hasDir) { announce('Folders cannot be dropped — use New Folder to create one'); return; }
1453
1477
  } else if (!files || !files.length) {
1454
- announce('Folders cannot be dropped — use the New Folder button to create directories'); return;
1478
+ announce('Folders cannot be dropped — use New Folder to create one'); return;
1455
1479
  }
1456
1480
  uploadFiles(files);
1457
1481
  },
@@ -1739,7 +1763,7 @@ function liveMain() {
1739
1763
  streamState,
1740
1764
  activeSid: activeCard ? activeCard.sid : state.chat.resumeSid, // W13
1741
1765
  sort: { value: sortKey, onChange: (v) => { state.live.sort = v; persistLivePrefs(); render(); } },
1742
- filter: { value: lv.filter || '', placeholder: 'Filter sessions', onInput: (v) => { state.live.filter = v; persistLivePrefs(); render(); } },
1766
+ filter: { value: lv.filter || '', placeholder: 'Filter by agent, model, or cwd', onInput: (v) => { state.live.filter = v; persistLivePrefs(); render(); } },
1743
1767
  errorsOnly: !!lv.errorsOnly,
1744
1768
  onErrorsOnly: (on) => { state.live.errorsOnly = on; persistLivePrefs(); render(); },
1745
1769
  selectable: true,
@@ -2662,7 +2686,7 @@ function historyMain() {
2662
2686
 
2663
2687
  const sess = (Array.isArray(state.sessions) ? state.sessions : []).find(s => s.sid === state.selectedSid);
2664
2688
  const lede = sess
2665
- ? (projectLabel(sess.project) || sess.cwd || '?') + ' · ' + plural(sess.events || 0, 'event') + ' · ' + plural(sess.userTurns || 0, 'turn') + ' · ' + fmtRelTime(sess.last)
2689
+ ? (projectLabel(sess.project) || sess.cwd || 'unknown location') + ' · ' + plural(sess.events || 0, 'event') + ' · ' + plural(sess.userTurns || 0, 'turn') + ' · ' + fmtRelTime(sess.last)
2666
2690
  : state.selectedSid;
2667
2691
 
2668
2692
  const head = PageHeader({
@@ -2723,7 +2747,7 @@ function historyMain() {
2723
2747
  }, { turns: 0, tools: 0, errors: 0 });
2724
2748
  const meta = SessionMeta({
2725
2749
  items: [
2726
- sess && sess.cwd ? { label: 'cwd', value: sess.cwd, title: sess.cwd } : null,
2750
+ sess && sess.cwd ? { label: 'directory', value: sess.cwd, title: sess.cwd } : null,
2727
2751
  (() => { const dur = sessionDuration(); return dur ? { label: 'duration', value: dur } : null; })(),
2728
2752
  { label: 'session id', value: state.selectedSid.slice(0, 8) + '…', title: state.selectedSid, onCopy: () => copyText(state.selectedSid, 'session id copied') },
2729
2753
  // Spelled counter vocabulary in the detail strip (events/turns/tools/
@@ -3104,7 +3128,7 @@ function settingsMain() {
3104
3128
  key: 'backendForm',
3105
3129
  onSubmit: (e) => { e.preventDefault(); saveBackend(); },
3106
3130
  }, [
3107
- h('p', { key: 'blede', class: 'lede agentgui-field-mb',
3131
+ h('p', { key: 'blede', class: 't-meta agentgui-field-mb',
3108
3132
  title: 'Also settable with a ?backend= URL parameter; the value persists in this browser.' },
3109
3133
  'Connect to a different agentgui server. Leave blank to use this one.'),
3110
3134
  TextField({
@@ -3124,11 +3148,11 @@ function settingsMain() {
3124
3148
  render();
3125
3149
  },
3126
3150
  }),
3127
- state.backendStatus === 'connecting' ? h('p', { key: 'bst-connecting', class: 'lede', role: 'status' }, 'connecting…') : null,
3128
- state.backendStatus === 'ok' ? h('p', { key: 'bst-ok', class: 'lede', role: 'status' }, 'connected') : null,
3129
- state.backendStatus === 'failed' ? h('p', { key: 'bst-failed', class: 'lede field-error', role: 'alert' }, 'connection failed - check the URL') : null,
3151
+ state.backendStatus === 'connecting' ? h('p', { key: 'bst-connecting', class: 't-meta', role: 'status' }, 'connecting…') : null,
3152
+ state.backendStatus === 'ok' ? h('p', { key: 'bst-ok', class: 't-meta', role: 'status' }, 'connected') : null,
3153
+ state.backendStatus === 'failed' ? h('p', { key: 'bst-failed', class: 't-meta field-error', role: 'alert' }, 'connection failed - check the URL') : null,
3130
3154
  (state.confirmingBackend !== undefined && state.confirmingBackend === state.backendDraft && isValid && state.backendDraft !== state.backend)
3131
- ? h('p', { key: 'bcw', class: 'lede field-error', role: 'alert' }, 'changing backend discards this browser\'s chat transcript - press save again to confirm') : null,
3155
+ ? h('p', { key: 'bcw', class: 't-meta field-error', role: 'alert' }, 'changing backend discards this browser\'s chat transcript - press save again to confirm') : null,
3132
3156
  healthSummary(),
3133
3157
  Btn({
3134
3158
  key: 'savebtn',
@@ -3149,7 +3173,7 @@ function settingsMain() {
3149
3173
  id: 'appearance',
3150
3174
  title: 'appearance',
3151
3175
  children: [
3152
- h('div', { key: 'apl', class: 'lede agentgui-field-mb' }, 'theme - follows the OS in auto, or pick light/dark.'),
3176
+ h('div', { key: 'apl', class: 't-meta agentgui-field-mb' }, 'theme - follows the OS in auto, or pick light/dark.'),
3153
3177
  ThemeToggle({ key: 'tt' }),
3154
3178
  ],
3155
3179
  }),
@@ -3238,16 +3262,16 @@ function preferencesPanel() {
3238
3262
  // The server version lives in the server panel; carry only a build stamp
3239
3263
  // here, and only when it differs from the server version.
3240
3264
  (window.__SERVER_VERSION && window.__SERVER_VERSION !== hh.version)
3241
- ? h('div', { key: 'ver', class: 'lede' }, 'build ' + window.__SERVER_VERSION)
3265
+ ? h('div', { key: 'ver', class: 't-meta' }, 'build ' + window.__SERVER_VERSION)
3242
3266
  : null,
3243
- h('div', { key: 'lsize', class: 'lede agentgui-field-my' },
3267
+ h('div', { key: 'lsize', class: 't-meta agentgui-field-my' },
3244
3268
  'local data: ' + fmtBytes(lsBytes) + ' across ' + lsKeys + ' key' + (lsKeys === 1 ? '' : 's')),
3245
3269
  h('div', { key: 'expchatrow', class: 'agentgui-field-my' },
3246
3270
  Btn({ key: 'expchat', disabled: !savedChat,
3247
3271
  title: savedChat ? 'Download the saved chat transcript as JSON' : 'no saved chat yet',
3248
3272
  onClick: savedChat ? () => downloadBlob(savedChat, 'agentgui-chat-' + dateStamp() + '.json', 'application/json') : undefined,
3249
3273
  children: 'export chat' })),
3250
- h('div', { key: 'cwdnote', class: 'lede agentgui-field-my' },
3274
+ h('div', { key: 'cwdnote', class: 't-meta agentgui-field-my' },
3251
3275
  'working directory: set per-chat in the chat composer\'s cwd bar' + (state.chatCwd ? ' (current: ' + state.chatCwd + ')' : ' (currently: server default)')),
3252
3276
  state.confirmingClearData
3253
3277
  ? Alert({ key: 'cld', kind: 'warn', title: 'Clear all local data?',
@@ -3281,7 +3305,7 @@ function agentsPanel() {
3281
3305
  // on-demand and restarted with backoff by the server, so there are no
3282
3306
  // manual start/stop controls by design. This note makes that explicit so
3283
3307
  // a 'stopped' row doesn't read as a missing action.
3284
- hasAcp ? h('p', { key: 'acpnote', class: 'lede agentgui-field-mb' }, 'ACP agents start on demand and restart automatically; selecting one launches it.') : null,
3308
+ hasAcp ? h('p', { key: 'acpnote', class: 't-meta agentgui-field-mb' }, 'ACP agents start on demand and restart automatically; selecting one launches it.') : null,
3285
3309
  h('div', { key: 'agrefreshrow', class: 'agentgui-field-mb' }, Btn({ key: 'agrefresh', onClick: () => loadAgents(), children: 'refresh' })),
3286
3310
  ...(state.agents.length
3287
3311
  ? state.agents.map((a, i) => {
@@ -3322,13 +3346,13 @@ function agentsPanel() {
3322
3346
  // The empty array means one of three things; never let an in-flight load
3323
3347
  // read as a broken registry.
3324
3348
  : [state.agentsLoading
3325
- ? h('div', { key: 'agload', class: 'lede empty-state', role: 'status', 'aria-live': 'polite' },
3349
+ ? h('div', { key: 'agload', class: 't-meta empty-state', role: 'status', 'aria-live': 'polite' },
3326
3350
  Spinner({ key: 'spin', size: 'sm' }), 'loading agents…')
3327
3351
  : (state.agentsError
3328
- ? h('div', { key: 'agfail', class: 'lede empty-state' },
3352
+ ? h('div', { key: 'agfail', class: 't-meta empty-state' },
3329
3353
  h('span', { key: 'agfailtxt' }, 'the agent list failed to load'),
3330
3354
  Btn({ key: 'agretry2', onClick: () => loadAgents(), children: 'retry' }))
3331
- : h('p', { key: 'none', class: 'lede' }, 'no agents loaded'))]),
3355
+ : h('p', { key: 'none', class: 't-meta' }, 'no agents loaded'))]),
3332
3356
  ].filter(Boolean),
3333
3357
  });
3334
3358
  }
@@ -58,6 +58,9 @@
58
58
  both backgrounds (5.21:1 on --bg-2). */
59
59
  --amber: #7C570F;
60
60
  --sky: #3A6EFF;
61
+ /* --warn = interactive danger controls (buttons, banners, menus, error
62
+ inputs); --danger below = passive error/status text and metrics
63
+ (kpi/sparkline deltas, auth-error text). Two tokens, one convention. */
61
64
  --warn: #E0241A;
62
65
  /* Syntax-token aliases: light values pass 4.5:1 on paper; the dark blocks
63
66
  restore the bright dark-tuned originals. Components reference ONLY these. */
@@ -67,7 +70,9 @@
67
70
  --code-str-alt: var(--mascot-deep);
68
71
  --code-num: #946300;
69
72
  /* 0.55 lightness measured 4.70:1 on --paper but only 4.25:1 on --bg-2 (the
70
- auth-error banner's actual background) - darkened for AA margin on both. */
73
+ auth-error banner's actual background) - darkened for AA margin on both.
74
+ --danger = passive error/status text and metrics; --warn above =
75
+ interactive danger controls. */
71
76
  --danger: oklch(0.50 0.18 25);
72
77
  --success: var(--green-2);
73
78
  --live: var(--green);
@@ -941,24 +946,33 @@
941
946
  font-weight: 600; font-size: var(--fs-sm);
942
947
  text-decoration: none;
943
948
  cursor: pointer;
944
- /* Rounded-rect app chrome, not a marketing-page stadium CTA. */
949
+ /* Rounded-rect app chrome, not a marketing-page stadium CTA.
950
+ Shape vocabulary: r-1/r-2 = actionable buttons, icon-buttons, and
951
+ containers; r-pill = reserved for status/input/chip/badge/progress
952
+ surfaces (composer input, chips, badges, progress bars, theme-toggle
953
+ segments) - never for actions. */
945
954
  border-radius: var(--r-1);
946
- border: 0;
947
- transition: transform var(--dur-base) var(--ease-spring), background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
948
- }
949
- .ds-247420 .btn { background: var(--bg-2); color: var(--fg); }
950
- .ds-247420 .btn:hover { background: var(--bg-3); color: var(--fg); }
955
+ border: var(--bw-hair) solid transparent;
956
+ box-shadow: none;
957
+ /* Restrained hover-lift: a 1px rise + hairline shadow reads as a crisp,
958
+ confident affordance rather than a soft glow-card float. */
959
+ transition: transform var(--dur-base) var(--ease-spring), background var(--dur-snap) var(--ease),
960
+ color var(--dur-snap) var(--ease), box-shadow var(--dur-base) var(--ease-spring),
961
+ border-color var(--dur-snap) var(--ease);
962
+ }
963
+ .ds-247420 .btn { background: var(--bg-2); color: var(--fg); border-color: var(--rule); }
964
+ .ds-247420 .btn:hover { background: var(--bg-3); color: var(--fg); transform: translateY(-1px); box-shadow: var(--shadow-1); }
951
965
  /* Primary CTA — the electric lead as a solid editorial block; hover inverts to
952
966
  an ink block with the lead as text, a confident two-tone flip. */
953
967
  .ds-247420 .btn-primary {
954
968
  background: var(--accent); color: var(--accent-fg);
955
969
  font-weight: 700; letter-spacing: var(--tr-caps);
956
970
  }
957
- .ds-247420 .btn-primary:hover { background: var(--fg); color: var(--accent-ink); }
971
+ .ds-247420 .btn-primary:hover { background: var(--fg); color: var(--accent-ink); transform: translateY(-1px); box-shadow: var(--shadow-2); }
958
972
  .ds-247420 .btn-ghost { background: transparent; color: var(--fg); box-shadow: inset 0 0 0 2px var(--fg); }
959
- .ds-247420 .btn-ghost:hover { background: var(--fg); color: var(--bg); }
960
- .ds-247420 .btn:active, .ds-247420 .btn-primary:active, .ds-247420 .btn-ghost:active { transform: translateY(2px); }
961
- .ds-247420 .btn:focus-visible, .ds-247420 .btn-primary:focus-visible, .ds-247420 .btn-ghost:focus-visible {
973
+ .ds-247420 .btn-ghost:hover { background: var(--fg); color: var(--bg); transform: translateY(-1px); }
974
+ .ds-247420 .btn:active, .ds-247420 .btn-primary:active, .ds-247420 .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
975
+ .ds-247420 .btn:focus-visible, .ds-247420 .btn-primary:focus-visible, .ds-247420 .btn-ghost:focus-visible, .ds-247420 .ds-icon-btn:focus-visible {
962
976
  outline: 2px solid var(--accent-ink);
963
977
  outline-offset: 2px;
964
978
  }
@@ -986,7 +1000,6 @@
986
1000
  .ds-247420 .ds-icon-btn-danger { background: var(--warn); color: var(--on-color); }
987
1001
  .ds-247420 .ds-icon-btn-danger:hover { filter: brightness(1.1); }
988
1002
  .ds-247420 .ds-icon-btn:active { transform: translateY(1px); }
989
- .ds-247420 .ds-icon-btn:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
990
1003
  .ds-247420 .ds-icon-btn:disabled, .ds-247420 .ds-icon-btn.is-disabled {
991
1004
  opacity: 0.5; cursor: not-allowed; pointer-events: none;
992
1005
  }
@@ -1071,6 +1084,10 @@
1071
1084
  margin: 0 0 var(--space-5);
1072
1085
  padding: var(--space-3);
1073
1086
  position: relative;
1087
+ /* Crisp, restrained elevation — a hairline border does the edge definition;
1088
+ the shadow only adds a whisper of lift, not a soft glow-card float. */
1089
+ box-shadow: var(--shadow-1);
1090
+ transition: box-shadow var(--dur-base) var(--ease), border-color var(--dur-snap) var(--ease);
1074
1091
  }
1075
1092
  /* Editorial accent spine — an opt-in printed-ink rule down the left edge that
1076
1093
  reads as a magazine pull-quote bar, not a glow card. */
@@ -1084,7 +1101,7 @@
1084
1101
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
1085
1102
  }
1086
1103
  .ds-247420 .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
1087
- .ds-247420 .panel.panel-wide { background: transparent; }
1104
+ .ds-247420 .panel.panel-wide { background: transparent; box-shadow: none; }
1088
1105
  .ds-247420 .panel-head {
1089
1106
  display: flex; align-items: baseline; justify-content: space-between;
1090
1107
  /* Shares the .panel-body 16px side padding so the caps label and the body
@@ -1114,7 +1131,7 @@
1114
1131
  grid-template-columns: minmax(80px, 12ch) minmax(0, 1fr) auto;
1115
1132
  gap: var(--space-3);
1116
1133
  align-items: baseline;
1117
- padding: 16px 22px;
1134
+ padding: var(--space-3) var(--space-4);
1118
1135
  background: var(--bg);
1119
1136
  border-radius: var(--r-2);
1120
1137
  color: var(--fg);
@@ -1491,7 +1508,7 @@
1491
1508
  display: inline-flex; align-items: center; gap: var(--space-hair);
1492
1509
  font-size: var(--fs-xs); font-weight: 600;
1493
1510
  }
1494
- .ds-247420 .kpi-delta-up { color: var(--success); }
1511
+ .ds-247420 .kpi-delta-up { color: var(--green); }
1495
1512
  .ds-247420 .kpi-delta-down { color: var(--danger); }
1496
1513
  .ds-247420 .ds-sparkline path { stroke: currentColor; }
1497
1514
  .ds-247420 .ds-sparkline-up { color: var(--success); }
@@ -2311,7 +2328,7 @@
2311
2328
  color: var(--fg-3); text-align: right;
2312
2329
  }
2313
2330
  .ds-247420 .ds-upload-item.done .ds-upload-fill { background: var(--success); }
2314
- .ds-247420 .ds-upload-item.done .ds-upload-pct { color: var(--success); }
2331
+ .ds-247420 .ds-upload-item.done .ds-upload-pct { color: var(--green); }
2315
2332
  .ds-247420 .ds-upload-item.error .ds-upload-fill { background: var(--warn); }
2316
2333
  .ds-247420 .ds-upload-item.error .ds-upload-pct { color: var(--warn); }
2317
2334
  /* Per-row upload recovery actions (replace / dismiss) — error rows are not dead ends. */
@@ -2719,17 +2736,17 @@
2719
2736
  .ds-247420 .chat-bubble.chat-md h3,
2720
2737
  .ds-247420 .chat-bubble.chat-md h4 {
2721
2738
  font-family: var(--ff-body); font-weight: 600;
2722
- margin: 12px 0 6px; line-height: 1.3;
2739
+ margin: var(--space-2) 0 var(--space-1); line-height: var(--lh-snug);
2723
2740
  }
2724
2741
  .ds-247420 .chat-bubble.chat-md h1 { font-size: 1.15em; }
2725
2742
  .ds-247420 .chat-bubble.chat-md h2 { font-size: 1.08em; }
2726
2743
  .ds-247420 .chat-bubble.chat-md h3 { font-size: 1.04em; }
2727
- .ds-247420 .chat-bubble.chat-md p { margin: 6px 0; }
2744
+ .ds-247420 .chat-bubble.chat-md p { margin: var(--space-2) 0; }
2728
2745
  .ds-247420 .chat-bubble.chat-md ul,
2729
- .ds-247420 .chat-bubble.chat-md ol { margin: 6px 0; padding-left: 22px; }
2730
- .ds-247420 .chat-bubble.chat-md li { margin: 3px 0; }
2746
+ .ds-247420 .chat-bubble.chat-md ol { margin: var(--space-2) 0; padding-left: 22px; }
2747
+ .ds-247420 .chat-bubble.chat-md li { margin: var(--space-1) 0; }
2731
2748
  .ds-247420 .chat-bubble.chat-md blockquote {
2732
- margin: 8px 0; padding: 4px 12px;
2749
+ margin: var(--space-2) 0; padding: 4px 12px;
2733
2750
  border-left: 3px solid color-mix(in oklab, currentColor 30%, transparent);
2734
2751
  color: color-mix(in oklab, currentColor 75%, transparent); font-style: italic;
2735
2752
  }
@@ -2738,7 +2755,7 @@
2738
2755
  border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
2739
2756
  padding: 12px 16px; border-radius: var(--r-1); overflow-x: auto;
2740
2757
  font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: 1.55;
2741
- color: var(--fg); margin: 8px 0;
2758
+ color: var(--fg); margin: var(--space-2) 0;
2742
2759
  }
2743
2760
  /* When injectCodeCopy has wrapped the fence (.chat-code-block), reserve room
2744
2761
  for the absolute .chat-code-lang tab. This selector outranks the bare
@@ -2805,7 +2822,7 @@
2805
2822
  .ds-247420 .chat-bubble.chat-code .token.boolean { color: var(--code-num, var(--sun)); }
2806
2823
  .ds-247420 .chat-bubble.chat-code .token.punctuation { color: var(--fg-2); }
2807
2824
  .ds-247420 .chat-bubble.chat-code .token.property,
2808
- .ds-247420 .chat-bubble.chat-code .token.attr-name { color: var(--sky); }
2825
+ .ds-247420 .chat-bubble.chat-code .token.attr-name { color: var(--code-keyword, var(--sky)); }
2809
2826
 
2810
2827
  .ds-247420 .chat-msg.you .chat-image,
2811
2828
  .ds-247420 .chat-msg .chat-image,
@@ -3746,7 +3763,7 @@
3746
3763
 
3747
3764
  .ds-247420 .ds-alert-info { --alert-tone: var(--sky); }
3748
3765
  .ds-247420 .ds-alert-success { --alert-tone: var(--green); }
3749
- .ds-247420 .ds-alert-warn { --alert-tone: var(--sun); }
3766
+ .ds-247420 .ds-alert-warn { --alert-tone: var(--amber); }
3750
3767
  .ds-247420 .ds-alert-error { --alert-tone: var(--warn); }
3751
3768
 
3752
3769
  .ds-247420 .ds-alert-icon {
@@ -6026,7 +6043,7 @@
6026
6043
  }
6027
6044
  .ds-247420 .cm-banner.tone-success {
6028
6045
  background: color-mix(in oklab, var(--success) 18%, var(--bg));
6029
- color: var(--success);
6046
+ color: var(--green);
6030
6047
  }
6031
6048
  .ds-247420 .cm-banner.tone-error {
6032
6049
  background: color-mix(in oklab, var(--danger) 18%, var(--bg));
@@ -6857,7 +6874,7 @@
6857
6874
  label sticks to the top of the scroll area for Claude-Desktop-style headers. */
6858
6875
  .ds-247420 .ds-session-group { display: flex; flex-direction: column; }
6859
6876
  .ds-247420 .ds-session-group-rows { display: flex; flex-direction: column; gap: 2px; }
6860
- .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-xs); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps); padding: var(--space-2) var(--space-2) var(--space-1); }
6877
+ .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps); padding: var(--space-2) var(--space-2) var(--space-1); }
6861
6878
  .ds-247420 .ds-session-row {
6862
6879
  position: relative; display: flex; align-items: center; gap: var(--space-2);
6863
6880
  width: 100%; padding: var(--space-2); min-height: 52px; margin-bottom: 2px;