@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.
- package/README.md +126 -14
- package/dist/annotations/types.d.ts +16 -0
- package/dist/app/App.d.ts +1 -1
- package/dist/app/App.js +3 -3
- package/dist/app/editor/AnchorScrollPlugin.js +2 -34
- package/dist/app/editor/AnnotationSurface.js +2 -1
- package/dist/app/editor/CorrectionPanelPlugin.js +10 -10
- package/dist/app/editor/DocumentOutline.d.ts +17 -0
- package/dist/app/editor/DocumentOutline.js +32 -0
- package/dist/app/editor/DragHandlePlugin.js +1 -1
- package/dist/app/editor/Editor.d.ts +10 -1
- package/dist/app/editor/Editor.js +3 -2
- package/dist/app/editor/OutlinePlugin.d.ts +14 -0
- package/dist/app/editor/OutlinePlugin.js +133 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +4 -4
- package/dist/app/editor/annotation-marks.d.ts +20 -0
- package/dist/app/editor/annotation-marks.js +40 -0
- package/dist/app/editor/documentOutlineHandle.d.ts +87 -0
- package/dist/app/editor/documentOutlineHandle.js +86 -0
- package/dist/app/editor/nodes/C4Component.js +6 -6
- package/dist/app/editor/nodes/DiagramContextMenu.js +5 -5
- package/dist/app/editor/scrollContainer.d.ts +18 -0
- package/dist/app/editor/scrollContainer.js +47 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/markdown/stringify.js +65 -0
- package/dist/styles.css +342 -221
- package/docs/decisions/adr-078.md +37 -2
- package/docs/decisions/adr-085.md +166 -0
- package/docs/decisions/adr-086.md +147 -0
- package/docs/decisions/adr-087.md +216 -0
- package/docs/decisions/adr-088.md +136 -0
- package/docs/decisions/adr-089.md +121 -0
- package/docs/editor-api.md +53 -1
- 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
|
|
100
|
-
>
|
|
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
|