@liminis/editor 0.2.2 → 0.4.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 (33) hide show
  1. package/LICENSE +0 -13
  2. package/README.md +158 -103
  3. package/dist/app/editor/CorrectionPanelPlugin.js +10 -11
  4. package/dist/app/editor/DragHandlePlugin.js +1 -1
  5. package/dist/app/editor/Editor.js +17 -5
  6. package/dist/app/editor/SelectionContextMenuPlugin.js +4 -4
  7. package/dist/app/editor/nodes/C4Component.js +8 -10
  8. package/dist/app/editor/nodes/C4Node.d.ts +1 -1
  9. package/dist/app/editor/nodes/DiagramContextMenu.js +5 -5
  10. package/dist/headless.d.ts +3 -5
  11. package/dist/headless.js +2 -4
  12. package/dist/index.d.ts +1 -1
  13. package/dist/styles.css +428 -325
  14. package/docs/decisions/adr-92-lexical-peer-range-policy.md +152 -0
  15. package/docs/decisions/adr-93-liminis-editor-defined-aliases.md +315 -0
  16. package/docs/decisions/adr-98-invert-token-direction.md +309 -0
  17. package/package.json +28 -27
  18. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +0 -35
  19. package/dist/app/editor/c4/C4InteractiveRenderer.js +0 -299
  20. package/dist/app/editor/c4/edge-clipping.d.ts +0 -24
  21. package/dist/app/editor/c4/edge-clipping.js +0 -139
  22. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +0 -38
  23. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +0 -112
  24. package/dist/app/editor/c4/layout.d.ts +0 -25
  25. package/dist/app/editor/c4/layout.js +0 -839
  26. package/dist/app/editor/c4/parser.d.ts +0 -19
  27. package/dist/app/editor/c4/parser.js +0 -410
  28. package/dist/app/editor/c4/render-to-string.d.ts +0 -24
  29. package/dist/app/editor/c4/render-to-string.js +0 -34
  30. package/dist/app/editor/c4/renderer.d.ts +0 -64
  31. package/dist/app/editor/c4/renderer.js +0 -569
  32. package/dist/app/editor/c4/types.d.ts +0 -203
  33. package/dist/app/editor/c4/types.js +0 -43
@@ -0,0 +1,152 @@
1
+ # ADR-92: The Lexical Peer Range Bumps Forward by a Narrow Single Caret, Never a Wide Band
2
+
3
+ **Date:** 2026-08-20
4
+ **Status:** Accepted
5
+ **Supersedes:** none
6
+ **Amends:** none
7
+ **Issue:** #92 (verveguy/liminis-editor)
8
+
9
+ ## Context
10
+
11
+ Every published version of `@liminis/editor`, including `0.2.2`, declared its
12
+ Lexical peer range as `^0.44.0` across all twelve Lexical family packages
13
+ (`lexical` and eleven `@lexical/*` scoped packages). Lexical itself moved on
14
+ to `0.49.0`. Because Lexical is a `0.x` package, `^0.44.0` resolves under
15
+ semver to `>=0.44.0 <0.45.0` — it admits patch releases of 0.44 only, and
16
+ excludes every 0.45+ release regardless of whether that particular release
17
+ was actually breaking. The practical effect: this package pinned every host
18
+ application to a Lexical line five minors behind, with no published version
19
+ offering a path forward.
20
+
21
+ This surfaced while refreshing `liminis-app`'s dependencies
22
+ (verveguy/liminis#1026): the Lexical family was bumped 0.44.0 → 0.49.0 and
23
+ tested in isolation there. It passed `liminis-app`'s own typecheck, lint,
24
+ build, and 3275-test suite — and was still wrong, because `liminis-app` would
25
+ have shipped in violation of this package's declared peer contract. That
26
+ app's suite does not exercise the editor deeply enough for a green run to be
27
+ evidence of Lexical compatibility; the only suite that can answer that
28
+ question is this package's own.
29
+
30
+ The peer-range shape was an open question, not a foregone conclusion: widen
31
+ the range to admit 0.44 through 0.49 (matching the issue's own suggested
32
+ example), or narrow it to `^0.49.0` and drop 0.44–0.48 support outright.
33
+
34
+ ## Decision
35
+
36
+ **The twelve Lexical peer ranges move to `^0.49.0` — a narrow single-caret
37
+ bump, not a wide multi-version band.** This is a deliberate, permanent
38
+ policy choice about how this package treats Lexical peer ranges going
39
+ forward, not a one-time bump: every future Lexical minor this package adopts
40
+ gets the same treatment — the range moves forward by exactly one caret,
41
+ paired with running the full suite against the new version, rather than
42
+ widening to claim a span of untested minors as compatible.
43
+
44
+ ### Why narrow, not wide
45
+
46
+ **Widening a peer range later is patch-safe; narrowing one is breaking.**
47
+ Starting narrow keeps both options open — if a wide band is ever wanted, it
48
+ can be added without breaking anyone. Starting wide commits the package to a
49
+ claim it can only walk back by breaking whoever relied on the wide end.
50
+
51
+ **This package's existing range philosophy for `react` does not transfer to
52
+ Lexical.** The `//peerDependencies` manifest comment already documents that
53
+ ranges are "the compatibility contract, not the tested matrix," and that
54
+ narrowing `react`'s range to match its `devDependency` version "would only
55
+ reject working consumers." That reasoning is sound for React specifically
56
+ *because* React's untested span sits inside a **stable major** — a
57
+ regression there is a bug in React, not a permitted outcome of its
58
+ versioning scheme. Lexical is `0.x`, where semver explicitly permits every
59
+ minor release to be breaking. A range like `>=0.44.0 <0.50.0` would assert
60
+ compatibility with six minors, five of them never run against this
61
+ package's own suite — a materially different, and unjustified, claim.
62
+
63
+ **No known consumer wants Lexical 0.44.** Both known consumers of this
64
+ package (`liminis-app` and the package's own test/build pipeline) are moving
65
+ *toward* 0.49, not sitting on 0.44 and wanting to stay there. The wide
66
+ band's benefit — "don't reject a working consumer on an old version" — is
67
+ close to hypothetical here, while its cost — asserting untested compatibility
68
+ across four intermediate minors known to contain breaking changes (see
69
+ Consequences) — is real.
70
+
71
+ A wide band remains available later, but only backed by CI matrix testing
72
+ across the band's endpoints — that is separate work, out of scope for this
73
+ decision.
74
+
75
+ ### What was verified before this decision was acted on
76
+
77
+ The twelve packages were bumped to `0.49.0` in both `peerDependencies` and
78
+ `devDependencies` (kept in lockstep, per this package's existing pairing
79
+ between the tested version and the declared floor), and the full
80
+ verification chain — `pnpm typecheck`, `pnpm lint`, `pnpm build`,
81
+ `pnpm test` (1591 tests), and `pnpm verify:package` (the packed-tarball
82
+ install-and-typecheck check against a fresh consumer) — ran clean with **no
83
+ source changes required**, despite Lexical's own changelog listing several
84
+ breaking changes in the 0.45–0.49 window (a `config()` protocol rework for
85
+ node classes in 0.49.0; `insertNodes` linebreak-handling changes and a
86
+ deprecated-export removal in 0.46.0; selection/backspace/reconcile changes
87
+ in 0.45.0). This package's sixteen custom Lexical node classes all define
88
+ explicit `getType`/`clone`/`importJSON` statics rather than relying on
89
+ auto-synthesis, which structurally avoided the highest-risk part of the
90
+ 0.49.0 change; the rest of the changelog's items apparently do not intersect
91
+ this package's actual usage patterns, since the suite exercises them and
92
+ passed unmodified.
93
+
94
+ ## Consequences
95
+
96
+ **Positive:**
97
+
98
+ - Consumers can now install this package alongside the current Lexical
99
+ release; the multi-minor stall the issue described cannot recur until
100
+ Lexical ships another minor this package hasn't yet adopted.
101
+ - The Lexical peer range's compatibility claim is now backed by an actual CI
102
+ run against the exact version it names — peer and devDependency are both
103
+ `0.49.0`, so no untested span exists in it. (React's own range still has
104
+ an intentionally untested lower half — peer `^19.2.0` vs. devDependency
105
+ `^19.2.5` — per the `//peerDependencies` note; that is unchanged by this
106
+ decision and follows the opposite, stable-major reasoning explained
107
+ above.)
108
+ - The policy is written down (`//lexicalPeerPolicy` in `package.json`, this
109
+ ADR, and the README's install section), so the next Lexical minor is a
110
+ known, bounded task rather than a rediscovery of this issue's reasoning.
111
+
112
+ **Negative:**
113
+
114
+ - **This is a breaking change.** `^0.44.0` and `^0.49.0` are non-overlapping
115
+ ranges; any consumer still on Lexical 0.44.0–0.48.x will see an
116
+ unmet-peer-dependency warning on installing the next release and must
117
+ upgrade its Lexical family to 0.49.0 first. That is the intended effect of
118
+ narrowing, not a regression.
119
+ - **The stall this issue describes will recur at every future Lexical
120
+ minor**, by design: this package's range never gets ahead of what it has
121
+ actually tested. The mitigation is that each recurrence is now a known,
122
+ bounded task (bump twelve versions, run the suite, adjust the range) with
123
+ a written rationale, rather than an accumulating five-minor gap discovered
124
+ incidentally by a downstream consumer's dependency refresh.
125
+
126
+ **Neutral:**
127
+
128
+ - `react`/`react-dom` peer ranges and all non-Lexical dependencies are
129
+ unaffected; this decision only concerns the Lexical family, and does not
130
+ reopen or alter the existing React range philosophy.
131
+ - No Lexical-API-floor guard test (the kind `tests/package-manifest-contract.test.ts`
132
+ already has for React) was added. That guard exists for React because its
133
+ range makes an untested-lower-half claim that needs policing; the narrow
134
+ Lexical range makes no such claim, so there is nothing analogous to guard
135
+ against.
136
+
137
+ ## References
138
+
139
+ - Issue #92 (this decision), verveguy/liminis#1026 (the originating
140
+ `liminis-app` dependency refresh that surfaced the stall)
141
+ - `package.json` — the `//peerDependencies` and `//lexicalPeerPolicy`
142
+ manifest comments; the twelve `peerDependencies`/`devDependencies` entries
143
+ - `README.md` — "Install" section, the Lexical `pnpm add` block and the
144
+ peer-range policy callout
145
+ - `CHANGELOG.md` — the `## Unreleased` / `### Breaking changes` entry
146
+ recording this bump
147
+ - `docs/decisions/adr-078.md`, `docs/decisions/adr-079.md` — prior ADRs on
148
+ consumer-facing package surface, cited as precedent for treating this as
149
+ ADR-worthy
150
+ - Related: #78 (a required member made 0.2.0 type-level breaking), #79
151
+ (token definitions documented as public API) — prior instances of
152
+ consumer-facing surface needing explicit treatment
@@ -0,0 +1,315 @@
1
+ # ADR-93: `--liminis-editor-*` Names Are Defined as Aliases of Their Legacy Names
2
+
3
+ **Date:** 2026-08-20
4
+ **Status:** Accepted (amended by ADR-98 — see the note marked *Amended by ADR-98* below; the alias direction this ADR chose is reversed)
5
+ **Supersedes:** none
6
+ **Amends:** none
7
+ **Amended by:** ADR-98 (#98 inverts this ADR's alias direction — the legacy names stop carrying the real defaults, and become shims to `--liminis-editor-*` instead)
8
+ **Issue:** #93 (verveguy/liminis-editor)
9
+
10
+ ## Context
11
+
12
+ ADR-087 (#51) renamed every *consumption* site to `--liminis-editor-*`,
13
+ deliberately leaving every *definition* site (`:root`, `.dark`, the
14
+ `@media print` block) declaring the old, pre-`0.2.0` names. ADR-092 (#79)
15
+ built a checked-in baseline guarding those definitions, and explicitly
16
+ deferred a third item: whether the package should ever *define*
17
+ `--liminis-editor-*` names directly, with the legacy names kept as an
18
+ alias layer underneath.
19
+
20
+ That gap is not theoretical. Zusammen, a downstream host, reads this
21
+ package's definitions directly — `var(--vscode-background)`,
22
+ `var(--vscode-border)`, etc. — mapping its own design-system tokens onto
23
+ them rather than overriding them
24
+ ([verveguy/zusammen#129](https://github.com/verveguy/zusammen/issues/129)).
25
+ It wants to migrate those reads onto `--liminis-editor-*` instead, but
26
+ before this change there was nothing to migrate to: every row in the
27
+ README's token table read "Has a default: No (inline fallback only)."
28
+
29
+ **The naive fix is the trap ADR-087 already reasoned through once, now on
30
+ the definition side.** Adding `--liminis-editor-foreground: <default>;` to
31
+ the package's own `:root` unconditionally would resolve every one of the
32
+ 250 consumption chains (`var(--liminis-editor-foreground, var(--vscode-foreground))`)
33
+ at the new name before ever reaching the old one — silently disabling the
34
+ ADR-087 fallback layer. A host like `liminis-app`, which supplies only
35
+ `--color-primary`/`--color-muted-foreground` (its own Tailwind `@theme`
36
+ tokens, coincidentally the fallback names `C4Component.tsx` already uses),
37
+ would stop tracking its own overrides with no error and no failing test —
38
+ exactly the failure mode ADR-092 exists to guard against.
39
+
40
+ Two further, non-obvious problems surfaced during implementation, not
41
+ anticipated by the plan:
42
+
43
+ 1. **A definition-side alias's own `var()` reference is textually
44
+ indistinguishable from a consumption site.** `scripts/lib/theming-tokens.mjs`'s
45
+ `consumptionSitesIn()` scans raw text for depth-0 `var(--x` occurrences
46
+ with no awareness of whether the enclosing statement is a custom-property
47
+ *declaration* (`--liminis-editor-foreground: var(--vscode-foreground);`)
48
+ or an ordinary property/JS-style value (`color: var(--liminis-editor-foreground, ...)`).
49
+ Adding the alias block as originally planned would have made every one
50
+ of the ~40 flat-aliased legacy names register as a newly "consumed"
51
+ token, growing the README table and `TOKEN_DESCRIPTIONS` maintenance
52
+ surface for tokens no host-facing code actually reads — a regression
53
+ against this issue's own explicit Out of Scope ("does not add or remove
54
+ rows" from the consumed/documented table).
55
+ 2. **A CSS block comment containing a literal `*/` substring inside its
56
+ text terminates early.** An early draft of the new `:root` block's
57
+ header comment read `--vscode-*/--slashmd-*/--checkbox-*` — the `*/`
58
+ embedded in `--vscode-*/--slashmd-*` closed the comment there, both for
59
+ real CSS parsers and for this repo's own `stripCssComments()`, leaving
60
+ the remainder of the intended comment as live (invalid) CSS text. This
61
+ would have shipped a real authoring bug, not just a test artifact.
62
+
63
+ ## Decision
64
+
65
+ > **Amended 2026-08-22 by ADR-98 (#98) — this clause no longer holds.** The
66
+ > direction chosen here is reversed: `--liminis-editor-*` names now carry the
67
+ > real defaults, and the legacy names below become one-line shims
68
+ > (`<legacy>: var(--liminis-editor-x);`). This ADR's own reasoning for why
69
+ > the *opposite* direction was unsafe — "adding `--liminis-editor-foreground:
70
+ > <default>` unconditionally would resolve every consumption chain at the
71
+ > new name before ever reaching the old one" — no longer applies once every
72
+ > internal consumption site reads `--liminis-editor-*` only (ADR-98's FR-003),
73
+ > which removes the nested legacy fallback this ADR's Decision explicitly
74
+ > chose to leave in place. This also reverses this ADR's own non-breaking
75
+ > guarantee below for one direction: a host that only *reads* a legacy name
76
+ > still gets a real value (the shim resolves fine), but a host that only
77
+ > *sets* a legacy name no longer themes the editor, since nothing internal
78
+ > reads that name to pick the override up — verified empirically, not
79
+ > assumed, and accepted as an intentional breaking change. See ADR-98's
80
+ > "Verified, not assumed" section for the full rationale. The rest of this
81
+ > section is kept as the historical record of what was decided and why in
82
+ > 2026-08-20, not edited to match the new direction.
83
+
84
+ **Legacy names keep carrying the real defaults. Each `--liminis-editor-*`
85
+ name becomes a new declaration that reads its legacy name via `var()`** —
86
+ `--liminis-editor-foreground: var(--vscode-foreground);` — added in a new
87
+ `:root`/`.dark` block placed immediately after the package's existing
88
+ `:root`/`.dark` blocks in `src/styles.css`. This is the direction the
89
+ spec's Background illustrated and Research confirmed by inspecting this
90
+ file's cascade structure directly: a CSS custom property resolves against
91
+ the *element's own* cascade at the point of use, not at declaration time,
92
+ so a single `:root`-level alias re-resolves correctly through `.dark`'s and
93
+ the `@media print` block's overrides of the *legacy* name it points to —
94
+ no per-block alias duplication is needed, and neither `.dark` nor the print
95
+ block needed to change for the ~40 tokens with a real existing declaration.
96
+
97
+ **All 59 renamed tokens (`PREVIOUS_NAME`'s full set) get a real declaration
98
+ in this pass, not only the two the originating issue named.** FR-001 says
99
+ "every token it currently consumes"; inspection of every consumption site
100
+ found 19 tokens with no existing `:root`/`.dark` legacy declaration to
101
+ alias to, not 2, split into three groups:
102
+
103
+ - **2 tokens** (`--vscode-input-bg`, `--vscode-foreground-muted`) resolve
104
+ today through an existing *nested* fallback at their own consumption
105
+ site. Their alias preserves that chain rather than flattening it:
106
+ `--liminis-editor-input-bg: var(--vscode-input-bg, var(--vscode-code-bg));`.
107
+ - **13 tokens** have no CSS custom property backing anywhere — only a
108
+ literal inline fallback at each consumption site (e.g.
109
+ `var(--vscode-focus-border, #007acc)`). Their alias reproduces that
110
+ literal as the alias's own fallback:
111
+ `--liminis-editor-focus-border: var(--vscode-focus-border, #007acc);` —
112
+ a host supplying nothing sees the same literal as before (FR-002); a
113
+ host supplying the legacy name still wins (FR-003). 6 of these 13 vary by
114
+ theme via a JS `isDark`/`dark` ternary at their consumption sites
115
+ (`button-background`, all 5 `menu-*` tokens); those 6 get a matching
116
+ `.dark` override in the new block, mirroring the same light/dark split
117
+ their consumption sites already express in JS.
118
+ - **4 tokens** (`--color-primary`, `--color-primary-100`,
119
+ `--color-muted-foreground`, `--color-muted-100`) are `liminis-app`'s own
120
+ Tailwind `@theme` brand tokens, for which the package only ever supplied
121
+ a literal final fallback, never asserted ownership. They are aliased
122
+ identically to the 13 above — the mechanism doesn't newly assert
123
+ ownership, it makes the existing fallback literal reachable under the new
124
+ name too.
125
+
126
+ **Two of the 17 no-backing tokens have a real cross-site literal
127
+ inconsistency, discovered by direct inspection (one — `--vscode-errorForeground`
128
+ — was already flagged by Specify; the second — `--vscode-toolbar-hoverBackground`
129
+ — was not):**
130
+
131
+ | Token | Sites | Canonical value chosen |
132
+ |---|---|---|
133
+ | `--vscode-errorForeground` | `#f14c4c` (2 sites: `.editor-link-broken`, `:hover`) vs. `#f44336` (2 sites: `.block-delete-button:hover .block-delete-icon`, `.search-close-button:hover`) | `#f14c4c` — tie broken toward the more semantically central "broken link" indicator over two hover-only chrome details |
134
+ | `--vscode-toolbar-hoverBackground` | `rgba(128, 128, 128, 0.15)` (2 sites) vs. `rgba(128, 128, 128, 0.1)` (1 site, `.drag-handle:hover .drag-handle-icon`) | `rgba(128, 128, 128, 0.15)` — majority (2 of 3) |
135
+
136
+ Once a token has a single real definition, it necessarily wins over every
137
+ site's own inline fallback (fallback only applies when the property is
138
+ genuinely unset) — there is no mechanism that both defines the token
139
+ (FR-001) and preserves two different resolved values at different sites.
140
+ **This unifies 3 sites' rendered color to the canonical value, a small,
141
+ real, intentional visual change**, called out here per this repository's
142
+ convention of recording what a decision costs. The three affected call
143
+ sites (`src/styles.css:1148`, `:1188`, `:1704`) each carry a one-line
144
+ comment noting they are superseded by the new canonical default. The
145
+ spec's Out of Scope section explicitly permits this as a side effect of
146
+ choosing FR-001's value, not as a design goal in its own right.
147
+
148
+ > **Amended 2026-08-22 by ADR-98 (#98) — this clause no longer holds.** Every
149
+ > consumption site's nested legacy fallback, called "provably dead code"
150
+ > below, is removed by ADR-98 — precisely because leaving it in place would
151
+ > mean this package still textually reads the legacy vocabulary ADR-98's
152
+ > FR-003 forbids. The "materially larger, coordinated change deferred out of
153
+ > this issue's scope" this paragraph anticipated is ADR-98 itself.
154
+
155
+ **Consumption sites are left untouched.** All 224 `styles.css` sites and 26
156
+ `.tsx` sites keep their existing `var(--liminis-editor-x, var(--old-name-x, ...))`
157
+ chains. Simplifying them to a single `var(--liminis-editor-x)` call was
158
+ identified as optional cleanup (spec Assumptions) but would strip every
159
+ renamed token's `immediateFallback`, breaking `resolvesToPreviousName()`
160
+ (FR-011's existing drift guard) for all 59 tokens unless that guard were
161
+ also redesigned to check the *definition* site's alias target instead —
162
+ a materially larger, coordinated change deferred out of this issue's scope.
163
+ The now-redundant nested legacy fallback at each site is provably dead code
164
+ once its definition-side alias exists, left in place rather than removed.
165
+
166
+ **`scripts/lib/theming-tokens.mjs`'s `consumptionSitesIn()` gains a
167
+ declaration-value check.** A new helper,
168
+ `isCustomPropertyDeclarationValue(text, varStartIndex)`, walks back from a
169
+ depth-0 `var(` to the nearest statement boundary (`;`, `{`, or `}`) and
170
+ tests whether everything between that boundary and the `var(` is exactly
171
+ `--name:` — the shape only a custom-property declaration's value position
172
+ can have (an ordinary CSS property name never starts with `--`). Each
173
+ site now carries an `isDeclarationValue` flag; `consumedTokens()`'s Pass 1
174
+ skips depth-0 sites where it is true. This keeps the "consumed" inventory —
175
+ and therefore the README table's row set and the `TOKEN_DESCRIPTIONS`
176
+ maintenance surface — exactly as it was before this issue, satisfying its
177
+ Out of Scope constraint, while still letting `defaultedTokens()` (a
178
+ separate, unchanged function that just scans for any `--name:` declaration)
179
+ pick up all 59 new aliases as newly "defined."
180
+
181
+ **No other change to `theming-tokens.mjs` or its `.d.mts`.**
182
+ `isCustomPropertyDeclarationValue` is internal, not exported;
183
+ `defaultedTokens()` needed no change to recognize the new aliases; the
184
+ ADR-092 baseline guard, `resolvesWithoutHost()`, and `resolvesToPreviousName()`
185
+ all continue to operate over the same "consumed"/"defined" definitions they
186
+ already had, now simply seeing a larger defined set.
187
+
188
+ ## Consequences
189
+
190
+ **Good:**
191
+
192
+ - A host can now read `--liminis-editor-x` directly and get a real value in
193
+ every override state — the entire point of this issue and the
194
+ prerequisite for zusammen#129's read migration (FR-001, SC-001, SC-006).
195
+ - A host supplying only a legacy name **at `:root`/`document.documentElement`**
196
+ is unaffected: the alias's `var()` re-resolves through that name's own
197
+ `.dark`/`@media print` overrides at the point of use, verified directly
198
+ against this file's actual cascade structure rather than assumed from the
199
+ general CSS-custom-property theory alone (FR-002, FR-003, User Story 2).
200
+ This is the standard integration point and the one this package's own
201
+ e2e coverage exercises. It does not extend to a legacy override scoped to
202
+ an arbitrary descendant element — see Bad/accepted below.
203
+ - The ADR-092 baseline guard now protects all 59 new alias declarations the
204
+ same way it already protected the legacy ones — a future accidental
205
+ deletion of an alias fails `pnpm test`, naming the token (FR-005, US4).
206
+ - The declaration-value fix to `consumptionSitesIn()` is general: it holds
207
+ for any future alias declaration of this shape without further
208
+ special-casing, not just the 59 added here.
209
+ - The premature-comment-close bug was caught by the same regenerated test
210
+ suite that caught the consumption-site pollution, before either shipped —
211
+ the CI guards this issue extends are what surfaced both problems.
212
+
213
+ **Bad / accepted:**
214
+
215
+ - **The `:root`-only alias does not track a legacy-name override scoped to
216
+ a descendant element below `:root`** (e.g. a per-widget wrapper div a
217
+ host applies its own theme vars to, rather than `document.documentElement`
218
+ itself) — found during PR review (CodeRabbit), confirmed empirically in
219
+ a running Electron app, not merely reasoned about. A custom property
220
+ inherits its parent's already-*computed* value, not the `var()`
221
+ expression that produced it, so the alias's `var(--legacy-x)` is fixed
222
+ using `:root`'s own value of `--legacy-x` and does not re-evaluate for a
223
+ change made further down the tree. Two alternatives were tried and
224
+ rejected, each empirically, in a running app:
225
+ - **Re-declaring the alias on every element (`*`)** does fix the
226
+ descendant case, but then an element's own `*`-matched declaration
227
+ shadows whatever `--liminis-editor-x` value it would otherwise have
228
+ inherited from an ancestor — including a host's own direct
229
+ `--liminis-editor-x` override set on that ancestor. This broke User
230
+ Story 3 (a directly-set `--liminis-editor-*` name must win), which is
231
+ higher-confidence/already-tested behavior — confirmed by a real,
232
+ reproducible test failure, not a theoretical concern.
233
+ - **Scoping the redeclaration to the package's own root wrapper class**
234
+ (`.editor-app-root`) reduces but does not eliminate that same
235
+ shadowing (still breaks a `--liminis-editor-*` override set above
236
+ `.editor-app-root`), and is additionally unreliable on its own terms:
237
+ not every integration renders that wrapper — this repo's own
238
+ `examples/electron` shell mounts a lower-level component without it,
239
+ so the alias would go undeclared there, confirmed by inspecting the
240
+ actual DOM in a running instance.
241
+ - No mechanism was found that recomputes a legacy-name change made
242
+ anywhere below `:root` (User Story 2) while also respecting a
243
+ `--liminis-editor-*` override set on any ancestor of the read element
244
+ (User Story 3) — CSS custom-property inheritance passes down an
245
+ already-resolved value, not a live formula, so a property can prefer
246
+ "inherit if my ancestor set one" or "always recompute from a
247
+ dependency," but not both conditionally per-element without a
248
+ mechanism this package doesn't have (e.g. `@property` plus JS
249
+ reconciliation, well beyond this issue's scope). **The `:root`-only
250
+ design is kept, and this scope limitation is accepted and documented**
251
+ here and in `src/styles.css`'s alias-block comment, per this
252
+ repository's convention of recording what a decision costs rather than
253
+ silently narrowing FR-002/FR-003's stated guarantee. Extending it to
254
+ arbitrary DOM scope, if a specific host needs it, is deferred to a
255
+ follow-up issue — it is a materially larger design problem than this
256
+ ADR anticipated.
257
+ - Three consumption sites (`--vscode-errorForeground` at 2 sites,
258
+ `--vscode-toolbar-hoverBackground` at 1 site) render a different,
259
+ intentionally-unified color than before this change. A host relying on
260
+ the specific pre-existing `#f44336`/`0.1`-alpha rendering at exactly
261
+ those sites will see a small visual shift. This is an unavoidable
262
+ consequence of giving these two tokens a single real definition (FR-001)
263
+ when their own consumption sites already disagreed with each other —
264
+ there is no mechanism that satisfies FR-001 without picking one value.
265
+ - The checked-in baseline grows from 41 to 100 entries in one change. Per
266
+ ADR-092's own design the guard is one-directional (`missing` fails,
267
+ `added` never does), so this is a mechanical `pnpm docs:theming-baseline`
268
+ diff, not a CI risk — but it is a large diff for a human reviewer to
269
+ read line-by-line.
270
+ - Every one of the 250 existing consumption sites keeps a nested legacy
271
+ fallback that is now provably redundant dead code (the definition-side
272
+ alias means the fallback branch can never be reached in practice). Removing
273
+ it was explicitly deferred (see Decision) rather than done here, so this
274
+ verbosity persists in `styles.css` and the five `.tsx` files until a
275
+ future change picks it up.
276
+ - `consumptionSitesIn()`'s declaration-value detection is a text-boundary
277
+ heuristic (nearest `;`/`{`/`}` before the `var(`), not a real CSS parser.
278
+ It is correct for every declaration in this codebase today (all
279
+ single-line), but a future multi-line custom-property declaration split
280
+ across a statement boundary could evade it.
281
+
282
+ **Neutral:**
283
+
284
+ - The `@media print` block needed no changes: its `:root, .dark { ... }`
285
+ selector already overrides every legacy name it touches, and each new
286
+ alias's `var()` reference re-resolves through that override the same way
287
+ it re-resolves through `.dark`'s.
288
+ - `liminis-app`'s own `--color-primary`/`--color-muted-foreground` overrides
289
+ are unaffected either way — the 4 brand-token aliases only make their
290
+ existing fallback literal reachable under the new name too, without
291
+ changing what either name resolves to when the host supplies its usual
292
+ overrides (SC-003).
293
+
294
+ ## References
295
+
296
+ - Issue #93 (this decision); Issue #79 / ADR-092's "Not decided here" —
297
+ this decision's direct origin (Item 3)
298
+ - `docs/decisions/adr-087.md` — the consumption-side rename and fallback
299
+ design this decision extends to the definition side, and the trap
300
+ ("that alternative is broken...") this decision's alias direction avoids
301
+ repeating on the definition side
302
+ - `docs/decisions/adr-092.md` — the checked-in baseline guard this
303
+ decision's new declarations are added to
304
+ - `src/styles.css` — the new `:root`/`.dark` alias block (after the
305
+ existing `.dark` block); the three superseded-literal comments at the
306
+ drag-handle-hover, block-delete-hover, and search-close-hover sites
307
+ - `scripts/lib/theming-tokens.mjs` — `isCustomPropertyDeclarationValue()`,
308
+ `consumptionSitesIn()`'s new `isDeclarationValue` field, and
309
+ `consumedTokens()`'s Pass 1 filter
310
+ - `scripts/lib/theming-defined-tokens-baseline.json` — regenerated via
311
+ `pnpm docs:theming-baseline`, 41 → 100 entries
312
+ - `README.md`, "Theming: CSS custom properties" — regenerated table and
313
+ hand-rewritten prose describing the alias mechanism
314
+ - [verveguy/zusammen#129](https://github.com/verveguy/zusammen/issues/129)
315
+ — the downstream migration this decision unblocks