@immediately-run/grove 0.1.1

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 (92) hide show
  1. package/README.md +122 -0
  2. package/docs/ENGINE_BOUNDARY.md +233 -0
  3. package/llms.txt +115 -0
  4. package/package.json +82 -0
  5. package/src/App.tsx +131 -0
  6. package/src/GroveApp.css +2774 -0
  7. package/src/GroveWiki.tsx +386 -0
  8. package/src/components/AssetImage.tsx +58 -0
  9. package/src/components/Backlinks.tsx +104 -0
  10. package/src/components/Callout.tsx +26 -0
  11. package/src/components/ChildPages.tsx +40 -0
  12. package/src/components/DefaultLayout.tsx +27 -0
  13. package/src/components/Directory.tsx +65 -0
  14. package/src/components/DirectoryList.test.tsx +275 -0
  15. package/src/components/DirectoryList.tsx +189 -0
  16. package/src/components/DirectoryView.tsx +68 -0
  17. package/src/components/DocList.tsx +109 -0
  18. package/src/components/DocsByTag.tsx +13 -0
  19. package/src/components/Drawer.tsx +45 -0
  20. package/src/components/EntryHeader.tsx +51 -0
  21. package/src/components/FamilyTree.tsx +95 -0
  22. package/src/components/GroveAgent.tsx +264 -0
  23. package/src/components/GroveFooter.tsx +20 -0
  24. package/src/components/GroveNav.tsx +102 -0
  25. package/src/components/Icon.tsx +56 -0
  26. package/src/components/Infobox.tsx +19 -0
  27. package/src/components/Kbd.tsx +10 -0
  28. package/src/components/KeyValue.tsx +32 -0
  29. package/src/components/Lede.tsx +6 -0
  30. package/src/components/More.tsx +10 -0
  31. package/src/components/Outlet.tsx +11 -0
  32. package/src/components/PageMeta.tsx +24 -0
  33. package/src/components/PageView.tsx +98 -0
  34. package/src/components/Quote.tsx +36 -0
  35. package/src/components/RecentlyUpdated.tsx +6 -0
  36. package/src/components/SafeEntryBody.tsx +72 -0
  37. package/src/components/SafeLayout.tsx +35 -0
  38. package/src/components/ScrollToFragment.tsx +63 -0
  39. package/src/components/Search.tsx +127 -0
  40. package/src/components/Sidebar.tsx +118 -0
  41. package/src/components/TableOfContents.test.tsx +163 -0
  42. package/src/components/TableOfContents.tsx +101 -0
  43. package/src/components/TagCloud.tsx +46 -0
  44. package/src/components/TagList.tsx +31 -0
  45. package/src/components/Timeline.tsx +55 -0
  46. package/src/components/Toc.tsx +14 -0
  47. package/src/components/WikiLink.tsx +112 -0
  48. package/src/data/themes.ts +14 -0
  49. package/src/devfs.d.ts +4 -0
  50. package/src/hooks/useContentComponents.ts +122 -0
  51. package/src/hooks/useCorpusMetadata.ts +43 -0
  52. package/src/hooks/useDirectoryListing.ts +56 -0
  53. package/src/hooks/useHeadings.ts +96 -0
  54. package/src/hooks/useOpenWikiBoot.ts +95 -0
  55. package/src/index.css +120 -0
  56. package/src/lib/compose.test.ts +92 -0
  57. package/src/lib/compose.ts +99 -0
  58. package/src/lib/content.test.ts +269 -0
  59. package/src/lib/content.ts +267 -0
  60. package/src/lib/contentRoot.ts +61 -0
  61. package/src/lib/corpusComponents.test.ts +101 -0
  62. package/src/lib/corpusComponents.ts +117 -0
  63. package/src/lib/corpusScan.test.ts +157 -0
  64. package/src/lib/corpusScan.ts +105 -0
  65. package/src/lib/directory.test.ts +216 -0
  66. package/src/lib/directory.ts +262 -0
  67. package/src/lib/fragment.test.ts +88 -0
  68. package/src/lib/fragment.ts +55 -0
  69. package/src/lib/frontmatter.ts +26 -0
  70. package/src/lib/layout.ts +84 -0
  71. package/src/lib/openWiki.test.ts +216 -0
  72. package/src/lib/openWiki.ts +84 -0
  73. package/src/lib/queries.test.ts +74 -0
  74. package/src/lib/queries.ts +84 -0
  75. package/src/lib/safeIntrinsics.test.tsx +99 -0
  76. package/src/lib/safeIntrinsics.tsx +77 -0
  77. package/src/lib/safeRender.test.ts +359 -0
  78. package/src/lib/safeSources.ts +25 -0
  79. package/src/lib/shell.ts +71 -0
  80. package/src/lib/sourceCache.test.ts +66 -0
  81. package/src/lib/sourceCache.ts +42 -0
  82. package/src/lib/tocScroll.test.ts +71 -0
  83. package/src/lib/tocScroll.ts +93 -0
  84. package/src/lib/wiki.test.ts +194 -0
  85. package/src/lib/wiki.ts +175 -0
  86. package/src/lib.ts +54 -0
  87. package/src/main.tsx +19 -0
  88. package/src/mdx.d.ts +9 -0
  89. package/src/mdxComponents.ts +89 -0
  90. package/src/test/setup.ts +19 -0
  91. package/viewer-manifest.schema.json +62 -0
  92. package/viewer.manifest.json +267 -0
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # immediately.run — starter template
2
+
3
+ A ready-to-run starter for building apps on
4
+ [immediately.run](https://immediately.run): React + TypeScript + Vite, wired to
5
+ the brand design system, with the project layout immediately.run expects.
6
+
7
+ ## Try it instantly
8
+
9
+ Try this template on [immediately.run](https://immediately.run/present/github/immediately-run/new-project-template/main/files/src/App.tsx)
10
+
11
+ > Using this as a starting point for your own app? After you push to your repo,
12
+ > update the link above to
13
+ > `https://immediately.run/present/github/<owner>/<repo>/<ref>/files/src/App.tsx`.
14
+
15
+ ## Use this template
16
+
17
+ 1. Create a new repo from this template (or copy the files).
18
+ 2. `npm install`
19
+ 3. `npm run dev` and start editing `src/App.tsx`.
20
+ 4. Push to GitHub and open it on immediately.run with the link above.
21
+
22
+ ## Fast loading on immediately.run (auto-cache)
23
+
24
+ immediately.run normally reads your sources from the GitHub API, which is slow
25
+ and rate-limited for anonymous visitors. This template ships a GitHub Action
26
+ ([`.github/workflows/cache.yml`](./.github/workflows/cache.yml)) that, on every
27
+ push to `main`, builds a pre-cached zip of your repo and publishes it to your
28
+ repo's **own GitHub Pages**. immediately.run finds it automatically at
29
+ `https://<owner>.github.io/<repo>/cached_repositories/main.zip` and loads from
30
+ there — falling back to the API if it's missing.
31
+
32
+ The cache also embeds a manifest sidecar, so visitors can push edits back to
33
+ GitHub even when the app was loaded from the zip.
34
+
35
+ **Setup:** push to `main` and the workflow does the rest. When the immediately-run
36
+ org's **deploy GitHub App** has **Pages: write** + **Administration: write** and its
37
+ `DEPLOY_APP_ID` / `DEPLOY_APP_PRIVATE_KEY` are org secrets, the workflow **enables
38
+ Pages for you** on the first run — no manual step. Otherwise, do it once:
39
+ **Settings → Pages → Build and deployment → Source: GitHub Actions**. (The cache may
40
+ lag a push by up to ~10 minutes of GitHub Pages CDN caching.)
41
+
42
+ ### Always run the newest commit
43
+
44
+ By default the cached version is served even if it's a few minutes behind
45
+ `main`. If your app must always reflect the very latest commit, add this to
46
+ `package.json`:
47
+
48
+ ```jsonc
49
+ {
50
+ "immediately.run": {
51
+ "requireLatest": true
52
+ }
53
+ }
54
+ ```
55
+
56
+ immediately.run still boots instantly from the cache, then checks in the
57
+ background (one API request) whether the cache is current and, if not, reloads
58
+ from GitHub.
59
+
60
+ ## How it's organized
61
+
62
+ immediately.run renders the **default export of `src/App.tsx`** — that's the
63
+ entry point, not `main.tsx`.
64
+
65
+ ```
66
+ src/
67
+ main.tsx # local vite dev/build entry only — immediately.run IGNORES this
68
+ App.tsx # ROOT: default export + imports the global CSS
69
+ index.css # fonts, design tokens (dark + light), resets
70
+ App.css # layout + component styles
71
+ mdx.d.ts # type shim so `import X from './x.mdx'` works
72
+ components/ # one default-exported React component per file
73
+ data/ # typed data arrays (NO components/JSX here)
74
+ hooks/ # custom hooks (NO components here)
75
+ assets/ # images you import, e.g. import logo from './assets/logo.png'
76
+ ```
77
+
78
+ The included page shows the core patterns: a data array mapped to cards
79
+ (`data/features.ts` → `components/Features.tsx`), a custom hook
80
+ (`hooks/useTheme.ts` → `components/ThemeSwitch.tsx`), and local React state
81
+ (`components/Counter.tsx`).
82
+
83
+ ## Filesystem access (`fs`)
84
+
85
+ immediately.run apps can read and write a filesystem by importing `fs` (async
86
+ only — `fs.promises.*` and callback style). This template has local-dev support
87
+ for it built in via [`@immediately-run/dev-fs`](https://github.com/immediately-run/dev-fs),
88
+ a Vite plugin (already wired into `vite.config.ts`) that bridges the same
89
+ filesystem to your real local disk during `vite dev`. See that repo for the
90
+ supported API and details.
91
+
92
+ ```ts
93
+ import fs from 'fs'
94
+
95
+ await fs.promises.writeFile('/data/notes.txt', 'hello', 'utf8')
96
+ const text = await fs.promises.readFile('/data/notes.txt', 'utf8')
97
+ ```
98
+
99
+ `main.tsx` runs a one-off round-trip smoke test in dev — check the browser
100
+ console for the `[dev-fs]` group, and delete it freely.
101
+
102
+ ## The rules that keep it working on immediately.run
103
+
104
+ See [`CLAUDE.md`](./CLAUDE.md) for the full list. The essentials:
105
+
106
+ - **Global CSS is imported from `App.tsx`, never only from `main.tsx`.**
107
+ - **A file that exports a component exports *only* components** — data, consts,
108
+ and helpers go in `data/`, `hooks/`, or `lib/`. `npm run lint` enforces this.
109
+ - **Pull colors, fonts, radii, and shadows from the tokens in `index.css`**
110
+ rather than hard-coding values.
111
+
112
+ ## Develop
113
+
114
+ Requires Node.js 20.19+ or 22.12+.
115
+
116
+ ```bash
117
+ npm install
118
+ npm run dev # local dev server
119
+ npm run build # tsc -b && vite build — must pass with no type errors
120
+ npm run lint # eslint — enforces the React Fast Refresh / HMR rule
121
+ npm run preview # serve the production build
122
+ ```
@@ -0,0 +1,233 @@
1
+ # The Grove engine boundary — what lives here, what lives in a corpus
2
+
3
+ **Status:** decision record · **Recorded:** 2026-08-13 · **Item:** R3-262
4
+
5
+ This repo is the **canonical Grove engine**. This document records why, what belongs in it,
6
+ and what deliberately does not — so the fork that exists downstream is a known specialization
7
+ rather than drift nobody is tracking. It exists because the drift already happened once: see
8
+ §4.
9
+
10
+ ## 1. Canonical home — this repo
11
+
12
+ `immediately-run/grove` is the engine. The `immediately-run/docs` wiki carries a **fork** of
13
+ it at its own repo root, and that fork is a recorded specialization, not a second engine.
14
+
15
+ The reason is dispatch. `REPO_CONTENT_DISPATCH_SPEC` resolves a content repo's
16
+ `opensWith: { task: 'open-wiki' }` marker through the binding registry to **a Grove repo**,
17
+ which then renders that repo's content. Self-dispatch is refused — the viewer must be a
18
+ *different program* than the content it renders — so the engine has to be a repo of its own.
19
+ There is no arrangement where "the docs wiki" is the thing a stranger's content dispatches to.
20
+
21
+ The reason the fork exists anyway is the kernel: `APP_ROOT` is hard-coded to `/app` (the repo
22
+ root) with no subdirectory anchoring (`sandbox/src/fsLayout.ts`), so the bare
23
+ `github/immediately-run/docs/main` URL only boots if the engine sits at the *docs* repo root.
24
+ That constraint is real and unresolved; it is why the fork is tolerated rather than deleted.
25
+
26
+ **The follow-through, not yet done:** once a viewer can render content from a mount
27
+ (R3-168/R3-169), `docs` becomes an ordinary content corpus dispatched to this engine and the
28
+ fork can go. Until then, an engine change lands **here first** and is ported to `docs`.
29
+
30
+ ## 1a. What Grove IS (R3-280, recorded 2026-08-19)
31
+
32
+ Packaging the engine as a library forced the question, so record the answer: **Grove is a
33
+ (plugin-extensible) kit of React components, prebuilt layouts and themes that together make
34
+ a wiki easy to build and good-looking by default — not a wiki engine in the traditional
35
+ sense.**
36
+
37
+ Most of what a traditional wiki engine owns is not here, and should not be. Routing
38
+ (`Routes`/`Route`, `sandboxPath`), MDX compilation, the frontmatter metadata index and its
39
+ sidecar, link spaces (`corpusRoot` + `$fs:`), `Include`, and heading anchors are all
40
+ **sandbox + SDK**, because they are cross-cutting concerns of *every* immediately.run app,
41
+ not of wikis. What is left for Grove to own — and it is a real thing to own — is the
42
+ component vocabulary, the chrome, the layout chain, the themes, and the defaults that make
43
+ the result look considered without anyone tuning it.
44
+
45
+ Future scope stays inside that boundary. Author-facing collaboration features (Google-Docs
46
+ style comment threads between authors, say) belong here because they are wiki-shaped
47
+ presentation and interaction. Anything a second viewer would also need belongs one layer
48
+ down — that is `PLATFORM_LAYERING_SPEC §5.1`'s tier test ("would Lodestar need to change it
49
+ to use it?"), and this framing is what makes it decidable rather than a judgement call.
50
+
51
+ ## 1b. The three composition modes, and what each needs from this repo
52
+
53
+ `PLATFORM_LAYERING_SPEC §1.1` names three modes, and since R3-280 all three are real:
54
+
55
+ | Mode | What it is | What it needs here |
56
+ |---|---|---|
57
+ | **M1 dispatch** | a content repo's marker resolves to this repo through the binding registry | `immediately.run.provides: open-wiki`, and `src/App.tsx` as the entry |
58
+ | **M2 fork** | engine + corpus in one repo under one identity (the docs wiki) | nothing extra — this is the historical shape (§1, §4) |
59
+ | **M3 library** | a thin shell imports the engine pinned and arranges it | the package name, the `exports` map, and **`viewer.manifest.json`** |
60
+
61
+ **The manifest is the override contract.** `viewer.manifest.json` declares the component
62
+ vocabulary — name, tier, whether a shell may override it, its props, whether it is a
63
+ sanitizing wrapper — and `composeComponents()` enforces it: overriding a name that is not
64
+ declared, or one declared `overridable: false`, throws rather than silently doing nothing.
65
+ Silence is the accident worth engineering against; a plain `{...base, ...overrides}` accepts
66
+ every typo, and a shell then ships an override that quietly stopped working when the engine
67
+ renamed something. `Outlet` is the standing locked case — replacing it detaches every layout
68
+ layer from the page it wraps, and the symptom is a blank body with no error.
69
+
70
+ `scripts/check-manifest.mjs` runs first in `npm run verify` and fails on drift in either
71
+ direction: a component in `GROVE_MDX` but not the manifest, or vice versa. It brace-matches
72
+ the object literal over a comment- and string-blanked copy of the source, so reformatting
73
+ `mdxComponents.ts` cannot change the outcome — the property R3-277c asks for, and one the
74
+ first draft of that script got wrong (a `}` inside a comment ended the scan early and the
75
+ gate reported the whole manifest as drifted).
76
+
77
+ The manifest FORMAT is viewer-generic (`viewer-manifest.schema.json`): a Lodestar manifest
78
+ with a node/edge vocabulary validates through the same schema. Only the entries are
79
+ per-viewer — which is also how corpus furniture (§3) becomes checkable, as `tier: "corpus"`
80
+ entries declared by the corpus rather than by this repo.
81
+
82
+ ## 2. What is engine
83
+
84
+ Anything that renders *any* corpus, and holds no opinion about what the corpus is about:
85
+
86
+ - the two render paths — compiled MDX via `<Include>`, and the **interpreter** path
87
+ (`SafeEntryBody` → the SDK's `parseSafeMdast`/`renderMdast`), selected by `render: safe` on
88
+ the home entry (wiki-wide) or a single entry;
89
+ - routing, path helpers and link classification (`lib/content.ts` — `hrefKeyCandidates`,
90
+ `linkKind`, `splitFragment`), and `<WikiLink>`'s resolution of author-relative hrefs;
91
+ - deep-linking (`lib/fragment.ts`, `ScrollToFragment`) and the raw-source read cache
92
+ (`lib/sourceCache.ts`);
93
+ - the layout chain (`lib/layout.ts`, `_layout.mdx` + `<Outlet/>`) and the shell primitives;
94
+ - the generic content vocabulary registered in `mdxComponents.ts` — `<DocList>`, `<TagCloud>`,
95
+ `<Backlinks>`, `<Timeline>`, `<FamilyTree>`, `<DirectoryList>`, and friends;
96
+ - **folder routing** (`lib/directory.ts`, `hooks/useDirectoryListing`, `<DirectoryView>`):
97
+ a URL naming a directory renders its `index.mdx` if the corpus wrote one, else the
98
+ generated listing. The route resolves `DirectoryList` **through the MDX component map**
99
+ rather than importing it, so a fork's or (with R3-174) a corpus's replacement reaches the
100
+ route as well as the tag — a half-override that only reached MDX bodies would be
101
+ invisible to whoever installed it.
102
+
103
+ ## 3. What is corpus furniture
104
+
105
+ Components that encode **one corpus's frontmatter conventions**. They live with that corpus,
106
+ not here. In the docs wiki that is `RoadmapBoard`, `RoadmapMeta`, `NextAvailable`,
107
+ `Dependencies`, `ProjectIndex`/`ProjectItems`/`ProjectMeta`, `TopicIndex`, `StatusBadge`,
108
+ `ItemCard`, plus `data/roadmap.ts`, `lib/roadmap.ts`, their `.rm-*` stylesheet block, the
109
+ `<Dependencies/>` slot in `PageView`, and `EntryHeader`'s `isItemMeta` → `<RoadmapMeta>` hook.
110
+ Every one of them reads `status:` / `project:` / `prs:` / `depends_on:` — fields that mean
111
+ something only to an engineering roadmap.
112
+
113
+ The measured shape of that boundary, taken 2026-08-13: the docs fork's `GroveApp.css` delta is
114
+ a **pure append** of 221 `.rm-*` lines, and its `mdxComponents.ts` and `EntryHeader.tsx` deltas
115
+ are *entirely* roadmap wiring. Nothing about the engine's own rendering had diverged. That is
116
+ what makes the split cheap to state and cheap to keep.
117
+
118
+ **How furniture is meant to travel, eventually.** R3-174 (frontmatter-discovered content
119
+ components, `type: component`, content-wins on a name clash) is the mechanism: a corpus ships
120
+ its own components and the viewer registers them, no fork. Note the constraint that follows —
121
+ **a content component is author JavaScript, so R3-174 is available only to a corpus rendered
122
+ on the compiled path.** The docs wiki qualifies (it left interpreter mode in R3-252). An
123
+ **interpreter-mode** wiki cannot have content components at all, and must use the engine's
124
+ vocabulary or extend the engine.
125
+
126
+ ## 4. Why this document exists
127
+
128
+ Between 2026-07-10 and 2026-08-13 every engine change landed only in the `docs` fork, and
129
+ nobody noticed. By the time it was measured, this repo had **no interpreter path at all** —
130
+ `grep -rn safe src/` returned zero matches — while the specs described Grove as an interpreter
131
+ and a planned dispatch feature assumed stock Grove would render foreign content
132
+ non-executably. It would have rendered it as *code*.
133
+
134
+ The engine's tests had drifted the same way, and unevenly: `fragment.ts` and `sourceCache.ts`
135
+ arrived with their tests in the same commit, but `lib/content.ts` and `SafeEntryBody.tsx` both
136
+ shipped bare and were untested for three weeks, gaining coverage only when a bug forced it. If
137
+ you add to this engine, the test goes in the same commit as the source.
138
+
139
+ ## 5. Test posture
140
+
141
+ `npm run verify` = `lint` + `build` (`tsc -b`, all three TS projects) + `test`.
142
+
143
+ `src/lib/safeRender.test.ts` drives the **real published** SDK safe renderer — the same bytes
144
+ the sandbox resolves on immediately.run — and proves the non-executable property: planted
145
+ `{fetch()}` / `<script>` / `import` stay inert, unknown components collapse to their children,
146
+ and heading ids exist for citations to land on.
147
+
148
+ `src/components/DirectoryList.test.tsx` carries a **dispatch packaging** block. The fork and
149
+ dispatch packagings differ in exactly one thing — `getContentRoot()` — and directory listings
150
+ touch both path spaces at once: they READ through the mount and LINK through the URL space.
151
+ Its load-bearing assertion is that the chroot prefix reaches **no** rendered href
152
+ (`expect(el.innerHTML).not.toContain('mnt')`); that is host knowledge the viewer may read
153
+ through but never publish, and the same mapping already leaked it once (R3-268). Proven
154
+ non-vacuous by fault injection — freezing `urlAnchor()` at the fork root, and stopping
155
+ `keyToHref` from stripping the anchor, each fail it.
156
+
157
+ **Driving the dispatch packaging locally** (how that block was checked against the real host,
158
+ 2026-08-18): serve the corpus and the viewer as **two** `immediately.run dev` servers, seed the
159
+ VIEWER's locator into `sessionStorage` under `ir-local-locator:<ns>/<repo>/<ref>` (the fragment
160
+ can only carry one endpoint/token, and it has to be the corpus's), and re-point the binding with
161
+ `#ir-dev-region=task.open-wiki&ir-dev-source=local/<ns>/<repo>/<ref>` — `ContentViewer`'s
162
+ `resolveViewerBinding` honours the §6.8 ephemeral layer for `task.<name>` exactly as it does for
163
+ a chrome region. Without that override, dispatch resolves to *published* Grove and the local
164
+ build is never exercised.
165
+
166
+ Two of its cases are **fixture-backed here and corpus-backed in `docs`**, deliberately. This
167
+ repo's sample corpus writes no `[[…]]` citations and no `#sec-…` deep-links, so a corpus read
168
+ would assert nothing — and the sweeps carry non-vacuity guards (`checked > 0`) that correctly
169
+ *fail* on an empty sweep rather than passing quietly. The fixtures make the property
170
+ deterministic here; the docs wiki keeps the large-corpus sweep, which is what R3-252 says that
171
+ harness is for. Do not "fix" a fixture by pointing it back at the corpus, and do not delete a
172
+ non-vacuity guard to make a sweep green.
173
+
174
+ ## 6. Authoring a layout under the interpreter (R3-263)
175
+
176
+ `_layout.mdx` renders through the **safe** renderer whenever the wiki declares `render: safe`
177
+ — it used to go through `<Include>` (the compiled path) regardless, which meant an
178
+ "interpreter" wiki still executed author JavaScript out of its shell. Four things an author
179
+ needs to know, all of which fail *quietly*:
180
+
181
+ 1. **Literal attributes only.** The renderer copies literal attributes and drops expression
182
+ ones, so `<Foo n={3}/>` arrives with no `n`.
183
+ 2. **Only allow-listed structural tags render as elements.** `src/lib/safeIntrinsics.tsx`
184
+ holds the closed set (`main`, `section`, `div`, …) with an attribute allow-list
185
+ (`className`, `id`, `data-*`, `aria-*`, …). A tag outside it collapses to a Fragment that
186
+ **keeps its children** — content survives, the wrapper and its class do not.
187
+ 3. **`import` lines are not resolved — and they are not silently dropped either.** The ESM
188
+ extension is off, so no ESM node is produced and the line renders as ordinary paragraph
189
+ **text, printed on the page**. Layouts are import-free by design; the vocabulary arrives
190
+ through the component map.
191
+ 4. **A block tag must open on its own line.** `<main className="x">text</main>` on one line is
192
+ consumed by micromark as an **HTML block** and renders as literal angle brackets; the same
193
+ tag opened on its own line parses as JSX. The symptom is "my layout is visible as markup"
194
+ with no error anywhere, and the fix is a line break, which nobody guesses.
195
+
196
+ **Why the allow-list rather than raw tags.** The SDK's `literalProps` does no name filtering
197
+ and no URL sanitizing — it copies every literal attribute verbatim onto whatever the map
198
+ resolves. Measured against React 19: `onclick=` and `onerror=` are dropped by React, and a
199
+ `javascript:` URL is blocked by React — but `style="…"` **throws** ("expects a mapping … not a
200
+ string"), `dangerouslySetInnerHTML="…"` **throws**, and `iframe srcdoc="<script>…"` **passes
201
+ straight through**. The two throwing cases are worse than they look: the throw is in the
202
+ *layout*, so one content file takes down the shell for every page. So the barrier is the
203
+ allow-list, and React's own defenses are a second layer rather than the only one.
204
+ `src/lib/safeIntrinsics.test.tsx` pins every one of those cases, including the raw-tag
205
+ behaviour that motivates the wrapper.
206
+
207
+ ## 7. Fork delta, re-measured after the R3-276a port (2026-08-25)
208
+
209
+ The R3-276a port (record queries + `MetadataSource`, grove #45) landed in both trees in
210
+ the same window. Measured immediately after, engine `src/` vs the docs fork's `src/`:
211
+
212
+ - **Byte-identical after the port:** `lib/queries.ts` + its tests, `Search.tsx`,
213
+ `FamilyTree.tsx` — the shared metadata-query surfaces have zero fork divergence.
214
+ - **Fork-only (the furniture, unchanged in kind):** the roadmap components
215
+ (`RoadmapBoard`, `RoadmapMeta`, `NextAvailable`, `Dependencies`, `ProjectIndex`,
216
+ `ProjectItems`, `ProjectMeta`, `TopicIndex`, `ItemCard`, `StatusBadge`), `data/roadmap.ts`,
217
+ `lib/roadmap.ts`, plus the `.rm-*` css block. Every one reads `status:`/`project:`/
218
+ `prs:`/`depends_on:` — corpus tooling, not engine.
219
+ - **Fork naming specialization:** `CONTENT_DIR` constant vs the engine's
220
+ `contentDir()` runtime call (the fork is single-packaging and knows its root at build
221
+ time); visible as ~8-line diffs in `Sidebar.tsx`, `TagCloud.tsx`, `ChildPages.tsx`.
222
+ - **Engine-ahead (NOT ported by R3-276a, pre-existing):** the fork's `App.tsx` is still
223
+ the pre-R3-169 monolith — it never needed the dispatch gate — and it lacks the newer
224
+ engine features (`GroveWiki` split, `DirectoryList`/`DirectoryView` folder routing,
225
+ `SafeLayout`, `TableOfContents`). That is drift the fork OWNS catching up on, tracked
226
+ separately from this boundary record; the fork renders the docs corpus on the surfaces
227
+ it has.
228
+
229
+ The R3-276a fork/dispatch parity drill (headless Chrome on `local.immediately.run`, same
230
+ throwaway corpus rendered both ways) found nav, search, and family-tree **identical**
231
+ between the modes: same labels, same in-corpus hrefs, same family labels
232
+ (`Engineering · Operations · Ada Lovelace · Alan Turing · Dorothy Vaughan`), with 83
233
+ viewer-dev-server requests proving the dispatch render exercised the local engine build.
package/llms.txt ADDED
@@ -0,0 +1,115 @@
1
+ # @immediately-run/grove — the viewer kit for directory-as-content wikis
2
+
3
+ > The Grove viewer: a kit of React components, layouts and themes for building immediately.run wikis. Composable as a fork, a dispatch target, or a pinned library. (v0.1.1)
4
+
5
+ Grove is NOT a wiki engine: routing, MDX compilation, the frontmatter index, link
6
+ spaces and heading anchors live in the sandbox + `@immediately-run/sdk`. What this
7
+ package owns is the component vocabulary, the chrome, the layout chain, the themes,
8
+ and the defaults. Three composition modes (PLATFORM_LAYERING_SPEC §1.1):
9
+
10
+ - **M1 dispatch** — a content repo with `opensWith: {task: "open-wiki"}` resolves here;
11
+ you write no code, the host mounts the corpus.
12
+ - **M2 fork** — engine + corpus in one repo (the docs wiki shape).
13
+ - **M3 library** — a thin shell imports this package pinned and composes it:
14
+ see `examples/thin-shell/`, and the override contract below.
15
+
16
+ ## Import surface (package.json exports)
17
+
18
+ - `@immediately-run/grove` → ./src/lib.ts
19
+ - `@immediately-run/grove/app` → ./src/App.tsx
20
+ - `@immediately-run/grove/manifest` → ./viewer.manifest.json
21
+ - `@immediately-run/grove/components` → ./src/mdxComponents.ts
22
+ - `@immediately-run/grove/styles.css` → ./src/GroveApp.css
23
+ - `@immediately-run/grove/theme.css` → ./src/index.css
24
+ - `@immediately-run/grove/package.json` → ./package.json
25
+
26
+ ## Composition entry (src/lib.ts)
27
+
28
+ - `GROVE_MDX`
29
+ - `GroveApp`
30
+ - `GroveShell`
31
+ - `GroveShellContext`
32
+ - `GroveWiki`
33
+ - `ManifestComponent`
34
+ - `ManifestOverrideError`
35
+ - `NavItem`
36
+ - `OutletContext`
37
+ - `SAFE_MDX`
38
+ - `VIEWER_MANIFEST`
39
+ - `ViewerManifest`
40
+ - `composeComponents`
41
+ - `contentDir`
42
+ - `getContentRoot`
43
+ - `homeKey`
44
+ - `isContentEntry`
45
+ - `isDispatched`
46
+ - `keyToFsPath`
47
+ - `keyToHref`
48
+ - `keyToInclude`
49
+ - `layoutChainForKey`
50
+ - `manifestNames`
51
+ - `overridableNames`
52
+ - `queryPaths`
53
+ - `readingTime`
54
+ - `sandboxPathToKey`
55
+ - `stripFrontmatter`
56
+ - `useShell`
57
+
58
+ ## Component vocabulary — the override contract (viewer.manifest.json)
59
+
60
+ This is THE two-way surface: what a corpus may use, and what a library-composing
61
+ shell may override. `composeComponents(base, overrides)` THROWS on a name that is
62
+ not declared here or is declared `overridable: false` — overriding never silently
63
+ no-ops. Tiers: `engine` renders any corpus · `chrome` is site furniture · `corpus`
64
+ is one corpus's conventions (declared by that corpus, not this repo).
65
+
66
+ - `a` (engine, overridable) — Wiki-link resolver: renders the resolved / broken / self states for an in-corpus href. Props: href: string?, children: node?.
67
+ - `Backlinks` (engine, overridable) — Entries linking to this one.
68
+ - `Callout` (engine, overridable) — An aside block with a kind (note / warning / …). Props: kind: string?, title: string?, children: node?.
69
+ - `ChildPages` (engine, overridable) — The entries nested under this one.
70
+ - `Directory` (engine, overridable) — The entries under one corpus directory. Props: path: string?.
71
+ - `DirectoryList` (engine, overridable) — A folder listing as a table, with metadata columns chosen by `columns` and narrowed to those the rows actually carry. Props: path: string?, title: string?, columns: string?, sort: string?, hidden: boolean?.
72
+ - `DocList` (engine, overridable) — A queried list of entries — the generic index primitive. Props: tag: string?, limit: number?.
73
+ - `DocsByTag` (engine, overridable) — Entries grouped under one tag. Props: tag: string?.
74
+ - `FamilyTree` (engine, overridable) — A relationship graph rendered from frontmatter links. Props: children: node?.
75
+ - `GroveFooter` (chrome, overridable) — The site footer.
76
+ - `GroveNav` (chrome, overridable) — Top/side navigation. Reads the shell context, so an override must too (or render its own).
77
+ - `GroveSidebar` (chrome, overridable) — The corpus tree sidebar.
78
+ - `img` (engine, overridable) — Resolves a mount-relative asset off the filesystem. Props: src: string?, alt: string?.
79
+ - `Infobox` (engine, overridable) — The boxed summary panel a wiki entry carries beside its lede. Props: title: string?, children: node?.
80
+ - `Kbd` (engine, overridable) — A keyboard key. Props: children: node?.
81
+ - `KeyValue` (engine, overridable) — A definition-list row for infobox-style facts. Props: children: node?.
82
+ - `Lede` (engine, overridable) — The opening paragraph, set apart typographically. Props: children: node?.
83
+ - `More` (engine, overridable) — A 'read more' pointer to a related entry. Props: href: string?, children: node?.
84
+ - `Outlet` (engine, LOCKED) — The layout chain's inward slot. NOT overridable — the chain's nesting is engine mechanics, and replacing it detaches every layer from the page it wraps.
85
+ - `PageMeta` (engine, overridable) — The entry's own metadata line (updated, reading time, …).
86
+ - `Quote` (engine, overridable) — A pull quote with an optional citation. Props: cite: string?, children: node?.
87
+ - `RecentlyUpdated` (engine, overridable) — The most recently updated entries. Props: limit: number?.
88
+ - `TableOfContents` (engine, overridable) — The on-this-page heading list, which keeps the active entry in view as the reader scrolls. Props: entryKey: string?, title: string?, className: string?.
89
+ - `TagCloud` (engine, overridable) — Every tag in the corpus, weighted by use. Props: limit: number?.
90
+ - `TagList` (engine, overridable) — The tags of one entry, as links to their indexes. Props: tags: string[]?.
91
+ - `Timeline` (engine, overridable) — A dated sequence of entries or events. Props: children: node?.
92
+ - `Toc` (engine, overridable) — The table of contents derived from the entry's heading anchors. Props: depth: number?.
93
+
94
+ ## Frontmatter keys the engine reads
95
+
96
+ - `site`
97
+ - `layout`
98
+ - `view`
99
+ - `frame`
100
+ - `render`
101
+ - `nav`
102
+ - `order`
103
+ - `tags`
104
+
105
+ Unknown keys pass through (carried, unread) — a corpus may keep its own vocabulary.
106
+
107
+ ## What lives one layer down (do not reimplement here)
108
+
109
+ - Platform client (RPC, capabilities, mounts, fs, editor, LLM): `@immediately-run/sdk`
110
+ — its llms.txt: https://immediately-run.github.io/immediately-run-sdk/llms.txt
111
+ - Link-space resolver + grammar canon: `@immediately-run/mdx-plugins`
112
+ - Non-executable MDX rendering: `@immediately-run/safe-content`
113
+
114
+ ---
115
+ _Generated from viewer.manifest.json + package.json by `scripts/gen-llms.mjs`; regenerate on vocabulary changes (verify checks freshness)._
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@immediately-run/grove",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "src/main.tsx",
6
+ "immediately.run": {
7
+ "$schema": "https://immediately.run/immediately-run-config.schema.json",
8
+ "provides": [
9
+ {
10
+ "task": "open-wiki",
11
+ "version": "1.0"
12
+ }
13
+ ]
14
+ },
15
+ "scripts": {
16
+ "dev": "vite",
17
+ "build": "tsc -b && vite build",
18
+ "lint": "eslint .",
19
+ "test": "vitest run",
20
+ "preview": "vite preview",
21
+ "verify": "npm run check:llms && npm run check:engine-api:selftest && npm run check:engine-api && npm run check:manifest && npm run check:deps && npm run lint && npm run build && npm run test",
22
+ "check:manifest": "node scripts/check-manifest.mjs",
23
+ "check:deps": "node scripts/check-app-dependencies.mjs --self-test && node scripts/check-app-dependencies.mjs",
24
+ "check:engine-api": "node scripts/check-engine-api.mjs",
25
+ "check:engine-api:selftest": "node scripts/check-engine-api.mjs --self-test",
26
+ "gen:llms": "node scripts/gen-llms.mjs",
27
+ "check:llms": "node scripts/gen-llms.mjs --check"
28
+ },
29
+ "dependencies": {
30
+ "@immediately-run/mdx-plugins": "0.4.0",
31
+ "@immediately-run/sdk": "^0.52.0",
32
+ "react": "^19.2.5",
33
+ "react-dom": "^19.2.5"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^9.39.4",
37
+ "@immediately-run/dev-fs": "^0.1.0",
38
+ "@mdx-js/rollup": "^3.1.0",
39
+ "@types/node": "^24.13.3",
40
+ "@types/react": "^19.2.14",
41
+ "@types/react-dom": "^19.2.3",
42
+ "@vitejs/plugin-react": "^6.0.1",
43
+ "eslint": "^9.39.4",
44
+ "eslint-plugin-react-hooks": "^7.1.1",
45
+ "eslint-plugin-react-refresh": "^0.5.2",
46
+ "globals": "^17.5.0",
47
+ "jsdom": "^30.0.1",
48
+ "typescript": "~6.0.2",
49
+ "typescript-eslint": "^8.58.2",
50
+ "vite": "^8.0.9",
51
+ "vitest": "^4.1.9",
52
+ "@immediately-run/mdx-plugins": "0.4.0"
53
+ },
54
+ "description": "The Grove viewer: a kit of React components, layouts and themes for building immediately.run wikis. Composable as a fork, a dispatch target, or a pinned library.",
55
+ "license": "Apache-2.0",
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/immediately-run/grove.git"
59
+ },
60
+ "exports": {
61
+ ".": "./src/lib.ts",
62
+ "./app": "./src/App.tsx",
63
+ "./manifest": "./viewer.manifest.json",
64
+ "./components": "./src/mdxComponents.ts",
65
+ "./styles.css": "./src/GroveApp.css",
66
+ "./theme.css": "./src/index.css",
67
+ "./package.json": "./package.json"
68
+ },
69
+ "files": [
70
+ "src",
71
+ "viewer.manifest.json",
72
+ "viewer-manifest.schema.json",
73
+ "README.md",
74
+ "docs/ENGINE_BOUNDARY.md",
75
+ "llms.txt"
76
+ ],
77
+ "peerDependencies": {
78
+ "@immediately-run/sdk": "^0.52.0",
79
+ "react": "^19.2.5",
80
+ "react-dom": "^19.2.5"
81
+ }
82
+ }