@liminis/editor 0.1.0 → 0.2.0

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 (35) hide show
  1. package/README.md +126 -14
  2. package/dist/annotations/types.d.ts +16 -0
  3. package/dist/app/App.d.ts +1 -1
  4. package/dist/app/App.js +3 -3
  5. package/dist/app/editor/AnchorScrollPlugin.js +2 -34
  6. package/dist/app/editor/AnnotationSurface.js +2 -1
  7. package/dist/app/editor/CorrectionPanelPlugin.js +10 -10
  8. package/dist/app/editor/DocumentOutline.d.ts +17 -0
  9. package/dist/app/editor/DocumentOutline.js +32 -0
  10. package/dist/app/editor/DragHandlePlugin.js +1 -1
  11. package/dist/app/editor/Editor.d.ts +10 -1
  12. package/dist/app/editor/Editor.js +3 -2
  13. package/dist/app/editor/OutlinePlugin.d.ts +14 -0
  14. package/dist/app/editor/OutlinePlugin.js +133 -0
  15. package/dist/app/editor/SelectionContextMenuPlugin.js +4 -4
  16. package/dist/app/editor/annotation-marks.d.ts +20 -0
  17. package/dist/app/editor/annotation-marks.js +40 -0
  18. package/dist/app/editor/documentOutlineHandle.d.ts +87 -0
  19. package/dist/app/editor/documentOutlineHandle.js +86 -0
  20. package/dist/app/editor/nodes/C4Component.js +6 -6
  21. package/dist/app/editor/nodes/DiagramContextMenu.js +5 -5
  22. package/dist/app/editor/scrollContainer.d.ts +18 -0
  23. package/dist/app/editor/scrollContainer.js +47 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.js +6 -0
  26. package/dist/markdown/stringify.js +65 -0
  27. package/dist/styles.css +342 -221
  28. package/docs/decisions/adr-078.md +37 -2
  29. package/docs/decisions/adr-085.md +166 -0
  30. package/docs/decisions/adr-086.md +147 -0
  31. package/docs/decisions/adr-087.md +216 -0
  32. package/docs/decisions/adr-088.md +136 -0
  33. package/docs/decisions/adr-089.md +121 -0
  34. package/docs/editor-api.md +53 -1
  35. package/package.json +23 -46
@@ -86,6 +86,40 @@ that exists but contains no class-bearing source.
86
86
  `publishConfig` makes that whole class of problem not arise. The cost is that
87
87
  the built path is exercised only in CI — which is what SC-005 mandates anyway.
88
88
 
89
+ > **Amended 2026-08-16 — the `publishConfig` mechanism is reversed as of
90
+ > 0.1.1.** It does not work, and it shipped a broken `0.1.0`.
91
+ >
92
+ > `publishConfig` **manifest-field** overrides (`main`, `types`, `exports`) are
93
+ > a pnpm and yarn feature. npm honours `publishConfig` only for *config* values
94
+ > — `access`, `registry`, `tag` — and ignores field overrides entirely.
95
+ > `.github/workflows/publish.yml` publishes with `npm publish`, so the swap to
96
+ > `dist/` never happened: `0.1.0` went to the registry declaring
97
+ > `"main": "./src/index.ts"` while `files` shipped only `dist/`. Every entry
98
+ > point resolved to a file that was not in the tarball, and the package could
99
+ > not be imported at all.
100
+ >
101
+ > This survived a verification pass that was, in itself, correct and thorough.
102
+ > `scripts/verify-package.mjs` asserted that the packed manifest pointed at
103
+ > `dist/` and that each target existed — but it packed with **pnpm** while the
104
+ > release packed with **npm**. It validated a tarball no consumer would ever
105
+ > receive. The lesson is narrower and more useful than "test more": *verify the
106
+ > artifact the release actually produces.*
107
+ >
108
+ > The entry points now live in the checked-in manifest as `dist/` paths, with
109
+ > no pack-time rewriting by anyone, and `verify-package.mjs` packs with npm and
110
+ > additionally asserts the packed entry points are byte-identical to the
111
+ > checked-in ones — so a client that rewrites them fails too.
112
+ >
113
+ > **What this costs is exactly what the decision above was buying.** An
114
+ > in-workspace consumer resolving `@liminis/editor` no longer lands on raw
115
+ > TypeScript, so the Tailwind `@source` directive and the bundler
116
+ > externalization exclusion described above must name the installed package
117
+ > rather than a sibling `src/` tree. That cost is now acceptable because the
118
+ > monorepo copy is being removed in favour of the published package
119
+ > (verveguy/liminis#1009) — the in-workspace consumer this protected is going
120
+ > away. The rest of the decision — the non-bundling `tsc` emit, `sideEffects`,
121
+ > the vendored wiki-link fix — is unaffected.
122
+
89
123
  `private: true` is kept: publishing is out of scope for #940, and `private`
90
124
  blocks `publish` without blocking `pack`.
91
125
 
@@ -96,8 +130,9 @@ blocks `publish` without blocking `pack`.
96
130
  > package is private. The guard is now `prepublishOnly` →
97
131
  > `scripts/guard-publish.mjs`, which refuses unless `LIMINIS_ALLOW_PUBLISH=1`
98
132
  > is set — a script rather than a flag precisely so that it can be tested, and
99
- > it is, in `tests/package-manifest-contract.test.ts`. The rest of this ADR is
100
- > unaffected: `publishConfig` and the non-bundling `tsc` emit stand as decided.
133
+ > it is, in `tests/package-manifest-contract.test.ts`. The non-bundling `tsc`
134
+ > emit stands as decided. (This amendment originally said `publishConfig` stood
135
+ > too. It does not — see the amendment above it, added later the same day.)
101
136
 
102
137
  ### 3. `mdast-util-wiki-link` is vendored, not merely un-patched.
103
138
 
@@ -0,0 +1,166 @@
1
+ # ADR-085: The Theming Token Reference Is Generated Into `README.md` and Guarded Against Drift
2
+
3
+ **Date:** 2026-08-17
4
+ **Status:** Accepted
5
+ **Supersedes:** none
6
+ **Amends:** none
7
+ **Issue:** #50 (verveguy/liminis-editor)
8
+
9
+ ## Context
10
+
11
+ The editor exposes its theming surface entirely through CSS custom properties
12
+ — a VS Code-style vocabulary (`--vscode-*`) plus a handful of `--slashmd-*`,
13
+ `--color-*` and `--checkbox-*` tokens. Before this issue, none of it was
14
+ documented: a host wanting to match its own palette had to read all of
15
+ `src/styles.css` (2,477 lines) to find the knobs, and nothing kept a written
16
+ list honest against the code even if one had been hand-authored.
17
+
18
+ The issue's own acceptance criteria (FR-002/FR-005 in the Plan stage) require
19
+ the reference to be *generated from source*, not transcribed once, and
20
+ (FR-008) require a CI-enforced guard. The spec (FR-003) additionally requires
21
+ each documented property to state what it controls in a short,
22
+ human-readable description — not merely its name and classification. The
23
+ guard therefore asserts three things: that the documented set equals the
24
+ consumed set, that every consumed token resolves without any host
25
+ configuration (a default, or a fallback at every call site), and that every
26
+ consumed token has a description. The resolves-without-host assertion is
27
+ exactly the rule issue #52 broke, where `--vscode-text` (a typo for
28
+ `--vscode-foreground`) had neither and silently dropped its declaration. #52
29
+ was fixed independently and had already merged to `main` by the time this
30
+ issue's implementation began, so this change did not need to bundle that fix
31
+ itself — but the guard's assertion still exists to catch the next occurrence
32
+ of the same bug class.
33
+
34
+ Three questions needed a specific, reviewable answer rather than being left
35
+ to convention:
36
+
37
+ 1. How does a generated table get **into** a checked-in markdown file, given
38
+ this repo already has one script (`generate-demo-docs.mjs`) that reads
39
+ `README.md` but none that writes into it?
40
+ 2. How is "structural" (affects layout) distinguished from "cosmetic"
41
+ (affects appearance only) for a token, given the source has no explicit
42
+ signal for this beyond the CSS property each token feeds?
43
+ 3. Where does the per-token "what it controls" description (FR-003) come
44
+ from, given the source has no comment or annotation convention that
45
+ states a token's purpose in prose?
46
+
47
+ ## Decision
48
+
49
+ **A shared, plain-JS extraction module — one generator, one guard, no second
50
+ list.** `scripts/lib/theming-tokens.mjs` exports pure functions (comment-strip,
51
+ consumed-token scan, defaulted-token scan, resolves-without-host check,
52
+ structural/cosmetic classification, description lookup, table render/parse). Both
53
+ `scripts/generate-theming-docs.mjs` (the CLI that writes `README.md`) and
54
+ `tests/theming-contract.test.ts` (the CI guard) import it. Plain `.mjs`, no
55
+ build step, matches the existing `scripts/lib/install-tarball.mjs` precedent;
56
+ a hand-written `.d.mts` sidecar gives the test file's `tsc --noEmit` pass real
57
+ types without introducing `checkJs` or a TypeScript rewrite of `scripts/`.
58
+
59
+ **Marker-delimited regeneration, not a one-time transcription.**
60
+ `README.md`'s new `### Theming: CSS custom properties` subsection carries a
61
+ `<!-- theming-tokens:start -->` / `<!-- theming-tokens:end -->` pair; the
62
+ generator rewrites only the text between them, leaving the surrounding prose
63
+ (intro, worked example, the forward note on #51's future rename) untouched.
64
+ The guard's staleness check regenerates the block in memory from current
65
+ `src/` and diffs it against what is actually committed — not "did someone
66
+ run the generator once," but "does the committed table match source right
67
+ now."
68
+
69
+ **Classification defaults to cosmetic; a small keyword table promotes the
70
+ few that touch layout.** `margin`/`padding`/`width`/`height`/`gap`/
71
+ `line-height`/`font-family`/`font-size` on a token's consumption site marks
72
+ it structural (currently: the five `--slashmd-h{1-5}-indent` tokens and
73
+ `--vscode-font-family`/`--vscode-font-size`); every other token — including
74
+ ones set through a CSS shorthand like `border: 1px solid var(--x)`, where
75
+ only the color segment is templated, and ones reached only through a JS
76
+ variable indirection with no adjacent property name in the source text — is
77
+ cosmetic by default. No hand-maintained override list exists: the two
78
+ ambiguous cases this codebase actually has are caught by the keyword table
79
+ itself (their property names literally appear next to the `var()` call in
80
+ `styles.css`), so a separate override map would have been an unused,
81
+ unexercised second place for this classification to drift.
82
+
83
+ **Descriptions are a hand-curated map, keyed by token name, checked for
84
+ completeness rather than generated.** `TOKEN_DESCRIPTIONS` in
85
+ `scripts/lib/theming-tokens.mjs` is a plain object mapping each token to one
86
+ sentence of prose, written by inspecting each token's actual consumption
87
+ site(s) in `src/` (e.g. `--vscode-focus-border` and `--vscode-focusBorder`
88
+ are two distinct, unrelated tokens — one styles a focused input border, the
89
+ other a drag-and-drop indicator — that no automatic heuristic on the name
90
+ alone could tell apart). This is deliberately *not* generated the way the
91
+ token set and classification are: FR-003 asks for meaning, which the source
92
+ does not encode anywhere machine-readable. What's generated is the
93
+ *completeness check* — `buildInventory` looks up every consumed token in the
94
+ map and the drift guard fails if any lookup misses, so the map cannot
95
+ silently fall behind the token set the way a truly hand-written README table
96
+ could.
97
+
98
+ **All three drift-guard assertions are demonstrated by mutation**, not merely
99
+ asserted — `tests/theming-contract.test.ts` writes throwaway fixtures under
100
+ `os.tmpdir()` (never into `src/`) and checks that an undocumented token, an
101
+ unresolvable token, and a token missing from `TOKEN_DESCRIPTIONS` are each
102
+ actually flagged, following the same mutation-testing shape the issue's
103
+ acceptance criteria require and the same fixture-directory convention
104
+ already used by other `scripts/lib/` consumers in this repo.
105
+
106
+ ## Consequences
107
+
108
+ **Good:**
109
+
110
+ - The token table can never silently drift from `src/`: adding, removing, or
111
+ renaming a `var(--x)` reference without running
112
+ `node scripts/generate-theming-docs.mjs` fails `pnpm test`.
113
+ - A host can theme the editor by reading `README.md` alone — no more reading
114
+ 2,477 lines of `styles.css` to find the knobs. Every row states what the
115
+ property actually controls (FR-003), not just its name and classification.
116
+ - The classification mechanism needs no maintenance as new tokens are added
117
+ in the ordinary case (a new color token classifies correctly with zero
118
+ code changes); it only needs attention if a genuinely new *kind* of
119
+ layout-affecting token is introduced, at which point the keyword table
120
+ (not a growing override list) is the one place to extend.
121
+
122
+ **Bad / accepted:**
123
+
124
+ - Classification correctness is not itself tested beyond the token *set* —
125
+ a future token that sets a property this repo's keyword table doesn't
126
+ recognize would default to "Cosmetic" without failing CI even if it were
127
+ actually structural. Accepted: worst case is a table cell in the wrong
128
+ column, not a broken guard, and building a second test for a heuristic
129
+ this small was judged not worth its own maintenance burden.
130
+ - Hand-editing the generated block between the markers is silently
131
+ overwritten the next time the generator runs, and produces a CI failure if
132
+ it isn't. This is deliberate — it is the entire point of the staleness
133
+ check — but it is a real constraint a future contributor needs to learn
134
+ once.
135
+ - `TOKEN_DESCRIPTIONS` is hand-written prose, not derived from source, so its
136
+ *accuracy* (as opposed to its completeness) is not tested — a description
137
+ could go stale if a token is repurposed for a different UI element without
138
+ updating its entry, and nothing would catch that. Accepted: the drift
139
+ guard's completeness check (every consumed token has *an* entry) is what
140
+ FR-003 actually requires and is what's mechanically enforceable; a
141
+ description's continued accuracy is a normal code-review concern for
142
+ future PRs that touch `src/`, the same as any other comment.
143
+
144
+ **Neutral:**
145
+
146
+ - The generated table currently lists 59 distinct tokens, not the 60 the
147
+ issue's own background section estimated. The scan is authoritative over
148
+ that estimate by construction (FR-002); the discrepancy traces to the
149
+ issue's manual family-by-family arithmetic (25 `--vscode-*` + 31
150
+ `--slashmd-*` where the source actually contains 24 and 30 respectively),
151
+ not to a change in what the code consumes.
152
+
153
+ ## References
154
+
155
+ - Issue #50 (this decision)
156
+ - Issue #52 (verveguy/liminis-editor) — the `--vscode-text` typo that
157
+ motivated the guard's "resolves without a host" assertion; fixed
158
+ independently before this issue's implementation began
159
+ - Issue #51 (verveguy/liminis-editor) — the deferred `--vscode-*` rename this
160
+ inventory exists to make complete rather than approximate
161
+ - `tests/adr-citations.test.ts`, `tests/package-manifest-contract.test.ts` —
162
+ the "scan repo, build a `Set`, diff, anti-vacuity-check" test shape this
163
+ guard follows
164
+ - `docs/decisions/adr-081.md` — prior art for a generated-artifact-with-a-single-
165
+ owner pattern in this repo, though that artifact is gitignored where this
166
+ one is checked in and diffable
@@ -0,0 +1,147 @@
1
+ # ADR-086: The Tailwind Requirement Is Dropped — Utility Classes Are Folded into `styles.css`
2
+
3
+ **Date:** 2026-08-18
4
+ **Status:** Accepted
5
+ **Supersedes:** none
6
+ **Amends:** ADR-075, ADR-078
7
+ **Issue:** #49 (verveguy/liminis-editor)
8
+
9
+ ## Context
10
+
11
+ `@liminis/editor`'s styling system is semantic CSS plus custom properties —
12
+ `src/styles.css` carries 2,477 lines, 358 selectors, and 60 `--vscode-*`
13
+ variables, all of it resolved at runtime with no build-time dependency. A
14
+ handful of Tailwind utility classes sat outside that system, at four sites:
15
+ the loading state and error banner in `App.tsx`, the `App` component's
16
+ `className` prop default, and the editor's root wrapper in `Editor.tsx`. Both
17
+ ADR-075 and ADR-078 document what that coupling cost a consumer: Tailwind v4
18
+ must be installed, its automatic source detection must be pointed at
19
+ `node_modules/@liminis/editor` (or the real path under a pnpm workspace's
20
+ `.pnpm/` tree) via an explicit `@source` directive, and — because the failure
21
+ mode is silent, not an error — the resulting generated CSS must be manually
22
+ inspected to confirm the utility actually appears.
23
+
24
+ verveguy/liminis#1016 measured what that `@source` directive actually
25
+ contributes in `liminis-app`, the one real consumer: two classes. `.ml-5`
26
+ (the editor's left margin — real) and `.list-item` (an inert false positive,
27
+ Tailwind's scanner matching the substring in
28
+ `data-list-item-paragraph-break`). A whole build-time framework requirement,
29
+ a documented footgun, and a silent failure mode, for one margin.
30
+
31
+ ## Decision
32
+
33
+ **Fold all four Tailwind-utility sites into semantic CSS, so the package's
34
+ styling contract is exactly "import `styles.css`."** Four new classes were
35
+ added to `src/styles.css` — `.editor-loading`, `.editor-error-banner`,
36
+ `.editor-app-root`, `.editor-container` — one per site, following the file's
37
+ existing `editor-*` naming convention. Each reproduces Tailwind v4's
38
+ default-theme resolution of the utility tokens it replaces exactly (e.g.
39
+ `gap-3` → `0.75rem` under v4's `0.25rem` base spacing unit, `bg-red-500` →
40
+ `oklch(63.7% 0.237 25.331)`, matching the file's existing use of `oklch(...)`
41
+ for other colors), so the shipped visual result is unchanged.
42
+
43
+ `.editor-inner` (`Editor.tsx`'s inner wrapper) keeps its existing name; only
44
+ the redundant Tailwind `relative` utility is dropped from its `className`,
45
+ replaced by `position: relative` added to the CSS rule directly. This
46
+ mattered beyond the comment: `DragHandlePlugin.tsx` positions the drag
47
+ handle, delete button, and drop indicator absolutely, relative to
48
+ `.editor-inner`'s bounding rect, so that element has to stay a positioned
49
+ ancestor.
50
+
51
+ The `App` component's `className` prop default — `'min-h-screen p-0'` — was
52
+ folded into the same treatment even though the issue's own source-reference
53
+ list didn't name it, because it is a `className` under `src/` carrying
54
+ Tailwind spacing tokens and both FR-005 and the issue's own acceptance
55
+ criterion ("no Tailwind utility class remains in any `className` in `src/`")
56
+ are written broadly enough to already cover it. It became `'editor-app-root'`,
57
+ backed by `min-height: 100vh`; `p-0` was dropped without replacement since
58
+ it's inert on a bare `div`.
59
+
60
+ No Tailwind dependency removal was needed: `tailwindcss` was never a
61
+ `package.json` dependency of this repository — the requirement was purely a
62
+ documentation-level ask to consumers, satisfied by removing the "If you use
63
+ Tailwind" section from `README.md`.
64
+
65
+ ## Amendments to ADR-075 / ADR-078
66
+
67
+ Both ADRs' "Negative / costs" and "Consequences" sections describe the
68
+ Tailwind `@source` requirement as an accepted, ongoing cost of the package
69
+ boundary:
70
+
71
+ - **ADR-075**, "Negative / costs" §1: names the `@source` directive as one of
72
+ four "build-config landmines" the package extraction introduced, guarded by
73
+ a test that lives in `liminis-app` (`editor-package-wiring.test.ts`), not
74
+ here. That landmine no longer exists — there is nothing left for the
75
+ `@source` directive to select, and the guard test (out of scope for this
76
+ repo, per the spec's Assumptions) becomes a check for an emptied-out
77
+ requirement rather than a real one.
78
+ - **ADR-078**, "Consequences" §"Two silent failure modes": documents the
79
+ `@source` requirement as the second of two silent failure modes a consumer
80
+ can hit, alongside the missing-`styles.css`-import case. Only the
81
+ `styles.css`-import failure mode remains after this change; the Tailwind
82
+ one is eliminated at the source rather than mitigated.
83
+
84
+ Neither ADR is edited in place — they remain an accurate record of what was
85
+ decided when the package boundary was drawn. This ADR is the up-to-date
86
+ pointer: a contributor who reads ADR-075 or ADR-078 today and needs to know
87
+ whether the `@source` coupling still applies should land here.
88
+
89
+ ## Consequences
90
+
91
+ **Good:**
92
+
93
+ - The package's styling contract is exactly one instruction — import
94
+ `styles.css` — which every consumer already does. No Tailwind version
95
+ requirement, no `@source` directive, no `.pnpm/` path resolution, no manual
96
+ verification step.
97
+ - The silent-failure mode ADR-078 warned about for the Tailwind `@source`
98
+ case is gone, not mitigated — there's nothing left to silently fail to
99
+ generate.
100
+ - Removing the utility classes touches nothing in the `--vscode-*` /
101
+ `--slashmd-*` custom-property system (see ADR-085): the loading state's
102
+ `text-[var(--vscode-foreground)]` already round-tripped through an
103
+ existing, documented, defaulted token, and moving it into a plain CSS rule
104
+ adds nothing to and removes nothing from the theming contract's tracked
105
+ token sets.
106
+
107
+ **Bad / accepted:**
108
+
109
+ - Layout constants that a host's own Tailwind config could previously
110
+ influence (e.g. a customized spacing scale changing what `ml-5` resolved
111
+ to) are now fixed CSS values, not host-tunable. This is an intended
112
+ behavior change, not a regression — Tailwind resolves at build time and CSS
113
+ variables at runtime, so treating Tailwind as the theming surface would
114
+ have meant forcing a rebuild to retheme, which contradicts the existing
115
+ runtime `--vscode-*` API. It is why this ships as `0.2.0`, not a patch.
116
+ - `bg-red-500`'s value is pinned to Tailwind v4's `oklch(63.7% 0.237 25.331)`
117
+ rather than v3's `#ef4444`. The two are visually near-identical but not
118
+ bit-identical on wide-gamut displays; v4 was chosen because the README
119
+ required Tailwind v4 up through this change, so v4 is what the shipped
120
+ color actually was.
121
+
122
+ **Neutral:**
123
+
124
+ - Adopting Tailwind as a peer dependency to gain a theming system was
125
+ explicitly considered and rejected as part of this decision, in favor of
126
+ the existing runtime CSS-variable API — see issue #50 and ADR-085 for what
127
+ that theming surface actually needs.
128
+ - Consumer-side follow-ups — removing the now-inert `@source` line from
129
+ `liminis-app`'s `src/renderer/styles/main.css`, and from Zusammen if it has
130
+ one — are out of scope for this repository and tracked separately in their
131
+ own repos.
132
+
133
+ ## References
134
+
135
+ - Issue #49 (this decision)
136
+ - verveguy/liminis#1016 — measurement of the `@source` directive's actual
137
+ contribution in `liminis-app` (two classes, one of them a false positive)
138
+ that motivated this change
139
+ - `docs/decisions/adr-075.md` — "Negative / costs" §1, names the `@source`
140
+ requirement this ADR removes
141
+ - `docs/decisions/adr-078.md` — "Consequences", documents the `@source`
142
+ silent-failure mode this ADR eliminates
143
+ - `docs/decisions/adr-085.md` — the runtime `--vscode-*` custom-property
144
+ theming system this decision leaves untouched, and the alternative to a
145
+ Tailwind-as-theming-surface approach
146
+ - Issue #50 (verveguy/liminis-editor) — theming follow-up that explicitly
147
+ considered and rejected Tailwind as a peer dependency
@@ -0,0 +1,216 @@
1
+ # ADR-087: The Theming Vocabulary Is Renamed to `--liminis-editor-*`, With Old Names Kept Live as Fallbacks
2
+
3
+ **Date:** 2026-08-18
4
+ **Status:** Accepted
5
+ **Supersedes:** none
6
+ **Amends:** ADR-085
7
+ **Issue:** #51 (verveguy/liminis-editor)
8
+
9
+ ## Context
10
+
11
+ ADR-085 (#50) inventoried the editor's theming surface and found it was not
12
+ one vocabulary but four, none of them the package's own: 30 `--slashmd-*`
13
+ properties (the name of the upstream project this editor was derived from),
14
+ 24 `--vscode-*` properties (the name of the VS Code extension it also came
15
+ from), 4 `--color-*`, and 1 `--checkbox-*`. `--vscode-*` at least reads as a
16
+ recognisable theming convention; `--slashmd-*` — the largest group — implies
17
+ a relationship to a project named "slashmd" that means nothing to an
18
+ adopter and does not exist for them. The defect is the inconsistency itself:
19
+ renaming only one of the four families would leave three, which is strictly
20
+ less coherent than renaming all of them or none. Since this is already a
21
+ breaking change and each consumer migration has a measured cost, splitting
22
+ it into a second pass later would mean paying that cost twice.
23
+
24
+ An org-wide `--liminis-*` vocabulary — splitting the ~42 tokens that are
25
+ generic design tokens (`foreground`, `border`, `link`, `font-family`, …)
26
+ from the editor-specific rest — was considered and rejected. Not every
27
+ consumer of this package is a Liminis product: Zusammen is a separate
28
+ product, and external adopters are neither. A non-Liminis host would
29
+ otherwise be forced to declare Liminis-branded globals for a component it
30
+ merely depends on. `--liminis-editor-*` keeps the future open in the
31
+ correct direction — if an org-wide `--liminis-*` vocabulary emerges later,
32
+ Liminis apps can map individual tokens
33
+ (`--liminis-editor-foreground: var(--liminis-foreground);`) without
34
+ requiring non-Liminis adopters to do anything, whereas retracting org-wide
35
+ globals once adopters depend on them is not similarly available.
36
+
37
+ Unresolved CSS custom properties do not error — the declaration is dropped
38
+ and the value falls back to inherited or initial. A clean-break rename
39
+ would therefore strip theming from `liminis-app` and Zusammen (and any
40
+ external adopter) silently: no failing test, no console warning, nothing
41
+ pointing at the cause. The issue's User Story 2 requires that a host
42
+ supplying only the old names keep theming exactly as before, with the
43
+ deprecation communicated in the release notes rather than enforced by
44
+ breakage.
45
+
46
+ ## Decision
47
+
48
+ **All 59 tokens move to one prefix, `--liminis-editor-*`, no split tier.**
49
+ FR-001 rejects the org-wide/editor-specific split explicitly (see Context);
50
+ every consumed property becomes `--liminis-editor-<suffix>`. One collision
51
+ required a deliberate deviation from pure prefix-stripping:
52
+ `--checkbox-border` and `--vscode-border` both strip to the bare suffix
53
+ `border`. The checkbox token keeps its discriminator —
54
+ `--liminis-editor-checkbox-border` — rather than colliding with
55
+ `--liminis-editor-border`. No other suffix is touched; pre-existing
56
+ inconsistent casing (`--vscode-focusBorder` vs. `--vscode-focus-border`)
57
+ carries over unchanged into `--liminis-editor-focusBorder` /
58
+ `--liminis-editor-focus-border`, since normalizing it is a separate,
59
+ unrequested migration surface.
60
+
61
+ **Defaults stay declared under the old name; only consumption sites move.**
62
+ This is the one non-obvious part of the design, and it runs opposite to the
63
+ "obviously symmetric" alternative (rename the `:root`/`.dark` declaration
64
+ too, and add a fallback in the `var()` call). That alternative is broken:
65
+ once the package's own `:root` unconditionally declares
66
+ `--liminis-editor-x`, that property is never unset at the point of use, so
67
+ a host overriding only the old name is silently ignored — the two custom
68
+ properties resolve independently, and the browser does not fail over from
69
+ one name to the other based on whether the *other* name was touched by a
70
+ host. The design actually shipped: `styles.css`'s `:root`/`.dark`/
71
+ `@media print` blocks are structurally untouched, still declaring
72
+ `--vscode-foreground` etc. directly; only every `var(...)` **consumption**
73
+ site was rewritten, to
74
+ `var(--liminis-editor-x, var(--old-name-x, ...existing fallback if any))`.
75
+ Concretely, all three host states now resolve correctly:
76
+
77
+ - Host sets nothing → `--liminis-editor-x` unset → falls to `--old-name-x`
78
+ → resolves to the package's still-old-named default. Unchanged.
79
+ - Host sets only the new name → resolves directly.
80
+ - Host sets only the old name (today's actual `liminis-app`/Zusammen state)
81
+ → `--liminis-editor-x` still unset anywhere → falls to `--old-name-x` →
82
+ the host's override. Unchanged from pre-rename behavior.
83
+
84
+ One visible side effect: every renamed token's "Has a default" column in
85
+ the generated `README.md` table now reads "No (inline fallback only)",
86
+ including tokens that read "Yes" before this change — the *new* name is
87
+ never declared directly, only ever satisfied through the fallback chain.
88
+ This is not a defect; it is the mechanism that makes the compatibility
89
+ story work, called out here and in `README.md` so a future contributor
90
+ does not "fix" it by giving the new name its own declaration (which would
91
+ silently reintroduce the exact breakage this ADR exists to avoid).
92
+
93
+ **The extraction script (#50's `scripts/lib/theming-tokens.mjs`) gains a
94
+ primary/nested distinction.** Wrapping all 59 consumption sites in a
95
+ two-level `var()` chain, without changing the extraction logic, would have
96
+ made the flat, nesting-unaware regex scan count the nested old-name
97
+ reference as a second, independently "consumed" token at every one of the
98
+ 59 sites — up to 118 entries instead of 59, doubling the README table and
99
+ the `TOKEN_DESCRIPTIONS` maintenance burden, and violating SC-001 ("a
100
+ previous-family name appears only as a fallback, never as the primary
101
+ consumed name"). `consumptionSitesIn` now walks each file with a
102
+ paren-aware scanner tracking `var()` nesting depth; `consumedTokens` keeps
103
+ only depth-0 sites as "consumed," and each depth-0 site records the name of
104
+ whatever `var()` call is nested immediately inside its own fallback (its
105
+ `immediateFallback`) — which, after this rename, is always the token's
106
+ previous name.
107
+
108
+ **`PREVIOUS_NAME` is an explicit, checked-in table, not derived by
109
+ convention.** In this codebase "strip the old prefix, add
110
+ `--liminis-editor-`" happens to reconstruct the new name from the old one
111
+ (modulo the one collision above), but the drift guard needs the *reverse*
112
+ direction — given a new name, which specific old family did it actually
113
+ come from — and reconstructing that from the new name alone is exactly the
114
+ kind of assumption FR-011 exists to not rely on. `PREVIOUS_NAME` maps all
115
+ 59 new names to their real previous names, and a new function,
116
+ `resolvesToPreviousName(name, consumed)`, checks every one of a token's
117
+ consumption sites' `immediateFallback` against it.
118
+
119
+ **The two pre-existing sites with no `:root`/`.dark` default of their own —
120
+ `--vscode-input-bg` (default was `var(--vscode-code-bg)`) and
121
+ `--vscode-foreground-muted` (default was `var(--vscode-border)`) — get a
122
+ four-level cascade**, e.g.
123
+ `var(--liminis-editor-input-bg, var(--vscode-input-bg, var(--liminis-editor-code-bg, var(--vscode-code-bg))))`.
124
+ No special-casing was needed to produce this: rewriting every occurrence of
125
+ `var(--old-name...)` (including ones already nested inside another token's
126
+ fallback) independently, in a single generic pass per token, composes
127
+ correctly regardless of processing order — the outer wrap's fallback
128
+ argument is whatever text was already there, including an inner match that
129
+ gets wrapped by its own pass. This keeps these two tokens' behavior
130
+ consistent with every other consumption of `--liminis-editor-code-bg` /
131
+ `--liminis-editor-border`: a host overriding only the new name of the
132
+ *inner* token still takes effect, rather than the two sites bypassing the
133
+ new vocabulary as a special case.
134
+
135
+ **Drift guard extended, not replaced (FR-011).**
136
+ `tests/theming-contract.test.ts` gained a fourth assertion — every consumed
137
+ token for which `PREVIOUS_NAME` has an entry must satisfy
138
+ `resolvesToPreviousName` at every call site — plus a mutation test proving
139
+ it fires for a renamed token consumed with no fallback at all. The three
140
+ pre-existing assertions from ADR-085 (documented-equals-consumed,
141
+ resolves-without-host, has-a-description) are unchanged in shape; one of
142
+ their existing mutation fixtures, which referenced `--checkbox-border`
143
+ directly as a "described" token, was updated to use the new
144
+ `--liminis-editor-checkbox-border` name now that `TOKEN_DESCRIPTIONS` is
145
+ keyed by the new vocabulary.
146
+
147
+ **Compatibility is documented as deprecation, not silence.** `README.md`
148
+ gained a "Migrating from the old names" section, and `CHANGELOG.md` (new —
149
+ no changelog existed in this repo before) documents, under the shared
150
+ `0.2.0` entry alongside #49, that the four old prefixes are deprecated and
151
+ will be removed in a future major release.
152
+
153
+ ## Consequences
154
+
155
+ **Good:**
156
+
157
+ - A host that upgrades and changes nothing keeps its exact theme — the
158
+ fallback-chain design makes User Story 2's independent test (host
159
+ supplies only old names, theme resolves unchanged) true by construction,
160
+ not by accident.
161
+ - The vocabulary an adopter reads about is now singular and package-owned;
162
+ none of the 59 documented properties names VS Code or an upstream project
163
+ the adopter has no relationship to.
164
+ - A partial rename — a new `--liminis-editor-*` name introduced without its
165
+ fallback to the correct previous name — now fails CI (FR-011), rather
166
+ than merging as a silent, host-breaking regression discoverable only by a
167
+ human noticing a host's theme went blank.
168
+ - The extraction script's primary/nested distinction is general: it holds
169
+ for the two pre-existing nested-default sites without any special-casing
170
+ in the scanner itself, and generalizes to any future token that needs a
171
+ multi-level fallback.
172
+
173
+ **Bad / accepted:**
174
+
175
+ - Every renamed token's "Has a default" column now reads "No (inline
176
+ fallback only)," even for the ~50 tokens that had a direct `:root`/`.dark`
177
+ declaration before this change. This is a real reduction in what that
178
+ column communicates (it no longer distinguishes "has a real default" from
179
+ "resolves via fallback chain" for the common case) — accepted as the
180
+ direct, unavoidable consequence of keeping compatibility, and called out
181
+ explicitly in `README.md` so it reads as intentional rather than a
182
+ generator bug.
183
+ - Every one of the 59 tokens now carries a permanent, two-name fallback
184
+ chain in `styles.css` and the five `.tsx` consumption sites, which stays
185
+ until a future major release removes the old names — a standing
186
+ verbosity cost in the source, not just the generated docs.
187
+ - `PREVIOUS_NAME` is a second, hand-maintained mapping the drift guard
188
+ depends on (not derived from `TOKEN_DESCRIPTIONS` or any other single
189
+ source), so a future contributor who renames a *new* token years from now
190
+ needs to know it exists.
191
+
192
+ **Neutral:**
193
+
194
+ - `--checkbox-border` renaming to `--liminis-editor-checkbox-border`,
195
+ rather than the "strip the prefix" convention's `--liminis-editor-border`,
196
+ is the one place the new vocabulary is not a mechanical function of the
197
+ old one. It is the correct fix for a genuine name collision, not a
198
+ stylistic choice, and is recorded directly in `PREVIOUS_NAME`'s
199
+ definition in `scripts/lib/theming-tokens.mjs` so it stays visible.
200
+ - `liminis-app` needed no code change for this issue: it supplies none of
201
+ the old-prefix overrides today, so its `--color-primary`/
202
+ `--color-muted-foreground` Tailwind `@theme` tokens (coincidental naming
203
+ overlap with two of the renamed tokens, not deliberate editor-theming
204
+ wiring) are unaffected either way.
205
+
206
+ ## References
207
+
208
+ - Issue #50 / ADR-085 (verveguy/liminis-editor) — the generated inventory
209
+ and drift guard this rename uses as its worklist and extends
210
+ - Issue #52 (verveguy/liminis-editor) — corrected the consumed-token count
211
+ from 60 to 59, which this issue's scope decision (all 59, one prefix)
212
+ builds on
213
+ - Issue #49 / ADR-086 (verveguy/liminis-editor) — the sibling breaking
214
+ change sharing this `0.2.0` release
215
+ - [verveguy/zusammen#125](https://github.com/verveguy/zusammen/issues/125)
216
+ — ported this rename into Zusammen's vendored copy of the editor source