agentgui 1.0.1037 → 1.0.1039

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,27 @@
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: pending
2907
+ - id: ai-tell-emoji-icons
2908
+ notes: sweep for emoji-as-icon usage and generic rounded-everything corner-radius patterns that read as templated AI output
2909
+ status: pending
2910
+ - id: ai-tell-generic-copy
2911
+ notes: sweep copy for generic AI-assistant boilerplate phrasing (Welcome!, Get started!, excessive exclamation) vs the established terse product voice
2912
+ status: pending
2913
+ - id: ai-tell-color-overuse
2914
+ notes: check for oversaturated accent overuse (everything colored) vs restrained tonal hierarchy
2915
+ status: pending
2916
+ - id: wfgy-critique-pass
2917
+ notes: run gui-ux-craft-style multi-lens workflow against the live GUI targeting AI-design-tell removal specifically, confirm+implement findings
2918
+ status: pending
2919
+ - id: kit-rebuild-vendor
2920
+ notes: rebuild design kit dist and re-vendor into site/app/vendor after any kit CSS/JS changes
2921
+ status: pending
2922
+ - id: browser-witness-final
2923
+ notes: browser-witness all changes live, 0 console errors, before/after screenshots
2924
+ status: pending
2925
+ - id: ci-green-push
2926
+ notes: push kit + agentgui changes, confirm CI green both repos
2927
+ status: pending
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1037",
3
+ "version": "1.0.1039",
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' }),
@@ -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 {
@@ -1167,7 +1164,7 @@
1167
1164
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
1168
1165
  }
1169
1166
  .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; }
1167
+ .ds-247420 .panel.panel-wide { background: transparent; box-shadow: none; border: none; }
1171
1168
  .ds-247420 .panel-head {
1172
1169
  display: flex; align-items: baseline; justify-content: space-between;
1173
1170
  /* Shares the .panel-body 16px side padding so the caps label and the body
@@ -1310,16 +1307,6 @@
1310
1307
  max-width: var(--measure-wide);
1311
1308
  position: relative; isolation: isolate;
1312
1309
  }
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
1310
  .ds-247420 .ds-hero-head { grid-area: title; display: grid; gap: var(--space-3); }
1324
1311
  .ds-247420 .ds-hero-body { grid-area: body; }
1325
1312
  /* The right column carries its own visual weight — a spined, halftone-
@@ -1339,13 +1326,6 @@
1339
1326
  width: var(--bw-chunk); background: var(--accent);
1340
1327
  border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
1341
1328
  }
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
1329
  .ds-247420 .ds-hero-stats { display: flex; flex-direction: column; gap: var(--space-2); }
1350
1330
  .ds-247420 .ds-hero-stat {
1351
1331
  font-family: var(--ff-mono); font-size: var(--fs-sm);
@@ -1421,16 +1401,6 @@
1421
1401
  opacity: var(--grain-opacity);
1422
1402
  mix-blend-mode: var(--grain-blend);
1423
1403
  }
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
1404
  /* ============================================================
1435
1405
  Marquee / ticker — brand kinetic device ("always open 24/7"). The track
1436
1406
  holds two identical runs so the loop is seamless; reduced-motion freezes it
@@ -2076,15 +2046,15 @@
2076
2046
  .ds-247420 .ds-file-row:active { transform: translateY(1px); }
2077
2047
  .ds-247420 .ds-file-row.active { background: var(--accent-tint); border-color: var(--accent); }
2078
2048
  .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); }
2049
+ .ds-247420 .ds-file-row[data-file-type="image"],
2050
+ .ds-247420 .ds-file-row[data-file-type="video"],
2051
+ .ds-247420 .ds-file-row[data-file-type="audio"],
2052
+ .ds-247420 .ds-file-row[data-file-type="code"],
2053
+ .ds-247420 .ds-file-row[data-file-type="text"],
2054
+ .ds-247420 .ds-file-row[data-file-type="archive"],
2055
+ .ds-247420 .ds-file-row[data-file-type="document"],
2056
+ .ds-247420 .ds-file-row[data-file-type="symlink"],
2057
+ .ds-247420 .ds-file-row[data-file-type="other"] { border-left-width: 3px; border-left-color: var(--rule); }
2088
2058
 
2089
2059
  /* Row internals — these are the classes FileRow actually renders. */
2090
2060
  .ds-247420 .ds-file-row .title {
@@ -2299,7 +2269,7 @@
2299
2269
  /* Stage — the scrollable content column. */
2300
2270
  .ds-247420 .ds-file-stage {
2301
2271
  display: flex; flex-direction: column; gap: var(--space-3);
2302
- width: 100%; max-width: 920px; margin: 0 auto;
2272
+ width: 100%; max-width: var(--measure-wide); margin: 0 auto;
2303
2273
  padding: var(--space-2) var(--space-3) var(--space-4);
2304
2274
  }
2305
2275
  /* When the stage is the app-main child, the shell already supplies top
@@ -2308,7 +2278,7 @@
2308
2278
  .ds-247420 .app-main .ds-file-stage { padding-top: 0; }
2309
2279
 
2310
2280
  /* 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
2281
+ --measure-wide cap/auto-margins, so the roots / toolbar / bulkbar / uploads / grid
2312
2282
  bands keep one consistent rhythm instead of touching edge-to-edge. Carries
2313
2283
  the same side/bottom padding as .ds-file-stage (top collapses the same way
2314
2284
  when mounted directly under .app-main, which already supplies top padding)
@@ -2531,7 +2501,6 @@
2531
2501
  max(var(--space-3), env(safe-area-inset-bottom))
2532
2502
  max(var(--space-3), env(safe-area-inset-left));
2533
2503
  background: color-mix(in oklab, var(--ink) 55%, transparent);
2534
- backdrop-filter: blur(3px);
2535
2504
  }
2536
2505
  @media (prefers-reduced-motion: no-preference) {
2537
2506
  .ds-247420 .ds-modal-backdrop { animation: ds-modal-fade var(--dur-base) var(--ease); }
@@ -2993,7 +2962,7 @@
2993
2962
  }
2994
2963
  .ds-247420 .chat-msg:hover .chat-image,
2995
2964
  .ds-247420 .chat-image:hover {
2996
- border-color: color-mix(in oklab, var(--accent) 35%, transparent);
2965
+ border-color: var(--rule);
2997
2966
  }
2998
2967
  .ds-247420 .chat-image img { display: block; width: 100%; height: auto; max-height: 360px; object-fit: cover; }
2999
2968
  .ds-247420 .chat-image .cap,
@@ -3008,12 +2977,12 @@
3008
2977
  max-width: min(100%, 360px);
3009
2978
  transition: background var(--dur-snap, .15s) var(--ease, ease), border-color var(--dur-snap, .15s) var(--ease, ease);
3010
2979
  }
3011
- .ds-247420 .chat-file:hover { background: var(--bg-3); border-color: color-mix(in oklab, var(--accent) 40%, transparent); }
2980
+ .ds-247420 .chat-file:hover { background: var(--bg-3); }
3012
2981
  .ds-247420 .chat-file .glyph {
3013
2982
  font-size: 22px; width: 36px; height: 36px;
3014
2983
  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);
2984
+ background: var(--bg-3);
2985
+ color: var(--fg-2); border-radius: var(--r-0); flex-shrink: 0; font-family: var(--ff-mono);
3017
2986
  }
3018
2987
  .ds-247420 .chat-file > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
3019
2988
  .ds-247420 .chat-file .name {
@@ -3034,7 +3003,7 @@
3034
3003
  border-radius: var(--r-1); overflow: hidden; max-width: min(100%, 480px);
3035
3004
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
3036
3005
  }
3037
- .ds-247420 .chat-msg:hover .chat-pdf { border-color: color-mix(in oklab, var(--accent) 30%, transparent); }
3006
+ .ds-247420 .chat-msg:hover .chat-pdf { border-color: var(--rule); }
3038
3007
  .ds-247420 .chat-pdf-head {
3039
3008
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
3040
3009
  background: color-mix(in oklab, var(--fg) 5%, var(--bg-2));
@@ -3061,7 +3030,6 @@
3061
3030
  .ds-247420 .chat-msg:hover .chat-link,
3062
3031
  .ds-247420 .chat-link:hover {
3063
3032
  background: var(--bg-3);
3064
- border-color: color-mix(in oklab, var(--accent) 40%, transparent);
3065
3033
  }
3066
3034
  .ds-247420 .chat-link:not(:has(.thumb)):not(:has(img)) { grid-template-columns: 1fr; }
3067
3035
  .ds-247420 .chat-link .thumb,
@@ -3094,7 +3062,7 @@
3094
3062
  transition: background var(--dur-snap, .12s) var(--ease, ease), border-color var(--dur-snap, .12s) var(--ease, ease), transform 0.12s ease;
3095
3063
  position: relative;
3096
3064
  }
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); }
3065
+ .ds-247420 .chat-reactions .rxn:hover { background: var(--bg-3); transform: scale(1.06); }
3098
3066
  .ds-247420 .chat-reactions .rxn.you {
3099
3067
  background: color-mix(in oklab, var(--accent) 18%, var(--bg-2));
3100
3068
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
@@ -7232,6 +7200,17 @@
7232
7200
  .ds-247420 .ds-context .row .meta { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-2); }
7233
7201
  .ds-247420 .ds-context .row .sub { font-size: var(--fs-xs); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
7234
7202
  .ds-247420 .ds-context-actions { display: flex; gap: var(--space-2); }
7203
+ /* Lighter fact groups for secondary context blocks (conversation/last turn/
7204
+ recent files): a small caps label + Row list with no card chrome, so a
7205
+ 2-row fact doesn't compete visually with the primary bordered panel above
7206
+ it. Separated from neighbors by a hairline rule, not a border box. */
7207
+ .ds-247420 .ds-context-group { padding-top: var(--space-3); border-top: var(--bw-hair) solid var(--rule); }
7208
+ .ds-247420 .ds-context-group-label {
7209
+ font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3);
7210
+ letter-spacing: var(--tr-caps); text-transform: uppercase;
7211
+ padding: 0 0 var(--space-2);
7212
+ }
7213
+ .ds-247420 .ds-context-group .row + .row { margin-top: 2px; }
7235
7214
 
7236
7215
  /* ============================================================================
7237
7216
  Cohesion sweep (gui-cohesion.js): one-product polish across surfaces.