@immediately-run/grove 0.1.1 → 0.1.3

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 (85) hide show
  1. package/README.md +43 -115
  2. package/llms.txt +6 -2
  3. package/package.json +20 -8
  4. package/src/App.tsx +11 -7
  5. package/src/GroveApp.css +485 -85
  6. package/src/GroveWiki.tsx +176 -54
  7. package/src/components/AssetImage.tsx +1 -12
  8. package/src/components/Backlinks.tsx +2 -2
  9. package/src/components/Catalogue.test.tsx +157 -0
  10. package/src/components/ContentTheme.test.tsx +146 -0
  11. package/src/components/ContentTheme.tsx +111 -0
  12. package/src/components/DocList.infinite.test.tsx +177 -0
  13. package/src/components/DocList.tsx +82 -9
  14. package/src/components/Drawer.tsx +14 -2
  15. package/src/components/EntryHeader.tsx +24 -15
  16. package/src/components/EntryImage.test.tsx +210 -0
  17. package/src/components/EntryImage.tsx +47 -0
  18. package/src/components/Galleries.test.tsx +98 -0
  19. package/src/components/GroveAgent.test.tsx +330 -0
  20. package/src/components/GroveAgent.tsx +269 -149
  21. package/src/components/GroveNav.test.tsx +229 -0
  22. package/src/components/GroveNav.tsx +69 -20
  23. package/src/components/Icon.tsx +0 -1
  24. package/src/components/InlineProse.tsx +37 -0
  25. package/src/components/LayoutGallery.tsx +65 -0
  26. package/src/components/PageView.tsx +19 -4
  27. package/src/components/Search.test.tsx +113 -0
  28. package/src/components/Search.tsx +53 -18
  29. package/src/components/Sidebar.test.tsx +135 -0
  30. package/src/components/Sidebar.tsx +114 -12
  31. package/src/components/TableOfContents.test.tsx +27 -16
  32. package/src/components/ThemeAssets.test.tsx +109 -0
  33. package/src/components/ThemeAssets.tsx +63 -0
  34. package/src/components/ThemeGallery.tsx +31 -0
  35. package/src/components/Timeline.tsx +5 -2
  36. package/src/components/WikiLink.tsx +2 -2
  37. package/src/data/catalogue.ts +54 -0
  38. package/src/data/themeFonts.ts +58 -0
  39. package/src/data/themes.ts +44 -4
  40. package/src/devfs.d.ts +5 -4
  41. package/src/hooks/{useCorpusMetadata.ts → useBundleMetadata.ts} +6 -6
  42. package/src/hooks/useContentComponents.ts +1 -1
  43. package/src/hooks/useEditAffordance.ts +99 -0
  44. package/src/hooks/useOpenWikiBoot.ts +1 -1
  45. package/src/hooks/useOverlayFocusDismiss.test.tsx +139 -0
  46. package/src/hooks/useOverlayFocusDismiss.ts +118 -0
  47. package/src/hooks/useScrollReset.test.tsx +132 -0
  48. package/src/hooks/useScrollReset.ts +52 -0
  49. package/src/index.css +9 -2
  50. package/src/lib/agentPrompt.test.ts +96 -0
  51. package/src/lib/agentPrompt.ts +86 -0
  52. package/src/lib/agentTools.test.ts +115 -0
  53. package/src/lib/agentTools.ts +132 -0
  54. package/src/lib/agentTranscript.ts +50 -0
  55. package/src/lib/assetPath.test.ts +36 -0
  56. package/src/lib/assetPath.ts +42 -0
  57. package/src/lib/collectionCalls.test.ts +69 -0
  58. package/src/lib/content.test.ts +10 -2
  59. package/src/lib/content.ts +7 -0
  60. package/src/lib/contentRoot.ts +16 -1
  61. package/src/lib/contentStylesheet.test.ts +80 -0
  62. package/src/lib/contentStylesheet.ts +103 -0
  63. package/src/lib/corpusScan.test.ts +37 -3
  64. package/src/lib/corpusScan.ts +17 -2
  65. package/src/lib/editTarget.test.ts +108 -0
  66. package/src/lib/editTarget.ts +93 -0
  67. package/src/lib/inlineProse.parity.test.ts +52 -0
  68. package/src/lib/layout.ts +29 -0
  69. package/src/lib/openWiki.test.ts +46 -5
  70. package/src/lib/openWiki.ts +18 -3
  71. package/src/lib/pageVariants.test.tsx +87 -0
  72. package/src/lib/queries.test.ts +33 -1
  73. package/src/lib/queries.ts +33 -1
  74. package/src/lib/reachCard.test.ts +94 -0
  75. package/src/lib/reachCard.ts +112 -0
  76. package/src/lib/shell.ts +17 -0
  77. package/src/lib/starterSweep.test.tsx +160 -0
  78. package/src/lib/starterSweep.ts +97 -0
  79. package/src/lib/themeAssets.test.ts +135 -0
  80. package/src/lib/themeAssets.ts +143 -0
  81. package/src/lib/themeSelection.test.ts +52 -0
  82. package/src/lib/themeSelection.ts +59 -0
  83. package/src/mdxComponents.ts +4 -0
  84. package/viewer-manifest.schema.json +37 -0
  85. package/viewer.manifest.json +133 -6
package/src/GroveApp.css CHANGED
@@ -6,6 +6,30 @@
6
6
  overflow-x: hidden;
7
7
  }
8
8
 
9
+ /* R3-316 — the escape hatch, pinned where content cannot reach: for !important
10
+ declarations EARLIER layers win, so grove.reset (the first layer, ordered in
11
+ index.css) beats grove.content no matter what a ui/stylesheet tries. A reader
12
+ can always reach a shipped theme without clearing storage — the one guarantee
13
+ the content layer must not be able to revoke. Proven by test (the hiding
14
+ vectors are enumerated), not by reading this comment. */
15
+ @layer grove.reset {
16
+ .grove-root .grove-theme-control,
17
+ .grove-root .grove-theme-menu {
18
+ display: revert !important;
19
+ visibility: visible !important;
20
+ opacity: 1 !important;
21
+ pointer-events: auto !important;
22
+ clip-path: none !important;
23
+ transform: none !important;
24
+ position: revert !important;
25
+ z-index: auto !important;
26
+ width: revert !important;
27
+ height: revert !important;
28
+ overflow: revert !important;
29
+ filter: none !important;
30
+ }
31
+ }
32
+
9
33
  /* ============ GROVE THEME TOKENS (the contract) ============ */
10
34
  .grove-root {
11
35
  --bg: #0a0b11;
@@ -36,6 +60,7 @@
36
60
  --wash: radial-gradient(60% 50% at 82% -2%, rgba(178, 133, 242, .12), transparent 60%), radial-gradient(48% 38% at 6% 4%, rgba(244, 154, 212, .06), transparent 60%);
37
61
  --disp-weight: 800;
38
62
  --prose-measure: 70ch;
63
+ --prose-leading: 1.6;
39
64
  --radius-shape: var(--r-lg);
40
65
  background: var(--bg);
41
66
  color: var(--ink);
@@ -66,69 +91,209 @@
66
91
  --accent-violet: #9a45e6;
67
92
  }
68
93
 
69
- /* —— alternate themes: identical DOM, CSS-only re-skin —— */
70
- .grove-root[data-grove-theme="pixies"] {
71
- --bg: #0c0410;
72
- --panel: #1a0a1e;
73
- --panel-2: #260f2c;
74
- --line: rgba(255, 120, 200, .16);
75
- --line-2: rgba(255, 120, 200, .3);
76
- --ink: #fce9f6;
77
- --ink-2: #d79bc9;
78
- --ink-3: #9c6a8e;
79
- --accent: #ff2d8e;
80
- --accent-2: #7b2ff7;
81
- --accent-3: #ffd23f;
82
- --grad: linear-gradient(96deg, #ffe14d 0%, #ff2d8e 50%, #9d29ff 100%);
83
- --glow: 0 0 0 1px rgba(255, 45, 142, .6), 0 0 30px rgba(255, 45, 142, .4);
84
- --accent-pink: #ff2d8e;
85
- --accent-violet: #9d29ff;
86
- --disp-weight: 900;
87
- --prose-measure: 66ch;
88
- --radius-shape: 8px;
89
- --wash: radial-gradient(70% 55% at 85% -5%, rgba(255, 45, 142, .2), transparent 60%), radial-gradient(55% 45% at 4% 2%, rgba(157, 41, 255, .16), transparent 60%);
90
- }
91
- .grove-root[data-grove-theme="family"] {
92
- --bg: #faf6f1;
93
- --panel: #fffdfa;
94
- --panel-2: #f4ece2;
94
+ /* —— catalogue themes (R3-310): identical DOM, CSS-only re-skin, token-only —
95
+ every block declares custom properties and NOTHING else (02-theme-contract §1;
96
+ scripts/check-theme-token-only.mjs gates it). Per R3-308 the bare
97
+ [data-grove-theme] block is the theme's PREFERRED polarity and the paired
98
+ [data-theme] block is the other; GroveWiki always emits data-theme.
99
+
100
+ `--sans` is the body-face ROLE: archive puts a serif in it (the contract's
101
+ documented decision — the faces themselves arrive as declared assets, never a
102
+ url()). */
103
+
104
+ /* — archive: encyclopedic reference — dense, quiet, contents-forward — */
105
+ .grove-root[data-grove-theme="archive"] {
106
+ --bg: #f7f5f1;
107
+ --panel: #fffefb;
108
+ --panel-2: #efede7;
109
+ --line: rgba(52, 60, 65, .16);
110
+ --line-2: rgba(52, 60, 65, .3);
111
+ --ink: #23282c;
112
+ --ink-2: #515a60;
113
+ --ink-3: #6d777d;
114
+ --accent: #7a5c20;
115
+ --accent-2: #3f5a63;
116
+ --accent-3: #8a6a2c;
117
+ --grad: linear-gradient(96deg, #b89a56 0%, #7a5c20 55%, #3f5a63 100%);
118
+ --glow: 0 0 0 1px rgba(122, 92, 32, .4), 0 8px 20px rgba(122, 92, 32, .16);
119
+ --accent-pink: #7a5c20;
120
+ --accent-violet: #3f5a63;
121
+ --sans: "Source Serif 4", Georgia, serif;
122
+ --disp-weight: 600;
123
+ --prose-measure: 76ch;
124
+ --prose-leading: 1.55;
125
+ --radius-shape: 2px;
126
+ --wash: radial-gradient(70% 60% at 50% -5%, rgba(122, 92, 32, .07), transparent 65%);
127
+ }
128
+ .grove-root[data-grove-theme="archive"][data-theme="dark"] {
129
+ --bg: #131517;
130
+ --panel: #1a1d20;
131
+ --panel-2: #21252a;
132
+ --line: rgba(196, 205, 210, .15);
133
+ --line-2: rgba(196, 205, 210, .26);
134
+ --ink: #e6e9e7;
135
+ --ink-2: #aeb6b6;
136
+ --ink-3: #8b9494;
137
+ --accent: #c9a35c;
138
+ --accent-2: #7fa7b3;
139
+ --accent-3: #b08b45;
140
+ --grad: linear-gradient(96deg, #c9a35c 0%, #8a6a2c 55%, #7fa7b3 100%);
141
+ --glow: 0 0 0 1px rgba(201, 163, 92, .45), 0 8px 22px rgba(201, 163, 92, .14);
142
+ --accent-pink: #c9a35c;
143
+ --accent-violet: #7fa7b3;
144
+ --wash: radial-gradient(70% 60% at 50% -5%, rgba(201, 163, 92, .07), transparent 65%);
145
+ }
146
+
147
+ /* — journal: a personal notebook — warm, short measure, soft frame — */
148
+ .grove-root[data-grove-theme="journal"] {
149
+ --bg: #faf5ec;
150
+ --panel: #fffdf8;
151
+ --panel-2: #f3ebdd;
95
152
  --line: rgba(120, 90, 60, .16);
96
153
  --line-2: rgba(120, 90, 60, .28);
97
- --ink: #3b342c;
98
- --ink-2: #7c7062;
99
- --ink-3: #a89c8c;
100
- --accent: #c8744f;
101
- --accent-2: #9a8f5e;
102
- --accent-3: #d9a06a;
103
- --grad: linear-gradient(96deg, #f3cf9a 0%, #e09a6a 50%, #c8744f 100%);
104
- --glow: 0 0 0 1px rgba(200, 116, 79, .4), 0 10px 26px rgba(200, 116, 79, .2);
105
- --accent-pink: #c8744f;
106
- --accent-violet: #9a8f5e;
107
- --disp-weight: 700;
108
- --prose-measure: 64ch;
109
- --radius-shape: 20px;
110
- --wash: radial-gradient(60% 50% at 82% -2%, rgba(224, 154, 106, .14), transparent 60%);
111
- }
112
- .grove-root[data-grove-theme="lotr"] {
113
- --bg: #ece2cc;
114
- --panel: #f4ebd6;
115
- --panel-2: #e4d8ba;
116
- --line: rgba(90, 70, 40, .22);
117
- --line-2: rgba(90, 70, 40, .36);
118
- --ink: #322712;
119
- --ink-2: #6a5c40;
120
- --ink-3: #998a68;
121
- --accent: #7a5a2a;
122
- --accent-2: #4a5a38;
123
- --accent-3: #9a7a3a;
124
- --grad: linear-gradient(96deg, #b89a56 0%, #8a6a36 50%, #4a5a38 100%);
125
- --glow: 0 0 0 1px rgba(122, 90, 42, .4), 0 8px 20px rgba(122, 90, 42, .22);
126
- --accent-pink: #7a5a2a;
127
- --accent-violet: #4a5a38;
154
+ --ink: #3a322a;
155
+ --ink-2: #6f6355;
156
+ --ink-3: #93866f;
157
+ --accent: #a25a3c;
158
+ --accent-2: #7d6b3f;
159
+ --accent-3: #c08552;
160
+ --grad: linear-gradient(96deg, #e8c9a0 0%, #c08552 50%, #a25a3c 100%);
161
+ --glow: 0 0 0 1px rgba(162, 90, 60, .38), 0 10px 26px rgba(162, 90, 60, .16);
162
+ --accent-pink: #a25a3c;
163
+ --accent-violet: #7d6b3f;
164
+ --sans: "Lora", Georgia, serif;
128
165
  --disp-weight: 700;
129
- --prose-measure: 72ch;
130
- --radius-shape: 4px;
131
- --wash: radial-gradient(70% 60% at 50% -5%, rgba(122, 90, 42, .1), transparent 65%);
166
+ --prose-measure: 60ch;
167
+ --prose-leading: 1.72;
168
+ --radius-shape: 18px;
169
+ --wash: radial-gradient(60% 50% at 82% -2%, rgba(192, 133, 82, .13), transparent 60%);
170
+ }
171
+ .grove-root[data-grove-theme="journal"][data-theme="dark"] {
172
+ --bg: #191410;
173
+ --panel: #201a15;
174
+ --panel-2: #2a221b;
175
+ --line: rgba(226, 200, 170, .14);
176
+ --line-2: rgba(226, 200, 170, .25);
177
+ --ink: #ede4d6;
178
+ --ink-2: #bdaa94;
179
+ --ink-3: #97876f;
180
+ --accent: #d2956b;
181
+ --accent-2: #a8935c;
182
+ --accent-3: #c08552;
183
+ --grad: linear-gradient(96deg, #d9b285 0%, #d2956b 50%, #a8935c 100%);
184
+ --glow: 0 0 0 1px rgba(210, 149, 107, .4), 0 10px 26px rgba(210, 149, 107, .13);
185
+ --accent-pink: #d2956b;
186
+ --accent-violet: #a8935c;
187
+ --wash: radial-gradient(60% 50% at 82% -2%, rgba(210, 149, 107, .1), transparent 60%);
188
+ }
189
+
190
+ /* — editorial: loud on purpose — a zine, a gig listing, a release archive — */
191
+ .grove-root[data-grove-theme="editorial"] {
192
+ --bg: #0d0d0f;
193
+ --panel: #141417;
194
+ --panel-2: #1b1b20;
195
+ --line: rgba(255, 214, 79, .18);
196
+ --line-2: rgba(255, 214, 79, .34);
197
+ --ink: #f4f1e6;
198
+ --ink-2: #c9c4b2;
199
+ --ink-3: #948f7d;
200
+ --accent: #ffd64f;
201
+ --accent-2: #ff4d2e;
202
+ --accent-3: #37d4a8;
203
+ --grad: linear-gradient(96deg, #ffd64f 0%, #ff4d2e 55%, #37d4a8 100%);
204
+ --glow: 0 0 0 1px rgba(255, 214, 79, .7), 0 0 34px rgba(255, 77, 46, .35);
205
+ --accent-pink: #ff4d2e;
206
+ --accent-violet: #37d4a8;
207
+ --disp-weight: 900;
208
+ --prose-measure: 68ch;
209
+ --prose-leading: 1.58;
210
+ --radius-shape: 0px;
211
+ --wash: radial-gradient(75% 55% at 88% -5%, rgba(255, 77, 46, .16), transparent 60%), radial-gradient(50% 40% at 2% 0%, rgba(55, 212, 168, .12), transparent 55%);
212
+ }
213
+ .grove-root[data-grove-theme="editorial"][data-theme="light"] {
214
+ --bg: #fbfaf6;
215
+ --panel: #ffffff;
216
+ --panel-2: #f1efe6;
217
+ --line: rgba(20, 20, 25, .2);
218
+ --line-2: rgba(20, 20, 25, .38);
219
+ --ink: #17171b;
220
+ --ink-2: #4c4c55;
221
+ --ink-3: #71717b;
222
+ --accent: #c23500;
223
+ --accent-2: #0f7a5f;
224
+ --accent-3: #8a6d00;
225
+ --grad: linear-gradient(96deg, #8a6d00 0%, #c23500 55%, #0f7a5f 100%);
226
+ --glow: 0 0 0 1px rgba(194, 53, 0, .55), 0 10px 24px rgba(194, 53, 0, .18);
227
+ --accent-pink: #c23500;
228
+ --accent-violet: #0f7a5f;
229
+ --wash: radial-gradient(75% 55% at 88% -5%, rgba(194, 53, 0, .08), transparent 60%);
230
+ }
231
+
232
+ /* — the catalogue galleries (R3-311): rows enumerated from the manifest — */
233
+ .gg-gallery {
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 8px;
237
+ }
238
+ .gg-section {
239
+ margin-top: 18px;
240
+ }
241
+ .gg-section h3 {
242
+ font-family: var(--disp);
243
+ font-weight: var(--disp-weight, 700);
244
+ font-size: 19px;
245
+ margin: 0 0 2px;
246
+ }
247
+ .gg-note {
248
+ color: var(--ink-2);
249
+ font-size: 13px;
250
+ margin: 0 0 10px;
251
+ }
252
+ .gg-row {
253
+ display: flex;
254
+ gap: 12px;
255
+ align-items: flex-start;
256
+ padding: 12px 14px;
257
+ border: 1px solid var(--line);
258
+ border-radius: var(--radius-shape, 10px);
259
+ background: var(--panel);
260
+ }
261
+ .gg-swatch {
262
+ flex: 0 0 auto;
263
+ width: 58px;
264
+ height: 38px;
265
+ border-radius: calc(var(--radius-shape, 10px) * 0.7);
266
+ border: 1px solid var(--line-2);
267
+ }
268
+ .gg-body {
269
+ min-width: 0;
270
+ }
271
+ .gg-title {
272
+ display: flex;
273
+ flex-wrap: wrap;
274
+ gap: 8px;
275
+ align-items: baseline;
276
+ font-weight: 600;
277
+ color: var(--ink);
278
+ }
279
+ .gg-title code {
280
+ font-family: var(--mono);
281
+ font-size: 12.5px;
282
+ background: var(--panel-2);
283
+ border: 1px solid var(--line);
284
+ border-radius: 6px;
285
+ padding: 1px 6px;
286
+ }
287
+ .gg-meta {
288
+ font-family: var(--mono);
289
+ font-size: 11px;
290
+ color: var(--ink-3);
291
+ }
292
+ .gg-summary {
293
+ color: var(--ink-2);
294
+ font-size: 13.5px;
295
+ line-height: 1.5;
296
+ margin-top: 3px;
132
297
  }
133
298
 
134
299
  /* ============ SHELL ============ */
@@ -238,6 +403,9 @@
238
403
  color: var(--ink);
239
404
  border-color: var(--accent-2);
240
405
  }
406
+ .icbtn:disabled {
407
+ opacity: .6;
408
+ }
241
409
  .icbtn svg {
242
410
  width: 17px;
243
411
  height: 17px;
@@ -413,6 +581,13 @@
413
581
  transition: .12s;
414
582
  cursor: pointer;
415
583
  }
584
+ button.gs-tree__row {
585
+ width: 100%;
586
+ background: transparent;
587
+ border: 0;
588
+ font: inherit;
589
+ text-align: left;
590
+ }
416
591
  .gs-tree__row:hover {
417
592
  background: var(--panel-2);
418
593
  color: var(--ink);
@@ -613,7 +788,9 @@
613
788
  /* prose */
614
789
  .grove-prose {
615
790
  max-width: var(--prose-measure);
616
- font: var(--body, 400 16px/1.5 var(--sans));
791
+ /* R3-310: the leading is a THEME token (the contract's gap-3 addition) — a dense
792
+ reference look and a roomy notebook differ here without selector changes. */
793
+ font: 400 16px/var(--prose-leading, 1.6) var(--sans);
617
794
  color: var(--ink);
618
795
  font-size: 16.5px;
619
796
  line-height: 1.62;
@@ -1300,6 +1477,37 @@ a.gdl-row {
1300
1477
  display: flex;
1301
1478
  flex-direction: column;
1302
1479
  }
1480
+ /* —— the windowed feed's sentinel + end (R3-314) ——
1481
+ The sentinel is a BUTTON (keyboard-reachable, keyboard-actionable) styled as
1482
+ the skeleton rows it precedes, so the batch arriving below it reads as the
1483
+ same surface continuing. The skeleton bars occupy real height via .sk-line,
1484
+ which is what keeps the boundary shift-free: the box exists before the rows
1485
+ do, and prefers-reduced-motion already stills the shimmer (§.sk rule). */
1486
+ .gdl-sentinel {
1487
+ display: flex;
1488
+ flex-direction: column;
1489
+ align-items: flex-start;
1490
+ gap: 2px;
1491
+ width: 100%;
1492
+ padding: 18px 4px;
1493
+ border: 0;
1494
+ border-bottom: 1px solid var(--line);
1495
+ background: none;
1496
+ text-align: left;
1497
+ cursor: pointer;
1498
+ }
1499
+ .gdl-sentinel:focus-visible {
1500
+ outline: 2px solid var(--accent);
1501
+ outline-offset: -2px;
1502
+ }
1503
+ .gdl-end {
1504
+ margin: 0;
1505
+ padding: 18px 4px;
1506
+ color: var(--ink-3);
1507
+ font-family: var(--mono);
1508
+ font-size: 12px;
1509
+ letter-spacing: 0.06em;
1510
+ }
1303
1511
  .gdl-row {
1304
1512
  display: grid;
1305
1513
  grid-template-columns: 1fr auto;
@@ -1324,6 +1532,33 @@ a.gdl-row {
1324
1532
  letter-spacing: -.01em;
1325
1533
  color: var(--ink);
1326
1534
  }
1535
+
1536
+ /* R3-531 — code spans inside frontmatter prose fields (titles, rows, cards,
1537
+ search hits, entry header). No bare `code` rule reaches these surfaces, so
1538
+ one grouped rule carries the prose-code treatment; the font-size is em-
1539
+ relative so the host's size (an 18px row title, a 52px h1) sets the scale
1540
+ and no line height breaks. */
1541
+ .grove-entry-header h1 code,
1542
+ .gdl-row__t code,
1543
+ .gdl-row__d code,
1544
+ .gdl-card__t code,
1545
+ .gdl-card__d code,
1546
+ .gtl-title code,
1547
+ .gtl-desc code,
1548
+ .grove-bl__t code,
1549
+ .grove-search__row .t code,
1550
+ .gs-tree__row code,
1551
+ .rm-card__t code,
1552
+ .rm-project__t code,
1553
+ .rm-project__d code {
1554
+ font-family: var(--mono);
1555
+ font-size: 0.86em;
1556
+ color: var(--accent-3);
1557
+ background: var(--panel-2);
1558
+ border: 1px solid var(--line);
1559
+ border-radius: var(--r-xs);
1560
+ padding: 1px 6px;
1561
+ }
1327
1562
  .gdl-row__d {
1328
1563
  font-size: 14px;
1329
1564
  color: var(--ink-2);
@@ -1381,6 +1616,25 @@ a.gdl-row {
1381
1616
  inset: 0;
1382
1617
  background: radial-gradient(70% 70% at 75% 15%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%);
1383
1618
  }
1619
+ /* R3-313 — the entry's `cover:` fills the lattice box when present; the lattice
1620
+ stays as the degrade (the box's own background shows through when the image is
1621
+ absent, so nothing shifts layout). */
1622
+ .gdl-card__cover {
1623
+ position: absolute;
1624
+ inset: 0;
1625
+ width: 100%;
1626
+ height: 100%;
1627
+ object-fit: cover;
1628
+ display: block;
1629
+ }
1630
+ .gtl-cover {
1631
+ width: 100%;
1632
+ max-height: 96px;
1633
+ object-fit: cover;
1634
+ border-radius: var(--r-sm);
1635
+ margin-bottom: 6px;
1636
+ display: block;
1637
+ }
1384
1638
  .gdl-card__foot {
1385
1639
  padding: 14px 15px;
1386
1640
  border-top: 1px solid var(--line);
@@ -1511,6 +1765,9 @@ a.gdl-row {
1511
1765
  .btn-primary:hover {
1512
1766
  box-shadow: var(--glow);
1513
1767
  }
1768
+ .btn-primary:disabled {
1769
+ opacity: .6;
1770
+ }
1514
1771
  .btn-ghost {
1515
1772
  display: inline-flex;
1516
1773
  align-items: center;
@@ -1620,6 +1877,14 @@ a.gdl-row {
1620
1877
  border-radius: var(--r-md);
1621
1878
  cursor: pointer;
1622
1879
  }
1880
+ button.grove-search__row {
1881
+ width: 100%;
1882
+ background: transparent;
1883
+ border: 0;
1884
+ font: inherit;
1885
+ color: inherit;
1886
+ text-align: left;
1887
+ }
1623
1888
  .grove-search__row:hover,
1624
1889
  .grove-search__row[data-sel="1"] {
1625
1890
  background: var(--panel-2);
@@ -2287,6 +2552,45 @@ a.gdl-row {
2287
2552
  width: 17px;
2288
2553
  height: 17px;
2289
2554
  }
2555
+ /* The composer's stop control (R3-608): visible while a run streams; the
2556
+ 38px round shape matches the send control and clears the 24px CSS px
2557
+ target floor (2.5.8). */
2558
+ .ga-foot .stop {
2559
+ width: 38px;
2560
+ height: 38px;
2561
+ border-radius: 50%;
2562
+ background: var(--panel-2, var(--bg));
2563
+ border: 1px solid var(--line-2);
2564
+ color: var(--ink);
2565
+ display: flex;
2566
+ align-items: center;
2567
+ justify-content: center;
2568
+ flex: 0 0 auto;
2569
+ cursor: pointer;
2570
+ }
2571
+ .ga-foot .stop svg {
2572
+ width: 15px;
2573
+ height: 15px;
2574
+ }
2575
+ /* The edit-refusal notice (R3-608, 3.3.1): the sentence rendered where the
2576
+ affordance was offered — nav, entry header, 404, agent panel. */
2577
+ .grove-edit-refused,
2578
+ .ga-edit-refused {
2579
+ font: 400 11.5px/1.5 var(--mono);
2580
+ color: var(--ink-2);
2581
+ border: 1px solid var(--line-2);
2582
+ border-radius: var(--r-pill, 999px);
2583
+ padding: 3px 9px;
2584
+ white-space: nowrap;
2585
+ }
2586
+ /* In the sticky nav the sentence must reflow, not push the cluster wide
2587
+ (1.4.10): it ellipsizes against the viewport's width and carries the full
2588
+ text in its title. */
2589
+ .grove-nav .grove-edit-refused {
2590
+ max-width: 40vw;
2591
+ overflow: hidden;
2592
+ text-overflow: ellipsis;
2593
+ }
2290
2594
  .ga-foot__hand {
2291
2595
  display: flex;
2292
2596
  align-items: center;
@@ -2294,53 +2598,107 @@ a.gdl-row {
2294
2598
  font-size: 11.5px;
2295
2599
  color: var(--ink-3);
2296
2600
  }
2297
- .ga-foot__hand a {
2601
+ .ga-foot__hand a,
2602
+ .ga-foot__hand button {
2298
2603
  display: inline-flex;
2299
2604
  align-items: center;
2300
2605
  gap: 5px;
2301
2606
  color: var(--ink-2);
2607
+ background: transparent;
2608
+ border: 0;
2609
+ padding: 0;
2610
+ font: inherit;
2611
+ cursor: pointer;
2302
2612
  }
2303
- .ga-foot__hand a:hover {
2613
+ .ga-foot__hand a:hover,
2614
+ .ga-foot__hand button:hover {
2304
2615
  color: var(--accent);
2305
2616
  }
2306
- .ga-foot__hand a svg {
2617
+ .ga-foot__hand a svg,
2618
+ .ga-foot__hand button svg {
2307
2619
  width: 12px;
2308
2620
  height: 12px;
2309
2621
  }
2310
2622
 
2311
2623
  /* agent banner states (no-key / forbidden / read-only) */
2312
- .ga-banner {
2313
- margin: 0;
2314
- border-radius: 0;
2315
- border: 0;
2624
+ /* The reach card (GROVE_AGENT_SPEC §6) — the session envelope, one row per
2625
+ capability, cause attached to every ✗. */
2626
+ .ga-reach {
2316
2627
  border-bottom: 1px solid var(--line);
2317
2628
  background: var(--panel-2);
2318
- padding: 14px 16px;
2629
+ padding: 10px 16px;
2319
2630
  display: flex;
2320
- gap: 11px;
2321
- align-items: flex-start;
2631
+ flex-direction: column;
2632
+ gap: 6px;
2322
2633
  }
2323
- .ga-banner svg {
2324
- width: 18px;
2325
- height: 18px;
2326
- color: var(--accent-3);
2634
+ .ga-reach__row {
2635
+ display: flex;
2636
+ align-items: baseline;
2637
+ gap: 8px;
2638
+ font-size: 12.5px;
2639
+ line-height: 1.45;
2640
+ }
2641
+ .ga-reach__mark {
2327
2642
  flex: 0 0 auto;
2328
- margin-top: 1px;
2643
+ width: 14px;
2644
+ text-align: center;
2645
+ font-family: var(--mono);
2646
+ font-size: 11px;
2329
2647
  }
2330
- .ga-banner__t {
2331
- font-weight: 600;
2332
- font-size: 13.5px;
2648
+ .ga-reach__row--ok .ga-reach__mark {
2649
+ color: var(--accent);
2650
+ }
2651
+ .ga-reach__row--blocked .ga-reach__mark {
2652
+ color: var(--ink-3);
2653
+ }
2654
+ .ga-reach__row--neutral .ga-reach__mark {
2655
+ color: var(--ink-3);
2656
+ }
2657
+ .ga-reach__label {
2333
2658
  color: var(--ink);
2334
- margin-bottom: 3px;
2659
+ font-weight: 500;
2335
2660
  }
2336
- .ga-banner__d {
2337
- font-size: 12.5px;
2661
+ .ga-reach__row--blocked .ga-reach__label {
2662
+ color: var(--ink-2);
2663
+ font-weight: 400;
2664
+ }
2665
+ .ga-reach__row--neutral .ga-reach__label {
2666
+ color: var(--ink-3);
2667
+ font-weight: 400;
2668
+ }
2669
+ .ga-reach__cause {
2338
2670
  color: var(--ink-2);
2671
+ }
2672
+ .ga-egress {
2673
+ margin: 0;
2674
+ padding: 8px 16px;
2675
+ border-bottom: 1px solid var(--line);
2676
+ font-size: 12px;
2339
2677
  line-height: 1.5;
2678
+ color: var(--ink-2);
2340
2679
  }
2341
- .ga-banner__d a {
2342
- color: var(--accent);
2343
- border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
2680
+ .ga-toast {
2681
+ margin: 10px 16px 0;
2682
+ border: 1px solid color-mix(in srgb, var(--accent-3) 45%, var(--line));
2683
+ border-radius: var(--r-md);
2684
+ background: color-mix(in srgb, var(--accent-3) 8%, var(--panel));
2685
+ padding: 9px 12px;
2686
+ font-size: 12.5px;
2687
+ color: var(--ink-2);
2688
+ }
2689
+ .ga-activity {
2690
+ display: flex;
2691
+ align-items: center;
2692
+ gap: 7px;
2693
+ font-family: var(--mono);
2694
+ font-size: 11.5px;
2695
+ color: var(--ink-3);
2696
+ padding: 2px 2px;
2697
+ }
2698
+ .ga-activity svg {
2699
+ width: 12px;
2700
+ height: 12px;
2701
+ flex: 0 0 auto;
2344
2702
  }
2345
2703
 
2346
2704
  /* ============ EXTENSIBILITY VIEWS ============ */
@@ -2554,6 +2912,48 @@ a.gdl-row {
2554
2912
  margin: 0;
2555
2913
  max-width: 100%;
2556
2914
  }
2915
+ /* —— the hero root starter's landing band (R3-309 bucket A) ——
2916
+ Full-bleed over TOP navigation: the band sits under .grove-nav (the base,
2917
+ top arrangement of .grove-shell) and spans the viewport, with the prose
2918
+ centred on the measure. No image element — the picture slot is the
2919
+ link-graph lattice / gradient until `cover:` lands (R3-313), so the band
2920
+ composes from tokens only, exactly like every other starter surface. */
2921
+ .grove-hero {
2922
+ grid-column: 1 / -1;
2923
+ min-height: clamp(300px, 52vh, 480px);
2924
+ display: flex;
2925
+ flex-direction: column;
2926
+ justify-content: flex-end;
2927
+ gap: 14px;
2928
+ padding: 48px clamp(20px, 6vw, 96px) 56px;
2929
+ background-image: var(--grad), var(--wash);
2930
+ background-blend-mode: soft-light;
2931
+ background-size: cover;
2932
+ background-position: center;
2933
+ border-bottom: 1px solid var(--line-2);
2934
+ }
2935
+ .grove-hero__eyebrow {
2936
+ margin: 0;
2937
+ font-family: var(--mono);
2938
+ font-size: 12px;
2939
+ letter-spacing: 0.18em;
2940
+ text-transform: uppercase;
2941
+ color: var(--ink-2);
2942
+ }
2943
+ .grove-hero h1 {
2944
+ margin: 0;
2945
+ max-width: 18ch;
2946
+ font-family: var(--disp);
2947
+ font-weight: var(--disp-weight);
2948
+ font-size: clamp(34px, 6vw, 64px);
2949
+ line-height: 1.05;
2950
+ }
2951
+ .grove-hero__lede {
2952
+ margin: 0;
2953
+ max-width: 52ch;
2954
+ color: var(--ink-2);
2955
+ font-size: 17px;
2956
+ }
2557
2957
  .grove-section__hero {
2558
2958
  margin-bottom: 30px;
2559
2959
  padding-bottom: 22px;