@immediately-run/grove 0.1.2 → 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 (77) hide show
  1. package/README.md +43 -115
  2. package/llms.txt +5 -2
  3. package/package.json +10 -8
  4. package/src/App.tsx +11 -7
  5. package/src/GroveApp.css +484 -152
  6. package/src/GroveWiki.tsx +105 -30
  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 +23 -12
  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 -150
  21. package/src/components/GroveNav.test.tsx +145 -5
  22. package/src/components/GroveNav.tsx +51 -8
  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 +18 -3
  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 +16 -7
  40. package/src/hooks/{useCorpusMetadata.ts → useBundleMetadata.ts} +6 -6
  41. package/src/hooks/useContentComponents.ts +1 -1
  42. package/src/hooks/useEditAffordance.ts +17 -4
  43. package/src/hooks/useOverlayFocusDismiss.test.tsx +139 -0
  44. package/src/hooks/useOverlayFocusDismiss.ts +118 -0
  45. package/src/hooks/useScrollReset.test.tsx +132 -0
  46. package/src/hooks/useScrollReset.ts +52 -0
  47. package/src/index.css +9 -2
  48. package/src/lib/agentPrompt.test.ts +96 -0
  49. package/src/lib/agentPrompt.ts +86 -0
  50. package/src/lib/agentTools.test.ts +115 -0
  51. package/src/lib/agentTools.ts +132 -0
  52. package/src/lib/agentTranscript.ts +50 -0
  53. package/src/lib/assetPath.test.ts +36 -0
  54. package/src/lib/assetPath.ts +42 -0
  55. package/src/lib/collectionCalls.test.ts +69 -0
  56. package/src/lib/content.test.ts +10 -2
  57. package/src/lib/content.ts +7 -0
  58. package/src/lib/contentStylesheet.test.ts +80 -0
  59. package/src/lib/contentStylesheet.ts +103 -0
  60. package/src/lib/corpusScan.test.ts +37 -3
  61. package/src/lib/corpusScan.ts +17 -2
  62. package/src/lib/inlineProse.parity.test.ts +52 -0
  63. package/src/lib/layout.ts +29 -0
  64. package/src/lib/pageVariants.test.tsx +87 -0
  65. package/src/lib/queries.test.ts +33 -1
  66. package/src/lib/queries.ts +33 -1
  67. package/src/lib/reachCard.test.ts +94 -0
  68. package/src/lib/reachCard.ts +112 -0
  69. package/src/lib/shell.ts +7 -0
  70. package/src/lib/starterSweep.test.tsx +160 -0
  71. package/src/lib/starterSweep.ts +97 -0
  72. package/src/lib/themeAssets.test.ts +135 -0
  73. package/src/lib/themeAssets.ts +143 -0
  74. package/src/lib/themeSelection.test.ts +2 -2
  75. package/src/mdxComponents.ts +4 -0
  76. package/viewer-manifest.schema.json +37 -0
  77. package/viewer.manifest.json +132 -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,137 +91,209 @@
66
91
  --accent-violet: #9a45e6;
67
92
  }
68
93
 
69
- /* —— alternate themes: identical DOM, CSS-only re-skin —— */
70
- /* R3-308: every theme ships BOTH polarities. The bare [data-grove-theme] block is
71
- the theme's PREFERRED polarity (the one it opens in absent other opinions); the
72
- paired block with an explicit [data-theme] is the other one. GroveWiki always
73
- emits data-theme, so a wiki follows the reader or the host into either polarity
74
- of ANY theme — alternates are no longer single-polarity by construction. */
75
- .grove-root[data-grove-theme="pixies"] {
76
- --bg: #0c0410;
77
- --panel: #1a0a1e;
78
- --panel-2: #260f2c;
79
- --line: rgba(255, 120, 200, .16);
80
- --line-2: rgba(255, 120, 200, .3);
81
- --ink: #fce9f6;
82
- --ink-2: #d79bc9;
83
- --ink-3: #9c6a8e;
84
- --accent: #ff2d8e;
85
- --accent-2: #7b2ff7;
86
- --accent-3: #ffd23f;
87
- --grad: linear-gradient(96deg, #ffe14d 0%, #ff2d8e 50%, #9d29ff 100%);
88
- --glow: 0 0 0 1px rgba(255, 45, 142, .6), 0 0 30px rgba(255, 45, 142, .4);
89
- --accent-pink: #ff2d8e;
90
- --accent-violet: #9d29ff;
91
- --disp-weight: 900;
92
- --prose-measure: 66ch;
93
- --radius-shape: 8px;
94
- --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%);
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%);
95
145
  }
96
- .grove-root[data-grove-theme="pixies"][data-theme="light"] {
97
- --bg: #fdf6fb;
98
- --panel: #ffffff;
99
- --panel-2: #f9edf7;
100
- --line: rgba(123, 47, 247, .18);
101
- --line-2: rgba(123, 47, 247, .32);
102
- --ink: #2c1030;
103
- --ink-2: #6d3f76;
104
- --ink-3: #7d5f84;
105
- --accent: #d10f74;
106
- --accent-2: #6b21c9;
107
- --accent-3: #8a5a00;
108
- --grad: linear-gradient(96deg, #ffe14d 0%, #ff2d8e 50%, #9d29ff 100%);
109
- --glow: 0 0 0 1px rgba(209, 15, 116, .35), 0 8px 22px rgba(209, 15, 116, .18);
110
- --accent-pink: #d10f74;
111
- --accent-violet: #6b21c9;
112
- --disp-weight: 900;
113
- --prose-measure: 66ch;
114
- --radius-shape: 8px;
115
- --wash: radial-gradient(70% 55% at 85% -5%, rgba(209, 15, 116, .08), transparent 60%), radial-gradient(55% 45% at 4% 2%, rgba(107, 33, 201, .06), transparent 60%);
116
- }
117
- .grove-root[data-grove-theme="family"] {
118
- --bg: #faf6f1;
119
- --panel: #fffdfa;
120
- --panel-2: #f4ece2;
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;
121
152
  --line: rgba(120, 90, 60, .16);
122
153
  --line-2: rgba(120, 90, 60, .28);
123
- --ink: #3b342c;
124
- --ink-2: #6b5f51;
125
- --ink-3: #a89c8c;
126
- --accent: #c8744f;
127
- --accent-2: #9a8f5e;
128
- --accent-3: #d9a06a;
129
- --grad: linear-gradient(96deg, #f3cf9a 0%, #e09a6a 50%, #c8744f 100%);
130
- --glow: 0 0 0 1px rgba(200, 116, 79, .4), 0 10px 26px rgba(200, 116, 79, .2);
131
- --accent-pink: #c8744f;
132
- --accent-violet: #9a8f5e;
133
- --disp-weight: 700;
134
- --prose-measure: 64ch;
135
- --radius-shape: 20px;
136
- --wash: radial-gradient(60% 50% at 82% -2%, rgba(224, 154, 106, .14), transparent 60%);
137
- }
138
- .grove-root[data-grove-theme="family"][data-theme="dark"] {
139
- --bg: #241d16;
140
- --panel: #2d251c;
141
- --panel-2: #372c21;
142
- --line: rgba(224, 154, 106, .16);
143
- --line-2: rgba(224, 154, 106, .3);
144
- --ink: #f2e9de;
145
- --ink-2: #c9b8a5;
146
- --ink-3: #a2937f;
147
- --accent: #e09a6a;
148
- --accent-2: #c8a06a;
149
- --accent-3: #d9b98a;
150
- --grad: linear-gradient(96deg, #f3cf9a 0%, #e09a6a 50%, #c8744f 100%);
151
- --glow: 0 0 0 1px rgba(224, 154, 106, .4), 0 10px 26px rgba(224, 154, 106, .2);
152
- --accent-pink: #e09a6a;
153
- --accent-violet: #c8a06a;
154
- --disp-weight: 700;
155
- --prose-measure: 64ch;
156
- --radius-shape: 20px;
157
- --wash: radial-gradient(60% 50% at 82% -2%, rgba(224, 154, 106, .1), transparent 60%);
158
- }
159
- .grove-root[data-grove-theme="lotr"] {
160
- --bg: #ece2cc;
161
- --panel: #f4ebd6;
162
- --panel-2: #e4d8ba;
163
- --line: rgba(90, 70, 40, .22);
164
- --line-2: rgba(90, 70, 40, .36);
165
- --ink: #322712;
166
- --ink-2: #6a5c40;
167
- --ink-3: #998a68;
168
- --accent: #7a5a2a;
169
- --accent-2: #4a5a38;
170
- --accent-3: #9a7a3a;
171
- --grad: linear-gradient(96deg, #b89a56 0%, #8a6a36 50%, #4a5a38 100%);
172
- --glow: 0 0 0 1px rgba(122, 90, 42, .4), 0 8px 20px rgba(122, 90, 42, .22);
173
- --accent-pink: #7a5a2a;
174
- --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;
175
165
  --disp-weight: 700;
176
- --prose-measure: 72ch;
177
- --radius-shape: 4px;
178
- --wash: radial-gradient(70% 60% at 50% -5%, rgba(122, 90, 42, .1), transparent 65%);
179
- }
180
- .grove-root[data-grove-theme="lotr"][data-theme="dark"] {
181
- --bg: #1d1810;
182
- --panel: #262014;
183
- --panel-2: #302818;
184
- --line: rgba(184, 154, 86, .18);
185
- --line-2: rgba(184, 154, 86, .32);
186
- --ink: #ede2c8;
187
- --ink-2: #c4b48e;
188
- --ink-3: #9a8a6c;
189
- --accent: #c9a45c;
190
- --accent-2: #8aa468;
191
- --accent-3: #b08a48;
192
- --grad: linear-gradient(96deg, #b89a56 0%, #8a6a36 50%, #4a5a38 100%);
193
- --glow: 0 0 0 1px rgba(201, 164, 92, .4), 0 8px 20px rgba(201, 164, 92, .22);
194
- --accent-pink: #c9a45c;
195
- --accent-violet: #8aa468;
196
- --disp-weight: 700;
197
- --prose-measure: 72ch;
198
- --radius-shape: 4px;
199
- --wash: radial-gradient(70% 60% at 50% -5%, rgba(201, 164, 92, .08), 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;
200
297
  }
201
298
 
202
299
  /* ============ SHELL ============ */
@@ -306,6 +403,9 @@
306
403
  color: var(--ink);
307
404
  border-color: var(--accent-2);
308
405
  }
406
+ .icbtn:disabled {
407
+ opacity: .6;
408
+ }
309
409
  .icbtn svg {
310
410
  width: 17px;
311
411
  height: 17px;
@@ -481,6 +581,13 @@
481
581
  transition: .12s;
482
582
  cursor: pointer;
483
583
  }
584
+ button.gs-tree__row {
585
+ width: 100%;
586
+ background: transparent;
587
+ border: 0;
588
+ font: inherit;
589
+ text-align: left;
590
+ }
484
591
  .gs-tree__row:hover {
485
592
  background: var(--panel-2);
486
593
  color: var(--ink);
@@ -681,7 +788,9 @@
681
788
  /* prose */
682
789
  .grove-prose {
683
790
  max-width: var(--prose-measure);
684
- 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);
685
794
  color: var(--ink);
686
795
  font-size: 16.5px;
687
796
  line-height: 1.62;
@@ -1368,6 +1477,37 @@ a.gdl-row {
1368
1477
  display: flex;
1369
1478
  flex-direction: column;
1370
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
+ }
1371
1511
  .gdl-row {
1372
1512
  display: grid;
1373
1513
  grid-template-columns: 1fr auto;
@@ -1392,6 +1532,33 @@ a.gdl-row {
1392
1532
  letter-spacing: -.01em;
1393
1533
  color: var(--ink);
1394
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
+ }
1395
1562
  .gdl-row__d {
1396
1563
  font-size: 14px;
1397
1564
  color: var(--ink-2);
@@ -1449,6 +1616,25 @@ a.gdl-row {
1449
1616
  inset: 0;
1450
1617
  background: radial-gradient(70% 70% at 75% 15%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%);
1451
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
+ }
1452
1638
  .gdl-card__foot {
1453
1639
  padding: 14px 15px;
1454
1640
  border-top: 1px solid var(--line);
@@ -1579,6 +1765,9 @@ a.gdl-row {
1579
1765
  .btn-primary:hover {
1580
1766
  box-shadow: var(--glow);
1581
1767
  }
1768
+ .btn-primary:disabled {
1769
+ opacity: .6;
1770
+ }
1582
1771
  .btn-ghost {
1583
1772
  display: inline-flex;
1584
1773
  align-items: center;
@@ -1688,6 +1877,14 @@ a.gdl-row {
1688
1877
  border-radius: var(--r-md);
1689
1878
  cursor: pointer;
1690
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
+ }
1691
1888
  .grove-search__row:hover,
1692
1889
  .grove-search__row[data-sel="1"] {
1693
1890
  background: var(--panel-2);
@@ -2355,6 +2552,45 @@ a.gdl-row {
2355
2552
  width: 17px;
2356
2553
  height: 17px;
2357
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
+ }
2358
2594
  .ga-foot__hand {
2359
2595
  display: flex;
2360
2596
  align-items: center;
@@ -2362,53 +2598,107 @@ a.gdl-row {
2362
2598
  font-size: 11.5px;
2363
2599
  color: var(--ink-3);
2364
2600
  }
2365
- .ga-foot__hand a {
2601
+ .ga-foot__hand a,
2602
+ .ga-foot__hand button {
2366
2603
  display: inline-flex;
2367
2604
  align-items: center;
2368
2605
  gap: 5px;
2369
2606
  color: var(--ink-2);
2607
+ background: transparent;
2608
+ border: 0;
2609
+ padding: 0;
2610
+ font: inherit;
2611
+ cursor: pointer;
2370
2612
  }
2371
- .ga-foot__hand a:hover {
2613
+ .ga-foot__hand a:hover,
2614
+ .ga-foot__hand button:hover {
2372
2615
  color: var(--accent);
2373
2616
  }
2374
- .ga-foot__hand a svg {
2617
+ .ga-foot__hand a svg,
2618
+ .ga-foot__hand button svg {
2375
2619
  width: 12px;
2376
2620
  height: 12px;
2377
2621
  }
2378
2622
 
2379
2623
  /* agent banner states (no-key / forbidden / read-only) */
2380
- .ga-banner {
2381
- margin: 0;
2382
- border-radius: 0;
2383
- 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 {
2384
2627
  border-bottom: 1px solid var(--line);
2385
2628
  background: var(--panel-2);
2386
- padding: 14px 16px;
2629
+ padding: 10px 16px;
2387
2630
  display: flex;
2388
- gap: 11px;
2389
- align-items: flex-start;
2631
+ flex-direction: column;
2632
+ gap: 6px;
2390
2633
  }
2391
- .ga-banner svg {
2392
- width: 18px;
2393
- height: 18px;
2394
- 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 {
2395
2642
  flex: 0 0 auto;
2396
- margin-top: 1px;
2643
+ width: 14px;
2644
+ text-align: center;
2645
+ font-family: var(--mono);
2646
+ font-size: 11px;
2397
2647
  }
2398
- .ga-banner__t {
2399
- font-weight: 600;
2400
- 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 {
2401
2658
  color: var(--ink);
2402
- margin-bottom: 3px;
2659
+ font-weight: 500;
2403
2660
  }
2404
- .ga-banner__d {
2405
- font-size: 12.5px;
2661
+ .ga-reach__row--blocked .ga-reach__label {
2406
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 {
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;
2407
2677
  line-height: 1.5;
2678
+ color: var(--ink-2);
2408
2679
  }
2409
- .ga-banner__d a {
2410
- color: var(--accent);
2411
- 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;
2412
2702
  }
2413
2703
 
2414
2704
  /* ============ EXTENSIBILITY VIEWS ============ */
@@ -2622,6 +2912,48 @@ a.gdl-row {
2622
2912
  margin: 0;
2623
2913
  max-width: 100%;
2624
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
+ }
2625
2957
  .grove-section__hero {
2626
2958
  margin-bottom: 30px;
2627
2959
  padding-bottom: 22px;