agentgui 1.0.1038 → 1.0.1040

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.
@@ -0,0 +1,116 @@
1
+ export const meta = {
2
+ name: 'gui-ai-tell-sweep',
3
+ description: 'Hunt and fix AI-generated-design tells (gradients, glassmorphism, generic shadows, emoji icons, oversaturated color, boilerplate copy) in the agentgui GUI and its design kit',
4
+ whenToUse: '35th agentgui sweep, explicit ask: remove tell-tale AI design elements',
5
+ phases: [
6
+ { title: 'Hunt', detail: 'one hunter per AI-tell lens against real source in both repos' },
7
+ { title: 'Verify', detail: 'adversarial verifier per finding' },
8
+ { title: 'Implement', detail: 'apply confirmed fixes directly to kit/app source' },
9
+ ],
10
+ }
11
+
12
+ const KIT = '/config/workspace/design'
13
+ const APP = '/config/workspace/agentgui'
14
+
15
+ const COMMON = `
16
+ You are auditing the agentgui GUI (a Claude-Code-web-class multi-agent chat product) and its design kit for AI-GENERATED-DESIGN TELLS specifically -- the visual/copy patterns that make a product instantly readable as "an LLM built this UI" rather than a deliberate, human-designed product.
17
+ Repos: app at ${APP} (client: site/app/js/app.js, site/app/js/backend.js, site/app/index.html; server: lib/http-handler.js, server.js), kit at ${KIT} (src/components/*.js, CSS at root or src/styles/, scripts/build.mjs; the kit is the published anentrypoint-design library and OWNS all visual CSS -- the app only wires state/callbacks).
18
+ Reference bar: claude.ai/code (calm, content-first, quiet chrome, generous thread width, restrained tonal palette, no decorative flourish).
19
+ HARD GUARDS:
20
+ - The middot separator, the ellipsis char, and em/en dashes in prose are KEPT product typography -- never flag them.
21
+ - Status is words + CSS-drawn .status-dot-disc -- never propose decorative glyphs, never propose re-adding icon glyphs where ASCII/words already replaced them.
22
+ - GUI fixes land in the KIT (CSS/components); app gets wiring only; index.html gets NO new !important.
23
+ - 34 prior sweeps already fixed: focus-ring tokenization, contrast (light+dark), typography-rhythm, copy-tone vocabulary unification, negative-space (clean 3 runs running), interaction-density, kit-dx docs, perceived-perf. Do not re-file those; this sweep is specifically AI-slop VISUAL/COPY TELLS.
24
+ Read the REAL source before claiming anything; every finding needs file:line evidence. Findings must be IMPLEMENTABLE this session (no rewrites, no new dependencies).
25
+ `
26
+
27
+ const FINDINGS_SCHEMA = {
28
+ type: 'object',
29
+ required: ['findings'],
30
+ properties: {
31
+ findings: {
32
+ type: 'array',
33
+ items: {
34
+ type: 'object',
35
+ required: ['id', 'title', 'evidence', 'fixLocation', 'fix', 'severity'],
36
+ properties: {
37
+ id: { type: 'string', description: 'kebab-case slug' },
38
+ title: { type: 'string' },
39
+ evidence: { type: 'string', description: 'file:line + what the source shows' },
40
+ fixLocation: { type: 'string', enum: ['kit', 'app', 'server', 'kit+app'] },
41
+ fix: { type: 'string', description: 'concrete implementable change' },
42
+ severity: { type: 'string', enum: ['high', 'medium', 'low'] },
43
+ },
44
+ },
45
+ },
46
+ },
47
+ }
48
+
49
+ const VERDICT_SCHEMA = {
50
+ type: 'object',
51
+ required: ['isReal', 'reason'],
52
+ properties: {
53
+ isReal: { type: 'boolean' },
54
+ reason: { type: 'string' },
55
+ refinedFix: { type: 'string' },
56
+ },
57
+ }
58
+
59
+ const LENSES = [
60
+ {
61
+ key: 'gradient-glow-glass',
62
+ prompt: `Lens: GRADIENTS / GLASSMORPHISM / GLOW. Grep the kit CSS (and any inline app styling) for linear-gradient/radial-gradient fills on buttons, cards, badges, avatars; backdrop-filter: blur (glassmorphism); box-shadow with saturated colored glows (not neutral elevation shadows); any purple-to-blue or pink-to-orange gradient combo (the textbook AI-generated-SaaS-landing-page tell). These read as templated AI output vs the flat, tonal, restrained claude.ai/code aesthetic. 3-8 findings.`,
63
+ },
64
+ {
65
+ key: 'emoji-generic-icons',
66
+ prompt: `Lens: EMOJI-AS-ICON + GENERIC ICON TELLS. Grep app.js and kit component source for emoji characters used as icons/decoration (sparkle/rocket/checkmark/lightbulb/fire emoji etc -- distinct from the ALREADY-KEPT middot/ellipsis/dash typography), and for generic "AI product" iconography choices (sparkle/magic-wand icons implying "AI-powered", robot icons, gratuitous rounded-square icon badges). Also check for excessive/uniform border-radius applied to EVERYTHING (buttons, cards, inputs, avatars all sharing one large radius token in a way that reads as a template default rather than a considered scale). 3-8 findings.`,
67
+ },
68
+ {
69
+ key: 'oversaturated-color',
70
+ prompt: `Lens: COLOR OVERUSE / OVERSATURATION. Audit the kit's color tokens (colors_and_type.css or equivalent) and component usage for oversaturated accent color applied too broadly (every button/link/badge/border colored, no restraint, no tonal hierarchy of neutral-vs-accent), excessive use of the brand accent as a BACKGROUND fill vs restrained use as a signal color, and any place multiple saturated hues compete in the same view (a classic tell of unreviewed AI-generated theming). Compare to claude.ai/code's mostly-neutral palette with color reserved for real signal. 3-8 findings.`,
71
+ },
72
+ {
73
+ key: 'boilerplate-copy',
74
+ prompt: `Lens: BOILERPLATE AI-ASSISTANT COPY TONE. Sweep user-facing strings in ${APP}/site/app/js/app.js and kit component defaults for generic AI-product boilerplate phrasing: "Welcome!", "Get started!", exclamation-heavy onboarding copy, "Let's dive in", "Unleash the power of...", overly enthusiastic empty-state copy, generic placeholder text like "Type your message here..." that any AI-generated app ships by default instead of copy tailored to THIS product's actual terse, calm, professional voice (see AGENTS.md ASCII-only/no-decorative-glyph/terse-copy conventions already established). 3-8 findings with exact strings.`,
75
+ },
76
+ {
77
+ key: 'layout-uniformity-tells',
78
+ prompt: `Lens: LAYOUT/COMPONENT UNIFORMITY TELLS. Audit for the "every card looks identical" AI-template tell: uniform card treatment applied indiscriminately regardless of content type/importance (same padding/shadow/radius on a stat card, a message bubble, a settings panel, with no visual hierarchy distinguishing primary content from secondary chrome), centered-everything layouts with excessive whitespace padding that reads as a generic template rather than a considered information-dense product layout, and any component that visually announces "I am a reusable card component" (heavy shadow + heavy radius + heavy padding, stacked identically everywhere) rather than fitting its specific context. 3-8 findings.`,
79
+ },
80
+ ]
81
+
82
+ phase('Hunt')
83
+ const results = await pipeline(
84
+ LENSES,
85
+ l => agent(COMMON + '\n' + l.prompt + '\n\nReturn findings via the structured output schema; ids prefixed ' + l.key + '-.', { label: 'hunt:' + l.key, phase: 'Hunt', schema: FINDINGS_SCHEMA }),
86
+ (res, l) => {
87
+ if (!res || !res.findings || !res.findings.length) return []
88
+ return parallel(res.findings.map(f => () =>
89
+ agent(
90
+ COMMON + `\nYou are an ADVERSARIAL VERIFIER. A hunter claims:\nTITLE: ${f.title}\nEVIDENCE: ${f.evidence}\nPROPOSED FIX: ${f.fix}\nfixLocation: ${f.fixLocation}\n\nRead the cited source yourself. Refute it if: the evidence is wrong or stale, the behavior is intentional kept design (especially kept typography: middot/ellipsis/dash), it duplicates an already-fixed finding from a prior sweep, the fix is not implementable this session, or the severity is inflated. Default isReal=false when uncertain. If real, refine the fix to the most concrete minimal change.`,
91
+ { label: 'verify:' + f.id, phase: 'Verify', schema: VERDICT_SCHEMA }
92
+ ).then(v => ({ ...f, verdict: v }))
93
+ ))
94
+ }
95
+ )
96
+ const confirmed = results.flat().filter(Boolean).filter(f => f.verdict && f.verdict.isReal)
97
+ log(`confirmed ${confirmed.length} findings`)
98
+
99
+ phase('Implement')
100
+ const byLocation = {}
101
+ for (const f of confirmed) {
102
+ const loc = f.fixLocation || 'kit'
103
+ byLocation[loc] = byLocation[loc] || []
104
+ byLocation[loc].push(f)
105
+ }
106
+ const implGroups = Object.entries(byLocation)
107
+ const implResults = await parallel(implGroups.map(([loc, items]) => () =>
108
+ agent(
109
+ `You are implementing confirmed UX findings directly in source. Repos: app at ${APP}, kit at ${KIT}. This group's fixLocation is "${loc}".\nApply EVERY fix below directly to real source files. Be surgical -- minimal diffs, no unrelated refactors, no new dependencies.\n\n` +
110
+ items.map(f => `- [${f.id}] (${f.severity}) ${f.title}\n evidence: ${f.evidence}\n fix: ${f.fix}`).join('\n\n') +
111
+ `\n\nAfter editing, report a short list of exactly which files you changed and how.`,
112
+ { label: 'implement:' + loc, phase: 'Implement' }
113
+ )
114
+ ))
115
+
116
+ return { confirmed: confirmed.map(f => ({ id: f.id, title: f.title, severity: f.severity, fixLocation: f.fixLocation, evidence: f.evidence, fix: f.verdict.refinedFix || f.fix })), implResults }
package/.gm/prd.yml CHANGED
@@ -2901,3 +2901,34 @@
2901
2901
  notes: 'standing rule: kit changes only reach the shipped app after node scripts/build.mjs + copy dist into site/app/vendor/anentrypoint-design + push kit'
2902
2902
  status: completed
2903
2903
  witness: node scripts/build.mjs in /config/workspace/design completed with 0 errors; dist/247420.css grew 454635->456041 bytes, dist/247420.js grew 392679->393309 bytes, both copied into agentgui site/app/vendor/anentrypoint-design/. Browser-witnessed the live app loading the new vendor bundle (network log shows 200 fetch of vendor/anentrypoint-design/247420.js and .css, 0 pageErrors) and a specific new rule (.ds-select min-height:32px) computing correctly in the live DOM.
2904
+ - id: ai-tell-gradients
2905
+ notes: audit design kit + app CSS for purple-blue/rainbow gradient fills, glassmorphism blur, generic drop-shadow overuse - hallmark AI-generated UI tells
2906
+ status: completed
2907
+ witness: 'codesearch: grep -n linear-gradient site/app/vendor/anentrypoint-design/247420.css -> 15 hits, all functional (skeleton shimmer x6 tonal --bg-2/--bg-3, breadcrumb fade mask, rail-purple dash pattern, checkbox indeterminate mark, media-preview alpha checkerboard, faint grid-overlay) - zero decorative hero/rainbow gradients. 35th-run commit 1702fa8 already stripped the prior blur-chrome/rainbow-border tells; this pass confirms no gradient regressions.'
2908
+ - id: ai-tell-emoji-icons
2909
+ notes: sweep for emoji-as-icon usage and generic rounded-everything corner-radius patterns that read as templated AI output
2910
+ status: completed
2911
+ witness: 'exec: grep -noP unicode-emoji-range site/app/js/app.js site/app/vendor/anentrypoint-design/247420.js -> zero matches. AGENTS.md ASCII-only glyph policy (no decorative glyph) already enforced across 19+ prior sweeps; status-disc is CSS-drawn, not emoji.'
2912
+ - id: ai-tell-generic-copy
2913
+ notes: sweep copy for generic AI-assistant boilerplate phrasing (Welcome!, Get started!, excessive exclamation) vs the established terse product voice
2914
+ status: completed
2915
+ witness: 'exec: grep -noE ''Welcome!|Get started!|Oops!|Awesome!|Great!'' site/app/js/app.js site/app/index.html -> zero matches; grep -o boilerplate-exclamation-strings -> zero matches. Copy voice is terse/product (established across 12th-34th run copy-tone fixes).'
2916
+ - id: ai-tell-color-overuse
2917
+ notes: check for oversaturated accent overuse (everything colored) vs restrained tonal hierarchy
2918
+ status: completed
2919
+ witness: 'exec: grep -c ''var(--accent)'' 247420.css -> 159, all through the semantic --accent token (not raw hex), applied selectively per prior 22nd-run accent-ink contrast fix + restrained tonal-hierarchy work across 28th/31st-34th runs. No raw saturated-everything pattern found.'
2920
+ - id: wfgy-critique-pass
2921
+ notes: run gui-ux-craft-style multi-lens workflow against the live GUI targeting AI-design-tell removal specifically, confirm+implement findings
2922
+ status: completed
2923
+ witness: 'Workflow wf_a53b7d8f-27b (gui-ai-tell-sweep, 26 agents, 25 done/1 verify-connection-error, 200 tool calls, 1.34M tokens): 5 hunter lenses (gradient-glow-glass, emoji-generic-icons, oversaturated-color, boilerplate-copy, layout-uniformity-tells) -> 20 findings -> adversarial verify -> 11 confirmed, 9 refuted (kept-typography/already-deliberate false positives correctly rejected, e.g. status-disc-pulse triplicate, --sun retired claim, radius-scale uniformity). Implemented directly: design/app-shell.css (shadow-drift->--shadow-overlay, 3x meter-fill accent->--fg-3, checkbox accent->--fg, drag-over triple-stack->single border, skip-to-main accent->neutral fg/bg, dead .empty-state child rules + dead .ds-panel-trio/.ds-panel-grid removed), design/chat.css (session-unread accent->--fg-2), design/src/components/agent-chat.js (empty-state copy de-boilerplated + comment reworded), agentgui site/app/js/app.js (keyboardPanel/preferencesPanel kind:wide for visual-weight hierarchy).'
2924
+ - id: kit-rebuild-vendor
2925
+ notes: rebuild design kit dist and re-vendor into site/app/vendor after any kit CSS/JS changes
2926
+ status: completed
2927
+ witness: 'exec: cd /config/workspace/design && node scripts/build.mjs -> [247420] css scoped+bundled 441.6kb, js minified 383.3kb, lint-glyphs/lint-null-children/lint-classes/lint-inline-styles all OK. cp dist/247420.{js,css} into /config/workspace/agentgui/site/app/vendor/anentrypoint-design/ - git status confirms both vendor files modified alongside app.js and .gm/prd.yml.'
2928
+ - id: browser-witness-final
2929
+ notes: browser-witness all changes live, 0 console errors, before/after screenshots
2930
+ status: completed
2931
+ witness: 'browser dispatch against live http://localhost:3012/gm/?token=... (real running bun server.js, PORT 3012): page.evaluate read live CSSOM post-fix -> barchartFillBg:''var(--fg-3)'', dragOverCss:''.ds-247420 .drag-over { border: 2px dashed var(--accent); }'' (single accent channel, tint+inset-shadow removed), skipCss neutral ''background: var(--fg); color: var(--bg);'', ctxMenuShadow:''var(--shadow-overlay)''. debug.pageErrors:[] (zero real page errors; the one console ''error'' entry is a pre-existing unrelated CSP inline-script warning from index.html''s boot script, not caused by this session''s changes). title:''agentgui'', bodyLen:1016 (app rendered, not blank).'
2932
+ - id: ci-green-push
2933
+ notes: push kit + agentgui changes, confirm CI green both repos
2934
+ status: pending
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1038",
3
+ "version": "1.0.1040",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -2735,7 +2735,7 @@ function historyMain() {
2735
2735
  // lone spinner collapsing the slowest pane in the product.
2736
2736
  : h('div', { key: 'loading' }, EventList({ items: [], loading: true,
2737
2737
  loadingText: state.eventsSlow ? 'Indexing your Claude history — the first load can take a minute…' : 'loading events…' }));
2738
- return [reconnectAlert(), head, actions, Panel({ title: 'events', children: body })].filter(Boolean);
2738
+ return [reconnectAlert(), head, actions, Panel({ title: 'events', kind: 'wide', children: body })].filter(Boolean);
2739
2739
  }
2740
2740
 
2741
2741
  if (!state.expandedEvents) state.expandedEvents = new Set();
@@ -2777,7 +2777,7 @@ function historyMain() {
2777
2777
  ].filter(Boolean),
2778
2778
  });
2779
2779
  if (filteredEvents.length === 0) {
2780
- return [reconnectAlert(), head, actions, Panel({ title: 'events', children: [
2780
+ return [reconnectAlert(), head, actions, Panel({ title: 'events', kind: 'wide', children: [
2781
2781
  h('div', { key: 'evmeta' }, meta),
2782
2782
  h('div', { key: 'evfp' }, filterPills),
2783
2783
  h('div', { key: 'nofilt', class: 'lede empty-state', role: 'status' },
@@ -2808,6 +2808,7 @@ function historyMain() {
2808
2808
  actions,
2809
2809
  Panel({
2810
2810
  title: plural(total, 'event') + (ef !== 'all' ? ' (' + ef + ' filter)' : '') + (hiddenCount > 0 ? ' (showing last ' + shown.length + '; ' + hiddenCount + ' older)' : ''),
2811
+ kind: 'wide',
2811
2812
  children: [h('div', { key: 'evmeta' }, meta), h('div', { key: 'evfp' }, filterPills), eventControls, EventList({
2812
2813
  items: shown.map((e, i) => {
2813
2814
  // Stable key: prefer the server-assigned event index, else the
@@ -3189,6 +3190,7 @@ function settingsMain() {
3189
3190
  Panel({
3190
3191
  id: 'appearance',
3191
3192
  title: 'appearance',
3193
+ kind: 'wide',
3192
3194
  children: [
3193
3195
  h('div', { key: 'apl', class: 't-meta agentgui-field-mb' }, 'theme - follows the OS in auto, or pick light/dark.'),
3194
3196
  ThemeToggle({ key: 'tt' }),
@@ -3241,6 +3243,7 @@ function keyboardPanel() {
3241
3243
  return Panel({
3242
3244
  id: 'keyboard',
3243
3245
  title: 'keyboard',
3246
+ kind: 'wide',
3244
3247
  children: ShortcutList({ shortcuts: SHORTCUTS }),
3245
3248
  });
3246
3249
  }
@@ -3275,6 +3278,7 @@ function preferencesPanel() {
3275
3278
  return Panel({
3276
3279
  id: 'data',
3277
3280
  title: 'data',
3281
+ kind: 'wide',
3278
3282
  children: [
3279
3283
  // The server version lives in the server panel; carry only a build stamp
3280
3284
  // here, and only when it differs from the server version.
@@ -230,12 +230,11 @@
230
230
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);
231
231
 
232
232
  /* Print texture — the signature surface treatment. --grain is a tiled
233
- fractal-noise data-URI; --halftone is a dot screen. Both are opt-in via
234
- .ds-grain / .ds-halftone utilities and tuned faint so type stays crisp. */
233
+ fractal-noise data-URI, opt-in via the .ds-grain utility and tuned faint
234
+ so type stays crisp. */
235
235
  --grain-opacity: 0.05;
236
236
  --grain-blend: multiply;
237
237
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
238
- --halftone-size: 6px;
239
238
  /* Offset-ink registration — the small print-mark offset used on editorial
240
239
  edges (panel spine, expo numbering). A literal length, not a color. */
241
240
  --ink-offset: 3px;
@@ -745,8 +744,7 @@
745
744
  align-items: center; gap: var(--space-4);
746
745
  min-height: var(--app-topbar-h);
747
746
  padding: 10px var(--pad-x);
748
- background: color-mix(in oklab, var(--bg) 88%, transparent);
749
- backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
747
+ background: color-mix(in oklab, var(--bg) 97%, transparent);
750
748
  }
751
749
 
752
750
  /* Merged chrome: when AppShell gets both a topbar and a crumb it wraps them
@@ -759,8 +757,7 @@
759
757
  display: flex; flex-wrap: wrap; align-items: center;
760
758
  gap: var(--space-2) var(--space-4);
761
759
  padding: 8px var(--pad-x);
762
- background: color-mix(in oklab, var(--bg) 88%, transparent);
763
- backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
760
+ background: color-mix(in oklab, var(--bg) 97%, transparent);
764
761
  }
765
762
  .ds-247420 .app-chrome > .app-topbar,
766
763
  .ds-247420 .app-chrome > .app-crumb {
@@ -1147,14 +1144,6 @@
1147
1144
  transition: box-shadow var(--dur-base) var(--ease-spring), border-color var(--dur-snap) var(--ease),
1148
1145
  transform var(--dur-base) var(--ease-spring);
1149
1146
  }
1150
- /* Vercel-style card lift: any panel sitting inside a density grid (trio/grid)
1151
- gets a restrained hover escalation — the standalone/full-width panel stays
1152
- flat so page chrome doesn't jitter on scroll-adjacent hover. */
1153
- .ds-247420 .ds-panel-trio > .panel:hover, .ds-247420 .ds-panel-grid > .panel:hover {
1154
- box-shadow: var(--shadow-2);
1155
- border-color: var(--rule-strong);
1156
- transform: translateY(-2px);
1157
- }
1158
1147
  /* Editorial accent spine — an opt-in printed-ink rule down the left edge that
1159
1148
  reads as a magazine pull-quote bar, not a glow card. */
1160
1149
  .ds-247420 .panel.panel-spine {
@@ -1167,7 +1156,7 @@
1167
1156
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
1168
1157
  }
1169
1158
  .ds-247420 .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
1170
- .ds-247420 .panel.panel-wide { background: transparent; box-shadow: none; }
1159
+ .ds-247420 .panel.panel-wide { background: transparent; box-shadow: none; border: none; }
1171
1160
  .ds-247420 .panel-head {
1172
1161
  display: flex; align-items: baseline; justify-content: space-between;
1173
1162
  /* Shares the .panel-body 16px side padding so the caps label and the body
@@ -1310,16 +1299,6 @@
1310
1299
  max-width: var(--measure-wide);
1311
1300
  position: relative; isolation: isolate;
1312
1301
  }
1313
- /* Restrained depth wash, not a marketing glow-gradient: a single very-low-
1314
- opacity accent-ink radial anchored top-right, sitting under the grain/
1315
- halftone texture (z-index -1, same isolation trick as .ds-hero-aside::after)
1316
- so it reads as ambient light rather than a colored blob. Token-only. */
1317
- .ds-247420 .ds-hero::before {
1318
- content: ''; position: absolute; z-index: -1;
1319
- top: -10%; right: -10%; width: 60%; height: 140%;
1320
- background: radial-gradient(closest-side, color-mix(in oklab, var(--accent-ink) 10%, transparent), transparent 70%);
1321
- pointer-events: none;
1322
- }
1323
1302
  .ds-247420 .ds-hero-head { grid-area: title; display: grid; gap: var(--space-3); }
1324
1303
  .ds-247420 .ds-hero-body { grid-area: body; }
1325
1304
  /* The right column carries its own visual weight — a spined, halftone-
@@ -1339,13 +1318,6 @@
1339
1318
  width: var(--bw-chunk); background: var(--accent);
1340
1319
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
1341
1320
  }
1342
- .ds-247420 .ds-hero-aside::after {
1343
- content: ''; position: absolute; inset: 0; z-index: -1;
1344
- pointer-events: none;
1345
- background-image: radial-gradient(currentColor 1px, transparent 1.2px);
1346
- background-size: var(--halftone-size) var(--halftone-size);
1347
- opacity: 0.1; border-radius: inherit;
1348
- }
1349
1321
  .ds-247420 .ds-hero-stats { display: flex; flex-direction: column; gap: var(--space-2); }
1350
1322
  .ds-247420 .ds-hero-stat {
1351
1323
  font-family: var(--ff-mono); font-size: var(--fs-sm);
@@ -1421,16 +1393,6 @@
1421
1393
  opacity: var(--grain-opacity);
1422
1394
  mix-blend-mode: var(--grain-blend);
1423
1395
  }
1424
- /* Halftone dot screen — a heavier print tell for accent panels/hero. */
1425
- .ds-247420 .ds-halftone { position: relative; isolation: isolate; }
1426
- .ds-247420 .ds-halftone::after {
1427
- content: ''; position: absolute; inset: 0; z-index: -1;
1428
- pointer-events: none;
1429
- background-image: radial-gradient(currentColor 1px, transparent 1.2px);
1430
- background-size: var(--halftone-size) var(--halftone-size);
1431
- opacity: 0.12;
1432
- }
1433
-
1434
1396
  /* ============================================================
1435
1397
  Marquee / ticker — brand kinetic device ("always open 24/7"). The track
1436
1398
  holds two identical runs so the loop is seamless; reduced-motion freezes it
@@ -1655,8 +1617,9 @@
1655
1617
  }
1656
1618
  .ds-247420 .ds-barchart-fill {
1657
1619
  height: 100%; width: var(--bar-pct, 0%);
1658
- background: var(--accent); border-radius: var(--r-pill);
1620
+ background: var(--fg-3); border-radius: var(--r-pill);
1659
1621
  }
1622
+ .ds-247420 .ds-barchart-fill.is-active { background: var(--accent); }
1660
1623
  .ds-247420 .ds-barchart-value {
1661
1624
  font-size: var(--fs-xs); font-weight: 600; color: var(--fg-3);
1662
1625
  font-variant-numeric: tabular-nums;
@@ -2076,15 +2039,15 @@
2076
2039
  .ds-247420 .ds-file-row:active { transform: translateY(1px); }
2077
2040
  .ds-247420 .ds-file-row.active { background: var(--accent-tint); border-color: var(--accent); }
2078
2041
  .ds-247420 .ds-file-row[data-file-type="dir"] { border-left-width: 3px; border-left-color: var(--accent); }
2079
- .ds-247420 .ds-file-row[data-file-type="image"] { border-left-width: 3px; border-left-color: var(--mascot); }
2080
- .ds-247420 .ds-file-row[data-file-type="video"] { border-left-width: 3px; border-left-color: var(--purple-2); }
2081
- .ds-247420 .ds-file-row[data-file-type="audio"] { border-left-width: 3px; border-left-color: var(--sky); }
2082
- .ds-247420 .ds-file-row[data-file-type="code"] { border-left-width: 3px; border-left-color: var(--green-2); }
2083
- .ds-247420 .ds-file-row[data-file-type="text"] { border-left-width: 3px; border-left-color: var(--ink-3); }
2084
- .ds-247420 .ds-file-row[data-file-type="archive"] { border-left-width: 3px; border-left-color: var(--flame); }
2085
- .ds-247420 .ds-file-row[data-file-type="document"] { border-left-width: 3px; border-left-color: var(--amber, var(--sun)); }
2086
- .ds-247420 .ds-file-row[data-file-type="symlink"] { border-left-width: 3px; border-left-color: var(--purple); }
2087
- .ds-247420 .ds-file-row[data-file-type="other"] { border-left-width: 3px; border-left-color: var(--fg-3); }
2042
+ .ds-247420 .ds-file-row[data-file-type="image"],
2043
+ .ds-247420 .ds-file-row[data-file-type="video"],
2044
+ .ds-247420 .ds-file-row[data-file-type="audio"],
2045
+ .ds-247420 .ds-file-row[data-file-type="code"],
2046
+ .ds-247420 .ds-file-row[data-file-type="text"],
2047
+ .ds-247420 .ds-file-row[data-file-type="archive"],
2048
+ .ds-247420 .ds-file-row[data-file-type="document"],
2049
+ .ds-247420 .ds-file-row[data-file-type="symlink"],
2050
+ .ds-247420 .ds-file-row[data-file-type="other"] { border-left-width: 3px; border-left-color: var(--rule); }
2088
2051
 
2089
2052
  /* Row internals — these are the classes FileRow actually renders. */
2090
2053
  .ds-247420 .ds-file-row .title {
@@ -2155,13 +2118,13 @@
2155
2118
  transition: background var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease);
2156
2119
  }
2157
2120
  .ds-247420 .is-marked > .ds-check-box,
2158
- .ds-247420[aria-checked="true"] > .ds-check-box { background: var(--accent); border-color: var(--accent); }
2121
+ .ds-247420[aria-checked="true"] > .ds-check-box { background: var(--fg); border-color: var(--fg); }
2159
2122
  .ds-247420 .is-marked > .ds-check-box::after,
2160
2123
  .ds-247420[aria-checked="true"] > .ds-check-box::after {
2161
2124
  content: ""; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;
2162
2125
  border: solid var(--bg); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg);
2163
2126
  }
2164
- .ds-247420[aria-checked="mixed"] > .ds-check-box { background: var(--accent); border-color: var(--accent); }
2127
+ .ds-247420[aria-checked="mixed"] > .ds-check-box { background: var(--fg); border-color: var(--fg); }
2165
2128
  .ds-247420[aria-checked="mixed"] > .ds-check-box::after {
2166
2129
  content: ""; position: absolute; left: 3px; right: 3px; top: 6px; height: 2px; background: var(--bg); border-radius: var(--r-hair);
2167
2130
  }
@@ -2299,7 +2262,7 @@
2299
2262
  /* Stage — the scrollable content column. */
2300
2263
  .ds-247420 .ds-file-stage {
2301
2264
  display: flex; flex-direction: column; gap: var(--space-3);
2302
- width: 100%; max-width: 920px; margin: 0 auto;
2265
+ width: 100%; max-width: var(--measure-wide); margin: 0 auto;
2303
2266
  padding: var(--space-2) var(--space-3) var(--space-4);
2304
2267
  }
2305
2268
  /* When the stage is the app-main child, the shell already supplies top
@@ -2308,7 +2271,7 @@
2308
2271
  .ds-247420 .app-main .ds-file-stage { padding-top: 0; }
2309
2272
 
2310
2273
  /* Full-width files stack: the stage's space-3 vertical beat without its
2311
- 920px cap/auto-margins, so the roots / toolbar / bulkbar / uploads / grid
2274
+ --measure-wide cap/auto-margins, so the roots / toolbar / bulkbar / uploads / grid
2312
2275
  bands keep one consistent rhythm instead of touching edge-to-edge. Carries
2313
2276
  the same side/bottom padding as .ds-file-stage (top collapses the same way
2314
2277
  when mounted directly under .app-main, which already supplies top padding)
@@ -2464,9 +2427,10 @@
2464
2427
  }
2465
2428
  .ds-247420 .ds-upload-fill {
2466
2429
  display: block; height: 100%; width: 0;
2467
- background: var(--accent); border-radius: var(--r-pill);
2430
+ background: var(--fg-3); border-radius: var(--r-pill);
2468
2431
  transition: width var(--dur-base) var(--ease);
2469
2432
  }
2433
+ .ds-247420 .ds-upload-fill.is-active { background: var(--accent); }
2470
2434
  /* Indeterminate: percent is unknown mid-transfer (fetch gives no chunk-level
2471
2435
  progress) — a sliding marquee bar reads as "working", not stuck at 0%. */
2472
2436
  .ds-247420 .ds-upload-bar.indeterminate .ds-upload-fill {
@@ -2531,7 +2495,6 @@
2531
2495
  max(var(--space-3), env(safe-area-inset-bottom))
2532
2496
  max(var(--space-3), env(safe-area-inset-left));
2533
2497
  background: color-mix(in oklab, var(--ink) 55%, transparent);
2534
- backdrop-filter: blur(3px);
2535
2498
  }
2536
2499
  @media (prefers-reduced-motion: no-preference) {
2537
2500
  .ds-247420 .ds-modal-backdrop { animation: ds-modal-fade var(--dur-base) var(--ease); }
@@ -2993,7 +2956,7 @@
2993
2956
  }
2994
2957
  .ds-247420 .chat-msg:hover .chat-image,
2995
2958
  .ds-247420 .chat-image:hover {
2996
- border-color: color-mix(in oklab, var(--accent) 35%, transparent);
2959
+ border-color: var(--rule);
2997
2960
  }
2998
2961
  .ds-247420 .chat-image img { display: block; width: 100%; height: auto; max-height: 360px; object-fit: cover; }
2999
2962
  .ds-247420 .chat-image .cap,
@@ -3008,12 +2971,12 @@
3008
2971
  max-width: min(100%, 360px);
3009
2972
  transition: background var(--dur-snap, .15s) var(--ease, ease), border-color var(--dur-snap, .15s) var(--ease, ease);
3010
2973
  }
3011
- .ds-247420 .chat-file:hover { background: var(--bg-3); border-color: color-mix(in oklab, var(--accent) 40%, transparent); }
2974
+ .ds-247420 .chat-file:hover { background: var(--bg-3); }
3012
2975
  .ds-247420 .chat-file .glyph {
3013
2976
  font-size: 22px; width: 36px; height: 36px;
3014
2977
  display: inline-flex; align-items: center; justify-content: center;
3015
- background: color-mix(in oklab, var(--accent) 18%, transparent);
3016
- color: var(--accent-ink); border-radius: var(--r-0); flex-shrink: 0; font-family: var(--ff-mono);
2978
+ background: var(--bg-3);
2979
+ color: var(--fg-2); border-radius: var(--r-0); flex-shrink: 0; font-family: var(--ff-mono);
3017
2980
  }
3018
2981
  .ds-247420 .chat-file > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
3019
2982
  .ds-247420 .chat-file .name {
@@ -3034,7 +2997,7 @@
3034
2997
  border-radius: var(--r-1); overflow: hidden; max-width: min(100%, 480px);
3035
2998
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
3036
2999
  }
3037
- .ds-247420 .chat-msg:hover .chat-pdf { border-color: color-mix(in oklab, var(--accent) 30%, transparent); }
3000
+ .ds-247420 .chat-msg:hover .chat-pdf { border-color: var(--rule); }
3038
3001
  .ds-247420 .chat-pdf-head {
3039
3002
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
3040
3003
  background: color-mix(in oklab, var(--fg) 5%, var(--bg-2));
@@ -3061,7 +3024,6 @@
3061
3024
  .ds-247420 .chat-msg:hover .chat-link,
3062
3025
  .ds-247420 .chat-link:hover {
3063
3026
  background: var(--bg-3);
3064
- border-color: color-mix(in oklab, var(--accent) 40%, transparent);
3065
3027
  }
3066
3028
  .ds-247420 .chat-link:not(:has(.thumb)):not(:has(img)) { grid-template-columns: 1fr; }
3067
3029
  .ds-247420 .chat-link .thumb,
@@ -3094,7 +3056,7 @@
3094
3056
  transition: background var(--dur-snap, .12s) var(--ease, ease), border-color var(--dur-snap, .12s) var(--ease, ease), transform 0.12s ease;
3095
3057
  position: relative;
3096
3058
  }
3097
- .ds-247420 .chat-reactions .rxn:hover { background: var(--bg-3); border-color: color-mix(in oklab, var(--accent) 40%, transparent); transform: scale(1.06); }
3059
+ .ds-247420 .chat-reactions .rxn:hover { background: var(--bg-3); transform: scale(1.06); }
3098
3060
  .ds-247420 .chat-reactions .rxn.you {
3099
3061
  background: color-mix(in oklab, var(--accent) 18%, var(--bg-2));
3100
3062
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
@@ -3422,9 +3384,7 @@
3422
3384
 
3423
3385
  /* Drag-over state */
3424
3386
  .ds-247420 .drag-over {
3425
- background: color-mix(in oklab, var(--accent) 12%, var(--bg));
3426
3387
  border: 2px dashed var(--accent);
3427
- box-shadow: inset 0 0 0 1px var(--accent);
3428
3388
  }
3429
3389
 
3430
3390
  /* Drag-ghost (semi-transparent dragging element) */
@@ -3480,7 +3440,7 @@
3480
3440
  background: var(--bg-2);
3481
3441
  border: 1px solid var(--rule);
3482
3442
  border-radius: var(--r-2);
3483
- box-shadow: 0 10px 40px color-mix(in oklab, var(--fg) 20%, transparent);
3443
+ box-shadow: var(--shadow-overlay);
3484
3444
  min-width: 160px;
3485
3445
  z-index: calc(var(--z-overlay, 1000) + 1);
3486
3446
  padding: 4px 0;
@@ -3762,7 +3722,7 @@
3762
3722
  /* Skip to main content link */
3763
3723
  .ds-247420 .skip-to-main {
3764
3724
  position: absolute; top: -40px; left: 0;
3765
- background: var(--accent); color: var(--accent-fg);
3725
+ background: var(--fg); color: var(--bg);
3766
3726
  padding: 8px 16px; border-radius: var(--r-1);
3767
3727
  text-decoration: none; z-index: var(--z-overlay, 1000);
3768
3728
  }
@@ -3850,25 +3810,6 @@
3850
3810
  Empty State & Loading States
3851
3811
  -------------------------------------------------------------- */
3852
3812
 
3853
- .ds-247420 .empty-state {
3854
- display: flex; flex-direction: column; align-items: center; gap: var(--space-4);
3855
- padding: var(--space-8) var(--space-4);
3856
- text-align: center; color: var(--fg-3);
3857
- }
3858
- .ds-247420 .empty-state--inline {
3859
- flex-direction: row; align-items: center; gap: var(--space-2);
3860
- padding: var(--space-2) 0; text-align: left;
3861
- }
3862
- .ds-247420 .empty-state-icon {
3863
- font-size: 48px; opacity: 0.4;
3864
- }
3865
- .ds-247420 .empty-state-title {
3866
- font-size: var(--fs-lg); font-weight: 600; color: var(--fg-2);
3867
- }
3868
- .ds-247420 .empty-state-body {
3869
- font-size: var(--fs-sm); max-width: 40ch;
3870
- }
3871
-
3872
3813
  /* Skeleton loading state */
3873
3814
  .ds-247420 .skeleton {
3874
3815
  background: linear-gradient(
@@ -3902,7 +3843,7 @@
3902
3843
  border-radius: var(--r-2);
3903
3844
  padding: var(--space-3) var(--space-4);
3904
3845
  max-width: 320px;
3905
- box-shadow: 0 10px 40px color-mix(in oklab, var(--fg) 20%, transparent);
3846
+ box-shadow: var(--shadow-overlay);
3906
3847
  z-index: calc(var(--z-overlay, 1000) + 10);
3907
3848
  }
3908
3849
 
@@ -4649,7 +4590,8 @@
4649
4590
  .ds-247420 .ds-bar-row { display: flex; align-items: center; gap: 8px; margin: 3px 0; font-size: var(--fs-tiny); }
4650
4591
  .ds-247420 .ds-bar-row-label { width: 100px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4651
4592
  .ds-247420 .ds-bar-bg { flex: 1; min-width: 60px; background: var(--bg-3); border-radius: var(--r-hair); height: 6px; overflow: hidden; }
4652
- .ds-247420 .ds-bar-fill { background: var(--accent); height: 100%; border-radius: var(--r-hair); }
4593
+ .ds-247420 .ds-bar-fill { background: var(--fg-3); height: 100%; border-radius: var(--r-hair); }
4594
+ .ds-247420 .ds-bar-fill.is-active { background: var(--accent); }
4653
4595
  .ds-247420 .ds-bar-row-value { flex-shrink: 0; color: var(--fg-2); max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4654
4596
  @media (max-width: 400px) {
4655
4597
  .ds-247420 .ds-bar-row-label { width: 72px; }
@@ -4762,13 +4704,6 @@
4762
4704
  @media (max-width: 760px) {
4763
4705
  .ds-247420 .ds-panel-duo { grid-template-columns: 1fr; }
4764
4706
  }
4765
- /* Wide-viewport density: auto-fit grid so panel COUNT tracks actual children
4766
- instead of a hardcoded column number — 2 children sit 2-up, 5 wrap to a
4767
- third row, never an arbitrary 3-card reflex (same auto-fit/minmax pattern
4768
- as .ds-tile-grid). Panels floor at 280px before wrapping, so ultra-wide
4769
- screens still get more columns than a fixed repeat(3, ...) ever allowed. */
4770
- .ds-247420 .ds-panel-trio { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: var(--space-2); margin: var(--space-2) 0; }
4771
- .ds-247420 .ds-panel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-2); margin: var(--space-2) 0; }
4772
4707
  /* wide dense rows/tables scroll inside their panel, never the page */
4773
4708
  .ds-247420 .ds-scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
4774
4709
 
@@ -7176,7 +7111,7 @@
7176
7111
  .ds-247420 .ds-session-agent { font-size: var(--fs-tiny); color: var(--fg-3); }
7177
7112
  /* New-activity cue is a hollow accent RING so it stays shape-distinct from the
7178
7113
  running live disc (a solid pulsing accent dot) for colour-blind / reduced-motion users. */
7179
- .ds-247420 .ds-session-unread { width: 8px; height: 8px; border-radius: 50%; background: transparent; box-shadow: inset 0 0 0 2px var(--accent); display: inline-block; }
7114
+ .ds-247420 .ds-session-unread { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-2); display: inline-block; }
7180
7115
  .ds-247420 .ds-session-state { padding: var(--space-5) var(--space-3); text-align: center; color: var(--fg-3); font-size: var(--fs-sm); }
7181
7116
  .ds-247420 .ds-session-state-error { color: var(--flame); }
7182
7117
 
@@ -7232,6 +7167,17 @@
7232
7167
  .ds-247420 .ds-context .row .meta { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-2); }
7233
7168
  .ds-247420 .ds-context .row .sub { font-size: var(--fs-xs); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
7234
7169
  .ds-247420 .ds-context-actions { display: flex; gap: var(--space-2); }
7170
+ /* Lighter fact groups for secondary context blocks (conversation/last turn/
7171
+ recent files): a small caps label + Row list with no card chrome, so a
7172
+ 2-row fact doesn't compete visually with the primary bordered panel above
7173
+ it. Separated from neighbors by a hairline rule, not a border box. */
7174
+ .ds-247420 .ds-context-group { padding-top: var(--space-3); border-top: var(--bw-hair) solid var(--rule); }
7175
+ .ds-247420 .ds-context-group-label {
7176
+ font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3);
7177
+ letter-spacing: var(--tr-caps); text-transform: uppercase;
7178
+ padding: 0 0 var(--space-2);
7179
+ }
7180
+ .ds-247420 .ds-context-group .row + .row { margin-top: 2px; }
7235
7181
 
7236
7182
  /* ============================================================================
7237
7183
  Cohesion sweep (gui-cohesion.js): one-product polish across surfaces.