@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.
- package/README.md +122 -0
- package/docs/ENGINE_BOUNDARY.md +233 -0
- package/llms.txt +115 -0
- package/package.json +82 -0
- package/src/App.tsx +131 -0
- package/src/GroveApp.css +2774 -0
- package/src/GroveWiki.tsx +386 -0
- package/src/components/AssetImage.tsx +58 -0
- package/src/components/Backlinks.tsx +104 -0
- package/src/components/Callout.tsx +26 -0
- package/src/components/ChildPages.tsx +40 -0
- package/src/components/DefaultLayout.tsx +27 -0
- package/src/components/Directory.tsx +65 -0
- package/src/components/DirectoryList.test.tsx +275 -0
- package/src/components/DirectoryList.tsx +189 -0
- package/src/components/DirectoryView.tsx +68 -0
- package/src/components/DocList.tsx +109 -0
- package/src/components/DocsByTag.tsx +13 -0
- package/src/components/Drawer.tsx +45 -0
- package/src/components/EntryHeader.tsx +51 -0
- package/src/components/FamilyTree.tsx +95 -0
- package/src/components/GroveAgent.tsx +264 -0
- package/src/components/GroveFooter.tsx +20 -0
- package/src/components/GroveNav.tsx +102 -0
- package/src/components/Icon.tsx +56 -0
- package/src/components/Infobox.tsx +19 -0
- package/src/components/Kbd.tsx +10 -0
- package/src/components/KeyValue.tsx +32 -0
- package/src/components/Lede.tsx +6 -0
- package/src/components/More.tsx +10 -0
- package/src/components/Outlet.tsx +11 -0
- package/src/components/PageMeta.tsx +24 -0
- package/src/components/PageView.tsx +98 -0
- package/src/components/Quote.tsx +36 -0
- package/src/components/RecentlyUpdated.tsx +6 -0
- package/src/components/SafeEntryBody.tsx +72 -0
- package/src/components/SafeLayout.tsx +35 -0
- package/src/components/ScrollToFragment.tsx +63 -0
- package/src/components/Search.tsx +127 -0
- package/src/components/Sidebar.tsx +118 -0
- package/src/components/TableOfContents.test.tsx +163 -0
- package/src/components/TableOfContents.tsx +101 -0
- package/src/components/TagCloud.tsx +46 -0
- package/src/components/TagList.tsx +31 -0
- package/src/components/Timeline.tsx +55 -0
- package/src/components/Toc.tsx +14 -0
- package/src/components/WikiLink.tsx +112 -0
- package/src/data/themes.ts +14 -0
- package/src/devfs.d.ts +4 -0
- package/src/hooks/useContentComponents.ts +122 -0
- package/src/hooks/useCorpusMetadata.ts +43 -0
- package/src/hooks/useDirectoryListing.ts +56 -0
- package/src/hooks/useHeadings.ts +96 -0
- package/src/hooks/useOpenWikiBoot.ts +95 -0
- package/src/index.css +120 -0
- package/src/lib/compose.test.ts +92 -0
- package/src/lib/compose.ts +99 -0
- package/src/lib/content.test.ts +269 -0
- package/src/lib/content.ts +267 -0
- package/src/lib/contentRoot.ts +61 -0
- package/src/lib/corpusComponents.test.ts +101 -0
- package/src/lib/corpusComponents.ts +117 -0
- package/src/lib/corpusScan.test.ts +157 -0
- package/src/lib/corpusScan.ts +105 -0
- package/src/lib/directory.test.ts +216 -0
- package/src/lib/directory.ts +262 -0
- package/src/lib/fragment.test.ts +88 -0
- package/src/lib/fragment.ts +55 -0
- package/src/lib/frontmatter.ts +26 -0
- package/src/lib/layout.ts +84 -0
- package/src/lib/openWiki.test.ts +216 -0
- package/src/lib/openWiki.ts +84 -0
- package/src/lib/queries.test.ts +74 -0
- package/src/lib/queries.ts +84 -0
- package/src/lib/safeIntrinsics.test.tsx +99 -0
- package/src/lib/safeIntrinsics.tsx +77 -0
- package/src/lib/safeRender.test.ts +359 -0
- package/src/lib/safeSources.ts +25 -0
- package/src/lib/shell.ts +71 -0
- package/src/lib/sourceCache.test.ts +66 -0
- package/src/lib/sourceCache.ts +42 -0
- package/src/lib/tocScroll.test.ts +71 -0
- package/src/lib/tocScroll.ts +93 -0
- package/src/lib/wiki.test.ts +194 -0
- package/src/lib/wiki.ts +175 -0
- package/src/lib.ts +54 -0
- package/src/main.tsx +19 -0
- package/src/mdx.d.ts +9 -0
- package/src/mdxComponents.ts +89 -0
- package/src/test/setup.ts +19 -0
- package/viewer-manifest.schema.json +62 -0
- 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
|
+
}
|