agentgui 1.0.939 → 1.0.941

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/AGENTS.md +12 -7
  2. package/lib/claude-runner-agents.js +25 -0
  3. package/lib/ws-handlers-util.js +27 -1
  4. package/package.json +1 -1
  5. package/server.js +10 -1
  6. package/site/app/index.html +14 -37
  7. package/site/app/js/app.js +506 -104
  8. package/site/app/js/backend.js +44 -32
  9. package/site/app/vendor/anentrypoint-design/247420.css +274 -86
  10. package/site/app/vendor/anentrypoint-design/247420.js +12 -12
  11. package/site/app/vendor/cdn/dompurify.js +9 -0
  12. package/site/app/vendor/cdn/fonts/1291de6d401a.woff2 +0 -0
  13. package/site/app/vendor/cdn/fonts/1ba89a87e0b8.woff2 +0 -0
  14. package/site/app/vendor/cdn/fonts/3644d51c507b.woff2 +0 -0
  15. package/site/app/vendor/cdn/fonts/4b91d2650dc2.woff2 +0 -0
  16. package/site/app/vendor/cdn/fonts/530d036ba64a.woff2 +0 -0
  17. package/site/app/vendor/cdn/fonts/570a2bdd8f8b.woff2 +0 -0
  18. package/site/app/vendor/cdn/fonts/5dd6d880fee9.woff2 +0 -0
  19. package/site/app/vendor/cdn/fonts/62de9143afe3.woff2 +0 -0
  20. package/site/app/vendor/cdn/fonts/64884efa2f11.woff2 +0 -0
  21. package/site/app/vendor/cdn/fonts/68cd7063be2e.woff2 +0 -0
  22. package/site/app/vendor/cdn/fonts/6c252abcf99b.woff2 +0 -0
  23. package/site/app/vendor/cdn/fonts/71e69e06516a.woff2 +0 -0
  24. package/site/app/vendor/cdn/fonts/9ea68c62083f.woff2 +0 -0
  25. package/site/app/vendor/cdn/fonts/c010f9b7d6b2.woff2 +0 -0
  26. package/site/app/vendor/cdn/fonts/d69723fc74be.woff2 +0 -0
  27. package/site/app/vendor/cdn/fonts/fonts.css +459 -0
  28. package/site/app/vendor/cdn/marked.js +8 -0
  29. package/site/app/vendor/cdn/prismjs/components/prism-bash.min.js +1 -0
  30. package/site/app/vendor/cdn/prismjs/components/prism-clike.min.js +1 -0
  31. package/site/app/vendor/cdn/prismjs/components/prism-core.min.js +1 -0
  32. package/site/app/vendor/cdn/prismjs/components/prism-css.min.js +1 -0
  33. package/site/app/vendor/cdn/prismjs/components/prism-diff.min.js +1 -0
  34. package/site/app/vendor/cdn/prismjs/components/prism-go.min.js +1 -0
  35. package/site/app/vendor/cdn/prismjs/components/prism-javascript.min.js +1 -0
  36. package/site/app/vendor/cdn/prismjs/components/prism-json.min.js +1 -0
  37. package/site/app/vendor/cdn/prismjs/components/prism-jsx.min.js +1 -0
  38. package/site/app/vendor/cdn/prismjs/components/prism-markdown.min.js +1 -0
  39. package/site/app/vendor/cdn/prismjs/components/prism-markup.min.js +1 -0
  40. package/site/app/vendor/cdn/prismjs/components/prism-python.min.js +1 -0
  41. package/site/app/vendor/cdn/prismjs/components/prism-rust.min.js +1 -0
  42. package/site/app/vendor/cdn/prismjs/components/prism-sql.min.js +1 -0
  43. package/site/app/vendor/cdn/prismjs/components/prism-toml.min.js +1 -0
  44. package/site/app/vendor/cdn/prismjs/components/prism-tsx.min.js +1 -0
  45. package/site/app/vendor/cdn/prismjs/components/prism-typescript.min.js +1 -0
  46. package/site/app/vendor/cdn/prismjs/components/prism-yaml.min.js +1 -0
@@ -6,7 +6,7 @@
6
6
  Source-of-truth tokens. Component sheet lives in app-shell.css.
7
7
  ============================================================ */
8
8
 
9
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,700&display=swap');
10
10
 
11
11
  .ds-247420 {
12
12
  /* Tree view indentation tokens */
@@ -33,6 +33,7 @@
33
33
  --mascot: #E84B8A;
34
34
  --mascot-2: #FF3CA7;
35
35
  --mascot-tint: #F5D2DF;
36
+ --mascot-deep: #B81F63;
36
37
 
37
38
  /* Signals */
38
39
  --sun: #F5C344;
@@ -53,7 +54,7 @@
53
54
 
54
55
  --accent: var(--green);
55
56
  --accent-fg: var(--paper);
56
- --accent-tint: color-mix(in oklab, var(--accent) 18%, var(--bg));
57
+ --accent-tint: color-mix(in oklab, var(--accent) 26%, var(--bg));
57
58
 
58
59
  --panel-bg: var(--bg);
59
60
  --panel-bg-2: var(--bg-2);
@@ -68,18 +69,17 @@
68
69
  --panel-accent-2: var(--accent-bright, var(--accent));
69
70
  --panel-shadow: 0 1px 0 color-mix(in oklab, var(--fg) 6%, transparent), 0 4px 14px color-mix(in oklab, var(--fg) 8%, transparent);
70
71
 
71
- /* Type — one family, used purposefully. Space Grotesk for everything
72
- non-mono. The display/narrow aliases stay so consumers that reference
73
- them keep working; they just resolve to the body font now. */
74
- --ff-display: 'Space Grotesk', system-ui, sans-serif;
75
- --ff-narrow: 'Space Grotesk', system-ui, sans-serif;
76
- --ff-body: 'Space Grotesk', system-ui, sans-serif;
72
+ /* Type — Inter for everything non-mono. The display/narrow aliases stay so
73
+ consumers that reference them keep working; they resolve to the body font. */
74
+ --ff-display: 'Inter', system-ui, sans-serif;
75
+ --ff-narrow: 'Inter', system-ui, sans-serif;
76
+ --ff-body: 'Inter', system-ui, sans-serif;
77
77
  --ff-mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
78
78
 
79
- --fs-micro: 11px;
80
- --fs-tiny: 12px;
81
- --fs-xs: 13px;
82
- --fs-sm: 14px;
79
+ --fs-micro: 12px;
80
+ --fs-tiny: 13px;
81
+ --fs-xs: 14px;
82
+ --fs-sm: 15px;
83
83
  --fs-body: 16px;
84
84
  --fs-lg: 18px;
85
85
  --fs-xl: 21px;
@@ -90,6 +90,15 @@
90
90
  --fs-hero: clamp(42px, 7cqi, 96px);
91
91
  --fs-mega: clamp(56px, 11cqi, 168px);
92
92
 
93
+ /* App/chrome typescale: the default heading scale above is tuned for
94
+ marketing/hero layouts (h1 up to 64px). Application chrome — page titles,
95
+ panel headers — needs a compact ceiling so a PageHeader title doesn't
96
+ dwarf the content. Opt in with `data-typescale="app"` on a container. */
97
+ --fs-h1-app: clamp(22px, 2.4cqi, 30px);
98
+ --fs-h2-app: clamp(19px, 2cqi, 24px);
99
+ --fs-h3-app: clamp(17px, 1.8cqi, 20px);
100
+ --fs-h4-app: clamp(15px, 1.6cqi, 17px);
101
+
93
102
  --lh-tight: 1.05;
94
103
  --lh-snug: 1.2;
95
104
  --lh-base: 1.55;
@@ -195,6 +204,7 @@
195
204
 
196
205
  .ds-247420[data-theme="ink"],
197
206
  .ds-247420[data-theme="dark"] {
207
+ color-scheme: dark;
198
208
  --bg: var(--ink);
199
209
  --bg-2: var(--ink-2);
200
210
  --bg-3: #464650;
@@ -381,6 +391,12 @@
381
391
  .ds-247420 h3, .ds-247420 .t-h3 { font-family: var(--ff-body); font-size: var(--fs-h3); line-height: var(--lh-snug); letter-spacing: -0.01em; font-weight: 600; margin: 0; }
382
392
  .ds-247420 h4, .ds-247420 .t-h4 { font-family: var(--ff-body); font-size: var(--fs-h4); line-height: var(--lh-snug); font-weight: 600; margin: 0; }
383
393
 
394
+ /* App/chrome typescale opt-in: compact heading ceiling for application UIs. */
395
+ .ds-247420[data-typescale="app"] h1, .ds-247420[data-typescale="app"] .t-h1 { font-size: var(--fs-h1-app); line-height: 1.15; }
396
+ .ds-247420[data-typescale="app"] h2, .ds-247420[data-typescale="app"] .t-h2 { font-size: var(--fs-h2-app); }
397
+ .ds-247420[data-typescale="app"] h3, .ds-247420[data-typescale="app"] .t-h3 { font-size: var(--fs-h3-app); }
398
+ .ds-247420[data-typescale="app"] h4, .ds-247420[data-typescale="app"] .t-h4 { font-size: var(--fs-h4-app); }
399
+
384
400
  .ds-247420 .t-hero { font-family: var(--ff-body); font-size: var(--fs-hero); line-height: var(--lh-tight); letter-spacing: var(--tr-tighter); font-weight: 600; margin: 0; }
385
401
  .ds-247420 .t-mega { font-family: var(--ff-body); font-size: var(--fs-mega); line-height: 0.95; letter-spacing: var(--tr-tighter); font-weight: 600; margin: 0; }
386
402
 
@@ -471,6 +487,11 @@
471
487
  .ds-247420 .app {
472
488
  display: flex; flex-direction: column;
473
489
  min-height: 100vh;
490
+ /* Definite height so the flex column resolves height:100% for descendants
491
+ (e.g. a full-height chat) at every breakpoint, not just where a desktop
492
+ media override happened to set explicit heights. dvh tracks mobile chrome. */
493
+ min-height: 100dvh;
494
+ height: 100dvh;
474
495
  background: var(--bg);
475
496
  color: var(--fg);
476
497
  /* Notched-device safe area padding (no-op on devices without notches) */
@@ -480,6 +501,15 @@
480
501
  padding-right: env(safe-area-inset-right);
481
502
  }
482
503
 
504
+ /* When the app shell is embedded inside a windowed surface (e.g. the freddie
505
+ dashboard mounted in a WM window via .fd-root) it is NOT a full-page app:
506
+ `min-height:100vh` and the absence of an explicit width make it collapse to
507
+ 0 width at desktop, where the responsive single-column @media rules don't
508
+ apply. Inside .fd-root it must fill its container instead. (At <=767px the
509
+ grid already linearizes, which is why the bug only showed at desktop width.) */
510
+ .ds-247420 .fd-root .app { width: 100%; height: 100%; min-height: 0; flex: 1 1 auto; }
511
+ .ds-247420 .fd-root .app-body { min-width: 0; }
512
+
483
513
  .ds-247420 {
484
514
  --app-status-h: var(--size-base);
485
515
  --app-topbar-h: var(--size-lg);
@@ -604,6 +634,21 @@
604
634
  height: 100%;
605
635
  }
606
636
  .ds-247420 .app-main > * { min-height: 0; }
637
+ /* The main region scrolls its own overflow at every breakpoint (previously
638
+ only ≥901px), so a fixed-height .app never clips route content and inner
639
+ panels don't fight the page scroll. */
640
+ .ds-247420 .app-main { overflow-y: auto; }
641
+ /* Reserve the scrollbar track so routes with/without overflow don't shift
642
+ the layout horizontally when the bar appears. */
643
+ .ds-247420 .app-main { scrollbar-gutter: stable; }
644
+ /* Jump-target anchors clear the inner-scroll top edge (no chrome overlap,
645
+ but a little breathing room when deep-linked within .app-main). */
646
+ .ds-247420 .app-main [id] { scroll-margin-top: var(--space-4); }
647
+ /* Full-height route children fill the region instead of scrolling the page. */
648
+ .ds-247420 .app-main > .chat,
649
+ .ds-247420 .app-main > .chat-area,
650
+ .ds-247420 .app-main > .ds-file-stage,
651
+ .ds-247420 .app-main > .grow { flex: 1 1 auto; min-height: 0; }
607
652
  .ds-247420 .app-main.narrow { max-width: var(--measure-narrow); margin: 0 auto; }
608
653
 
609
654
  @media (min-width: 1400px) {
@@ -688,7 +733,7 @@
688
733
  .ds-247420 .ds-badge {
689
734
  display: inline-flex; align-items: center; justify-content: center;
690
735
  min-width: 18px; height: 18px; padding: 0 6px;
691
- font-size: 11px; font-weight: 600; line-height: 1;
736
+ font-size: var(--fs-micro); font-weight: 600; line-height: 1;
692
737
  border-radius: 999px;
693
738
  background: var(--bg-3); color: var(--fg-2);
694
739
  }
@@ -722,6 +767,9 @@
722
767
  .ds-247420 .chip.tone-error { background: var(--flame); color: var(--paper); }
723
768
  .ds-247420 .chip.tone-success { background: var(--green-tint); color: var(--green-deep); }
724
769
  .ds-247420 .chip.tone-disabled { background: var(--bg-3); color: var(--fg-3); }
770
+ .ds-247420 .chip.tone-ok { background: var(--green-tint); color: var(--green-deep); }
771
+ .ds-247420 .chip.tone-miss { background: var(--flame); color: var(--paper); }
772
+ .ds-247420 .chip.tone-neutral { background: var(--bg-3); color: var(--fg-2); }
725
773
 
726
774
  .ds-247420 .glyph {
727
775
  display: inline-flex; align-items: center; justify-content: center;
@@ -734,9 +782,11 @@
734
782
  Panel — soft tonal container, no border decoration
735
783
  ============================================================ */
736
784
  .ds-247420 .panel {
737
- background: var(--bg);
785
+ background: var(--panel-1, var(--bg-2));
738
786
  border-radius: var(--r-3);
739
787
  margin: 0 0 var(--space-5);
788
+ padding: var(--space-3);
789
+ box-shadow: var(--panel-shadow);
740
790
  position: relative;
741
791
  }
742
792
  .ds-247420 .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
@@ -847,6 +897,27 @@
847
897
  }
848
898
  .ds-247420 .cli .copy:hover { background: #6A6A70; }
849
899
 
900
+ /* Multi-line CLI block: when .cli holds .cli-line / .cli-cmt children
901
+ (quickstart scripts, multi-command snippets) it stacks as a column
902
+ instead of the single-line install row. */
903
+ .ds-247420 .cli:has(.cli-line),
904
+ .ds-247420 .cli:has(.cli-cmt) {
905
+ flex-direction: column; align-items: stretch; gap: 0;
906
+ font-size: var(--fs-sm); line-height: 1.6;
907
+ }
908
+ .ds-247420 .cli-line {
909
+ display: flex; gap: 10px;
910
+ font-family: var(--ff-mono); font-size: 13px; line-height: 1.6;
911
+ padding: 3px 0;
912
+ }
913
+ .ds-247420 .cli-line .prompt { color: var(--green-2); flex: 0 0 auto; user-select: none; }
914
+ .ds-247420 .cli-line .cmd { flex: 1 1 auto; white-space: pre-wrap; word-break: break-word; color: var(--paper); }
915
+ .ds-247420 .cli-cmt {
916
+ color: var(--fg-3);
917
+ font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1.6;
918
+ padding: 3px 0; white-space: pre-wrap; word-break: break-word;
919
+ }
920
+
850
921
  /* ============================================================
851
922
  Receipt — key/value table
852
923
  ============================================================ */
@@ -884,6 +955,11 @@
884
955
  .ds-247420 table td { padding: 14px 16px; border-top: 1px solid var(--rule); }
885
956
  .ds-247420 table tr.clickable { cursor: pointer; }
886
957
  .ds-247420 table tr.clickable:hover td { background: var(--bg-2); }
958
+ .ds-247420 table tr.clickable:focus-visible {
959
+ outline: 2px solid var(--accent);
960
+ outline-offset: -2px;
961
+ }
962
+ .ds-247420 table tr.clickable:focus-visible td { background: var(--bg-2); }
887
963
 
888
964
  /* ============================================================
889
965
  Changelog
@@ -1004,9 +1080,8 @@
1004
1080
  Mobile Portrait Breakpoint (480px and below)
1005
1081
  ────────────────────────────────────────────────────────────────────── */
1006
1082
  @media (max-width: 480px) {
1007
- /* App Layout drawer handled in 900px block below */
1008
- .ds-247420 .app-body { grid-template-columns: 1fr !important; }
1009
- .ds-247420 .app-body.no-side { grid-template-columns: 1fr; }
1083
+ /* App Layout: single-column + drawer is handled once in the ≤900px block;
1084
+ no need to re-declare grid-template-columns here (was a redundant !important). */
1010
1085
 
1011
1086
  /* Topbar Navigation */
1012
1087
  .ds-247420 .app-topbar {
@@ -1017,9 +1092,16 @@
1017
1092
  padding: 12px 10px;
1018
1093
  min-height: 44px;
1019
1094
  }
1095
+ /* Keep primary nav reachable on small screens (apps without a sidebar have
1096
+ no other entry point). Compact it and let it scroll horizontally rather
1097
+ than hiding it entirely. */
1020
1098
  .ds-247420 .app-topbar nav {
1021
- display: none; /* Hide full nav on very small screens */
1099
+ display: flex; gap: 2px;
1100
+ overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
1101
+ max-width: 60vw;
1022
1102
  }
1103
+ .ds-247420 .app-topbar nav::-webkit-scrollbar { display: none; }
1104
+ .ds-247420 .app-topbar nav a { padding: 10px 8px; white-space: nowrap; }
1023
1105
  .ds-247420 .brand { font-size: var(--fs-tiny); font-weight: 600; }
1024
1106
 
1025
1107
  /* Search */
@@ -1031,12 +1113,7 @@
1031
1113
  .ds-247420 .app-main { padding: var(--space-4) var(--space-2); }
1032
1114
  .ds-247420 .app-main.narrow { max-width: 100%; margin: 0; }
1033
1115
 
1034
- /* Typography Scaling */
1035
- .ds-247420 .t-hero { font-size: clamp(28px, 8vw, 48px); }
1036
- .ds-247420 .t-mega { font-size: clamp(24px, 6vw, 42px); }
1037
- .ds-247420 h1, .ds-247420 .t-h1 { font-size: clamp(22px, 6vw, 36px); }
1038
- .ds-247420 h2, .ds-247420 .t-h2 { font-size: clamp(18px, 5vw, 28px); }
1039
- .ds-247420 h3, .ds-247420 .t-h3 { font-size: clamp(16px, 4.5vw, 24px); }
1116
+ /* Headings stay on the fluid base clamps (cqi) — no mobile re-clamp. */
1040
1117
 
1041
1118
  /* Panel & Row */
1042
1119
  .ds-247420 .panel { margin: 0 0 var(--space-3); }
@@ -1101,10 +1178,10 @@
1101
1178
  .ds-247420 .row-form input,
1102
1179
  .ds-247420 .row-form textarea { padding: 11px 12px; font-size: var(--fs-sm); }
1103
1180
 
1104
- /* Hero Section */
1181
+ /* Hero Section — keep fluid base font-size, just unconstrain width on mobile. */
1105
1182
  .ds-247420 .ds-hero { padding: var(--space-6) 0 var(--space-5); }
1106
- .ds-247420 .ds-hero-title { font-size: clamp(28px, 7vw, 48px); max-width: 100%; }
1107
- .ds-247420 .ds-hero-body { font-size: var(--fs-lg); max-width: 100%; }
1183
+ .ds-247420 .ds-hero-title { max-width: 100%; }
1184
+ .ds-247420 .ds-hero-body { max-width: 100%; }
1108
1185
 
1109
1186
  /* Table Responsiveness */
1110
1187
  .ds-247420 table { font-size: var(--fs-xs); }
@@ -1169,12 +1246,7 @@
1169
1246
  .ds-247420 .app-main { padding: var(--space-6) var(--space-4); }
1170
1247
  .ds-247420 .app-main.narrow { max-width: 100%; }
1171
1248
 
1172
- /* Typography */
1173
- .ds-247420 .t-hero { font-size: clamp(32px, 6.5cqi, 56px); }
1174
- .ds-247420 .t-mega { font-size: clamp(28px, 5.5cqi, 80px); }
1175
- .ds-247420 h1, .ds-247420 .t-h1 { font-size: clamp(24px, 5cqi, 48px); }
1176
- .ds-247420 h2, .ds-247420 .t-h2 { font-size: clamp(20px, 4cqi, 36px); }
1177
- .ds-247420 h3, .ds-247420 .t-h3 { font-size: clamp(18px, 3.5cqi, 28px); }
1249
+ /* Headings stay on the fluid base clamps (cqi) — no tablet re-clamp. */
1178
1250
 
1179
1251
  /* File list — single column rows on tablet (cards only via data-columns) */
1180
1252
  .ds-247420 .ds-file-row {
@@ -1204,9 +1276,9 @@
1204
1276
  .ds-247420 .panel-head { padding: var(--space-4) var(--space-4); }
1205
1277
  .ds-247420 .panel-body { padding: var(--space-2) var(--space-3); }
1206
1278
 
1207
- /* Hero */
1279
+ /* Hero — fluid base font-size; only width unconstrained on tablet. */
1208
1280
  .ds-247420 .ds-hero { padding: var(--space-7) 0 var(--space-6); max-width: 100%; }
1209
- .ds-247420 .ds-hero-title { font-size: clamp(32px, 6cqi, 56px); max-width: 100%; }
1281
+ .ds-247420 .ds-hero-title { max-width: 100%; }
1210
1282
 
1211
1283
  /* Row */
1212
1284
  .ds-247420 .row {
@@ -1640,7 +1712,7 @@
1640
1712
  display: inline-flex; align-items: center; justify-content: center;
1641
1713
  border: var(--bw-hair) solid var(--rule); border-radius: 5px;
1642
1714
  background: var(--bg); color: var(--accent-fg);
1643
- font-size: 11px; line-height: 1; cursor: pointer;
1715
+ font-size: var(--fs-micro); line-height: 1; cursor: pointer;
1644
1716
  opacity: 0; transition: opacity var(--dur-base) var(--ease), background var(--dur-snap) var(--ease);
1645
1717
  }
1646
1718
  .ds-247420 .ds-file-row:hover .ds-file-check,
@@ -1719,7 +1791,6 @@
1719
1791
  .ds-247420 .chat {
1720
1792
  display: flex; flex-direction: column;
1721
1793
  flex: 1; min-height: 0; gap: var(--space-3);
1722
- padding-bottom: calc(var(--app-status-h, 42px) + var(--space-2));
1723
1794
  }
1724
1795
  .ds-247420 .chat-head {
1725
1796
  display: flex; align-items: baseline; gap: 8px;
@@ -1730,6 +1801,7 @@
1730
1801
  margin-bottom: var(--space-2);
1731
1802
  }
1732
1803
  .ds-247420 .chat-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
1804
+ .ds-247420 .chat-head .ds-chat-title,
1733
1805
  .ds-247420 .chat-head > span:nth-child(2) {
1734
1806
  font-family: var(--ff-body); font-size: var(--fs-h4);
1735
1807
  font-weight: 600; color: var(--fg);
@@ -1743,21 +1815,21 @@
1743
1815
  flex: 1; min-height: 0; overflow-y: auto;
1744
1816
  padding: var(--space-2) 0;
1745
1817
  scrollbar-width: thin; scrollbar-color: var(--bg-3) transparent;
1746
- scroll-behavior: smooth;
1818
+ }
1819
+ @media (prefers-reduced-motion: no-preference) {
1820
+ .ds-247420 .chat-thread { scroll-behavior: smooth; }
1747
1821
  }
1748
1822
  .ds-247420 .chat-thread::-webkit-scrollbar { width: 8px; }
1749
1823
  .ds-247420 .chat-thread::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
1750
- .ds-247420 .chat-thread:empty::before {
1751
- content: 'no messages yet\astart the conversation';
1752
- white-space: pre;
1824
+ .ds-247420 .chat-empty {
1825
+ margin: auto;
1826
+ text-align: center;
1753
1827
  color: var(--fg-3);
1754
1828
  font-family: var(--ff-mono);
1755
- font-size: var(--fs-sm);
1756
- text-align: center;
1757
- margin: auto;
1758
- opacity: 0.6;
1759
1829
  pointer-events: none;
1760
1830
  }
1831
+ .ds-247420 .chat-empty-title { font-size: var(--fs-sm); margin: 0 0 var(--space-1); }
1832
+ .ds-247420 .chat-empty-sub { font-size: var(--fs-tiny); margin: 0; opacity: .7; }
1761
1833
 
1762
1834
  .ds-247420 .chat-msg { display: flex; gap: 12px; align-items: flex-start; padding: 6px 0; position: relative; min-width: 0; }
1763
1835
  .ds-247420 .chat-msg.you { flex-direction: row-reverse; }
@@ -1791,10 +1863,10 @@
1791
1863
 
1792
1864
  .ds-247420 .chat-bubble {
1793
1865
  padding: 10px 14px; background: var(--bg-2); color: var(--fg);
1794
- border-radius: 14px; line-height: 1.55;
1866
+ max-width: 100%;
1867
+ border-radius: var(--r-2); line-height: 1.55;
1795
1868
  word-wrap: break-word; overflow-wrap: anywhere;
1796
1869
  font-size: var(--fs-sm);
1797
- max-width: clamp(220px, min(75vw, 28em), 480px);
1798
1870
  min-width: 0;
1799
1871
  transition: transform 0.12s ease, box-shadow 0.12s ease;
1800
1872
  }
@@ -2078,27 +2150,34 @@
2078
2150
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 20%, transparent);
2079
2151
  outline: none;
2080
2152
  }
2081
- .ds-247420 .chat-composer .send,
2082
- .ds-247420 .chat-composer button {
2153
+ .ds-247420 .chat-composer .send {
2083
2154
  width: 44px; height: 44px; border-radius: 50%;
2084
2155
  background: var(--accent); color: var(--accent-fg);
2085
- border: 0; cursor: pointer; font-size: 18px; font-weight: 700;
2156
+ border: 0; cursor: pointer;
2086
2157
  display: inline-flex; align-items: center; justify-content: center;
2087
- flex-shrink: 0; font-family: var(--ff-body);
2158
+ flex-shrink: 0;
2088
2159
  transition: background var(--dur-snap, .15s) var(--ease, ease), transform .1s ease;
2089
- will-change: transform; contain: layout style paint;
2090
2160
  }
2091
- .ds-247420 .chat-composer .send:hover,
2092
- .ds-247420 .chat-composer button:hover { transform: scale(1.04); }
2093
- .ds-247420 .chat-composer .send:active,
2094
- .ds-247420 .chat-composer button:active { transform: scale(0.96); }
2161
+ .ds-247420 .chat-composer .send:disabled { opacity: .5; cursor: not-allowed; }
2162
+ /* Ghost icon buttons in the toolbar (attach / emoji / more) — NOT the round
2163
+ accent send button. */
2164
+ .ds-247420 .composer-btn {
2165
+ width: 40px; height: 40px; border-radius: 50%;
2166
+ background: transparent; color: var(--fg-3);
2167
+ border: 0; cursor: pointer;
2168
+ display: inline-flex; align-items: center; justify-content: center;
2169
+ flex-shrink: 0;
2170
+ transition: background var(--dur-snap, .15s) var(--ease, ease), color var(--dur-snap, .15s) var(--ease, ease);
2171
+ }
2172
+ .ds-247420 .composer-btn:hover { background: color-mix(in oklab, var(--fg) 8%, transparent); color: var(--fg); }
2173
+ .ds-247420 .chat-composer .send:hover { transform: scale(1.04); }
2174
+ .ds-247420 .chat-composer .send:active { transform: scale(0.96); }
2095
2175
  .ds-247420 .chat-composer .send:focus-visible,
2096
- .ds-247420 .chat-composer button:focus-visible {
2176
+ .ds-247420 .composer-btn:focus-visible {
2097
2177
  outline: 2px solid var(--accent);
2098
2178
  outline-offset: 2px;
2099
2179
  }
2100
- .ds-247420 .chat-composer .send:disabled,
2101
- .ds-247420 .chat-composer button:disabled {
2180
+ .ds-247420 .chat-composer .send:disabled {
2102
2181
  background: var(--bg-3); color: var(--fg-3); cursor: not-allowed; transform: none;
2103
2182
  }
2104
2183
 
@@ -2110,7 +2189,8 @@
2110
2189
  border-radius: 50%; flex-shrink: 0;
2111
2190
  }
2112
2191
  .ds-247420 .aicat-meta { display: flex; flex-direction: column; font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); }
2113
- .ds-247420 .aicat-meta .name { color: var(--mascot); font-weight: 600; }
2192
+ .ds-247420 .aicat-meta .name { color: var(--mascot-deep, var(--mascot)); font-weight: 600; }
2193
+ .ds-247420[data-theme="dark"] .aicat-meta .name, .ds-247420[data-theme="ink"] .aicat-meta .name { color: var(--mascot); }
2114
2194
 
2115
2195
  /* ============================================================
2116
2196
  Sidebar polish: hide zero-count badges; tonal active state;
@@ -2118,7 +2198,7 @@
2118
2198
  ============================================================ */
2119
2199
  .ds-247420 .app-side .count {
2120
2200
  background: color-mix(in oklab, var(--mascot) 92%, transparent);
2121
- padding: 1px 8px; font-size: 10px; font-weight: 700;
2201
+ padding: 1px 8px; font-size: var(--fs-micro); font-weight: 700;
2122
2202
  font-family: var(--ff-mono); letter-spacing: 0.04em;
2123
2203
  min-width: 18px; text-align: center;
2124
2204
  }
@@ -2239,10 +2319,14 @@
2239
2319
  transform: scale(0.98);
2240
2320
  }
2241
2321
 
2242
- /* Enhanced hover transitions */
2322
+ /* Enhanced hover transitions — transition only the properties that actually
2323
+ change (not `all`, which animates layout and is a perf trap), and drop the
2324
+ blanket `will-change` (it forces a permanent compositor layer per element). */
2243
2325
  .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost, .ds-247420 button, .ds-247420 a.row, .ds-247420 .row, .ds-247420 .chip, .ds-247420[role="button"], .ds-247420 input[type="checkbox"], .ds-247420 input[type="radio"] {
2244
- transition: all var(--dur-snap) var(--ease);
2245
- will-change: background-color, color, transform;
2326
+ transition: background-color var(--dur-snap) var(--ease),
2327
+ color var(--dur-snap) var(--ease),
2328
+ box-shadow var(--dur-snap) var(--ease),
2329
+ transform var(--dur-snap) var(--ease);
2246
2330
  }
2247
2331
 
2248
2332
  /* Prevent double-tap zoom on buttons (iOS) */
@@ -2383,6 +2467,9 @@
2383
2467
  .ds-247420 input[type="email"],
2384
2468
  .ds-247420 input[type="password"],
2385
2469
  .ds-247420 input[type="number"],
2470
+ .ds-247420 input[type="search"],
2471
+ .ds-247420 input[type="url"],
2472
+ .ds-247420 input[type="tel"],
2386
2473
  .ds-247420 textarea,
2387
2474
  .ds-247420 select {
2388
2475
  transition: background var(--dur-snap) var(--ease),
@@ -2394,10 +2481,74 @@
2394
2481
  .ds-247420 input[type="email"]::placeholder,
2395
2482
  .ds-247420 input[type="password"]::placeholder,
2396
2483
  .ds-247420 input[type="number"]::placeholder,
2484
+ .ds-247420 input[type="search"]::placeholder,
2485
+ .ds-247420 input[type="url"]::placeholder,
2397
2486
  .ds-247420 textarea::placeholder {
2398
2487
  color: var(--fg-3);
2399
2488
  }
2400
2489
 
2490
+ /* ── Field controls: themed base for TextField / Select / SearchInput ──
2491
+ Root fix: previously only `transition` was set, so themed apps got
2492
+ browser-default white boxes in dark mode and labels collided with inputs
2493
+ because `.ds-field` had no layout. */
2494
+ .ds-247420 .ds-field {
2495
+ display: flex;
2496
+ flex-direction: column;
2497
+ gap: var(--space-1, 6px);
2498
+ align-items: stretch;
2499
+ width: 100%;
2500
+ }
2501
+ .ds-247420 .ds-field-label {
2502
+ font-size: var(--fs-sm, 14px);
2503
+ color: var(--fg-2);
2504
+ line-height: 1.3;
2505
+ }
2506
+ .ds-247420 .ds-field-hint {
2507
+ font-size: var(--fs-tiny, 13px);
2508
+ color: var(--fg-3);
2509
+ line-height: 1.35;
2510
+ }
2511
+ .ds-247420 .ds-field-count {
2512
+ font-size: var(--fs-tiny, 13px);
2513
+ color: var(--fg-3);
2514
+ align-self: flex-end;
2515
+ }
2516
+
2517
+ .ds-247420 .ds-field input,
2518
+ .ds-247420 .ds-field textarea,
2519
+ .ds-247420 .ds-field .ds-select,
2520
+ .ds-247420 .ds-search-input {
2521
+ width: 100%;
2522
+ box-sizing: border-box;
2523
+ font: inherit;
2524
+ color: var(--fg);
2525
+ background: var(--bg-2);
2526
+ border: 0;
2527
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule);
2528
+ border-radius: var(--r-2, 10px);
2529
+ padding: 10px 14px;
2530
+ }
2531
+ .ds-247420 .ds-field textarea { min-height: calc(4 * 1.5em); resize: vertical; }
2532
+ .ds-247420 .ds-search-input::placeholder { color: var(--fg-3); }
2533
+
2534
+ .ds-247420 .ds-field input:focus-visible,
2535
+ .ds-247420 .ds-field textarea:focus-visible,
2536
+ .ds-247420 .ds-field .ds-select:focus-visible,
2537
+ .ds-247420 .ds-search-input:focus-visible {
2538
+ outline: none;
2539
+ box-shadow: inset 0 0 0 2px var(--accent);
2540
+ }
2541
+ .ds-247420 .ds-field input:disabled,
2542
+ .ds-247420 .ds-field textarea:disabled,
2543
+ .ds-247420 .ds-field .ds-select:disabled {
2544
+ opacity: .55;
2545
+ cursor: not-allowed;
2546
+ }
2547
+ .ds-247420 .ds-field input[aria-invalid="true"],
2548
+ .ds-247420 .ds-field textarea[aria-invalid="true"] {
2549
+ box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--flame, #d64545);
2550
+ }
2551
+
2401
2552
  /* Clear button for text inputs */
2402
2553
  .ds-247420 input[type="text"]:not(:placeholder-shown) + .input-clear,
2403
2554
  .ds-247420 input[type="email"]:not(:placeholder-shown) + .input-clear,
@@ -2446,9 +2597,10 @@
2446
2597
  }
2447
2598
  .ds-247420 .skip-to-main:focus { top: 10px; }
2448
2599
 
2449
- /* Reduced motion preferences */
2600
+ /* Reduced motion preferences — scoped to the DS surface so it doesn't reach
2601
+ into and neutralize the host document's own motion. */
2450
2602
  @media (prefers-reduced-motion: reduce) {
2451
- .ds-247420 * {
2603
+ .ds-247420 *, .ds-247420 .app * {
2452
2604
  animation-duration: 0.01ms !important;
2453
2605
  animation-iteration-count: 1 !important;
2454
2606
  transition-duration: 0.01ms !important;
@@ -2456,17 +2608,14 @@
2456
2608
  }
2457
2609
 
2458
2610
  /* ────────────────────────────────────────────────────────────
2459
- Performance: GPU Acceleration & Paint Optimization
2611
+ Performance
2460
2612
  ────────────────────────────────────────────────────────────── */
2461
2613
 
2462
- /* Promote hover-interactive elements to compositing layer */
2463
- .ds-247420 .btn:hover, .ds-247420 .btn-primary:hover, .ds-247420 .btn-ghost:hover, .ds-247420 a:hover, .ds-247420 .row:hover, .ds-247420 button:hover, .ds-247420[role="button"]:hover {
2464
- transform: translateZ(0);
2465
- }
2466
-
2467
- /* Contain layout repaints on self-contained components */
2468
- .ds-247420 .panel, .ds-247420 .row, .ds-247420 .chip, .ds-247420 .btn, .ds-247420 button {
2469
- contain: layout style paint;
2614
+ /* Limit layout/style containment to self-contained components. `paint` is
2615
+ intentionally omitted it clips focus rings, tooltips and dropdowns that
2616
+ emanate from a contained .row/.btn. */
2617
+ .ds-247420 .panel, .ds-247420 .row {
2618
+ contain: layout style;
2470
2619
  }
2471
2620
 
2472
2621
  /* ────────────────────────────────────────────────────────────
@@ -2475,11 +2624,13 @@
2475
2624
 
2476
2625
  @media (hover: none) and (pointer: coarse) {
2477
2626
  /* Mobile devices: larger touch targets, faster responses */
2478
- .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost, .ds-247420 button, .ds-247420 a.row, .ds-247420[role="button"] {
2627
+ .ds-247420 .btn, .ds-247420 .btn-primary, .ds-247420 .btn-ghost, .ds-247420 button, .ds-247420 a.row, .ds-247420[role="button"], .ds-247420 .ds-icon-btn, .ds-247420 .composer-btn {
2479
2628
  min-height: 48px;
2480
2629
  min-width: 48px;
2481
2630
  padding: 12px 20px;
2482
2631
  }
2632
+ /* Icon buttons stay square — the padding bump above would distort them. */
2633
+ .ds-247420 .ds-icon-btn, .ds-247420 .composer-btn { padding: 0; }
2483
2634
 
2484
2635
  /* Remove hover effects on touch devices (use active instead) */
2485
2636
  .ds-247420 .btn:hover, .ds-247420 .btn-primary:hover, .ds-247420 .btn-ghost:hover {
@@ -2502,12 +2653,9 @@
2502
2653
  Theme Transition Smoothness
2503
2654
  ────────────────────────────────────────────────────────────── */
2504
2655
 
2505
- .ds-247420 {
2506
- transition: background-color var(--dur-base) var(--ease),
2507
- color var(--dur-base) var(--ease);
2508
- }
2509
-
2510
- .ds-247420 body {
2656
+ /* Scoped to the DS wrapper so the design system never reaches out and
2657
+ animates the host document's html/body. */
2658
+ .ds-247420, .ds-247420 .app {
2511
2659
  transition: background-color var(--dur-base) var(--ease),
2512
2660
  color var(--dur-base) var(--ease);
2513
2661
  }
@@ -2743,12 +2891,17 @@
2743
2891
  padding: 10px 12px;
2744
2892
  }
2745
2893
  .ds-247420 .app-side .group { width: 100%; }
2746
- .ds-247420 .app-crumb { display: none; }
2894
+ /* Keep the crumb on mobile apps mount contextual controls (model picker,
2895
+ actions, live status) in crumb-right, so hiding it strands them. Collapse
2896
+ the breadcrumb trail/leaf instead and let the right cluster wrap. */
2897
+ .ds-247420 .app-crumb { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 8px 12px; }
2898
+ .ds-247420 .app-crumb > .sep, .ds-247420 .app-crumb > span:not(.crumb-right):not(.leaf) { display: none; }
2899
+ .ds-247420 .app-crumb .crumb-right { margin-left: 0; }
2747
2900
  .ds-247420 .chat-head { padding: var(--space-2) var(--space-3) var(--space-2); margin-bottom: var(--space-2); }
2748
2901
  .ds-247420 .app-main { padding-left: var(--space-3); padding-right: var(--space-3); }
2749
2902
  }
2750
2903
 
2751
- /* Drawer scrim */
2904
+ /* Drawer scrim — lives inside .app-body, shown when drawer open ≤900px */
2752
2905
  .ds-247420 .app-side-scrim {
2753
2906
  display: none;
2754
2907
  position: fixed; inset: 0;
@@ -2756,8 +2909,41 @@
2756
2909
  z-index: 49;
2757
2910
  }
2758
2911
  @media (max-width: 900px) {
2759
- .ds-247420 .app-body.side-open ~ .app-side-scrim,
2760
- .ds-247420 .app-side-shell.open ~ .app-side-scrim { display: block; }
2912
+ .ds-247420 .app-body.side-open .app-side-scrim { display: block; }
2913
+ }
2914
+
2915
+ /* Mobile nav toggle (hamburger) — hidden on desktop, shown ≤900px */
2916
+ .ds-247420 .app-side-toggle {
2917
+ display: none;
2918
+ position: fixed; top: calc((var(--app-topbar-h) - 32px) / 2); left: 10px;
2919
+ z-index: 51;
2920
+ width: 36px; height: 36px;
2921
+ align-items: center; justify-content: center;
2922
+ font-size: 18px; line-height: 1;
2923
+ background: var(--bg-2); color: var(--fg);
2924
+ border: 1px solid var(--rule, color-mix(in oklab, var(--fg) 12%, transparent));
2925
+ border-radius: var(--r-1, 6px);
2926
+ cursor: pointer;
2927
+ }
2928
+ .ds-247420 .app-side-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
2929
+ @media (max-width: 900px) {
2930
+ .ds-247420 .app-side-toggle { display: inline-flex; }
2931
+ .ds-247420 .app-topbar .brand { margin-left: 44px; }
2932
+ }
2933
+
2934
+ /* Desktop: the app shell is exactly viewport-height and contains its own
2935
+ scroll, so only .app-main (and the side rail) scroll — never the page too.
2936
+ Pinning .app to 100dvh + overflow:hidden is what prevents the double
2937
+ scrollbar that a min-height:100vh shell + inner overflow:auto produces.
2938
+ .app-body fills the gap between topbar/crumb and status via flex:1 rather
2939
+ than a hand-computed calc() that drifts from the real chrome heights. */
2940
+ @media (min-width: 901px) {
2941
+ .ds-247420 .app { height: 100dvh; min-height: 0; overflow: hidden; }
2942
+ /* Embedded shell (freddie WM window) fills its container, not the viewport. */
2943
+ .ds-247420 .fd-root .app { height: 100%; overflow: visible; }
2944
+ .ds-247420 .app-body { min-height: 0; flex: 1 1 auto; }
2945
+ .ds-247420 .app-side-shell { overflow-y: auto; min-height: 0; }
2946
+ .ds-247420 .app-main { overflow-y: auto; min-height: 0; }
2761
2947
  }
2762
2948
 
2763
2949
  /* Mobile (≤480) status bar compact; hide tail item */
@@ -4303,6 +4489,8 @@
4303
4489
  .ds-247420 .fd-chat { display: flex; flex-direction: column; gap: var(--space-2, 10px); height: 100%; min-height: 0; }
4304
4490
  .ds-247420 .fd-chat-thread { flex: 1 1 auto; min-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-2, 10px); padding: var(--space-2, 10px); }
4305
4491
  .ds-247420 .fd-page-error { white-space: pre-wrap; overflow-wrap: anywhere; }
4492
+ /* Visually-hidden polite live region — announces async busy/done to SR users. */
4493
+ .ds-247420 .fd-sr-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
4306
4494
  /* page-level responsive: tighten padding on narrow viewports */
4307
4495
  @media (max-width: 640px) {
4308
4496
  .ds-247420 .fd-page-inner { padding: var(--space-2, 10px); gap: var(--space-2, 10px); }