@haklex/rich-litexml 0.15.1 → 0.15.2

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.
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: litexml-authoring
3
+ description: Use when writing Haklex articles with Markdown plus LiteXML, choosing the appropriate Haklex node for content, authoring LiteXML fragments, or converting a LightXML/LiteXML string or file into Lexical SerializedEditorState JSON, Markdown, or Static Render HTML via the `litexml` CLI.
4
+ user_invocable: true
5
+ ---
6
+
7
+ # LiteXML Authoring
8
+
9
+ Use this skill when an article needs Haklex-specific rich nodes, or when the user asks to convert LiteXML to Lexical JSON / Markdown / HTML via the `litexml` CLI.
10
+
11
+ The skill is split into a thin index (this file) plus per-topic references. Load the matching reference when you start the actual work — the index is intentionally short so the format decision table and node selection map fit in one read.
12
+
13
+ ## References
14
+
15
+ | File | When to load |
16
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
17
+ | [`references/cli.md`](./references/cli.md) | Running the `litexml` binary — every flag, every input/output mode, every runtime caveat. |
18
+ | [`references/nodes-structural.md`](./references/nodes-structural.md) | Authoring with structural tags (`<p>`, headings, lists, tables, links) and inline formatting (`<b>`, `<em>`, `<code>`, …). |
19
+ | [`references/nodes-extensions.md`](./references/nodes-extensions.md) | Authoring with Haklex extension tags (media, code, math, callouts, containers, footnotes, chat, poll, …). |
20
+ | [`references/authoring-recipes.md`](./references/authoring-recipes.md) | CDATA usage, block IDs, nested-content rules, similar-node disambiguation, validation, adding new nodes. |
21
+
22
+ `packages/rich-editor/docs/markdown-flavor-litexml.md` is the canonical tag contract — read it before changing any tag name or attribute schema.
23
+
24
+ ## Format decision
25
+
26
+ | Content requirement | Use |
27
+ | ----------------------------------------------------------------------- | ----------------------------------------------------------------- |
28
+ | Plain prose, headings, simple links, lists, quotes, tables, code fences | **Markdown** |
29
+ | Any Haklex extension tag is required | **LiteXML** — for the whole fragment, including surrounding prose |
30
+ | A block needs stable identity for later edits | LiteXML with `id="..."` (maps to `$.blockId`) |
31
+ | Fresh poll/chat content | LiteXML without IDs — the reader mints them |
32
+
33
+ Once a fragment contains any LiteXML tag, **all** surrounding prose in that fragment must also be LiteXML. Markdown is not parsed inside a LiteXML fragment.
34
+
35
+ ## CLI quick start
36
+
37
+ ```bash
38
+ # inside the haklex monorepo
39
+ pnpm --silent litexml input.xml --format json --compact # → SerializedEditorState
40
+ pnpm --silent litexml input.xml --format markdown # → Markdown
41
+ pnpm --silent litexml input.xml --format html --open # → HTML preview, browser
42
+
43
+ # outside the monorepo
44
+ npx --yes -p @haklex/rich-litexml-cli litexml input.xml --format json
45
+ ```
46
+
47
+ Full flag reference, output forms, theme/variant/lang options, error modes, validation pipelines: [`references/cli.md`](./references/cli.md).
48
+
49
+ ## Node selection map
50
+
51
+ Detailed `When` / `Avoid when` / params / body rules for each tag live in [`nodes-structural.md`](./references/nodes-structural.md) and [`nodes-extensions.md`](./references/nodes-extensions.md). Use this table only to decide which reference to open.
52
+
53
+ | Need | Tag | Reference |
54
+ | ------------------------------------------------------ | --------------------------------------------------------------------------- | ---------- |
55
+ | Paragraph, heading, list, table, link, inline format | `<p>` / `<h*>` / `<ul>` / `<ol>` / `<table>` / `<a>` / `<b>` / `<code>` / … | structural |
56
+ | Quote (with optional `attribution`) | `<blockquote>` | structural |
57
+ | Single image / multi-image / video / embed / link card | `<img>` / `<gallery>` / `<video>` / `<embed>` / `<link-card>` | extensions |
58
+ | Code (single / multi-file) | `<codeblock>` / `<code-snippet>` | extensions |
59
+ | Diagram (Mermaid / opaque) | `<mermaid>` / `<excalidraw>` | extensions |
60
+ | Math (inline / block) | `<math>` / `<math display="block">` | extensions |
61
+ | Callout (semantic / page-wide) | `<alert>` / `<banner>` | extensions |
62
+ | Collapsible / nested doc / multi-column | `<details>` / `<nested-doc>` / `<grid><cell>` | extensions |
63
+ | Inline annotation | `<spoiler>` / `<ruby>` / `<mention>` / `<tag>` / `<comment>` | extensions |
64
+ | Footnote | `<footnote>` + `<footnote-section>` | extensions |
65
+ | Chat / poll | `<chat>` / `<poll>` | extensions |
66
+ | Internal review marker | `<agent-diff>` | extensions |
67
+
68
+ ## Cross-cutting rules
69
+
70
+ - Canonical lowercase tag names. Do **not** emit legacy aliases (`<linkcard>`, `<codesnippet>`, `<nesteddoc>`, `<code-block>`).
71
+ - Quote every attribute value.
72
+ - Escape XML-sensitive text (`&amp;`, `&lt;`, `&gt;`, `&quot;`). Use CDATA for opaque JSON, drawing snapshots, and multi-line code bodies.
73
+ - `<alert>`, `<banner>`, `<nested-doc>`, `<grid><cell>` hold a **fresh nested editor state** — wrap their body in block tags (`<p>`, `<h*>`, …), not bare text.
74
+ - A fragment that mixes any extension tag with prose must be wholly LiteXML; Markdown syntax is not parsed inside.
75
+
76
+ Details, gotchas, and the full disambiguation matrix: [`references/authoring-recipes.md`](./references/authoring-recipes.md).
77
+
78
+ ## Validation
79
+
80
+ After producing or editing LiteXML, round-trip to compact JSON to confirm the registry parses every tag:
81
+
82
+ ```bash
83
+ pnpm --silent litexml '<doc><p>Hello</p></doc>' --format json --compact
84
+ ```
85
+
86
+ If the command succeeds but a downstream editor cannot materialize a node, the runtime is missing that Haklex node class — re-check the consumer's `nodes` registration, not the CLI.
@@ -0,0 +1,141 @@
1
+ # Authoring recipes and gotchas
2
+
3
+ Recurring patterns that come up when writing LiteXML fragments. Each entry is intentionally small — pull the matching one when you hit the situation.
4
+
5
+ ## When to use CDATA
6
+
7
+ Wrap body content in `<![CDATA[ ... ]]>` whenever the body is opaque or contains characters that XML would otherwise escape:
8
+
9
+ | Situation | Required? |
10
+ | ------------------------------------------------ | ------------------------------------------------------------------- |
11
+ | `<excalidraw>` JSON snapshot | yes |
12
+ | `<codeblock>` containing `<`, `>`, `&`, or `]]>` | yes |
13
+ | `<codeblock>` containing only safe characters | optional, but preferred for multi-line code |
14
+ | `<file>` body inside `<code-snippet>` | same rules as `<codeblock>` |
15
+ | `<mermaid>` source | optional; usually unnecessary because Mermaid syntax is XML-safe |
16
+ | `<math>` equation | rarely needed; KaTeX source uses `\`, not XML metacharacters |
17
+ | `<p>`, `<h1>`, `<li>`, `<td>` prose | never — escape entities instead (`&amp;`, `&lt;`, `&gt;`, `&quot;`) |
18
+
19
+ CDATA inside `<codeblock>` and `<file>` is parsed via a small linkedom-specific extraction (`extractCdataText`). Do **not** nest CDATA sections or place trailing `]]>` inside the body.
20
+
21
+ ## Block IDs (`id` attribute)
22
+
23
+ - **Omit** for fresh content. The hydrating editor generates an ID on insert.
24
+ - **Add** when later tool calls need to target the exact block, or when porting an existing document that already carries IDs (so cross-references / diff entries stay attached).
25
+ - The `id` attribute maps to `$.blockId`. It is preserved on round-trip (`litexml ... --format json` then back).
26
+ - Tags that accept `id`: every block-level tag in [`nodes-structural.md`](./nodes-structural.md) and [`nodes-extensions.md`](./nodes-extensions.md) except inline tags and `<tr>` / `<th>` / `<td>`.
27
+
28
+ ## Quote attribution
29
+
30
+ `<blockquote>` accepts an optional `attribution` attribute. The reader emits `type: "rich-quote"` when present and `type: "quote"` when absent — both materialize as the same `RichQuoteNode` in the editor.
31
+
32
+ ```xml
33
+ <blockquote attribution="— Wang Xizhi, Lantingji Xu">
34
+ <p>未尝不临文嗟悼,不能喻之于怀。</p>
35
+ </blockquote>
36
+
37
+ <blockquote>
38
+ <p>An unattributed quote.</p>
39
+ </blockquote>
40
+ ```
41
+
42
+ ## Nested block content vs inline content
43
+
44
+ Containers that hold a fresh `SerializedEditorState` (and therefore need block children, not inline text):
45
+
46
+ - `<alert>`
47
+ - `<banner>`
48
+ - `<nested-doc>`
49
+ - `<grid><cell>`
50
+
51
+ Always wrap the body in block tags (`<p>`, `<h2>`, `<ul>`, etc.):
52
+
53
+ ```xml
54
+ <alert type="tip">
55
+ <p>Wrap the body in a paragraph.</p>
56
+ </alert>
57
+
58
+ <!-- bad: bare text body -->
59
+ <alert type="tip">Bare text — works at parse time but won't round-trip cleanly.</alert>
60
+ ```
61
+
62
+ `<details>` is different: it uses ordinary block children (no nested editor state). Either form is fine, but use `<p>` for consistency.
63
+
64
+ ## Mixing extension tags with prose
65
+
66
+ If a fragment contains **any** LiteXML extension tag, the surrounding prose must also be expressed as LiteXML — Markdown is not parsed inside a LiteXML fragment.
67
+
68
+ ```xml
69
+ <!-- correct -->
70
+ <h2>Install</h2>
71
+ <p>Run the command:</p>
72
+ <codeblock lang="bash">pnpm add @haklex/rich-editor</codeblock>
73
+ <p>Then import the editor.</p>
74
+
75
+ <!-- wrong: Markdown syntax inside a LiteXML fragment is rendered literally -->
76
+ ## Install
77
+ Run the command:
78
+ <codeblock lang="bash">pnpm add @haklex/rich-editor</codeblock>
79
+ Then import the editor.
80
+ ```
81
+
82
+ The right time to write pure Markdown is when the whole document has no extension tags. As soon as one shows up, lift the whole document into LiteXML.
83
+
84
+ ## Choosing similar nodes
85
+
86
+ | Decision | Use | Not |
87
+ | ---------------------------------------- | --------------------------------- | --------------------------------------------- |
88
+ | Quoted external text | `<blockquote>` | `<alert>` (use for warnings/tips, not quotes) |
89
+ | Editorial warning | `<alert type="warning">` | `<blockquote>` |
90
+ | Page-wide announcement | `<banner>` | `<alert>` |
91
+ | Multi-column comparison data | `<table>` | `<grid>` |
92
+ | Multi-column editorial layout | `<grid>` | `<table>` |
93
+ | Inline citation | `<a>` | `<link-card>` |
94
+ | Standalone reference deserving a preview | `<link-card>` | `<a>` |
95
+ | YouTube / Vimeo embed | `<embed url="..." source="..."/>` | `<img>` (which would be a static thumbnail) |
96
+ | Direct video asset | `<video src="..."/>` | `<embed>` |
97
+ | Single image | `<img>` | `<gallery>` |
98
+ | Image set | `<gallery>` | many `<img>` |
99
+ | Single-language code | `<codeblock>` | `<code-snippet>` |
100
+ | Multi-file example | `<code-snippet>` | repeated `<codeblock>` |
101
+ | Mermaid-expressible diagram | `<mermaid>` | `<excalidraw>` |
102
+ | Opaque hand-drawn snapshot | `<excalidraw>` | `<mermaid>` |
103
+ | Inline equation | `<math>` | `<math display="block">` |
104
+ | Standalone equation | `<math display="block">` | inline `<math>` |
105
+ | Collapsible secondary content | `<details>` | `<spoiler>` (inline only) |
106
+ | Hidden inline reveal | `<spoiler>` | `<details>` |
107
+ | Reader-visible callout | `<alert>` / `<banner>` | `<comment>` |
108
+ | Reviewer note (not for end readers) | `<comment>` | `<alert>` |
109
+ | Survey / live vote | `<poll>` | `<ul>` / `<ol>` |
110
+ | Static option list | `<ul>` | `<poll>` |
111
+ | Task checklist | `<ul type="check">` | `<poll>` |
112
+ | Transcript | `<chat>` | `<blockquote>` |
113
+
114
+ ## Validation
115
+
116
+ After producing or editing a LiteXML fragment, convert to compact JSON to confirm the registry parses every tag:
117
+
118
+ ```bash
119
+ pnpm --silent litexml '<doc><p>Hello</p></doc>' --format json --compact
120
+ ```
121
+
122
+ For round-trip verification on an article file:
123
+
124
+ ```bash
125
+ litexml article.xml --format json -o /tmp/state.json
126
+ litexml /tmp/state.json --format markdown -o /tmp/article.md
127
+ ```
128
+
129
+ Any node that disappears or changes shape across the round-trip is a writer/reader bug — file against `@haklex/rich-litexml`, not the CLI.
130
+
131
+ When `litexml` succeeds but a downstream editor cannot materialize a node, the consumer is missing that Haklex node class. The CLI registry is authoritative for what _can_ be parsed; node class registration is the consumer's responsibility.
132
+
133
+ ## Adding a new node
134
+
135
+ If you find yourself wanting a tag that this skill does not document, the underlying machinery is in `packages/rich-litexml/`. Following its `CLAUDE.md`:
136
+
137
+ 1. Add a writer in `packages/rich-litexml/src/writers/` (SerializedNode JSON → XML).
138
+ 2. Add a reader in `packages/rich-litexml/src/readers/` (XML → SerializedNode JSON).
139
+ 3. Register both in `createDefaultRegistry()` in `src/default-registry.ts`.
140
+ 4. Add a roundtrip test in `tests/`.
141
+ 5. Update this skill's [`nodes-extensions.md`](./nodes-extensions.md) so the new tag is discoverable from the authoring side.
@@ -0,0 +1,166 @@
1
+ # litexml CLI reference
2
+
3
+ `@haklex/rich-litexml-cli` publishes the `litexml` binary. It accepts **LiteXML** or **Lexical SerializedEditorState JSON** as input (auto-detected) and emits one of three target formats: **HTML preview**, **Lexical JSON**, or **Markdown**.
4
+
5
+ This file is the canonical reference for every flag, every input/output mode, and the runtime caveats. The main `SKILL.md` only lists the format decision matrix; come here when you need the actual command.
6
+
7
+ ## Invocation forms
8
+
9
+ ### Inside this monorepo
10
+
11
+ ```bash
12
+ pnpm --silent litexml INPUT --format FMT [options]
13
+ ```
14
+
15
+ The repo's root `package.json` exposes `litexml` as a pnpm-runnable script that resolves the local workspace build. Always pair with `--silent` so pnpm chatter does not corrupt stdout when piping into a file.
16
+
17
+ ### Outside the repo
18
+
19
+ ```bash
20
+ npx --yes -p @haklex/rich-litexml-cli@VERSION litexml INPUT --format FMT [options]
21
+ ```
22
+
23
+ Or after a global install:
24
+
25
+ ```bash
26
+ npm install -g @haklex/rich-litexml-cli
27
+ litexml INPUT --format FMT [options]
28
+ ```
29
+
30
+ `INPUT` is a file path, `-`, or an inline LiteXML / JSON string; `FMT` is one of `html | json | markdown`; `VERSION` is the published version tag. See the next sections for the concrete forms.
31
+
32
+ The package depends on `@haklex/rich-compose`, `@haklex/rich-headless`, and `@haklex/rich-litexml` at the same version, and reads `@haklex/rich-compose/dist/style.css` plus `dist/litexml-html-preview-client.js` at runtime via `require.resolve`. Do not strip those packages in a custom install.
33
+
34
+ ## Input
35
+
36
+ | Form | Example | Notes |
37
+ | ------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------- |
38
+ | File path | `litexml article.xml --format json` | Path is detected via `existsSync`. Reads with `utf8`. |
39
+ | Stdin | `cat article.xml \| litexml - --format json` | Explicit `-` reads from `fd 0`. Also used when input arg is omitted entirely. |
40
+ | Inline string | `litexml '<p>hi</p>' --format json` | If the arg does not match an existing file and starts with `<` or `{`, it is parsed directly. |
41
+ | Force input format | `litexml input.txt --input-format litexml` | Use when auto-detection cannot decide (e.g. unusual whitespace or empty leading lines). |
42
+
43
+ **Auto-detection rule** (`src/input.ts`): first non-whitespace character — `<` → LiteXML, `{` → JSON, anything else → error. Override with `--input-format litexml|json` (alias `-i`).
44
+
45
+ ## Output
46
+
47
+ | Form | Example |
48
+ | ------------------ | ----------------------------------------------------- |
49
+ | Stdout (default) | `litexml input.xml --format html > article.html` |
50
+ | Explicit file | `litexml input.xml --format html -o article.html` |
51
+ | Browser preview | `litexml input.xml --format html --open` |
52
+ | Write **and** open | `litexml input.xml --format html -o page.html --open` |
53
+
54
+ When `--open` is set without `-o`, the CLI writes the HTML to a tmp file (`mkdtemp` under `os.tmpdir()`) and opens it via `open` / `cmd /c start` / `xdg-open` depending on platform.
55
+
56
+ Stdout EPIPE is handled gracefully (`process.stdout.on('error', ...)`), so piping into `head` / `less` will not crash the process.
57
+
58
+ ## Flags
59
+
60
+ | Flag | Alias | Applies to | Default | Meaning |
61
+ | ----------------------- | ----- | ----------- | ------------ | ----------------------------------------------------------------------------- |
62
+ | `--format <fmt>` | `-f` | all | **required** | `html` \| `json` \| `markdown`. |
63
+ | `--input-format <fmt>` | `-i` | all | auto | Force `litexml` or `json`. |
64
+ | `--output <file>` | `-o` | all | stdout | Write rendered output to a file. |
65
+ | `--compact` | | `json` only | off | Strip indentation from emitted JSON. |
66
+ | `--theme <light\|dark>` | | `html` only | `light` | Sets `<meta color-scheme>` and `<html>` background. |
67
+ | `--variant <v>` | | `html` only | `article` | `article` (sans, 16px) \| `note` (CJK serif, 16px) \| `comment` (sans, 14px). |
68
+ | `--title <t>` | | `html` only | derived | Sets the HTML `<title>`. Default: input filename or `Haklex LiteXML Preview`. |
69
+ | `--lang <l>` | | `html` only | `en` | Sets `<html lang>`. |
70
+ | `--open` | | `html` only | off | Open the generated HTML in the system browser. |
71
+ | `--help` | `-h` | — | — | Print help and exit `0`. |
72
+
73
+ Irrelevant flags are not errors — the CLI prints a warning on stderr (`Warning: ignoring options not applicable to --format <fmt>: ...`) and continues. Use `2>/dev/null` to suppress.
74
+
75
+ ## Output formats in detail
76
+
77
+ ### `--format json` — Lexical SerializedEditorState
78
+
79
+ Emits a full `SerializedEditorState` (i.e. `{ "root": { ... } }`), not a node array. Use this when you need to feed the result into `editor.parseEditorState(JSON.stringify(state))` or persist it to a database.
80
+
81
+ ```bash
82
+ litexml input.xml --format json > state.json
83
+ litexml input.xml --format json --compact # single-line, smallest
84
+ litexml input.xml --format json -o state.json
85
+ litexml '<p>hi</p>' --format json --compact # inline → JSON, one-shot
86
+ ```
87
+
88
+ When the input is already JSON, the CLI still rebuilds it via `JSON.parse` → format → emit. This is intentional: it lets you re-format / compact existing state files via the same binary.
89
+
90
+ ### `--format markdown` — `$toMarkdown()` via rich-headless
91
+
92
+ ```bash
93
+ litexml input.xml --format markdown > article.md
94
+ litexml state.json --format markdown # auto-detected as JSON
95
+ litexml state.json --format markdown -i json # force
96
+ ```
97
+
98
+ The Markdown writer is `@haklex/rich-headless`'s `$toMarkdown()`. Coverage includes all standard nodes plus haklex extensions (mentions, footnotes, KaTeX, code blocks, tables, banners, alerts, spoilers, etc.). When a Haklex node has no Markdown analogue, the headless writer emits a sensible textual fallback rather than failing.
99
+
100
+ Useful pairings:
101
+
102
+ ```bash
103
+ # Round-trip: render the canonical Markdown that an article would export as
104
+ litexml article.xml --format markdown | diff - article.md
105
+
106
+ # Convert a stored Lexical state back into editable Markdown
107
+ litexml state.json --format markdown -o editable.md
108
+ ```
109
+
110
+ ### `--format html` — full preview document
111
+
112
+ Writes a standalone HTML document with:
113
+
114
+ - `@haklex/rich-compose/dist/style.css` inlined inside `<style>`.
115
+ - The `SerializedEditorState` embedded as `<script id="haklex-litexml-payload" type="application/json">`.
116
+ - `@haklex/rich-compose/dist/litexml-html-preview-client.js` inlined inside a trailing `<script>`. The bundle reads the payload script and renders into `#haklex-litexml-root` via the same Rich Compose renderer used by the static renderer.
117
+
118
+ The resulting file is fully self-contained — no external network requests, no missing CSS — and can be opened from any disk path. Theme / variant / title / lang are baked into the document at render time.
119
+
120
+ ```bash
121
+ litexml input.xml --format html > preview.html
122
+ litexml input.xml --format html -o preview.html
123
+ litexml input.xml --format html --open # tmpfile + system open
124
+ litexml input.xml --format html --theme dark -o dark.html
125
+ litexml input.xml --format html --variant note --lang ja -o note.html
126
+ litexml input.xml --format html --title "Draft v3" -o draft.html
127
+ ```
128
+
129
+ If you build the CLI from source and `@haklex/rich-compose` has not been built, the HTML format will fail with `Cannot resolve @haklex/rich-compose asset "style.css"`. Run `pnpm --filter @haklex/rich-compose build` first.
130
+
131
+ ## Exit behavior
132
+
133
+ - Success: exit code `0`. Output goes to stdout (or `-o` path).
134
+ - Error: exit code `1`, message + full `HELP` text written to stderr. Common causes: missing `--format`, unknown flag, empty input, malformed JSON, unresolvable compose asset.
135
+ - `--help`: exit code `0`, help text written to stdout.
136
+ - EPIPE on stdout (e.g. piping into `head`): exit code `0` silently.
137
+
138
+ ## Validation recipe
139
+
140
+ After producing or editing a LiteXML fragment, convert to compact JSON to confirm the registry parses every tag:
141
+
142
+ ```bash
143
+ pnpm --silent litexml '<doc><p>Hello</p></doc>' --format json --compact
144
+ ```
145
+
146
+ If the command succeeds but a downstream editor cannot materialize a node, the runtime is missing that Haklex node class — re-check the consumer's `nodes` registration, not the CLI.
147
+
148
+ For round-trip verification on a real article:
149
+
150
+ ```bash
151
+ litexml article.xml --format json | litexml - --format markdown -i json
152
+ ```
153
+
154
+ Any node that round-trips differently between LiteXML → JSON → Markdown is a writer/reader bug in `@haklex/rich-litexml` or a missing Markdown export in `@haklex/rich-headless`.
155
+
156
+ ## Common mistakes
157
+
158
+ | Mistake | Symptom | Fix |
159
+ | ------------------------------------------------ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------- |
160
+ | Omitting `--format` | `Missing required option: --format <html\|json\|markdown>.` | Always supply `-f` / `--format`. |
161
+ | Forgetting `--silent` when piping | pnpm progress lines mixed into the output file | `pnpm --silent litexml ... > out.html`. |
162
+ | Passing inline LiteXML without quotes | Shell parses `<` as redirect | Wrap in single quotes: `litexml '<p>x</p>' -f json`. |
163
+ | Using `--compact` with `-f html` | Warning, no effect | `--compact` is JSON-only. |
164
+ | Running `-f html` without compose build | `Cannot resolve @haklex/rich-compose asset "style.css".` | Run `pnpm --filter @haklex/rich-compose build` (only inside a source checkout). |
165
+ | Treating output as node array | Downstream `editor.parseEditorState` rejects the JSON | Output is `SerializedEditorState` (`{root: {...}}`), not a node list. |
166
+ | Mixing Markdown syntax inside a LiteXML fragment | `**bold**` rendered literally | If the fragment contains any LiteXML tag, write the whole surrounding prose as LiteXML. |
@@ -0,0 +1,399 @@
1
+ # Haklex extension nodes
2
+
3
+ These tags are not part of CommonMark. Each one maps to a Haklex-specific Lexical node and is the only way to author content that those node renderers can pick up. For prose scaffolding (paragraphs, lists, tables, links, inline formatting) see [`nodes-structural.md`](./nodes-structural.md).
4
+
5
+ Every block-level extension may carry `id="..."` → `$.blockId`. Omit for fresh authoring; preserve when editing.
6
+
7
+ ## Media
8
+
9
+ ### `<img>` — image
10
+
11
+ - **When**: A single image, optionally with caption and explicit dimensions.
12
+ - **Avoid when**: Multiple related images — use `<gallery>`. Decorative inline glyph — use the renderer's icon system.
13
+ - **Required**: `src`.
14
+ - **Optional**: `id`, `alt`, `width` (numeric), `height` (numeric), `caption`, `thumbhash`, `accent` (color string for image rendering).
15
+ - **Body**: self-closing.
16
+ - **Inside `<gallery>`**: `<img>` becomes a gallery image item instead of a standalone image node — only `src` and `alt` are read.
17
+
18
+ ```xml
19
+ <img src="/photo.jpg" alt="Cover" width="1200" height="800" caption="At dusk." accent="#ff8855" />
20
+ ```
21
+
22
+ ### `<video>` — video
23
+
24
+ - **When**: A directly playable video asset.
25
+ - **Avoid when**: Provider-hosted clip (YouTube, Vimeo) — use `<embed>`.
26
+ - **Required**: `src`.
27
+ - **Optional**: `id`, `poster` (preview image URL), `width`, `height`.
28
+ - **Body**: self-closing.
29
+
30
+ ```xml
31
+ <video src="/clip.mp4" poster="/thumb.jpg" />
32
+ ```
33
+
34
+ ### `<link-card>` — rich link preview
35
+
36
+ - **When**: A standalone reference URL that deserves a visible card (title, description, favicon, hero image).
37
+ - **Avoid when**: Inline citation in the middle of prose — use `<a>`.
38
+ - **Required**: `url`.
39
+ - **Optional**: `id`, `source`, `title`, `description`, `favicon`, `image`.
40
+ - **Body**: self-closing.
41
+
42
+ ```xml
43
+ <link-card url="https://haklex.dev"
44
+ title="Haklex"
45
+ description="Lexical-based rich editor ecosystem."
46
+ favicon="https://haklex.dev/icon.svg" />
47
+ ```
48
+
49
+ ### `<embed>` — provider embed
50
+
51
+ - **When**: A third-party provider embed such as YouTube, Vimeo, CodePen, Tweet, etc. The renderer detects the provider and produces the right iframe / oEmbed payload.
52
+ - **Avoid when**: A static screenshot — use `<img>`.
53
+ - **Required**: `url`.
54
+ - **Optional**: `id`, `source` (provider hint, e.g. `youtube`, `vimeo`).
55
+ - **Body**: self-closing.
56
+
57
+ ```xml
58
+ <embed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" source="youtube" />
59
+ ```
60
+
61
+ ### `<gallery>` — image gallery
62
+
63
+ - **When**: A related image set displayed together.
64
+ - **Avoid when**: A single figure — use `<img>`.
65
+ - **Required**: one or more `<img>` children.
66
+ - **Optional**: `id`, `layout` (defaults to `grid`).
67
+ - **Body**: only `<img src="..." alt="..." />` children. Other tags are ignored.
68
+
69
+ ```xml
70
+ <gallery layout="grid">
71
+ <img src="/a.jpg" alt="A" />
72
+ <img src="/b.jpg" alt="B" />
73
+ <img src="/c.jpg" alt="C" />
74
+ </gallery>
75
+ ```
76
+
77
+ ## Code
78
+
79
+ ### `<codeblock>` — single code listing
80
+
81
+ - **When**: One code listing in a single language.
82
+ - **Avoid when**: Multi-file example (e.g. `index.ts` + `package.json`) — use `<code-snippet>`. Short inline phrase — use `<code>`.
83
+ - **Required**: none (empty body is allowed).
84
+ - **Optional**: `id`, `lang`.
85
+ - **Body**: raw code text. For shebangs, imports, template strings, XML-sensitive characters (`<`, `&`), or multi-line code, prefer `<![CDATA[...]]>` so the body is preserved verbatim.
86
+
87
+ ```xml
88
+ <codeblock lang="ts"><![CDATA[
89
+ import { foo } from './bar';
90
+ const x = <T>(t: T): T => t;
91
+ ]]></codeblock>
92
+ ```
93
+
94
+ Note: the canonical tag is `<codeblock>`, **not** `<code-block>`.
95
+
96
+ ### `<code-snippet>` — multi-file code example
97
+
98
+ - **When**: An example that spans multiple named files.
99
+ - **Avoid when**: Single file — use `<codeblock>`.
100
+ - **Required**: one or more `<file name="..." lang="...">code</file>` children.
101
+ - **Optional on `<code-snippet>`**: `id`.
102
+ - **Required on `<file>`**: `name`.
103
+ - **Optional on `<file>`**: `lang`.
104
+ - **Body of `<file>`**: raw code text; use CDATA for multi-line or XML-sensitive code.
105
+
106
+ ```xml
107
+ <code-snippet>
108
+ <file name="index.ts" lang="ts"><![CDATA[
109
+ export const main = () => console.log('hi');
110
+ ]]></file>
111
+ <file name="package.json" lang="json"><![CDATA[
112
+ { "type": "module" }
113
+ ]]></file>
114
+ </code-snippet>
115
+ ```
116
+
117
+ ## Math
118
+
119
+ ### `<math>` — inline or block equation
120
+
121
+ - **When**: A KaTeX equation.
122
+ - **Required**: equation text in the body.
123
+ - **Optional**: `display` (`"block"` for standalone equation; omit for inline), `color` (only honored on inline math).
124
+ - **Body**: equation source.
125
+
126
+ ```xml
127
+ <p>Inline: <math>E = mc^2</math>.</p>
128
+
129
+ <math display="block">\int_0^1 x^2\, dx = \tfrac{1}{3}</math>
130
+
131
+ <p><math color="#ff5577">a^2 + b^2 = c^2</math></p>
132
+ ```
133
+
134
+ The reader chooses `katex-block` vs `katex-inline` based on `display`. Block math is rendered in its own block; inline math sits inside paragraph text.
135
+
136
+ ## Diagrams
137
+
138
+ ### `<mermaid>` — Mermaid diagram
139
+
140
+ - **When**: A diagram expressible as Mermaid source.
141
+ - **Avoid when**: Freeform / hand-drawn diagram — use `<excalidraw>`.
142
+ - **Required**: none.
143
+ - **Optional**: `id`.
144
+ - **Body**: Mermaid source text. Use `&#10;` to encode newlines if you must keep the body on one line; otherwise normal element text with real newlines is fine.
145
+
146
+ ```xml
147
+ <mermaid>
148
+ flowchart LR
149
+ A --> B
150
+ B --> C
151
+ </mermaid>
152
+ ```
153
+
154
+ ### `<excalidraw>` — opaque Excalidraw snapshot
155
+
156
+ - **When**: An opaque drawing snapshot that must round-trip exactly.
157
+ - **Avoid when**: A diagram you would otherwise express in Mermaid — pick `<mermaid>`.
158
+ - **Required**: snapshot body (preferred) or `snapshot` attribute (legacy).
159
+ - **Optional**: `id`, `snapshot` (legacy attribute form).
160
+ - **Body**: opaque JSON inside `<![CDATA[...]]>`. Empty snapshots may be self-closing.
161
+
162
+ ```xml
163
+ <excalidraw><![CDATA[{"elements":[],"appState":{}}]]></excalidraw>
164
+ ```
165
+
166
+ ## Callouts and admonitions
167
+
168
+ ### `<alert>` — semantic admonition
169
+
170
+ - **When**: Inline-of-section semantic admonitions (note, tip, warning, important, caution).
171
+ - **Avoid when**: Top-of-page announcement — use `<banner>`.
172
+ - **Required**: none (empty body allowed).
173
+ - **Optional**: `id`, `type` (`note` default | `tip` | `important` | `warning` | `caution`).
174
+ - **Body**: nested LiteXML block content (usually `<p>...</p>`). Stored as a nested `SerializedEditorState`.
175
+
176
+ ```xml
177
+ <alert type="warning">
178
+ <p>This operation cannot be undone.</p>
179
+ </alert>
180
+ ```
181
+
182
+ ### `<banner>` — prominent editorial notice
183
+
184
+ - **When**: Page-level announcement or contextual banner.
185
+ - **Avoid when**: Inline admonition — use `<alert>`.
186
+ - **Required**: none.
187
+ - **Optional**: `id`, `type`. Renderer-supported values include `note`, `tip`, `important`, `warning`, `caution`, plus informational `info` and `success`. Pick the value consistent with the target renderer.
188
+ - **Body**: nested LiteXML block content.
189
+
190
+ ```xml
191
+ <banner type="info">
192
+ <p>The article was updated on 2026-05-16.</p>
193
+ </banner>
194
+ ```
195
+
196
+ ## Containers
197
+
198
+ ### `<details>` — collapsible
199
+
200
+ - **When**: Optional secondary explanation that should default to collapsed.
201
+ - **Avoid when**: Required reading — render inline.
202
+ - **Required**: none.
203
+ - **Optional**: `id`, `summary` (visible collapsed label), `open` (`"true"` to start expanded; omit otherwise).
204
+ - **Body**: block children.
205
+
206
+ ```xml
207
+ <details summary="Implementation notes" open="false">
208
+ <p>Internals…</p>
209
+ </details>
210
+ ```
211
+
212
+ ### `<nested-doc>` — independently editable nested document
213
+
214
+ - **When**: A nested editor state that the consumer renders or edits as its own document.
215
+ - **Avoid when**: Plain grouping — use `<grid>` or just sequential blocks.
216
+ - **Required**: none.
217
+ - **Optional**: `id`.
218
+ - **Body**: nested LiteXML block content (becomes its own `SerializedEditorState`).
219
+
220
+ ```xml
221
+ <nested-doc>
222
+ <h3>Nested section</h3>
223
+ <p>Body.</p>
224
+ </nested-doc>
225
+ ```
226
+
227
+ ### `<grid>` — editorial multi-column layout
228
+
229
+ - **When**: Two-up / three-up editorial layout where each cell is independent.
230
+ - **Avoid when**: Data with row/column semantics — use `<table>`.
231
+ - **Required**: one or more `<cell>...</cell>` children.
232
+ - **Optional**: `id`, `cols` (numeric, default `2`), `gap` (CSS length string, default `16px`).
233
+ - **Body of `<cell>`**: nested LiteXML block content (becomes its own `SerializedEditorState`).
234
+
235
+ ```xml
236
+ <grid cols="3" gap="24px">
237
+ <cell><h3>A</h3><p>One</p></cell>
238
+ <cell><h3>B</h3><p>Two</p></cell>
239
+ <cell><h3>C</h3><p>Three</p></cell>
240
+ </grid>
241
+ ```
242
+
243
+ ## Inline annotations
244
+
245
+ ### `<spoiler>` — hidden inline text
246
+
247
+ - **When**: Text the reader can intentionally reveal.
248
+ - **Avoid when**: A collapsible block — use `<details>`.
249
+ - **Required**: none.
250
+ - **Optional**: none.
251
+ - **Body**: inline children. Must appear inside an inline-accepting parent (e.g. `<p>`).
252
+
253
+ ```xml
254
+ <p>The killer is <spoiler>the butler</spoiler>.</p>
255
+ ```
256
+
257
+ ### `<ruby>` — East Asian ruby annotation
258
+
259
+ - **When**: A base text needs a pronunciation/reading overlay.
260
+ - **Avoid when**: A plain parenthetical — use prose.
261
+ - **Required**: base text in the body.
262
+ - **Optional**: `rt` (the reading).
263
+ - **Body**: inline base text.
264
+
265
+ ```xml
266
+ <p><ruby rt="haklex">Haklex</ruby> エディタ。</p>
267
+ ```
268
+
269
+ ### `<mention>` — handle reference
270
+
271
+ - **When**: A person or account reference that carries platform + handle metadata.
272
+ - **Avoid when**: Plain name with no identity metadata — write prose.
273
+ - **Required**: `platform`, `handle`.
274
+ - **Optional**: none.
275
+ - **Body**: display name text. Defaults to handle if empty.
276
+
277
+ ```xml
278
+ <p>Author: <mention platform="github" handle="innei">Innei</mention>.</p>
279
+ ```
280
+
281
+ ### `<tag>` — inline topical label
282
+
283
+ - **When**: An inline tag chip (`#ai`, `#editor`).
284
+ - **Avoid when**: A document-level section category — use a heading.
285
+ - **Required**: text body.
286
+ - **Optional**: none.
287
+ - **Body**: plain text.
288
+
289
+ ```xml
290
+ <p>Topics: <tag>AI</tag> <tag>Lexical</tag>.</p>
291
+ ```
292
+
293
+ ### `<comment>` — inline reviewer annotation
294
+
295
+ - **When**: An inline note that should render as a reviewer comment (yellow highlight, side margin marker, etc.).
296
+ - **Avoid when**: Reader-facing aside — use prose, `<alert>`, or `<details>`.
297
+ - **Required**: text body.
298
+ - **Optional**: none.
299
+ - **Body**: plain text.
300
+
301
+ ```xml
302
+ <p>The pipeline scales linearly<comment>citation needed</comment>.</p>
303
+ ```
304
+
305
+ ### `<footnote>` and `<footnote-section>` — footnotes
306
+
307
+ - **When**: Academic-style references with collapsible definitions.
308
+ - **Avoid when**: Primary content — keep it in the body.
309
+
310
+ `<footnote>`:
311
+
312
+ - **Required**: `ref` — must match a `<def ref="...">` later.
313
+ - **Optional**: none.
314
+ - **Body**: self-closing.
315
+
316
+ `<footnote-section>`:
317
+
318
+ - **Required**: one or more `<def ref="...">definition text</def>`.
319
+ - **Optional**: `id` on `<footnote-section>`.
320
+ - **Body**: `<def>` children only.
321
+
322
+ ```xml
323
+ <p>Lexical 0.44<footnote ref="1" /> is the runtime.</p>
324
+
325
+ <footnote-section>
326
+ <def ref="1">See Facebook's Lexical 0.44 release notes.</def>
327
+ </footnote-section>
328
+ ```
329
+
330
+ ## Interactive
331
+
332
+ ### `<chat>` — conversation transcript
333
+
334
+ - **When**: A user/agent or user/user transcript that should render as chat bubbles.
335
+ - **Avoid when**: A normal quote — use `<blockquote>`.
336
+ - **Required**: `<participants>` block + `<messages>` block.
337
+ - **Optional on `<chat>`**: `id`, `variant` (`user-agent` default | `user-user`).
338
+ - **Required on `<participant>`**: `kind` (`user` | `agent`).
339
+ - **Optional on `<participant>`**: `id` (auto-minted if missing), `name`, `avatar`.
340
+ - **Required on `<message>`**: `participant` (matching a participant `id`).
341
+ - **Optional on `<message>`**: `id` (auto-minted if missing).
342
+ - **Body of `<message>`**: plain text.
343
+
344
+ ```xml
345
+ <chat variant="user-agent">
346
+ <participants>
347
+ <participant id="u1" kind="user" name="Innei" />
348
+ <participant id="a1" kind="agent" name="Haklex" />
349
+ </participants>
350
+ <messages>
351
+ <message id="m1" participant="u1">How do I add a node?</message>
352
+ <message id="m2" participant="a1">Register a reader and a writer in default-registry.ts.</message>
353
+ </messages>
354
+ </chat>
355
+ ```
356
+
357
+ ### `<poll>` — reader choice / survey
358
+
359
+ - **When**: A live poll readers can vote on.
360
+ - **Avoid when**: A static list of options — use `<ul>` / `<ol>`.
361
+ - **Required**: one `<question>` + one or more `<option>`.
362
+ - **Optional on `<poll>`**: `id`, `poll-id` (auto-minted if missing), `mode` (`single` default | `multiple`), `close-at` (ISO-like timestamp), `show-results` (`always` | `after-vote` | `after-close`).
363
+ - **Optional on `<option>`**: `id` (auto-minted if missing).
364
+ - **Body of `<question>`** / **`<option>`**: plain text.
365
+
366
+ ```xml
367
+ <poll mode="single" show-results="after-vote">
368
+ <question>Pick a runtime</question>
369
+ <option>Bun</option>
370
+ <option>Node</option>
371
+ <option>Deno</option>
372
+ </poll>
373
+ ```
374
+
375
+ For freshly authored polls, **omit** `poll-id` and option `id` — the reader mints them. Preserve them when editing an existing poll so vote counts stay attached to the right option.
376
+
377
+ ## Internal / agent
378
+
379
+ ### `<agent-diff>` — review/diff marker
380
+
381
+ - **When**: Internal agent review UI rendering inline diff markers.
382
+ - **Avoid when**: Normal article authoring — do not introduce unless implementing the review pipeline.
383
+ - **Required**: none.
384
+ - **Optional**: `id`, `op` (defaults to `insert`), `entry` (id of a diff entry to link to).
385
+ - **Body**: self-closing.
386
+
387
+ ```xml
388
+ <agent-diff op="insert" entry="diff-7" />
389
+ ```
390
+
391
+ ## Cross-cutting rules
392
+
393
+ - **`id` attribute** on any block-level extension sets `$.blockId`. Omit for fresh content; preserve when editing.
394
+ - **Quoting**: all attribute values must be quoted.
395
+ - **Escaping**: in element text, escape `&` `<` `>` `"` as `&amp;` `&lt;` `&gt;` `&quot;`.
396
+ - **CDATA**: required for opaque JSON snapshots (`<excalidraw>`), strongly recommended for multi-line or XML-sensitive code bodies (`<codeblock>`, `<code-snippet><file>`).
397
+ - **Nested block content** is required inside `<alert>`, `<banner>`, `<nested-doc>`, `<details>`, `<grid><cell>`. The body becomes a fresh `SerializedEditorState` rooted at the container, so Markdown does **not** work inside them.
398
+ - **Canonical tag names** only. Do not emit legacy aliases (`<linkcard>`, `<codesnippet>`, `<nesteddoc>`, `<code-block>`).
399
+ - **Unregistered tags** serialize to `<node type="..." data='{...}' />` — data is preserved but opaque. Always prefer a registered tag.
@@ -0,0 +1,150 @@
1
+ # Structural and inline-formatting nodes
2
+
3
+ Tags in this file are the standard CommonMark-shaped building blocks. They are always available without any extension package. Use them to build the prose around extension nodes. If a fragment contains **any** LiteXML extension tag, the surrounding prose must also be expressed via these tags — Markdown syntax is not parsed inside LiteXML fragments.
4
+
5
+ For the Haklex-specific tags (alerts, banners, code blocks, math, etc.), see [`nodes-extensions.md`](./nodes-extensions.md).
6
+
7
+ ## Block tags
8
+
9
+ ### `<p>` — paragraph
10
+
11
+ - **When**: Default container for ordinary prose. The body of nearly every block-level extension (`<alert>`, `<banner>`, `<details>`, `<nested-doc>`, `<grid><cell>`, `<blockquote>`, `<li>`) is one or more `<p>` elements.
12
+ - **Avoid when**: The content is a heading, list item, or any specialized container — use the matching tag instead.
13
+ - **Required**: none.
14
+ - **Optional**: `id` (sets `$.blockId` for stable references).
15
+ - **Body**: inline content (text, links, inline formatting tags, inline extension nodes).
16
+
17
+ ```xml
18
+ <p>Plain prose with an <a href="https://example.com">inline link</a>.</p>
19
+ ```
20
+
21
+ ### `<h1>` … `<h6>` — headings
22
+
23
+ - **When**: Section hierarchy and document scan anchors.
24
+ - **Avoid when**: You only want to style text — headings imply structural meaning and contribute to the document outline.
25
+ - **Required**: none.
26
+ - **Optional**: `id`.
27
+ - **Body**: inline content.
28
+
29
+ ```xml
30
+ <h2 id="install">Installation</h2>
31
+ ```
32
+
33
+ ### `<blockquote>` — quote
34
+
35
+ - **When**: Quoted external text or cited excerpt. The reader emits `type: "rich-quote"` when `attribution` is present, otherwise `type: "quote"`. Both hydrate to `RichQuoteNode` in the editor; the distinction is a writer-side optimization for fixtures without attribution.
36
+ - **Avoid when**: Warnings or editorial callouts — use `<alert>` or `<banner>`.
37
+ - **Required**: at least one block child (typically `<p>`).
38
+ - **Optional**: `id`, `attribution` (citation line — author, source, work).
39
+ - **Body**: block children.
40
+
41
+ ```xml
42
+ <blockquote attribution="— Wang Xizhi, Lantingji Xu">
43
+ <p>未尝不临文嗟悼,不能喻之于怀。</p>
44
+ </blockquote>
45
+ ```
46
+
47
+ ### `<ul>`, `<ol>` — lists
48
+
49
+ - **When**: Sequences of items. `<ul>` for unordered, `<ol>` for ordered. Set `type="check"` on `<ul>` for a task list.
50
+ - **Avoid when**: Two-dimensional comparison — use `<table>` or `<grid>`. Boolean survey choices — use `<poll>`.
51
+ - **Required**: one or more `<li>` children.
52
+ - **Optional on `<ul>`**: `id`, `type="check"`.
53
+ - **Optional on `<ol>`**: `id`, `start` (numeric starting value, default `1`).
54
+
55
+ ```xml
56
+ <ul>
57
+ <li><p>First</p></li>
58
+ <li><p>Second</p></li>
59
+ </ul>
60
+
61
+ <ol start="3">
62
+ <li><p>Third</p></li>
63
+ </ol>
64
+
65
+ <ul type="check">
66
+ <li checked="true"><p>Done</p></li>
67
+ <li checked="false"><p>Todo</p></li>
68
+ </ul>
69
+ ```
70
+
71
+ ### `<li>` — list item
72
+
73
+ - **Required**: at least one block child.
74
+ - **Optional**: `id`, `checked` (`"true"` / `"false"`, only meaningful inside `<ul type="check">`).
75
+ - **Body**: block children (usually a single `<p>`; nested `<ul>` / `<ol>` is allowed).
76
+
77
+ ### `<table>`, `<tr>`, `<th>`, `<td>` — tables
78
+
79
+ - **When**: Dense factual comparison with rows and columns.
80
+ - **Avoid when**: Editorial multi-column layout — use `<grid>`.
81
+ - **Required**: `<table>` contains `<tr>`; each `<tr>` contains `<th>` or `<td>`; each cell contains block children (typically `<p>`).
82
+ - **Optional**: `id` on `<table>`.
83
+ - **Body**: cells contain `<p>` (or other block content). Header state is encoded by the tag — `<th>` for headers, `<td>` for data cells.
84
+
85
+ ```xml
86
+ <table>
87
+ <tr>
88
+ <th><p>Tag</p></th>
89
+ <th><p>Use</p></th>
90
+ </tr>
91
+ <tr>
92
+ <td><p>p</p></td>
93
+ <td><p>Paragraph</p></td>
94
+ </tr>
95
+ </table>
96
+ ```
97
+
98
+ ### `<hr />` — horizontal rule
99
+
100
+ - **When**: Hard section break.
101
+ - **Required**: self-closing.
102
+ - **Optional**: `id`.
103
+
104
+ ```xml
105
+ <hr />
106
+ ```
107
+
108
+ ## Inline tags
109
+
110
+ ### `<a>` — link
111
+
112
+ - **When**: Inline citation or hyperlink.
113
+ - **Avoid when**: The URL deserves a rich preview — use `<link-card>` (see extensions).
114
+ - **Required**: `href` attribute.
115
+ - **Optional**: `target`, `title`.
116
+ - **Body**: inline children (text and inline formatting tags).
117
+
118
+ ```xml
119
+ <a href="https://example.com" target="_blank" title="More info">Example</a>
120
+ ```
121
+
122
+ `<a>` writers emit either `type: "link"` or `type: "autolink"` depending on origin; both hydrate to a `LinkNode`. From an authoring perspective, always emit `<a>`.
123
+
124
+ ### `<br />` — line break
125
+
126
+ Soft break inside a paragraph. Self-closing.
127
+
128
+ ### Inline formatting tags
129
+
130
+ These map to text-node format flags. Wrap inline text only.
131
+
132
+ | Format | LiteXML |
133
+ | ----------- | ------------------------------------------------------------ |
134
+ | Bold | `<b>text</b>` or `<strong>text</strong>` |
135
+ | Italic | `<i>text</i>` or `<em>text</em>` |
136
+ | Strike | `<s>text</s>`, `<del>text</del>`, or `<strike>text</strike>` |
137
+ | Underline | `<u>text</u>` |
138
+ | Inline code | `<code>text</code>` |
139
+ | Subscript | `<sub>text</sub>` |
140
+ | Superscript | `<sup>text</sup>` |
141
+ | Highlight | `<mark>text</mark>` |
142
+
143
+ These tags **must** appear inside an inline-accepting parent (`<p>`, `<h1>`-`<h6>`, `<li>`, `<th>`, `<td>`, etc.). Nesting is allowed: `<b><i>both</i></b>`.
144
+
145
+ ## Cross-cutting rules
146
+
147
+ - **`id` attribute** on any block tag sets `$.blockId`, which is the Lexical-side anchor used by tool calls that need to target a specific block. Omit for fresh content; preserve when editing existing fragments.
148
+ - **Attribute quoting**: all attribute values must be quoted (`<p id="intro">`, not `<p id=intro>`).
149
+ - **XML escaping** in body text: `&amp;`, `&lt;`, `&gt;`, `&quot;`. Use CDATA when the body is opaque or contains many sensitive characters (typically only needed for code / drawing snapshots, not for ordinary prose).
150
+ - **Canonical tag names**: lowercase, hyphenated. Do not emit legacy aliases (`<linkcard>`, `<codesnippet>`, `<nesteddoc>`, `<code-block>`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-litexml",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Bidirectional Lexical SerializedNode <-> XML conversion with plugin registry",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,7 +27,8 @@
27
27
  },
28
28
  "main": "./dist/node.mjs",
29
29
  "files": [
30
- "dist"
30
+ "dist",
31
+ ".claude/skills/litexml-authoring/**"
31
32
  ],
32
33
  "dependencies": {
33
34
  "lexical": "^0.44.0",