@liminis/editor 0.2.1 → 0.3.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 CHANGED
@@ -43,12 +43,20 @@ produce a broken editor context — peering is what prevents both.
43
43
 
44
44
  ```bash
45
45
  pnpm add react@^19 react-dom@^19 \
46
- lexical@^0.44 @lexical/react@^0.44 @lexical/code@^0.44 \
47
- @lexical/code-prism@^0.44 @lexical/link@^0.44 @lexical/list@^0.44 \
48
- @lexical/mark@^0.44 @lexical/markdown@^0.44 @lexical/rich-text@^0.44 \
49
- @lexical/selection@^0.44 @lexical/table@^0.44 @lexical/utils@^0.44
46
+ lexical@^0.49 @lexical/react@^0.49 @lexical/code@^0.49 \
47
+ @lexical/code-prism@^0.49 @lexical/link@^0.49 @lexical/list@^0.49 \
48
+ @lexical/mark@^0.49 @lexical/markdown@^0.49 @lexical/rich-text@^0.49 \
49
+ @lexical/selection@^0.49 @lexical/table@^0.49 @lexical/utils@^0.49
50
50
  ```
51
51
 
52
+ > **The Lexical peer ranges are single-caret, not a wide band.** Lexical is
53
+ > also `0.x`, where every minor is permitted to break — unlike React's range
54
+ > above, which spans an untested-but-stable range, the Lexical range names
55
+ > exactly the one minor this package is built and tested against. Expect it
56
+ > to move forward (e.g. to `^0.50`) only after this package has adopted and
57
+ > tested that release, not automatically at every upstream release. See
58
+ > `docs/decisions/adr-92-lexical-peer-range-policy.md`.
59
+
52
60
  pnpm v8+ and npm v7+ install peers automatically, so in practice the first
53
61
  command is usually enough.
54
62
 
@@ -199,7 +207,25 @@ Neither is a package defect, but only one of them tells you what is wrong.
199
207
  The editor's colors, borders and a few layout metrics are all CSS custom
200
208
  properties under a single package-owned vocabulary, `--liminis-editor-*`.
201
209
  Every one of them resolves with no host configuration at all — the table
202
- below exists so you can *override* a palette, not so you can complete one.
210
+ below exists so you can *override* a palette, and also so you can *consume*
211
+ one: every property this package declares with a value anywhere in
212
+ `styles.css` is part of this package's public API, readable directly by a
213
+ host, not only overridable. That now includes every `--liminis-editor-*` name
214
+ itself (see "Has a default" below) as well as the pre-`0.2.0` names that still
215
+ carry the real defaults (`--vscode-*`, `--slashmd-*`, `--checkbox-*`, and a
216
+ couple of standalone legacy names) — each `--liminis-editor-*` declaration is
217
+ an alias of its previous name (`--liminis-editor-x: var(--previous-name-x);`),
218
+ so both names resolve to the same value and either can be read directly
219
+ (ADR-93). (Legacy names like `--color-*` that appear only as an inner link in
220
+ a fallback chain, with no `:root`/`.dark` declaration of their own, are not
221
+ part of the defined set this paragraph describes — only the property that
222
+ *carries* the value is.) Some hosts read these definitions directly — mapping
223
+ their own design tokens onto them with `var(--x)` — rather than overriding
224
+ them. Renaming or removing a definition is therefore a breaking change, the
225
+ same as any other change to a supported API surface described under
226
+ "Versioning policy" above, and is distinct from renaming a *consumption* site
227
+ (covered separately in "Migrating from the old names" below) — this package's
228
+ CI guards against a definition disappearing unintentionally (ADR-092).
203
229
 
204
230
  **Previous name** is the pre-`0.2.0` name this property replaced — the exact
205
231
  name to look for if you're migrating a host that still overrides the old
@@ -207,82 +233,92 @@ vocabulary (`—` for a property that was never renamed). **Controls** is a
207
233
  short description of what the token actually affects. **Kind** distinguishes
208
234
  tokens that only affect appearance (`Cosmetic`) from the handful that also
209
235
  affect layout (`Structural` — heading indents and the base font). **Has a
210
- default** marks tokens with a `--liminis-editor-*` `:root`/`.dark`
211
- declaration in `styles.css` directly; every row below reads "No (inline
212
- fallback only)" because each token instead resolves through a fallback chain
213
- `--liminis-editor-x` first, then its previous name (which still carries the
214
- real `:root`/`.dark` default), so a host supplying only the old name keeps
215
- theming unchanged. See "Migrating from the old names" below.
236
+ default** marks tokens declared with a value in `styles.css` directly; every
237
+ row below now reads "Yes" each `--liminis-editor-*` name carries its own
238
+ `:root`/`.dark` declaration, an alias that reads its previous name via `var()`
239
+ (ADR-93), so a host reading `--liminis-editor-x` gets a real value with no
240
+ configuration, and a host overriding only the previous name at
241
+ `:root`/`document.documentElement` still wins, because the alias re-resolves
242
+ through that name's own `.dark`/`@media print` overrides at the point of use
243
+ (ADR-93 documents a scope limitation for an override applied to a descendant
244
+ element instead). See "Migrating from the old names" below.
216
245
 
217
246
  This table is generated from `src/` by `node scripts/generate-theming-docs.mjs`
218
247
  and checked for staleness in CI (`tests/theming-contract.test.ts`) — a `var(--x)`
219
248
  added to the source without regenerating this block fails the build. Do not
220
249
  hand-edit the rows between the markers below. See ADR-085 for how the
221
- generation and drift guard work, and ADR-087 for the rename and its
222
- compatibility design.
250
+ generation and drift guard work, ADR-087 for the rename and its compatibility
251
+ design, ADR-092 for the separate guard that protects the full *defined* set —
252
+ every token declared with a value in `styles.css`, per the checked-in baseline
253
+ (`scripts/lib/theming-defined-tokens-baseline.json`) — against an
254
+ unintentional rename or removal (run `pnpm docs:theming-baseline` after a
255
+ deliberate one), and ADR-93 for the `--liminis-editor-*` alias declarations
256
+ that make the defined set (baseline) a superset of this table's rows —
257
+ every consumed `--liminis-editor-*` name is now also a defined one — rather
258
+ than the disjoint sets they were before.
223
259
 
224
260
  <!-- theming-tokens:start -->
225
261
  | Custom property | Previous name | Controls | Kind | Has a default |
226
262
  | --- | --- | --- | --- | --- |
227
- | `--liminis-editor-background` | `--vscode-background` | Base background color of the editor surface and its popovers/menus. | Cosmetic | No (inline fallback only) |
228
- | `--liminis-editor-bold-color` | `--slashmd-bold-color` | Text color of bold (`**text**`) markdown spans. | Cosmetic | No (inline fallback only) |
229
- | `--liminis-editor-border` | `--vscode-border` | Default border color used throughout the editor chrome. | Cosmetic | No (inline fallback only) |
230
- | `--liminis-editor-button-background` | `--vscode-button-background` | Background of primary action buttons (e.g. the correction panel's "Apply" button). | Cosmetic | No (inline fallback only) |
231
- | `--liminis-editor-button-foreground` | `--vscode-button-foreground` | Text color of primary action buttons. | Cosmetic | No (inline fallback only) |
232
- | `--liminis-editor-callout-caution-bg` | `--slashmd-callout-caution-bg` | Background of "caution" callout blocks. | Cosmetic | No (inline fallback only) |
233
- | `--liminis-editor-callout-caution-border` | `--slashmd-callout-caution-border` | Left border accent of "caution" callout blocks. | Cosmetic | No (inline fallback only) |
234
- | `--liminis-editor-callout-important-bg` | `--slashmd-callout-important-bg` | Background of "important" callout blocks. | Cosmetic | No (inline fallback only) |
235
- | `--liminis-editor-callout-important-border` | `--slashmd-callout-important-border` | Left border accent of "important" callout blocks. | Cosmetic | No (inline fallback only) |
236
- | `--liminis-editor-callout-note-bg` | `--slashmd-callout-note-bg` | Background of "note" callout blocks. | Cosmetic | No (inline fallback only) |
237
- | `--liminis-editor-callout-note-border` | `--slashmd-callout-note-border` | Left border accent of "note" callout blocks. | Cosmetic | No (inline fallback only) |
238
- | `--liminis-editor-callout-tip-bg` | `--slashmd-callout-tip-bg` | Background of "tip" callout blocks. | Cosmetic | No (inline fallback only) |
239
- | `--liminis-editor-callout-tip-border` | `--slashmd-callout-tip-border` | Left border accent of "tip" callout blocks. | Cosmetic | No (inline fallback only) |
240
- | `--liminis-editor-callout-warning-bg` | `--slashmd-callout-warning-bg` | Background of "warning" callout blocks. | Cosmetic | No (inline fallback only) |
241
- | `--liminis-editor-callout-warning-border` | `--slashmd-callout-warning-border` | Left border accent of "warning" callout blocks. | Cosmetic | No (inline fallback only) |
242
- | `--liminis-editor-checkbox-border` | `--checkbox-border` | Border color of unchecked task-list checkboxes. | Cosmetic | No (inline fallback only) |
243
- | `--liminis-editor-code-bg` | `--vscode-code-bg` | Background of inline code spans and fenced code blocks. | Cosmetic | No (inline fallback only) |
244
- | `--liminis-editor-errorForeground` | `--vscode-errorForeground` | Text color for error and validation messages. | Cosmetic | No (inline fallback only) |
245
- | `--liminis-editor-external-link` | `--vscode-external-link` | Text color of links that point outside the document. | Cosmetic | No (inline fallback only) |
246
- | `--liminis-editor-focus-border` | `--vscode-focus-border` | Border color of the toolbar link-input field when focused. | Cosmetic | No (inline fallback only) |
247
- | `--liminis-editor-focusBorder` | `--vscode-focusBorder` | Color of the drag-and-drop position indicator while reordering blocks. | Cosmetic | No (inline fallback only) |
248
- | `--liminis-editor-font-family` | `--vscode-font-family` | Base font family for the editor content. | Structural | No (inline fallback only) |
249
- | `--liminis-editor-font-size` | `--vscode-font-size` | Base font size for the editor content. | Structural | No (inline fallback only) |
250
- | `--liminis-editor-foreground` | `--vscode-foreground` | Default text color throughout the editor. | Cosmetic | No (inline fallback only) |
251
- | `--liminis-editor-foreground-muted` | `--vscode-foreground-muted` | Hover border color for the frontmatter tray's raw-view toggle. | Cosmetic | No (inline fallback only) |
252
- | `--liminis-editor-h1-color` | `--slashmd-h1-color` | Text color of level-1 (`#`) headings. | Cosmetic | No (inline fallback only) |
253
- | `--liminis-editor-h1-indent` | `--slashmd-h1-indent` | Left margin of level-1 (`#`) headings. | Structural | No (inline fallback only) |
254
- | `--liminis-editor-h2-color` | `--slashmd-h2-color` | Text color of level-2 (`##`) headings. | Cosmetic | No (inline fallback only) |
255
- | `--liminis-editor-h2-indent` | `--slashmd-h2-indent` | Left margin of level-2 (`##`) headings. | Structural | No (inline fallback only) |
256
- | `--liminis-editor-h3-color` | `--slashmd-h3-color` | Text color of level-3 (`###`) headings. | Cosmetic | No (inline fallback only) |
257
- | `--liminis-editor-h3-indent` | `--slashmd-h3-indent` | Left margin of level-3 (`###`) headings. | Structural | No (inline fallback only) |
258
- | `--liminis-editor-h4-color` | `--slashmd-h4-color` | Text color of level-4 (`####`) headings. | Cosmetic | No (inline fallback only) |
259
- | `--liminis-editor-h4-indent` | `--slashmd-h4-indent` | Left margin of level-4 (`####`) headings. | Structural | No (inline fallback only) |
260
- | `--liminis-editor-h5-color` | `--slashmd-h5-color` | Text color of level-5 (`#####`) headings. | Cosmetic | No (inline fallback only) |
261
- | `--liminis-editor-h5-indent` | `--slashmd-h5-indent` | Left margin of level-5 (`#####`) headings. | Structural | No (inline fallback only) |
262
- | `--liminis-editor-input-bg` | `--vscode-input-bg` | Background of the toolbar link-input field. | Cosmetic | No (inline fallback only) |
263
- | `--liminis-editor-inputValidation-errorBackground` | `--vscode-inputValidation-errorBackground` | Background of inline validation-error messages. | Cosmetic | No (inline fallback only) |
264
- | `--liminis-editor-italic-color` | `--slashmd-italic-color` | Text color of italic (`*text*`) markdown spans. | Cosmetic | No (inline fallback only) |
265
- | `--liminis-editor-link` | `--vscode-link` | Text color of in-document links. | Cosmetic | No (inline fallback only) |
266
- | `--liminis-editor-menu-background` | `--vscode-menu-background` | Background of context menus (block, selection and correction menus). | Cosmetic | No (inline fallback only) |
267
- | `--liminis-editor-menu-border` | `--vscode-menu-border` | Border color of context menus. | Cosmetic | No (inline fallback only) |
268
- | `--liminis-editor-menu-foreground` | `--vscode-menu-foreground` | Text color of context menu items. | Cosmetic | No (inline fallback only) |
269
- | `--liminis-editor-menu-selectionBackground` | `--vscode-menu-selectionBackground` | Background of a hovered/selected context menu item. | Cosmetic | No (inline fallback only) |
270
- | `--liminis-editor-menu-separatorBackground` | `--vscode-menu-separatorBackground` | Color of separator lines inside context menus. | Cosmetic | No (inline fallback only) |
271
- | `--liminis-editor-muted-100` | `--color-muted-100` | Background of the C4 diagram layout-toggle buttons when inactive. | Cosmetic | No (inline fallback only) |
272
- | `--liminis-editor-muted-foreground` | `--color-muted-foreground` | Icon/text color of the C4 diagram layout-toggle buttons when inactive. | Cosmetic | No (inline fallback only) |
273
- | `--liminis-editor-notificationsInfoIcon-foreground` | `--vscode-notificationsInfoIcon-foreground` | Color of informational icons in inline notifications. | Cosmetic | No (inline fallback only) |
274
- | `--liminis-editor-primary` | `--color-primary` | Icon/text color of the C4 diagram layout-toggle buttons when active. | Cosmetic | No (inline fallback only) |
275
- | `--liminis-editor-primary-100` | `--color-primary-100` | Background of the C4 diagram layout-toggle buttons when active. | Cosmetic | No (inline fallback only) |
276
- | `--liminis-editor-selection` | `--vscode-selection` | Background color of selected/highlighted text. | Cosmetic | No (inline fallback only) |
277
- | `--liminis-editor-token-comment` | `--slashmd-token-comment` | Syntax-highlight color for comments in fenced code blocks. | Cosmetic | No (inline fallback only) |
278
- | `--liminis-editor-token-function` | `--slashmd-token-function` | Syntax-highlight color for function names in fenced code blocks. | Cosmetic | No (inline fallback only) |
279
- | `--liminis-editor-token-keyword` | `--slashmd-token-keyword` | Syntax-highlight color for keywords in fenced code blocks. | Cosmetic | No (inline fallback only) |
280
- | `--liminis-editor-token-operator` | `--slashmd-token-operator` | Syntax-highlight color for operators in fenced code blocks. | Cosmetic | No (inline fallback only) |
281
- | `--liminis-editor-token-property` | `--slashmd-token-property` | Syntax-highlight color for object properties in fenced code blocks. | Cosmetic | No (inline fallback only) |
282
- | `--liminis-editor-token-punctuation` | `--slashmd-token-punctuation` | Syntax-highlight color for punctuation in fenced code blocks. | Cosmetic | No (inline fallback only) |
283
- | `--liminis-editor-token-selector` | `--slashmd-token-selector` | Syntax-highlight color for CSS selectors in fenced code blocks. | Cosmetic | No (inline fallback only) |
284
- | `--liminis-editor-token-variable` | `--slashmd-token-variable` | Syntax-highlight color for variables in fenced code blocks. | Cosmetic | No (inline fallback only) |
285
- | `--liminis-editor-toolbar-hoverBackground` | `--vscode-toolbar-hoverBackground` | Background of a toolbar button on hover. | Cosmetic | No (inline fallback only) |
263
+ | `--liminis-editor-background` | `--vscode-background` | Base background color of the editor surface and its popovers/menus. | Cosmetic | Yes |
264
+ | `--liminis-editor-bold-color` | `--slashmd-bold-color` | Text color of bold (`**text**`) markdown spans. | Cosmetic | Yes |
265
+ | `--liminis-editor-border` | `--vscode-border` | Default border color used throughout the editor chrome. | Cosmetic | Yes |
266
+ | `--liminis-editor-button-background` | `--vscode-button-background` | Background of primary action buttons (e.g. the correction panel's "Apply" button). | Cosmetic | Yes |
267
+ | `--liminis-editor-button-foreground` | `--vscode-button-foreground` | Text color of primary action buttons. | Cosmetic | Yes |
268
+ | `--liminis-editor-callout-caution-bg` | `--slashmd-callout-caution-bg` | Background of "caution" callout blocks. | Cosmetic | Yes |
269
+ | `--liminis-editor-callout-caution-border` | `--slashmd-callout-caution-border` | Left border accent of "caution" callout blocks. | Cosmetic | Yes |
270
+ | `--liminis-editor-callout-important-bg` | `--slashmd-callout-important-bg` | Background of "important" callout blocks. | Cosmetic | Yes |
271
+ | `--liminis-editor-callout-important-border` | `--slashmd-callout-important-border` | Left border accent of "important" callout blocks. | Cosmetic | Yes |
272
+ | `--liminis-editor-callout-note-bg` | `--slashmd-callout-note-bg` | Background of "note" callout blocks. | Cosmetic | Yes |
273
+ | `--liminis-editor-callout-note-border` | `--slashmd-callout-note-border` | Left border accent of "note" callout blocks. | Cosmetic | Yes |
274
+ | `--liminis-editor-callout-tip-bg` | `--slashmd-callout-tip-bg` | Background of "tip" callout blocks. | Cosmetic | Yes |
275
+ | `--liminis-editor-callout-tip-border` | `--slashmd-callout-tip-border` | Left border accent of "tip" callout blocks. | Cosmetic | Yes |
276
+ | `--liminis-editor-callout-warning-bg` | `--slashmd-callout-warning-bg` | Background of "warning" callout blocks. | Cosmetic | Yes |
277
+ | `--liminis-editor-callout-warning-border` | `--slashmd-callout-warning-border` | Left border accent of "warning" callout blocks. | Cosmetic | Yes |
278
+ | `--liminis-editor-checkbox-border` | `--checkbox-border` | Border color of unchecked task-list checkboxes. | Cosmetic | Yes |
279
+ | `--liminis-editor-code-bg` | `--vscode-code-bg` | Background of inline code spans and fenced code blocks. | Cosmetic | Yes |
280
+ | `--liminis-editor-errorForeground` | `--vscode-errorForeground` | Text color for error and validation messages. | Cosmetic | Yes |
281
+ | `--liminis-editor-external-link` | `--vscode-external-link` | Text color of links that point outside the document. | Cosmetic | Yes |
282
+ | `--liminis-editor-focus-border` | `--vscode-focus-border` | Border color of the toolbar link-input field when focused. | Cosmetic | Yes |
283
+ | `--liminis-editor-focusBorder` | `--vscode-focusBorder` | Color of the drag-and-drop position indicator while reordering blocks. | Cosmetic | Yes |
284
+ | `--liminis-editor-font-family` | `--vscode-font-family` | Base font family for the editor content. | Structural | Yes |
285
+ | `--liminis-editor-font-size` | `--vscode-font-size` | Base font size for the editor content. | Structural | Yes |
286
+ | `--liminis-editor-foreground` | `--vscode-foreground` | Default text color throughout the editor. | Cosmetic | Yes |
287
+ | `--liminis-editor-foreground-muted` | `--vscode-foreground-muted` | Hover border color for the frontmatter tray's raw-view toggle. | Cosmetic | Yes |
288
+ | `--liminis-editor-h1-color` | `--slashmd-h1-color` | Text color of level-1 (`#`) headings. | Cosmetic | Yes |
289
+ | `--liminis-editor-h1-indent` | `--slashmd-h1-indent` | Left margin of level-1 (`#`) headings. | Structural | Yes |
290
+ | `--liminis-editor-h2-color` | `--slashmd-h2-color` | Text color of level-2 (`##`) headings. | Cosmetic | Yes |
291
+ | `--liminis-editor-h2-indent` | `--slashmd-h2-indent` | Left margin of level-2 (`##`) headings. | Structural | Yes |
292
+ | `--liminis-editor-h3-color` | `--slashmd-h3-color` | Text color of level-3 (`###`) headings. | Cosmetic | Yes |
293
+ | `--liminis-editor-h3-indent` | `--slashmd-h3-indent` | Left margin of level-3 (`###`) headings. | Structural | Yes |
294
+ | `--liminis-editor-h4-color` | `--slashmd-h4-color` | Text color of level-4 (`####`) headings. | Cosmetic | Yes |
295
+ | `--liminis-editor-h4-indent` | `--slashmd-h4-indent` | Left margin of level-4 (`####`) headings. | Structural | Yes |
296
+ | `--liminis-editor-h5-color` | `--slashmd-h5-color` | Text color of level-5 (`#####`) headings. | Cosmetic | Yes |
297
+ | `--liminis-editor-h5-indent` | `--slashmd-h5-indent` | Left margin of level-5 (`#####`) headings. | Structural | Yes |
298
+ | `--liminis-editor-input-bg` | `--vscode-input-bg` | Background of the toolbar link-input field. | Cosmetic | Yes |
299
+ | `--liminis-editor-inputValidation-errorBackground` | `--vscode-inputValidation-errorBackground` | Background of inline validation-error messages. | Cosmetic | Yes |
300
+ | `--liminis-editor-italic-color` | `--slashmd-italic-color` | Text color of italic (`*text*`) markdown spans. | Cosmetic | Yes |
301
+ | `--liminis-editor-link` | `--vscode-link` | Text color of in-document links. | Cosmetic | Yes |
302
+ | `--liminis-editor-menu-background` | `--vscode-menu-background` | Background of context menus (block, selection and correction menus). | Cosmetic | Yes |
303
+ | `--liminis-editor-menu-border` | `--vscode-menu-border` | Border color of context menus. | Cosmetic | Yes |
304
+ | `--liminis-editor-menu-foreground` | `--vscode-menu-foreground` | Text color of context menu items. | Cosmetic | Yes |
305
+ | `--liminis-editor-menu-selectionBackground` | `--vscode-menu-selectionBackground` | Background of a hovered/selected context menu item. | Cosmetic | Yes |
306
+ | `--liminis-editor-menu-separatorBackground` | `--vscode-menu-separatorBackground` | Color of separator lines inside context menus. | Cosmetic | Yes |
307
+ | `--liminis-editor-muted-100` | `--color-muted-100` | Background of the C4 diagram layout-toggle buttons when inactive. | Cosmetic | Yes |
308
+ | `--liminis-editor-muted-foreground` | `--color-muted-foreground` | Icon/text color of the C4 diagram layout-toggle buttons when inactive. | Cosmetic | Yes |
309
+ | `--liminis-editor-notificationsInfoIcon-foreground` | `--vscode-notificationsInfoIcon-foreground` | Color of informational icons in inline notifications. | Cosmetic | Yes |
310
+ | `--liminis-editor-primary` | `--color-primary` | Icon/text color of the C4 diagram layout-toggle buttons when active. | Cosmetic | Yes |
311
+ | `--liminis-editor-primary-100` | `--color-primary-100` | Background of the C4 diagram layout-toggle buttons when active. | Cosmetic | Yes |
312
+ | `--liminis-editor-selection` | `--vscode-selection` | Background color of selected/highlighted text. | Cosmetic | Yes |
313
+ | `--liminis-editor-token-comment` | `--slashmd-token-comment` | Syntax-highlight color for comments in fenced code blocks. | Cosmetic | Yes |
314
+ | `--liminis-editor-token-function` | `--slashmd-token-function` | Syntax-highlight color for function names in fenced code blocks. | Cosmetic | Yes |
315
+ | `--liminis-editor-token-keyword` | `--slashmd-token-keyword` | Syntax-highlight color for keywords in fenced code blocks. | Cosmetic | Yes |
316
+ | `--liminis-editor-token-operator` | `--slashmd-token-operator` | Syntax-highlight color for operators in fenced code blocks. | Cosmetic | Yes |
317
+ | `--liminis-editor-token-property` | `--slashmd-token-property` | Syntax-highlight color for object properties in fenced code blocks. | Cosmetic | Yes |
318
+ | `--liminis-editor-token-punctuation` | `--slashmd-token-punctuation` | Syntax-highlight color for punctuation in fenced code blocks. | Cosmetic | Yes |
319
+ | `--liminis-editor-token-selector` | `--slashmd-token-selector` | Syntax-highlight color for CSS selectors in fenced code blocks. | Cosmetic | Yes |
320
+ | `--liminis-editor-token-variable` | `--slashmd-token-variable` | Syntax-highlight color for variables in fenced code blocks. | Cosmetic | Yes |
321
+ | `--liminis-editor-toolbar-hoverBackground` | `--vscode-toolbar-hoverBackground` | Background of a toolbar button on hover. | Cosmetic | Yes |
286
322
  <!-- theming-tokens:end -->
287
323
 
288
324
  **A worked example.** To match a host's own palette, override a subset of
@@ -326,6 +362,25 @@ prefix swap (`--checkbox-border` became `--liminis-editor-checkbox-border`,
326
362
  not `--liminis-editor-border`, to avoid colliding with `--vscode-border`'s
327
363
  new name), so look it up rather than deriving it.
328
364
 
365
+ Reading, not just overriding, is also migratable: a host that currently reads
366
+ a legacy name directly (e.g. mapping its own design tokens onto
367
+ `--vscode-foreground`) can switch that read to the corresponding
368
+ `--liminis-editor-*` name and get an identical value, **as long as it isn't
369
+ also setting the `--liminis-editor-*` name itself** — a directly-set
370
+ `--liminis-editor-*` value always wins over the previous name's own value,
371
+ by ordinary cascade precedence (see "Has a default" below), so the two reads
372
+ are identical only while the host relies solely on the previous name. Until
373
+ ADR-93, `--liminis-editor-*` names were only ever *consumed*, never
374
+ themselves declared with a value — there was nothing for such a host to
375
+ migrate its reads to. Every `--liminis-editor-*` name now carries its own
376
+ `:root`/`.dark` alias declaration that resolves to the previous name's own
377
+ value in that legacy-only case, so the read migration is a like-for-like
378
+ swap with no observable difference (see verveguy/zusammen#129, the migration
379
+ this addresses). This guarantee holds for a previous-name override applied
380
+ at `:root`/`document.documentElement`, the standard integration point; see
381
+ ADR-93 for a documented limitation when a host scopes its override to a
382
+ descendant element instead.
383
+
329
384
  ## Annotations
330
385
 
331
386
  An annotation is a range-anchored marker over document text whose anchor
@@ -1,9 +1,18 @@
1
- import type { DocumentOutlineHandle } from './documentOutlineHandle.js';
1
+ import type { DocumentOutlineHandle, OutlineEntry } from './documentOutlineHandle.js';
2
2
  export interface DocumentOutlineProps {
3
3
  /** The controller created by `createDocumentOutlineHandle()` and also passed to `<Editor documentOutlineHandle={…}>`. */
4
4
  handle: DocumentOutlineHandle;
5
5
  /** Additional class name on the outer `<nav>`, for host placement/layout (FR-008). */
6
6
  className?: string;
7
+ /**
8
+ * Fires with the clicked entry in addition to `handle.scrollToHeading`.
9
+ * `handle.scrollToHeading` no-ops when no Lexical editor is connected
10
+ * (the markdown-derived/raw-mode path, issue #84) — a raw-mode host has no
11
+ * other way to react to a click, since it doesn't render the entry list
12
+ * itself, so this is how it navigates its own (non-Lexical) editor via
13
+ * `entry.line`.
14
+ */
15
+ onEntrySelect?: (entry: OutlineEntry) => void;
7
16
  }
8
17
  /**
9
18
  * Renders a navigable "on this page" heading outline for one editor
@@ -14,4 +23,4 @@ export interface DocumentOutlineProps {
14
23
  * placement, width-gating, and collapse/persistence are the host's concern
15
24
  * (FR-008) — this component only renders the list and reacts to the handle.
16
25
  */
17
- export declare function DocumentOutline({ handle, className }: DocumentOutlineProps): import("react").JSX.Element | null;
26
+ export declare function DocumentOutline({ handle, className, onEntrySelect }: DocumentOutlineProps): import("react").JSX.Element | null;
@@ -9,7 +9,7 @@ import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';
9
9
  * placement, width-gating, and collapse/persistence are the host's concern
10
10
  * (FR-008) — this component only renders the list and reacts to the handle.
11
11
  */
12
- export function DocumentOutline({ handle, className }) {
12
+ export function DocumentOutline({ handle, className, onEntrySelect }) {
13
13
  // Wrapped rather than passed directly: `handle.subscribe`/`handle.getSnapshot`
14
14
  // are unbound method references off a plain interface value.
15
15
  const subscribe = useCallback((onStoreChange) => handle.subscribe(onStoreChange), [handle]);
@@ -27,6 +27,9 @@ export function DocumentOutline({ handle, className }) {
27
27
  return null;
28
28
  return (_jsx("nav", { className: className ? `editor-outline ${className}` : 'editor-outline', "aria-label": "Document outline", children: _jsx("ul", { className: "editor-outline-list", ref: listRef, children: snapshot.entries.map((entry) => {
29
29
  const isActive = entry.index === snapshot.activeIndex;
30
- return (_jsx("li", { "data-outline-index": entry.index, "data-outline-level": entry.level, className: isActive ? 'editor-outline-item editor-outline-item-active' : 'editor-outline-item', children: _jsxs("button", { type: "button", className: "editor-outline-item-button", "aria-current": isActive ? 'location' : undefined, onClick: () => handle.scrollToHeading(entry.index), children: [_jsx("span", { className: "editor-outline-indicator", "aria-hidden": "true" }), _jsx("span", { className: "editor-outline-item-text", children: entry.text })] }) }, entry.index));
30
+ return (_jsx("li", { "data-outline-index": entry.index, "data-outline-level": entry.level, className: isActive ? 'editor-outline-item editor-outline-item-active' : 'editor-outline-item', children: _jsxs("button", { type: "button", className: "editor-outline-item-button", "aria-current": isActive ? 'location' : undefined, onClick: () => {
31
+ handle.scrollToHeading(entry.index);
32
+ onEntrySelect?.(entry);
33
+ }, children: [_jsx("span", { className: "editor-outline-indicator", "aria-hidden": "true" }), _jsx("span", { className: "editor-outline-item-text", children: entry.text })] }) }, entry.index));
31
34
  }) }) }));
32
35
  }
@@ -240,6 +240,20 @@ function CursorRestorePlugin({ contentVersion, cursorToRestoreRef, }) {
240
240
  const cursorToRestore = cursorToRestoreRef.current;
241
241
  if (!cursorToRestore)
242
242
  return;
243
+ // Only restore the caret if the user was actually in the editor when the
244
+ // reload arrived. A content reload is not always the user's doing: an
245
+ // agent writing the open file reaches here too, and re-focusing then pulls
246
+ // the caret out of whatever the user is typing in — a chat prompt beside
247
+ // the editor, say — mid-sentence. Restoring a caret nobody is looking at
248
+ // buys nothing, so an unfocused editor is left entirely alone (no
249
+ // selection write either, so reconciliation cannot move focus as a side
250
+ // effect). Same rule as `AutoFocusPlugin`: never take focus unasked.
251
+ // Read synchronously, before the update below can perturb focus.
252
+ const rootElement = editor.getRootElement();
253
+ const hadFocus = rootElement !== null &&
254
+ (rootElement === document.activeElement || rootElement.contains(document.activeElement));
255
+ if (!hadFocus)
256
+ return;
243
257
  console.log('[CursorRestorePlugin] Restoring cursor', {
244
258
  contentVersion,
245
259
  offset: cursorToRestore.offset,
@@ -262,11 +276,9 @@ function CursorRestorePlugin({ contentVersion, cursorToRestoreRef, }) {
262
276
  console.log('[CursorRestorePlugin] Selection restored at offset', newOffset);
263
277
  }
264
278
  });
265
- // Re-focus the editor
266
- const rootElement = editor.getRootElement();
267
- if (rootElement) {
268
- rootElement.focus({ preventScroll: true });
269
- }
279
+ // Re-focus the editor. Reached only when it already had focus above, so
280
+ // this restores focus the reload disturbed rather than taking it.
281
+ rootElement.focus({ preventScroll: true });
270
282
  });
271
283
  }, [editor, contentVersion, cursorToRestoreRef]);
272
284
  return null;
@@ -23,6 +23,13 @@ export interface OutlineEntry {
23
23
  level: 1 | 2 | 3 | 4 | 5;
24
24
  /** Heading text with inline content (including inline code) reduced to plain text. */
25
25
  text: string;
26
+ /**
27
+ * 1-based source line of the heading, matching mdast's
28
+ * `position.start.line`. Only populated on markdown-derived entries (see
29
+ * `documentOutlineMarkdown.ts`) — the live-Lexical path has no source-line
30
+ * mapping to supply, so it leaves this unset.
31
+ */
32
+ line?: number;
26
33
  }
27
34
  export interface DocumentOutlineSnapshot {
28
35
  entries: OutlineEntry[];
@@ -53,6 +60,21 @@ export interface DocumentOutlineHandle {
53
60
  getSnapshot(): DocumentOutlineSnapshot;
54
61
  /** Scroll the editor to the heading at `index`. No-ops if no editor is currently connected, or `index` is out of range. */
55
62
  scrollToHeading(index: number): void;
63
+ /**
64
+ * Derive entries from `markdown` (see `deriveOutlineFromMarkdown` in
65
+ * `documentOutlineMarkdown.ts`) and publish them — the raw-mode
66
+ * counterpart to `OutlinePlugin` feeding entries from live Lexical state.
67
+ * Call this on every content change in a host with no mounted Lexical
68
+ * editor (issue #84).
69
+ */
70
+ publishFromMarkdown(markdown: string): void;
71
+ /**
72
+ * Resolve `line` against the entries from the most recent
73
+ * `publishFromMarkdown` call and publish the corresponding active index —
74
+ * the raw-mode counterpart to `OutlinePlugin`'s scroll-spy. Pass `null`
75
+ * for "no active heading yet" (e.g. before first scroll).
76
+ */
77
+ setActiveLine(line: number | null): void;
56
78
  }
57
79
  /**
58
80
  * The concrete object `createDocumentOutlineHandle()` returns. Consumers only
@@ -66,9 +88,15 @@ export declare class OutlineHandleImpl implements DocumentOutlineHandle {
66
88
  private snapshot;
67
89
  private readonly listeners;
68
90
  private scrollImpl;
91
+ /** Entries from the most recent `publishFromMarkdown` call, so `setActiveLine` can resolve against them independently. */
92
+ private markdownEntries;
93
+ /** Last line passed to `setActiveLine`, kept so a subsequent `publishFromMarkdown` re-resolves against it. */
94
+ private lastActiveLine;
69
95
  subscribe: (onStoreChange: () => void) => (() => void);
70
96
  getSnapshot: () => DocumentOutlineSnapshot;
71
97
  scrollToHeading: (index: number) => void;
98
+ publishFromMarkdown: (markdown: string) => void;
99
+ setActiveLine: (line: number | null) => void;
72
100
  /** Called by `OutlinePlugin` while its editor is mounted. */
73
101
  connect(scrollImpl: (index: number) => void): void;
74
102
  /** Called by `OutlinePlugin` on unmount, so a stale handle no-ops rather than scrolling a torn-down editor. */
@@ -11,6 +11,7 @@
11
11
  * instance — it is naturally scoped to that instance, so mounting more than
12
12
  * one editor at once never aggregates headings across them.
13
13
  */
14
+ import { deriveOutlineFromMarkdown, resolveActiveOutlineIndex } from './documentOutlineMarkdown.js';
14
15
  /**
15
16
  * Not exported, so nothing outside this module can reference it. Its only
16
17
  * purpose is to make `DocumentOutlineHandle` nominal instead of structural:
@@ -30,7 +31,7 @@ function snapshotsEqual(a, b) {
30
31
  for (let i = 0; i < a.entries.length; i++) {
31
32
  const x = a.entries[i];
32
33
  const y = b.entries[i];
33
- if (x.index !== y.index || x.level !== y.level || x.text !== y.text)
34
+ if (x.index !== y.index || x.level !== y.level || x.text !== y.text || x.line !== y.line)
34
35
  return false;
35
36
  }
36
37
  return true;
@@ -47,6 +48,10 @@ export class OutlineHandleImpl {
47
48
  snapshot = EMPTY_SNAPSHOT;
48
49
  listeners = new Set();
49
50
  scrollImpl = null;
51
+ /** Entries from the most recent `publishFromMarkdown` call, so `setActiveLine` can resolve against them independently. */
52
+ markdownEntries = [];
53
+ /** Last line passed to `setActiveLine`, kept so a subsequent `publishFromMarkdown` re-resolves against it. */
54
+ lastActiveLine = null;
50
55
  subscribe = (onStoreChange) => {
51
56
  this.listeners.add(onStoreChange);
52
57
  return () => {
@@ -57,6 +62,16 @@ export class OutlineHandleImpl {
57
62
  scrollToHeading = (index) => {
58
63
  this.scrollImpl?.(index);
59
64
  };
65
+ publishFromMarkdown = (markdown) => {
66
+ this.markdownEntries = deriveOutlineFromMarkdown(markdown);
67
+ const activeIndex = resolveActiveOutlineIndex(this.markdownEntries, this.lastActiveLine);
68
+ this.publish({ entries: this.markdownEntries, activeIndex });
69
+ };
70
+ setActiveLine = (line) => {
71
+ this.lastActiveLine = line;
72
+ const activeIndex = resolveActiveOutlineIndex(this.markdownEntries, line);
73
+ this.publish({ entries: this.markdownEntries, activeIndex });
74
+ };
60
75
  /** Called by `OutlinePlugin` while its editor is mounted. */
61
76
  connect(scrollImpl) {
62
77
  this.scrollImpl = scrollImpl;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Derives `OutlineEntry` entries directly from markdown text, independent of
3
+ * any Lexical editor state — the raw-mode counterpart to `OutlinePlugin`'s
4
+ * live-tree derivation (issue #84). A host with no mounted Lexical editor
5
+ * (liminis#1022's raw markdown mode) has no heading DOM/tree to walk, but it
6
+ * does have the markdown text and, per this issue's spec, is expected to know
7
+ * which source line is currently in view — this module turns both into the
8
+ * same `OutlineEntry` shape `OutlinePlugin` produces, plus a source line
9
+ * neither Lexical nor the DOM can supply.
10
+ */
11
+ import type { OutlineEntry } from './documentOutlineHandle.js';
12
+ /**
13
+ * Derive `OutlineEntry[]` from a markdown string alone — no Lexical editor,
14
+ * no DOM. Mirrors `OutlinePlugin.readHeadingEntries()`: only top-level nodes
15
+ * are inspected (headings nested inside blockquotes/lists are not walked
16
+ * into, matching `$getRoot().getChildren()`), levels are restricted to
17
+ * H1–H5 (mdast allows depth 6; excluded exactly like the Lexical path
18
+ * excludes H6), and identity is positional (`index`), not by `text`.
19
+ *
20
+ * Unlike the Lexical path, an empty-title heading is still included — see
21
+ * ADR for why this issue does not replicate the pre-extraction app's
22
+ * `if (!title) continue`.
23
+ */
24
+ export declare function deriveOutlineFromMarkdown(markdown: string): OutlineEntry[];
25
+ /**
26
+ * Resolve which entry is "active" for a supplied source `line` — the
27
+ * raw-mode counterpart to `OutlinePlugin`'s viewport-based scroll-spy. An
28
+ * entry is active from its own line up to (but not including) the next
29
+ * entry's line, matching the spec's literal FR-006 semantics (no tolerance/
30
+ * fudge factor — that is host-side scroll-spy tuning, per the plan).
31
+ *
32
+ * Returns `null` when `line` is `null` (no active heading yet, e.g. before
33
+ * first scroll) or falls before the first heading.
34
+ */
35
+ export declare function resolveActiveOutlineIndex(entries: OutlineEntry[], line: number | null): number | null;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Derives `OutlineEntry` entries directly from markdown text, independent of
3
+ * any Lexical editor state — the raw-mode counterpart to `OutlinePlugin`'s
4
+ * live-tree derivation (issue #84). A host with no mounted Lexical editor
5
+ * (liminis#1022's raw markdown mode) has no heading DOM/tree to walk, but it
6
+ * does have the markdown text and, per this issue's spec, is expected to know
7
+ * which source line is currently in view — this module turns both into the
8
+ * same `OutlineEntry` shape `OutlinePlugin` produces, plus a source line
9
+ * neither Lexical nor the DOM can supply.
10
+ */
11
+ import { parseMarkdown, isHeading, isText, isInlineCode } from '../../markdown/parse.js';
12
+ /**
13
+ * Recursively reduces inline content to plain text, matching Lexical's
14
+ * `getTextContent()` semantics: every inline node's text is concatenated in
15
+ * order with no separator, bottoming out at `text`/`inlineCode` leaves. This
16
+ * mirrors `OutlinePlugin.test.tsx`'s inline-code fixture (`'See ' + 'inline
17
+ * code'` → `'See inline code'`) rather than enumerating every mdast inline
18
+ * type by name.
19
+ */
20
+ function extractText(node) {
21
+ if (isText(node) || isInlineCode(node))
22
+ return node.value;
23
+ if ('children' in node && Array.isArray(node.children)) {
24
+ return node.children.map((child) => extractText(child)).join('');
25
+ }
26
+ return '';
27
+ }
28
+ /**
29
+ * Derive `OutlineEntry[]` from a markdown string alone — no Lexical editor,
30
+ * no DOM. Mirrors `OutlinePlugin.readHeadingEntries()`: only top-level nodes
31
+ * are inspected (headings nested inside blockquotes/lists are not walked
32
+ * into, matching `$getRoot().getChildren()`), levels are restricted to
33
+ * H1–H5 (mdast allows depth 6; excluded exactly like the Lexical path
34
+ * excludes H6), and identity is positional (`index`), not by `text`.
35
+ *
36
+ * Unlike the Lexical path, an empty-title heading is still included — see
37
+ * ADR for why this issue does not replicate the pre-extraction app's
38
+ * `if (!title) continue`.
39
+ */
40
+ export function deriveOutlineFromMarkdown(markdown) {
41
+ const { root } = parseMarkdown(markdown);
42
+ const entries = [];
43
+ let index = 0;
44
+ for (const node of root.children) {
45
+ if (!isHeading(node))
46
+ continue;
47
+ if (node.depth < 1 || node.depth > 5)
48
+ continue;
49
+ const text = node.children.map((child) => extractText(child)).join('');
50
+ entries.push({
51
+ index: index++,
52
+ level: node.depth,
53
+ text,
54
+ line: node.position?.start?.line,
55
+ });
56
+ }
57
+ return entries;
58
+ }
59
+ /**
60
+ * Resolve which entry is "active" for a supplied source `line` — the
61
+ * raw-mode counterpart to `OutlinePlugin`'s viewport-based scroll-spy. An
62
+ * entry is active from its own line up to (but not including) the next
63
+ * entry's line, matching the spec's literal FR-006 semantics (no tolerance/
64
+ * fudge factor — that is host-side scroll-spy tuning, per the plan).
65
+ *
66
+ * Returns `null` when `line` is `null` (no active heading yet, e.g. before
67
+ * first scroll) or falls before the first heading.
68
+ */
69
+ export function resolveActiveOutlineIndex(entries, line) {
70
+ if (line === null)
71
+ return null;
72
+ let active = null;
73
+ for (const entry of entries) {
74
+ if (entry.line === undefined)
75
+ continue;
76
+ if (entry.line <= line)
77
+ active = entry.index;
78
+ else
79
+ break;
80
+ }
81
+ return active;
82
+ }
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export { DocumentOutline } from './app/editor/DocumentOutline.js';
22
22
  export type { DocumentOutlineProps } from './app/editor/DocumentOutline.js';
23
23
  export { createDocumentOutlineHandle } from './app/editor/documentOutlineHandle.js';
24
24
  export type { DocumentOutlineHandle, DocumentOutlineSnapshot, OutlineEntry, } from './app/editor/documentOutlineHandle.js';
25
+ export { deriveOutlineFromMarkdown, resolveActiveOutlineIndex } from './app/editor/documentOutlineMarkdown.js';
25
26
  export { OPEN_ANNOTATION_COMPOSER_COMMAND } from './app/editor/annotationCommands.js';
26
27
  export type { AnnotationCreateEvent } from './app/editor/AnnotationPlugin.js';
27
28
  export type { Annotation, AnnotationKind, AnnotationKindConfig, AnnotationKindConfigs, AnnotationCreateAffordance, AnnotationMarkerStyle, AnnotationPresentation, AnnotationEditorHandle, MarkerTarget, } from './annotations/types.js';
package/dist/index.js CHANGED
@@ -15,12 +15,18 @@ export { createHostMessageApi, useHostMessages } from './host/messages.js';
15
15
  // --- Components ------------------------------------------------------------
16
16
  export { App } from './app/App.js';
17
17
  export { Editor } from './app/editor/index.js';
18
- // --- Document outline (issue #69) -------------------------------------------
18
+ // --- Document outline (issue #69, markdown-derived path issue #84) --------
19
19
  // `createDocumentOutlineHandle()` makes the controller shared between one
20
20
  // `<Editor documentOutlineHandle={…}>` and one `<DocumentOutline handle={…}>`
21
21
  // — create it once per editor instance and pass the same object to both.
22
+ // A host with no mounted Lexical editor (e.g. a raw markdown mode) can feed
23
+ // the same handle from markdown text alone via `handle.publishFromMarkdown`/
24
+ // `handle.setActiveLine`; `deriveOutlineFromMarkdown`/
25
+ // `resolveActiveOutlineIndex` are exported standalone for a host that wants
26
+ // the pure derivation without going through the handle.
22
27
  export { DocumentOutline } from './app/editor/DocumentOutline.js';
23
28
  export { createDocumentOutlineHandle } from './app/editor/documentOutlineHandle.js';
29
+ export { deriveOutlineFromMarkdown, resolveActiveOutlineIndex } from './app/editor/documentOutlineMarkdown.js';
24
30
  // --- Annotations, React surface (ADR-077) ---------------------------------
25
31
  // The kind-configuration types a host needs to turn the mechanism on, plus the
26
32
  // create-event shape. The DOM-free anchor model, resolver and marker-target