@nativele/ts-dom 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +5 -0
  3. package/README.zh-TW.md +5 -0
  4. package/dist/{a11y-CiEJj_e6.mjs → a11y-KXbo-8o3.mjs} +119 -27
  5. package/dist/cli.mjs +273 -50
  6. package/dist/{lint-Did4w-NL.mjs → lint-DUsdDKZ-.mjs} +33 -8
  7. package/dist/oxlint-plugin.mjs +3 -3
  8. package/dist/vite-plugin.mjs +1 -1
  9. package/docs/architecture.md +188 -0
  10. package/docs/components.md +367 -0
  11. package/docs/contributing.md +80 -0
  12. package/docs/design.md +55 -0
  13. package/docs/lists.md +150 -0
  14. package/docs/performance.md +24 -0
  15. package/docs/recipes.md +189 -0
  16. package/docs/templates.md +73 -0
  17. package/docs/tooling.md +251 -0
  18. package/llms.txt +5 -5
  19. package/package.json +2 -1
  20. package/skills/README.md +19 -2
  21. package/skills/nativele-angular/SKILL.md +15 -11
  22. package/skills/nativele-architecture/SKILL.md +14 -11
  23. package/skills/nativele-ci/SKILL.md +148 -0
  24. package/skills/nativele-core/SKILL.md +16 -13
  25. package/skills/nativele-formatting/SKILL.md +167 -0
  26. package/skills/nativele-integration/SKILL.md +11 -8
  27. package/skills/nativele-linting/SKILL.md +174 -0
  28. package/skills/nativele-monorepo/SKILL.md +298 -0
  29. package/skills/nativele-react/SKILL.md +12 -9
  30. package/skills/nativele-state-management/SKILL.md +4 -4
  31. package/skills/nativele-svelte/SKILL.md +10 -8
  32. package/skills/nativele-tailwind/SKILL.md +8 -7
  33. package/skills/nativele-tanstack-query/SKILL.md +5 -5
  34. package/skills/nativele-testing/SKILL.md +19 -13
  35. package/skills/nativele-typescript/SKILL.md +186 -0
  36. package/skills/nativele-vite/SKILL.md +18 -14
  37. package/skills/nativele-vue/SKILL.md +9 -7
  38. package/skills/nativele-zod/SKILL.md +6 -6
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: nativele-ci
3
+ description: "Make a project's existing CI check the Nativele parts reliably (@nativele/ts-dom): generate before any type check, which ts-dom commands and flags give a trustworthy pass or fail, machine-readable output, committed or git-ignored generated files, keeping a check-only job from writing, caching, and what real-browser tests need. Use when CI is added or changed in a project that depends on @nativele/ts-dom, or when CI fails with TS2307 on ./x.html.js or passes although the templates are wrong."
4
+ metadata:
5
+ tier: official
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
+ tools: "typescript@7.0.2 npm@11.19.1 pnpm@12.16.1"
9
+ author: "Nativele maintainers"
10
+ detects: "typescript"
11
+ ---
12
+
13
+ # Nativele in CI
14
+
15
+ ## When to use
16
+
17
+ - The project depends on `@nativele/ts-dom` and you add, review or change its CI (any provider: GitHub Actions, GitLab CI, Jenkins, a shell script on a build machine), or a pre-merge hook that runs the same checks.
18
+ - CI fails on a clean checkout with `TS2307: Cannot find module './x.html.js'`, fails with `stale-generated`, or passes although a template is wrong.
19
+ - This Skill owns the Nativele steps inside the project's CI: which `ts-dom` commands run, with which flags, in which order relative to the other steps, and how their result is read. It keeps the CI provider, the job layout, the install step and the package manager as they are. It does not own the test framework and its browser set-up (`nativele-testing`), the build and its scripts (`nativele-vite`, or the framework's Skill), the tsconfig (`nativele-typescript`), lint configuration (`nativele-linting`) or formatter configuration and the whitespace mode (`nativele-formatting`); it states what CI needs from them.
20
+
21
+ `detects` is `typescript`: no package in `package.json` shows that a project has CI. What this Skill keeps reliable is the type check of the templates, which needs `typescript` installed (without it `ts-dom check` fails with `tsc-not-found`, Known limitations 2), so `ts-dom doctor` offers it to the Nativele projects that type-check.
22
+
23
+ ## Versions
24
+
25
+ | Tool | Verified version |
26
+ |---|---|
27
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
28
+ | typescript | 7.0.2 |
29
+ | Node.js | 24.22.2 |
30
+ | npm | 11.19.1 (`npm ci`) |
31
+ | pnpm | 12.16.1 (`pnpm install`, `pnpm exec ts-dom`) |
32
+ | Google Chrome | 148.0.7778.178 (headless; only the `moveBefore` check) |
33
+
34
+ Verified on 2026-11-06 on Linux with fresh `git clone`s of a project built from the packed library (generated files git-ignored, and a variant with them committed), running every command below with `CI=true`, no terminal (`setsid -w`, stdin from `/dev/null`), and with deliberate errors. No CI provider was run: the steps are shell commands, which every provider runs as they are. With another major version of `@nativele/ts-dom`, treat every exit code below as unverified.
35
+
36
+ ## Set-up
37
+
38
+ Check first, then change only the Nativele steps. Never replace the CI provider, merge or split jobs, change the install command beyond what step 4 names, or rewrite steps that are not about Nativele.
39
+
40
+ 1. **Read the CI that exists.** Find its configuration (`.github/workflows/*.yml`, `.gitlab-ci.yml`, `Jenkinsfile`, `azure-pipelines.yml`, `.circleci/config.yml`, a script the build machine calls) and list, per job, the steps in order: checkout, install, and every step that type-checks, builds or tests, including the ones hidden in a `package.json` script (`npm run build` that starts with `tsc -b`). Note the package manager from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`) and run `ts-dom` through it (`npx ts-dom`, `pnpm exec ts-dom`, or the project's own script); both `npx` and `pnpm exec` gave the same output and exit codes.
41
+ 2. **Find out whether the generated files are committed.** `git ls-files | grep -E '(\.html\.ts|ts-dom-tags\.ts)$'` prints nothing (and exits 1) when they are not, which is the usual set-up with `*.html.ts` and `ts-dom-tags.ts` in `.gitignore`. The answer decides between step 5 and step 6. Do not change whether they are committed: that is the project's choice.
42
+ 3. **Find the folders and flags the project generates with**: the folders passed to `ts-dom gen`/`check` in its scripts, the `dirs` of `tsDom()` in the Vite config, and `--whitespace=`, `--include=`, `--exclude=`. CI passes exactly the same. On a clean checkout a `ts-dom check` without `--whitespace` writes the files with whitespace kept as written; when the project generates with `--whitespace=collapse`, CI then checks different generated files than the developers have (`ts-dom gen --check --whitespace=collapse` reported them as `outdated`). Calling the project's own script (`npm run check`, say) keeps them in step. CI passes the mode the project uses; which mode that is belongs to `nativele-formatting`.
43
+ 4. **Check that the CI install includes dev dependencies**, or at least `typescript` (and `vue-tsc` in a Vue project). With dev dependencies left out (`npm ci --omit=dev`), `ts-dom check` reports `error [tsc-not-found]` and exits 1, so the job fails instead of passing with the types unchecked (Known limitations 2).
44
+ 5. **Generated files not committed (the usual case).** Generate before anything type-checks. Put `ts-dom check <dirs>` (with the flags from step 3) after the install and before every step that type-checks, builds or tests, or make sure the step that comes first generates (a script that starts with `ts-dom gen <dirs> &&` or `ts-dom check <dirs> &&`). A type check that runs first fails on the clean checkout CI starts from: `npm run build` with `"build": "tsc"` exited 2 with `TS2307: Cannot find module './user-card.html.js'`. When the step that type-checks first is a `package.json` script, `ts-dom doctor` reports it as `typecheck-before-gen` (a warning); state the order to the owner of that script (the build Skill or the framework's Skill) instead of rewriting it here. When the step is written directly in the CI configuration (`npx tsc --noEmit`), doctor does not see it: it reads `package.json` scripts only, and printed `the set-up looks right` next to a workflow file and a `.gitlab-ci.yml` that ran `tsc` first. Move or precede that step yourself; it is a Nativele step in the CI. `ts-dom check` runs the same `tsc` over the project after generating, so a separate `tsc --noEmit` step that only repeats it can go; keep it when it checks another tsconfig (`-p`) or extra flags. In a workspace, each package runs its own `ts-dom check` on its own folder, and a component package generates or builds before the apps that use it (`nativele-monorepo`).
45
+ Do not use `ts-dom check --no-write` here: with nothing generated it reports every generated file as `stale-generated` (missing) plus the `TS2307` errors, and exits 1. Writing git-ignored files does not change what git tracks: after `ts-dom check`, `git status --porcelain` was empty.
46
+ 6. **Generated files committed.** CI must find out whether they are current, not refresh them: run `ts-dom gen --check <dirs>` (only the generated files: missing, outdated, orphaned) and `ts-dom check --no-write <dirs>` (the template diagnostics, `tsc` and lint on the files as committed, plus the same staleness as errors). A plain `ts-dom check` here passes on a template edited without regenerating, because it regenerates first: it printed `ts-dom check: no issues`, exit 0, and left `src/user-card.html.ts` modified.
47
+ 7. **Decide what fails the job**, and say it to the user rather than choosing silently:
48
+ - `ts-dom check` exits 1 on any error; warnings (lint `data-flow`, `--a11y`) leave it at 0, and `--strict` makes them fail too. `tsc-not-found` is an error.
49
+ - `ts-dom doctor` exits 1 only on an error (verified: `no-tsconfig`, `tsconfig-no-dom-lib`, `dir-not-found`); warnings (verified: `typecheck-before-gen`, `stale-generated`) and `info` lines (`skill-available`, `skill-outdated`) never fail it unless `--strict` is passed, which makes a warning exit 1 too. Run it after the step that generates: before it, every generated file is a `stale-generated` (missing) warning. To fail on doctor warnings, use `--strict` or read its `--json` (see Example).
50
+ - A folder that does not exist is an error for `check` and `doctor` (`dir-not-found`), so a wrong path in CI fails instead of checking nothing.
51
+ 8. **Machine-readable output.** `--json` on `check`, `doctor`, `lint` and `gen --check` prints one JSON document on stdout and nothing on stderr, with the same exit code as the text form: `{ "issues": [{ file, line, col, rule, severity, message }] }` (`doctor` issues may lack `file` and `line`), and `{ "stale": [{ file, reason }], "errors": [...] }` for `gen --check`. In text form, errors and warnings go to stderr as `file:line:col severity [rule-id] message Fix: ...`, and the success line (`ts-dom check: no issues`, `ts-dom gen --check: generated files are up to date`, `ts-dom doctor: the set-up looks right`) and doctor's `info` lines to stdout. No command prints color codes, prompts or reads stdin. `ts-dom gen` prints nothing when it succeeds.
52
+ 9. **Caching.** Leave dependency caching as the project has it. Do not cache the generated files: `ts-dom check` rewrites outdated ones and deletes orphaned ones anyway, and with `--no-write` or `gen --check` a restored copy is what gets judged instead of the checkout. `ts-dom` keeps no cache of its own: after `ts-dom check` no file outside the generated ones was new or changed, and there was no `node_modules/.cache`.
53
+ 10. **Real-browser tests.** CI needs a Chromium-based browser on the runner when the project has real-browser tests: Nativele moves kept list items with `moveBefore` where the browser has it, which a DOM shim does not have (the architecture guide's Testing lists what needs a real browser). Headless Chrome 148 has `Element.prototype.moveBefore` (it printed `function`). How the tests find and start the browser (`CHROME_BIN`, launch arguments, the test projects) belongs to the testing Skill (`nativele-testing`); this Skill only requires that the browser exists on the runner and that the generated files exist before the tests start (the Vite plugin writes them when Vitest starts with it; otherwise step 5).
54
+ 11. **Run Verify** on a fresh clone, not in your working copy, where the generated files already exist.
55
+
56
+ ## Example
57
+
58
+ The shell steps for one job, after the provider's checkout; put each command where the project's CI already runs its shell commands. Keep the project's own install command and its other steps; this shows only where the Nativele steps go. `src` stands for the project's folders and flags from Set-up step 3.
59
+
60
+ Generated files git-ignored:
61
+
62
+ ```sh
63
+ set -e
64
+ npm ci # the project's install, with dev dependencies
65
+ npx ts-dom check src # generates, then template diagnostics, tsc and lint; exit 1 on an error
66
+ npx ts-dom doctor src # the set-up; exit 1 only on an error
67
+ # ...the project's own build and test steps, unchanged
68
+ test -z "$(git status --porcelain)" # optional: nothing tracked changed
69
+ ```
70
+
71
+ Generated files committed:
72
+
73
+ ```sh
74
+ set -e
75
+ npm ci
76
+ npx ts-dom gen --check src # the committed generated files are current; writes nothing
77
+ npx ts-dom check --no-write src # template diagnostics, tsc and lint on the committed files; writes nothing
78
+ npx ts-dom doctor src
79
+ # ...the project's own build and test steps, unchanged
80
+ ```
81
+
82
+ Failing on doctor warnings too (optional, the project's choice), from its JSON:
83
+
84
+ ```sh
85
+ npx ts-dom doctor src --json | node -e 'const { issues } = JSON.parse(require("fs").readFileSync(0, "utf8")); const bad = issues.filter((i) => i.severity !== "info"); for (const i of bad) console.error(i.severity, i.rule, i.message); process.exit(bad.length ? 1 : 0)'
86
+ ```
87
+
88
+ What these gave on fresh clones of the test project:
89
+
90
+ | Case | Steps | Result |
91
+ |---|---|---|
92
+ | clean, git-ignored | first block | exit 0; `ts-dom check: no issues`; doctor printed the `typecheck-before-gen` warning of the project's `"build": "tsc"` and exited 0 |
93
+ | `{{ nope }}` committed in a template, git-ignored | first block | exit 1 at `ts-dom check`: `src/user-card.html:1:20 error [missing-binding] "nope" is used by {{ nope }} ...` |
94
+ | clean, committed | second block | exit 0; `generated files are up to date`, `no issues`, `the set-up looks right` |
95
+ | template changed and committed without regenerating, committed | second block | exit 1 at `gen --check`: `src/user-card.html.ts:1:1 error [stale-generated] The generated file is outdated. Fix: run ts-dom gen.` |
96
+ | the same, with the first block | first block | `ts-dom check` exit 0, then `test -z` failed on ` M src/user-card.html.ts` |
97
+
98
+ ## Known limitations
99
+
100
+ 1. **`ts-dom check --no-write` needs the generated files to exist.** It is for projects that commit them. With git-ignored files on a clean checkout it always fails (`stale-generated` for each file, `TS2307`), so use `ts-dom check`; the files it writes are ignored by git.
101
+ 2. **Warnings never fail `check` without `--strict`.** Without `typescript` installed, `ts-dom check` reports `.:1:1 error [tsc-not-found]` and exits 1, because nothing was type-checked. Install dev dependencies in CI; `check --strict` also fails on lint warnings.
102
+ 3. **A writing `ts-dom check` hides stale committed files.** It refreshes them and passes. In a project that commits them, run `gen --check` or `check --no-write`, or follow `check` with a `git status --porcelain` test.
103
+ 4. **`ts-dom gen --check` checks only the generated files.** With a template that names nothing in its component, it printed `generated files are up to date` and exited 0; `ts-dom check` is what finds that. It does report codegen errors (`forbidden-tag` for a `<script>` in a template, exit 1).
104
+ 5. **`ts-dom doctor` does not read CI configuration:** warnings and `info` exit 0 unless `--strict` is passed (warnings then exit 1), and a `tsc` step written into the CI file before generation is not reported. Use its `--json` (Example) and Set-up step 5.
105
+ 6. **`ts-dom gen --watch` never exits** (it printed `[ts-dom] watching src` and ran until killed). Never use it, or a script that starts it, in CI.
106
+ 7. **The whitespace mode comes from the files or the flag.** On a clean checkout, generation without `--whitespace` writes `preserve`; pass the project's flag in CI (Set-up step 3); the mode itself is chosen by `nativele-formatting`.
107
+ 8. **`ts-dom check` type-checks what the tsconfig covers, not only the folders it is given**: `ts-dom check web` (a folder that does not exist) still reported a type error from `src`. The folders decide what is generated and linted; which config `tsc` reads belongs to the TypeScript Skill.
108
+
109
+ ## Verify
110
+
111
+ On a fresh clone of the project (`git clone <repo> /tmp/ci-try && cd /tmp/ci-try`), with the project's install, run the CI's Nativele steps exactly as the CI configuration has them, without a terminal:
112
+
113
+ ```sh
114
+ CI=true setsid -w sh -c '<the steps>' </dev/null; echo "exit=$?" # expect: exit=0
115
+ npx ts-dom check <dirs> # git-ignored: expect "ts-dom check: no issues"
116
+ npx ts-dom gen --check <dirs> # committed: expect "ts-dom gen --check: generated files are up to date"
117
+ npx ts-dom doctor <dirs> # expect: no error lines; "the set-up looks right" when there is no warning either
118
+ git status --porcelain # expect: empty
119
+ ```
120
+
121
+ Then prove the job fails when it should, and undo each change afterwards:
122
+
123
+ - Add `<p>{{ nope }}</p>` to a template of a component, commit it on a throwaway branch, run the steps on a fresh clone: expect exit 1 and `error [missing-binding] "nope" is used by {{ nope }}` at the template line. Code that uses the broken component may report its own `[tsc]` errors next to it (for example a `TS2345` where the element is appended); their messages say they follow from the contract error, and they go away with the template fix.
124
+ - Committed files only: change a template's text without running `ts-dom gen`, commit, run the steps: expect exit 1 and `error [stale-generated] The generated file is outdated.`
125
+ - Run the steps with the dev dependencies left out: expect `check` to exit 1 with `error [tsc-not-found]`; that is Known limitations 2, and the CI install must not do it.
126
+
127
+ ## Troubleshooting
128
+
129
+ | Symptom | Cause | Fix |
130
+ |---|---|---|
131
+ | `TS2307: Cannot find module './x.html.js' or its corresponding type declarations` in CI only | a type check, build or test step runs before anything generates; locally the files already exist | Set-up step 5: `ts-dom check <dirs>` first, or a script that starts with `ts-dom gen <dirs> &&` (owned by the build or framework Skill) |
132
+ | `error [stale-generated] The generated file is missing` from `check --no-write` on every file | `--no-write` on a clean checkout of a project that does not commit the generated files | `ts-dom check` without `--no-write` (Known limitations 1) |
133
+ | `error [stale-generated] The generated file is outdated` from `gen --check` | a template or `define()` changed and the committed generated files were not regenerated | run `ts-dom gen <dirs>` with the project's flags and commit the result |
134
+ | `stale-generated` (outdated) for every file although the developer regenerated | CI generates or checks with another `--whitespace` (or other folders, `--include`, `--exclude`) than the project | pass the project's flags (Set-up step 3) |
135
+ | CI passes, but a broken template reaches the main branch | `typescript` not installed (`tsc-not-found` is an error, so only a step that ignores its exit code lets it through), a writing `check` on committed files, or `gen --check` alone | Known limitations 2, 3, 4 |
136
+ | `[dir-not-found] The folder src does not exist` | the commands default to `src` in the folder they run in | pass the folders the project uses, from the folder with `package.json`; in a workspace, from each package's folder (`nativele-monorepo`) |
137
+ | `tsc-failed`: `tsc did not check the types` | no `tsconfig.json` where `ts-dom check` runs, or a config `tsc` rejects | run it from the project folder, or `--project=<path>`; the tsconfig belongs to the TypeScript Skill |
138
+ | the job never ends | a step runs `ts-dom gen --watch` (or a dev server) | use `ts-dom check` or `ts-dom gen` |
139
+ | real-browser tests fail to launch Chrome | no Chromium-based browser on the runner, or not where the tests look | install one on the runner; the testing Skill covers how the tests find it |
140
+
141
+ ## See also
142
+
143
+ - What each tool checks, `ts-dom check`, `doctor`, `gen --check` and which command writes files: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#checks
144
+ - Generating before `tsc`, committing or ignoring the generated files, `--whitespace`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
145
+ - The generated `x.html.ts`: https://github.com/Pixmi/nativele/blob/main/docs/templates.md#how-types-are-generated
146
+ - Moving list items with `moveBefore`: https://github.com/Pixmi/nativele/blob/main/docs/lists.md#moving-items-connecting-and-subscriptions
147
+ - Which tests need a real browser: https://github.com/Pixmi/nativele/blob/main/docs/architecture.md#testing
148
+ - How Skills share a project, and who owns which configuration: https://github.com/Pixmi/nativele/blob/main/skills/README.md#how-skills-work-together
@@ -3,8 +3,8 @@ name: nativele-core
3
3
  description: "Build and change web components with Nativele (@nativele/ts-dom): the write-check-fix loop, templates, state, lists and data flow. Use whenever a project depends on @nativele/ts-dom and you add or change an .html template or a component."
4
4
  metadata:
5
5
  tier: official
6
- nativele: ">=0.5.0 <0.6.0"
7
- verified: "2026-11-05"
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
8
  tools: "typescript@7.0.2"
9
9
  author: "Nativele maintainers"
10
10
  detects: "@nativele/ts-dom"
@@ -23,17 +23,17 @@ A component is two files. The HTML holds structure and names (`{{ name }}`, `dat
23
23
 
24
24
  | Tool | Verified version |
25
25
  |---|---|
26
- | @nativele/ts-dom | 0.5.0 (the last agent trial) |
26
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
27
27
  | typescript | 7.0.2 |
28
28
 
29
- Verified on 2026-11-05 by agent trials: fresh agents built real components with only this Skill, and the results were checked independently. The library's own CI type-checks, lints and runs the examples in the repository in real Chrome.
29
+ Verified on 2026-11-06 by agent trials: fresh agents built real components with only this Skill, and the results were checked independently. The library's own CI type-checks, lints and runs the examples in the repository in real Chrome.
30
30
 
31
31
  ## Set-up
32
32
 
33
- 1. `npm i @nativele/ts-dom` and `npm i -D typescript`, and set `"type": "module"` in `package.json` (with or without a bundler).
33
+ 1. `npm i @nativele/ts-dom` and `npm i -D typescript`.
34
34
  2. Put the components under `src/`, the folder every `ts-dom` command scans by default; for another folder, pass it to each command (`npx ts-dom check web/ui`).
35
- 3. `tsconfig.json` with `"strict": true`, `"lib"` including `"DOM"`, and `"moduleResolution"` `"NodeNext"` or `"bundler"`; its `include` must cover the folders with templates. A complete one for the no-bundler case is in the setup guide: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
36
- 4. With Vite, add `tsDom()` from `@nativele/ts-dom/vite` to `plugins`. Without a bundler, `ts-dom gen` plus `tsc` compiles the code, and the page needs an import map for `@nativele/ts-dom` plus a `<script type="module">` for the compiled entry, a module that imports the top-level components: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#running-in-the-browser-no-bundler
35
+ 3. The tsconfig must cover the folders with templates and meet Nativele's few requirements (`strict`, `DOM`, a resolution that finds packages, and `"type": "module"` when `tsc` emits): check and adjust it with the TypeScript Skill (`npx ts-dom skills nativele-typescript`). A complete one for the no-bundler case is in the setup guide: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
36
+ 4. With Vite, add `tsDom()` from `@nativele/ts-dom/vite` to `plugins`, and set up the rest with the Vite Skill (`npx ts-dom skills nativele-vite`): `tsDom()` generates for the dev server and `vite build`, not for a `tsc` that runs before them, so Vite's template script `tsc && vite build` fails on a clean checkout until it generates first. Without a bundler, `ts-dom gen` plus `tsc` compiles the code, and the page needs an import map for `@nativele/ts-dom` plus a `<script type="module">` for the compiled entry, a module that imports the top-level components: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#running-in-the-browser-no-bundler
37
37
  5. Add `*.html.ts` and `ts-dom-tags.ts` to `.gitignore`: they are generated.
38
38
  6. Run `npx ts-dom doctor` once; fix every error and warning it reports. Its `info` lines name the other Skills that fit the project.
39
39
 
@@ -120,7 +120,7 @@ define("todo-app", TodoApp);
120
120
  ## The loop
121
121
 
122
122
  1. Write the `.html` and the `.ts` of a component together. Import the template as `./x.html.js`.
123
- 2. Run `npx ts-dom check` (add `--json` to read the result as data). It regenerates the generated files, runs `tsc`, points template contract errors at the template line and column, and runs the lint rules. Use `--no-write` where the working tree must not change.
123
+ 2. Run `npx ts-dom check` (add `--json` to read the result as data). It regenerates the generated files, runs `tsc`, points template contract errors at the template line and column, and runs the lint rules. Use `--no-write` only where the generated files are committed; CI steps are covered by `npx ts-dom skills nativele-ci`.
124
124
  3. Fix every issue by its `Fix:` hint. Never cast a type error away, and never edit `x.html.ts` or `ts-dom-tags.ts`: they are generated.
125
125
  4. `npx ts-dom rules --explain=<id>` explains any rule id from a diagnostic, with a wrong and a right example.
126
126
  5. Repeat until `ts-dom check` prints `no issues`.
@@ -128,14 +128,14 @@ define("todo-app", TodoApp);
128
128
  ## Rules
129
129
 
130
130
  - No expressions in HTML: `{{ name }}` is a single identifier. Compute in `derive` (or in TypeScript) and fill a named hole.
131
- - Change state only with `this.set()`. Rendering is synchronous; `this.batch()` combines updates.
131
+ - Read state and props in methods as `this.state.x` and `this.props.x` (in `render`, from its `view`); change state only with `this.set()`. Rendering is synchronous; `this.batch()` combines updates.
132
132
  - Once a component has `state`, `props` or `derive`, every `{{ hole }}` and `data-if` outside a `<template data-each>` must name one of them (holes inside a plain-element list are filled by `replace`). Show store data by copying it into state in the `bind` callback.
133
133
  - `store.set` replaces the whole value; a component's `this.set` merges. Spread the rest yourself: `store.set((s) => ({ ...s, status: "loading" }))`.
134
134
  - Lists, attributes and focus go in `render(view, changed)`. `changed` holds the state, prop and derived names that changed; annotate it with the names you check (`changed: ReadonlySet<"items" | "visible">`), never `ReadonlySet<string>`.
135
135
  - Props go down (the parent sets them), events go up (`emit`, handled with `data-on-<event>` on the child tag). `emit` does not bubble; data needed several levels away goes in a shared `createStore` that the components `bind`.
136
136
  - State, props and the view are deeply read-only: replace objects instead of changing them (`{ ...todo, done: true }`).
137
137
  - A `data-each` list cannot be nested: the inner list belongs to its own item component.
138
- - A list of plain elements (no item component) has neither `data-key` nor `data-prop`; fill it with `this.lists.x.replace(items, key, fill)` in `render`: https://github.com/Pixmi/nativele/blob/main/docs/lists.md
138
+ - A list of plain elements (no item component) has neither `data-key` nor `data-prop`; fill it with `this.lists.x.replace(items, (item) => item.id, (item, { text, refs, show, el }) => { ... })` in `render`. In `fill`, a hole inside the item (any name, `<li>{{ label }}</li>`) is set with `text.label = ...`, a `data-ref` inside it is `refs.x`, a `data-if` inside it is `show("name", on)`, and `el` is the item's root element; an event handler on an element of the item receives `(e: Event, hit: EventHit)` with the item's key in `hit.key`: https://github.com/Pixmi/nativele/blob/main/docs/lists.md
139
139
  - For a list of components, write `data-key` and `data-prop` on the `<template data-each>`, give the item component `role="listitem"` inside `<ul>` or `<ol>`, assign `this.lists.x.items = ...` in `render`, and type item handlers as `hit: EventHit<Item, "id">`. Each item component gets its item through that one prop; to pass more (selected, leading), map the items to objects that carry it before assigning.
140
140
  - Do not name a handler or prop like a DOM or Nativele member (`remove`, `click`, `title`, `text`, `refs`, `state`, `render`, ...), or an emitted event like a native event (`click`, `change`, `input`, ...); `ts-dom check` reports both.
141
141
 
@@ -151,7 +151,7 @@ define("todo-app", TodoApp);
151
151
  ```sh
152
152
  npx ts-dom check --strict # expect: ts-dom check: no issues
153
153
  npx ts-dom doctor # expect: ts-dom doctor: the set-up looks right (info lines are suggestions)
154
- npx tsc --noEmit # expect: no errors
154
+ npx tsc --noEmit # after check, which generated the files: expect no errors
155
155
  ```
156
156
 
157
157
  Then exercise the changed component in a real browser (the project's own test runner, or the browser).
@@ -160,18 +160,21 @@ Then exercise the changed component in a real browser (the project's own test ru
160
160
 
161
161
  | Symptom | Cause | Fix |
162
162
  |---|---|---|
163
- | `ReferenceError: exports is not defined` in the browser | `package.json` has no `"type": "module"`, so `tsc` emitted CommonJS | set `"type": "module"`, delete the output folder, run `tsc` again |
163
+ | `ReferenceError: exports is not defined` in the browser | `package.json` has no `"type": "module"`, so `tsc` emitted CommonJS | set `"type": "module"` (`nativele-typescript`), delete the output folder, run `tsc` again |
164
164
  | `Failed to resolve module specifier "@nativele/ts-dom"` | no bundler and no import map | add the import map from the setup guide before the module script |
165
165
  | `Cannot find module './x.html.js'` | the generated file is missing | run `npx ts-dom check` (or `ts-dom gen`) |
166
166
  | `missing-binding` at a template line | a hole or `data-if` name that state, props and derive do not have | add the field, or rename the hole |
167
167
  | A list stays empty | `render` never assigns it, or guards it with a name that is not in `changed` | assign `this.lists.x.items` in `render`, guarded by the name of what it shows |
168
168
  | A child component does not update | an object prop was changed in place | pass a new object |
169
- | No types in a parent's `refs.child` | the child class is not exported, or is defined outside the scanned folders | `export class`, and keep it in a folder `ts-dom gen` scans |
169
+ | No types in a parent's `refs.child` | the child class is not exported, or is defined outside the scanned folders | `export class`, and keep it in a folder `ts-dom gen` scans; a component from another workspace package comes from that package's exports (`nativele-monorepo`) |
170
170
 
171
171
  ## See also
172
172
 
173
+ The guides linked here are also in the installed package, under `node_modules/@nativele/ts-dom/docs/`.
174
+
173
175
  - Templates: https://github.com/Pixmi/nativele/blob/main/docs/templates.md
174
176
  - Components and data flow: https://github.com/Pixmi/nativele/blob/main/docs/components.md
175
177
  - Lists: https://github.com/Pixmi/nativele/blob/main/docs/lists.md
176
178
  - Recipes (loading states, dialogs, forms, store data): https://github.com/Pixmi/nativele/blob/main/docs/recipes.md
177
179
  - Setup, checks and lint: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
180
+ - The tsconfig, lint, formatter, CI and workspace set-up: `npx ts-dom skills nativele-typescript`, `nativele-linting`, `nativele-formatting`, `nativele-ci`, `nativele-monorepo`
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: nativele-formatting
3
+ description: "Keep a formatter (oxfmt, Prettier, vp fmt) and Nativele (@nativele/ts-dom) consistent without replacing the formatter or its options: what reformatting does to .html templates and their rendered spacing, which whitespace mode to generate with, how to check that a reformat changed no text, and keeping the formatter off the generated files. Use when a project with @nativele/ts-dom formats its templates, when ts-dom doctor reports formatter-whitespace, or when spacing or text changed after formatting a template."
4
+ metadata:
5
+ tier: official
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
+ tools: "oxfmt@0.76.0 prettier@3.9.10 vite-plus@1.5.0 typescript@6.0.3"
9
+ author: "Nativele maintainers"
10
+ detects: "prettier oxfmt"
11
+ ---
12
+
13
+ # Nativele + formatters
14
+
15
+ ## When to use
16
+
17
+ - The project depends on `@nativele/ts-dom` and formats its files with oxfmt, Prettier or `vp fmt` (Vite+, which runs its bundled oxfmt). Signals: `oxfmt` or `prettier` in `package.json`, a script that runs `oxfmt`, `prettier` or `vp fmt`, a `fmt` block in `vite.config.*`, a `.oxfmtrc.json`, `.oxfmtrc.jsonc`, `oxfmt.config.ts`, `.prettierrc*`, `prettier.config.*` or a `prettier` key in `package.json`.
18
+ - `ts-dom doctor` reports `formatter-whitespace`, a formatter's `--check` fails on a generated `x.html.ts`, or text on the page or in a test changed after a template was formatted (words run together, a gap appeared, `textContent` gained newlines).
19
+ - This Skill owns the formatter's configuration and ignore files as far as Nativele is concerned, and the choice of whitespace mode. It does not own the scripts or the Vite config that pass the mode to `ts-dom gen` (`nativele-vite`, or the framework's Skill), the tsconfig (`nativele-typescript`), lint (`nativele-linting`), tests (`nativele-testing`) or CI (`nativele-ci`); it states what they need and leaves the how to them.
20
+ - Not for choosing or setting up a formatter: it starts from the formatter the project already has, and never replaces it, reformats files the task did not touch, or changes its options (`printWidth`, `htmlWhitespaceSensitivity`, ...).
21
+
22
+ Why it matters: `ts-dom gen` turns every text node of a template into data, including whitespace, so where a formatter puts line breaks and indentation becomes part of the component. Which whitespace survives depends on the mode the template is generated with.
23
+
24
+ ## Versions
25
+
26
+ | Tool | Verified version |
27
+ |---|---|
28
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
29
+ | oxfmt | 0.76.0 |
30
+ | prettier | 3.9.10 |
31
+ | vite-plus | 1.5.0 (`vp fmt` runs oxfmt 0.76.0) |
32
+ | typescript | 6.0.3 |
33
+
34
+ Verified on 2026-11-06 in fresh projects built from the packed library, with each formatter at its defaults, by comparing the generated node data before and after formatting and the text rendered in Google Chrome 148. With another major version of a formatter (or an oxfmt other than 0.76.x, which is pre-1.0), treat every statement about its output as unverified and run the checks in step 5.
35
+
36
+ ## Set-up
37
+
38
+ Check first, then change only what fails. Keep the formatter, its configuration and its options as they are.
39
+
40
+ 1. **Find the formatter and what it reaches.** Look for the signals in "When to use". Then run the formatter's own check on the folder with the templates, as the project runs it (`npx oxfmt --check <dir>`, `npx prettier --check <dir>`, `npx vp fmt --check`), after `npx ts-dom gen <dir>` so the generated files exist. The listed files show whether it formats the `.html` templates and whether it touches the generated `x.html.ts` and `ts-dom-tags.ts`. oxfmt and Prettier format `.html` by default.
41
+ 2. **Read the current whitespace mode and doctor's view.**
42
+ - A generated `x.html.ts` written with `collapse` has `// whitespace: collapse` as its second line; without that line it was written with `preserve`.
43
+ - Look for `--whitespace=` in the `package.json` scripts and `whitespace:` in the `tsDom()` options of the Vite config.
44
+ - Run `npx ts-dom doctor <dir>`. When it detects a formatter and nothing asks for `collapse`, it prints `warning [formatter-whitespace] A formatter (oxfmt or Prettier) is set up, and templates keep their whitespace as written, so reformatting a template can change the spacing on the page. Fix: generate with whitespace "collapse" ...`. When a script or the Vite config asks for `collapse` but a generated file was written with `preserve`, it reports that file. Warnings leave the exit code at 0.
45
+ 3. **Keep the formatter off the generated files.** They are rewritten by every `ts-dom gen` and `ts-dom check`, so a reformatted one is reported as `error [stale-generated] The generated file is outdated` by `ts-dom gen --check` and `ts-dom check --no-write`, and the formatter's `--check` fails again after the next generation.
46
+ - If `.gitignore` lists `*.html.ts` and `ts-dom-tags.ts` (as the guides recommend), there is nothing to add: oxfmt, Prettier and `vp fmt` skip git-ignored files when they format a folder.
47
+ - If the project commits the generated files, add the two patterns to the ignore list the formatter already uses, as new lines, without touching the rest: `.prettierignore` (read by Prettier and by oxfmt) with `**/*.html.ts` and `**/ts-dom-tags.ts`; or oxfmt's `ignorePatterns` array in its existing config file; or, for `vp fmt`, `fmt.ignorePatterns` in the existing `defineConfig` of `vite.config.ts`. Do not create a second config file for a formatter that already has one.
48
+ - Do not ignore the templates (`*.html`): the formatter is what reports misnested markup that `ts-dom gen` accepts (Known limitations 4).
49
+
50
+ Keep it off the installed Skills too. `ts-dom skills add` copies them into `.agents/skills/` (or `.claude/skills/`), which `.gitignore` usually does not list, so a `--check` over the whole project fails on their Markdown. Add that folder to the formatter's own ignore file (`.prettierignore`, or oxfmt's `ignorePatterns`), next to whatever it already ignores.
51
+ 4. **Generate with whitespace `collapse`**, which doctor asks for when a formatter reaches the templates. What each mode does to a template after formatting, measured with oxfmt and Prettier at their defaults:
52
+
53
+ | Formatter change | `preserve` | `collapse` |
54
+ |---|---|---|
55
+ | Line breaks and indentation in running text | same rendering; `textContent` gains the newlines and indentation | same rendering and the same text |
56
+ | Newline and indentation added inside a block or inline-block (`<p>`, `<button>`, `<li>`) | same rendering; `textContent` gains them (`"\n Toggle\n"`) | same rendering and the same text (`"Toggle"`) |
57
+ | Newline added between two elements the formatter treats as blocks (`<div>`) that CSS makes `inline-block` | a gap appears between them (4px in the test) | no gap, as before |
58
+ | Line breaks inside an element styled `white-space: pre-line` or `pre-wrap` | the formatter's line breaks show on the page | as before |
59
+ | A space between two inline elements (`</a> <time>`) turned into a line break | same rendering | same rendering and the same text: the line break becomes one space |
60
+ | `<pre>`, `<textarea>` | unchanged | unchanged |
61
+
62
+ `collapse` keeps only the whitespace the browser renders: a run of whitespace between two inline-level siblings becomes one space, and whitespace next to a block-level element or at the start and end of a block or inline-block element's content is dropped. It judges each element by its default display, as both formatters do, so reformatting changes neither the page nor `textContent`; an element whose CSS changes its display is still judged by its tag (Known limitations 1). The mode is set where `ts-dom gen` runs, which this Skill does not own. State this requirement to the owner of the scripts and the Vite config (`nativele-vite`, or the framework's Skill): every `ts-dom gen`, `ts-dom gen --watch` and `ts-dom check` in the scripts that can write the generated files passes `--whitespace=collapse`, and, when the project uses the Vite plugin, it has the same mode too (`tsDom({ dirs: [...], whitespace: "collapse" })`): both write the generated files. A run without the flag keeps the mode a file was last written with, but on a clean checkout it writes `preserve`. Then regenerate once with the flag. If the user decides to keep `preserve`, leave it: doctor keeps the warning, and the rows above say what reformatting can change.
63
+ 5. **Check that a reformat changed no text** (`collapse` only). Generate before formatting, keep a copy, format, generate again, and compare the generated data one node per line:
64
+
65
+ ```sh
66
+ npx ts-dom gen src/nv --whitespace=collapse
67
+ cp src/nv/issue-meta.html.ts /tmp/before.ts
68
+ npx prettier --write src/nv/issue-meta.html # or oxfmt, vp fmt
69
+ npx ts-dom gen src/nv
70
+ diff <(tail -1 /tmp/before.ts | tr ',' '\n') <(tail -1 src/nv/issue-meta.html.ts | tr ',' '\n')
71
+ ```
72
+
73
+ No output means the generated data, and so the text, is the same. A template with a `<style>` block can show one changed line: the formatter's CSS indentation inside the style string, which does not change the page; any other changed line is a real change. With the versions above it printed nothing for every template measured: inline elements and text broken over lines, content moved onto its own lines inside `<p>` and `<button>`, and blocks put on separate lines. Run it when the formatter's version differs from the Versions table.
74
+ 6. **Page documents are not templates.** A file that starts with `<!doctype html>` or `<html>` is a page entry, which `ts-dom gen` does not process, and the browser parses it with normal HTML rules; the whitespace mode does not apply, and formatting it needs nothing from Nativele. Both formatters rewrite `<!DOCTYPE html>` to `<!doctype html>` and keep a JSON `importmap` valid; `ts-dom gen` still skips the page afterwards.
75
+ 7. **State what the other areas need**, without changing them:
76
+ - Tests (`nativele-testing`): with `collapse`, `textContent` does not change when a template is reformatted, but it is not the text the page shows between blocks: an `<h2>` followed by a `<p>` gives `"Hello, NAME!You have"`, before and after formatting, while `innerText` in a real browser separates them with a blank line. Assert on the element that holds the text, or on `innerText` in a real browser. With `preserve`, `textContent` gains the formatter's newlines and indentation (the table in step 4); compare it with its whitespace normalized (`el.textContent.replace(/\s+/g, " ").trim()`).
77
+ - CI (`nativele-ci`): run the formatter's `--check`, and pass the same mode to every `ts-dom gen` and `ts-dom check` there (`--whitespace=collapse`): on a clean checkout a run without it writes `preserve`, and `--no-write` with a mode different from the one the files were written with reports them as `stale-generated`.
78
+ 8. **Run Verify.**
79
+
80
+ ### What formatting does to Nativele markup
81
+
82
+ Both formatters, at their defaults, on templates that pass `ts-dom check`; every formatted template still passed `ts-dom check`:
83
+
84
+ | Markup | What the formatter does | Effect |
85
+ |---|---|---|
86
+ | `{{ name }}` | kept as written (`{{name}}` stays without spaces); at the line width, both may break inside it: `{{` at the end of a line and `name }}` on the next | none: `{{\n name }}` is the same hole for `ts-dom gen`, `tsc` and the runtime |
87
+ | `data-ref`, `data-if`, `data-on-*`, `data-each` values | unchanged; long attribute lists are put one per line | none |
88
+ | `class`, `style` | `class=" one two "` becomes `class="one two"`; `style="color:red"` becomes `style="color: red"` | none |
89
+ | `<template data-each>` | content kept against the tags (`<template data-each="rows"\n ><li ...>...</li></template\n>`); Prettier adds whitespace inside the `<li>` | none: whitespace around or inside the item root is accepted |
90
+ | `<style scoped>` | the CSS is reformatted, `scoped` kept | none |
91
+ | Void elements | `<input>` becomes `<input />` | none: the same generated data |
92
+ | `<pre>`, `<textarea>` | a newline is added after the opening tag | none: the HTML parser drops a newline right after the tag |
93
+ | Text next to holes | reflowed at the line width | as the table in step 4 |
94
+
95
+ ## Example
96
+
97
+ A component whose status line separates its parts with plain spaces, in a project that formats with Prettier and generates with `collapse`.
98
+
99
+ ```html
100
+ <!-- src/nv/issue-meta.html, before formatting -->
101
+ <p class="meta"><strong>{{ status }}</strong> <a href="#owner">{{ owner }}</a> <time>{{ opened }}</time> <span>{{ comments }}</span></p>
102
+ ```
103
+
104
+ ```ts
105
+ // src/nv/issue-meta.ts
106
+ import { Component, define } from "@nativele/ts-dom";
107
+ import tpl from "./issue-meta.html.js";
108
+
109
+ export class IssueMeta extends Component(tpl, {
110
+ state: { status: "Open", owner: "ann", opened: "2 days ago", comments: "3 comments" },
111
+ }) {}
112
+
113
+ define("issue-meta", IssueMeta);
114
+ ```
115
+
116
+ `npx prettier --write src/nv/issue-meta.html` writes:
117
+
118
+ ```html
119
+ <p class="meta">
120
+ <strong>{{ status }}</strong> <a href="#owner">{{ owner }}</a>
121
+ <time>{{ opened }}</time> <span>{{ comments }}</span>
122
+ </p>
123
+ ```
124
+
125
+ `ts-dom check` still passes, the comparison in step 5 prints nothing, and the page shows `Open ann 2 days ago 3 comments`, as before formatting; the element's `textContent` is the same string. oxfmt (line width 100) breaks before `<span>` instead, with the same result. Each line break between the inline elements is generated as one space, like the spaces written on one line.
126
+
127
+ ## Known limitations
128
+
129
+ 1. **`collapse` judges an element by its tag, not by its CSS.** It drops the whitespace next to a block-level tag and inside the edges of a block or inline-block tag, as both formatters treat those tags, so a `<div>` styled `display: inline` loses the space next to it before any formatting: `<div class="il">alpha</div> <div class="il">beta</div>` renders `alphabeta` (with `preserve`, `alpha beta`); `&#32;` there is dropped too. Write inline content with an inline tag (`<span>`), around which the formatters and `collapse` keep the spaces; do not switch to `preserve` for it, which brings back the other rows of the table.
130
+ 2. **Doctor finds a formatter through these signals**: `oxfmt` or `prettier` in the dependencies, a `prettier` key in `package.json`, a script running `oxfmt`, `vp fmt` or `prettier`, a `fmt` block in `vite.config.*`, `.oxfmtrc.json`, `.oxfmtrc.jsonc`, `oxfmt.config.*`, `.prettierrc`, `.prettierrc.*` or `prettier.config.*`. In a workspace package, doctor also finds them in the parent folders up to the workspace root. A formatter configured anywhere else is not reported. Apply step 4 when step 1 finds a formatter, whatever doctor says.
131
+ 3. **Ignoring templates silences doctor only for Prettier.** A `.prettierignore` line `*.html` stops doctor counting Prettier as a formatter; oxfmt also honors that file and skips the templates, but doctor still warns when `oxfmt` is a dependency.
132
+ 4. **The formatter is the markup check.** `<section><p><div>{{ a }}</p></div></section>` passes `ts-dom gen` (the HTML parser silently repairs it into a different tree), while `oxfmt --check` stops with `Syntax error: unexpected end tag` (exit code 2) and `prettier --check` with `SyntaxError: Unexpected closing tag "p"`. Keep the templates in the formatter's scope.
133
+ 5. **oxfmt formats a git-ignored file named on its command line**: `oxfmt src/nv/user-card.html.ts` rewrites it although `.gitignore` lists `*.html.ts`; `.prettierignore` and `ignorePatterns` do stop it (oxfmt then prints `Expected at least one target file`). Prettier skips a git-ignored file in both cases. Point the formatter at folders, or use `.prettierignore`/`ignorePatterns` when tools pass file names to it.
134
+ 6. **Prettier changes SVG text.** It formats the content of an SVG `<text>` as blocks, so where text and a `<tspan>` touch (`Hello<tspan>World</tspan>`) it can put a line break between them, which renders as a space in either whitespace mode; oxfmt keeps them inline. Keep such text on one line with `<!-- prettier-ignore -->` before the `<svg>`.
135
+
136
+ ## Verify
137
+
138
+ ```sh
139
+ npx ts-dom gen src/nv --whitespace=collapse # writes the modules; prints nothing
140
+ sed -n 2p src/nv/<name>.html.ts # expect: // whitespace: collapse
141
+ npx ts-dom doctor src/nv # expect: no formatter-whitespace issue
142
+ npx oxfmt --check src/nv # or prettier --check, vp fmt --check; expect: the generated *.html.ts and ts-dom-tags.ts are not listed
143
+ npx ts-dom check src/nv --no-write --whitespace=collapse # after the gen above; expect: ts-dom check: no issues
144
+ ```
145
+
146
+ Then run the project's own format check and build scripts from a clean checkout (no generated files): both pass, and the files this task edited (a Vite config with a new option, for example) are formatted too. For every template the formatter rewrote in this task, the step 5 comparison prints nothing. In the browser, the component's text reads as before formatting.
147
+
148
+ ## Troubleshooting
149
+
150
+ | Symptom | Cause | Fix |
151
+ |---|---|---|
152
+ | `warning [formatter-whitespace] A formatter (oxfmt or Prettier) is set up, and templates keep their whitespace as written` | templates are generated with `preserve` | step 4 |
153
+ | `warning [formatter-whitespace] The script "<name>" asks for whitespace "collapse", but this generated file was written with "preserve"` | a `ts-dom gen` or `ts-dom check` ran without the flag on a clean folder | run the named script once; make every generating run pass the flag (step 4) |
154
+ | Two words run together on the page with `collapse` (`alphabeta`) | a block-level tag (`<div>`) that CSS makes inline, next to the space | write it with an inline tag such as `<span>` (Known limitations 1) |
155
+ | A gap appears between elements, or the formatter's line breaks show on the page | `preserve` with elements styled `inline-block` or `white-space: pre-line`/`pre-wrap` | generate with `collapse` (step 4) |
156
+ | A test of `textContent` fails after formatting (`"\n Toggle\n"`) | `preserve`: the formatter's whitespace is part of the text | generate with `collapse` (step 4), or compare normalized text (step 7) |
157
+ | `error [stale-generated] The generated file is outdated` right after `oxfmt` or `prettier --write` | the formatter rewrote a generated `x.html.ts` | step 3, then `npx ts-dom gen <dir>` |
158
+ | `oxfmt --check` or `prettier --check` fails on `*.html.ts` after every `ts-dom check` | the generated files are not ignored | step 3 |
159
+ | `Syntax error: unexpected end tag` (oxfmt) or `Unexpected closing tag` (Prettier) on a template that `ts-dom check` accepts | misnested markup | fix the nesting at the reported line (Known limitations 4) |
160
+
161
+ ## See also
162
+
163
+ - Whitespace modes, the recorded mode and `--whitespace=collapse`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
164
+ - What each tool checks, including the formatter, and `ts-dom doctor`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#checks
165
+ - Template markup and holes: https://github.com/Pixmi/nativele/blob/main/docs/templates.md#html-syntax
166
+ - The generated files, page documents and why they are not committed: https://github.com/Pixmi/nativele/blob/main/docs/templates.md#how-types-are-generated
167
+ - How Skills share a project, and who owns which configuration: https://github.com/Pixmi/nativele/blob/main/skills/README.md#how-skills-work-together
@@ -3,8 +3,8 @@ name: nativele-integration
3
3
  description: "Use Nativele web components inside an app built with a UI framework that has no Nativele Skill of its own (Preact, Solid, Lit, ...; React, Vue, Svelte and Angular have their own): defining before render, props as properties, listening to events, children and slots, lifecycle, and template types. Use when a project depends on @nativele/ts-dom and on such a framework."
4
4
  metadata:
5
5
  tier: official
6
- nativele: ">=0.5.1 <0.6.0"
7
- verified: "2026-11-05"
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
8
  tools: "preact@11.1.1 vite@8.3.6 @preact/preset-vite@2.10.6 typescript@7.0.2"
9
9
  author: "Nativele maintainers"
10
10
  detects: "preact solid-js"
@@ -25,18 +25,18 @@ Nativele components are native custom elements. The framework renders their tags
25
25
 
26
26
  | Tool | Verified version |
27
27
  |---|---|
28
- | @nativele/ts-dom | 0.5.1 (the last agent trial) |
28
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
29
29
  | preact | 11.1.1 |
30
30
  | vite | 8.3.6 |
31
31
  | @preact/preset-vite | 2.10.6 |
32
32
  | typescript | 7.0.2 |
33
33
 
34
- Verified on 2026-11-05 in Google Chrome 148, starting from `create-vite` 9.3.4 with the `preact-ts` template, with the example project in the Nativele repository (`skills/nativele-integration/example`), and by an agent trial: a fresh agent did real work with only the Skill, and the result was checked independently. The example is a reference; it is not run in CI. The procedure was also followed earlier in a Vue app made with the `vue-ts` template. The rules come from the custom element standard and hold for any framework, but each framework's own behavior (the answers to step 3) is verified only for Preact 11 here and for the frameworks that have their own Skill. With Preact 10, treat the Preact answers below as unverified.
34
+ Verified on 2026-11-06 in Google Chrome 148, starting from `create-vite` 9.3.4 with the `preact-ts` template, with the example project in the Nativele repository (`skills/nativele-integration/example`). The procedure was also followed in a Vue app made with the `vue-ts` template. The rules come from the custom element standard and hold for any framework, but each framework's own behavior (the answers to step 3) is verified only for Preact 11 here and for the frameworks that have their own Skill. With Preact 10, treat the Preact answers below as unverified.
35
35
 
36
36
  ## Set-up
37
37
 
38
38
  1. Keep the Nativele components in their own folder, for example `src/nv/`, each as `x.html` plus `x.ts`, written with the core Skill.
39
- 2. Connect the build: with Vite, add `tsDom({ dirs: ["src/nv"] })` from `@nativele/ts-dom/vite` to `plugins`, and pass the same folder to every `ts-dom` command (`npx ts-dom check src/nv`). In the tsconfig whose `include` covers that folder, set `"strict": true` (in Vite's templates that is `tsconfig.app.json`; some starters, such as `preact-ts`, leave it off). If the project lints with oxlint, add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` and `"ts-dom/unused-markup": "error"` to `rules`. Add `*.html.ts` and `ts-dom-tags.ts` to `.gitignore`. Run `npx ts-dom doctor src/nv` and fix what it reports.
39
+ 2. Connect the build: with Vite, add `tsDom({ dirs: ["src/nv"] })` from `@nativele/ts-dom/vite` to `plugins`, and pass the same folder to every `ts-dom` command (`npx ts-dom check src/nv`). The tsconfig whose `include` covers that folder (in Vite's templates `tsconfig.app.json`; some starters, such as `preact-ts`, do not set `strict`) must meet Nativele's requirements: check and adjust it with `npx ts-dom skills nativele-typescript`. If the project lints, the ts-dom rules go into its linter: `npx ts-dom skills nativele-linting`. Add `*.html.ts` and `ts-dom-tags.ts` to `.gitignore`. Run `npx ts-dom doctor src/nv` and fix what it reports.
40
40
  3. Find out, from the framework's documentation on custom elements (often called "web components"), these four things before writing any template:
41
41
 
42
42
  | Question | Why it matters |
@@ -57,7 +57,7 @@ Verified on 2026-11-05 in Google Chrome 148, starting from `create-vite` 9.3.4 w
57
57
  Many frameworks decide between property and attribute by checking whether the element already has the property, which is only true after `define()` has run.
58
58
 
59
59
  5. Give the framework's template type checker the Nativele tags. `ts-dom gen` writes `ts-dom-tags.ts`, which adds every exported component to `HTMLElementTagNameMap`; the element type members a declaration reads (`props` and `"~emits"`) are in https://github.com/Pixmi/nativele/blob/main/docs/components.md#element-types-for-framework-declarations. Build the framework's tag declaration from these, so new components need no edit; "In Preact (verified)" below has the one for Preact's JSX, `nativele-react` the one for React and `nativele-vue` the Vue one. Check how the framework names an event listener in its types: Preact and React type `item-select` as the prop `onitem-select`, Vue types `@item-select` as `onItemSelect`. If the framework has no typed templates, skip this step.
60
- 6. The generated files are not committed, so on a clean checkout a build script that runs the type checker before the bundler (the `preact-ts` template's `"build": "tsc -b && vite build"`) fails with `TS2307: Cannot find module './x.html.js'`. Put `ts-dom gen src/nv &&` in front: `"build": "ts-dom gen src/nv && tsc -b && vite build"`. Do the same in front of whatever type check the framework's template runs (`vue-tsc -b`, `svelte-check`). Only a project whose type check is plain `tsc` replaces it with `ts-dom check`, which generates and type-checks: `"build": "ts-dom check src/nv && vite build"`, as `nativele-vite` sets it up. The dev server needs no change: the Vite plugin generates the files when it starts.
60
+ 6. The generated files are not committed, so on a clean checkout a build script that runs the type checker before the bundler (the `preact-ts` template's `"build": "tsc -b && vite build"`) fails with `TS2307: Cannot find module './x.html.js'` (why: `nativele-typescript`). Put `ts-dom gen src/nv &&` in front: `"build": "ts-dom gen src/nv && tsc -b && vite build"`. Do the same in front of whatever type check the framework's template runs (`vue-tsc -b`, `svelte-check`). Only a project whose type check is plain `tsc` replaces it with `ts-dom check`, which generates and type-checks: `"build": "ts-dom check src/nv && vite build"`, as `nativele-vite` sets it up. The dev server needs no change: the Vite plugin generates the files when it starts. CI starts from a clean checkout; the rest of what its job needs is in `npx ts-dom skills nativele-ci`.
61
61
 
62
62
  ## Example
63
63
 
@@ -174,13 +174,13 @@ A reference implementation of these checks is the [`verify.mjs`](https://github.
174
174
 
175
175
  | Symptom | Cause | Fix |
176
176
  |---|---|---|
177
- | `TS2307: Cannot find module './x.html.js'` from the build on a clean checkout | the type check runs before the generated files exist | start the build script with `ts-dom gen src/nv &&` (step 6) |
177
+ | `TS2307: Cannot find module './x.html.js'` from the build on a clean checkout | the type check runs before the generated files exist | start the build script with `ts-dom gen src/nv &&` (step 6); for CI, `nativele-ci` |
178
178
  | An object prop shows `[object Object]`, or the element has an attribute holding it | the framework set an attribute: the element was not defined yet, or the binding syntax sets attributes | import the module before mounting; use the framework's property binding, or a ref (limitation 1) |
179
179
  | The framework warns that it cannot resolve the component `info-card` | its compiler treats the tag as a framework component | mark hyphenated tags as custom elements in the framework's compiler options |
180
180
  | The event listener never runs | the name was changed (camelCase; Preact's `onItemSelect` listens to `itemselect`), or the listener is on an ancestor | listen with the exact name (`onitem-select`), on the element that emits |
181
181
  | Children vanish, appear outside the slot, or the framework throws during an update (Preact: `NotFoundError: Failed to execute 'moveBefore'`) | framework-managed children inside a Light DOM component with slots | `shadow: true`, or a stable element per slot |
182
182
  | `connected()` does not run when a keyed list reorders | the framework moved the element with `moveBefore`, so it stayed connected | nothing to fix: its subscriptions continue (limitation 5) |
183
- | `ts-dom check` reports `TS2307: Cannot find module` for a framework file (`./App.vue`, ...) | plain `tsc` cannot read the framework's files | Vue: keep `vue-tsc` in `package.json` (Vue's own type checker; `ts-dom check` uses it when listed). Other formats: pass `--project=` a tsconfig whose `include` covers only the Nativele folder, and type-check the rest with the framework's tool |
183
+ | `ts-dom check` reports `TS2307: Cannot find module` for a framework file (`./App.vue`, ...) | plain `tsc` cannot read the framework's files | Vue: keep `vue-tsc` in `package.json` (Vue's own type checker; `ts-dom check` uses it when listed). Other formats: pass `--project=` a tsconfig whose `include` covers only the Nativele folder (`nativele-typescript`), and type-check the rest with the framework's tool |
184
184
  | `Property 'nv-info-card' does not exist on type 'JSX.IntrinsicElements'`, or the framework's type checker does not know the tag | no declaration built from `ts-dom-tags.ts`, or the component class is not exported | add the declaration (step 5), export the class, run `ts-dom gen` |
185
185
 
186
186
  ## See also
@@ -190,4 +190,7 @@ A reference implementation of these checks is the [`verify.mjs`](https://github.
190
190
  - Lifecycle: https://github.com/Pixmi/nativele/blob/main/docs/components.md#components
191
191
  - Moved elements, connecting and subscriptions: https://github.com/Pixmi/nativele/blob/main/docs/lists.md#moving-items-connecting-and-subscriptions
192
192
  - Setup and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
193
+ - The tsconfig and the framework's type checker: https://github.com/Pixmi/nativele/blob/main/skills/nativele-typescript/SKILL.md
194
+ - The ts-dom rules in the project's linter: https://github.com/Pixmi/nativele/blob/main/skills/nativele-linting/SKILL.md
195
+ - CI: https://github.com/Pixmi/nativele/blob/main/skills/nativele-ci/SKILL.md
193
196
  - Complete framework integrations: `npx ts-dom skills nativele-react`, `npx ts-dom skills nativele-vue`