@nativele/ts-dom 0.5.4 → 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 +69 -0
  2. package/README.md +6 -1
  3. package/README.zh-TW.md +6 -1
  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 +51 -14
  21. package/skills/nativele-angular/SKILL.md +15 -11
  22. package/skills/nativele-architecture/SKILL.md +29 -16
  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 +12 -9
  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 +13 -10
  30. package/skills/nativele-state-management/SKILL.md +6 -6
  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 +22 -16
  35. package/skills/nativele-typescript/SKILL.md +186 -0
  36. package/skills/nativele-vite/SKILL.md +21 -17
  37. package/skills/nativele-vue/SKILL.md +10 -8
  38. package/skills/nativele-zod/SKILL.md +6 -6
@@ -3,8 +3,8 @@ name: nativele-architecture
3
3
  description: "Lay out an app built with Nativele that has several pages: feature folders with their components, stores and tests, shared components and app-wide stores, pages as custom elements that a router mounts (the Navigation API with URLPattern, or universal-router), lazy-loaded page modules, one scanned folder for the generated files, and which behavior goes to unit, real-browser and end-to-end tests. Use when starting such an app or restructuring one, or when a project depends on @nativele/ts-dom and universal-router."
4
4
  metadata:
5
5
  tier: official
6
- nativele: ">=0.5.3 <0.6.0"
7
- verified: "2026-11-05"
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
8
  tools: "vite@8.3.6 typescript@7.0.2 vitest@5.1.0 happy-dom@21.0.4 @vitest/browser-playwright@5.1.0 playwright@1.64.1 @playwright/test@1.64.1 create-vite@9.3.4 universal-router@10.0.3"
9
9
  author: "Nativele maintainers"
10
10
  detects: "universal-router"
@@ -26,7 +26,7 @@ metadata:
26
26
 
27
27
  | Tool | Verified version |
28
28
  |---|---|
29
- | @nativele/ts-dom | 0.5.3 |
29
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
30
30
  | vite | 8.3.6 |
31
31
  | typescript | 7.0.2 |
32
32
  | create-vite (`vanilla-ts` template, the starting point) | 9.3.4 |
@@ -35,9 +35,9 @@ metadata:
35
35
  | @vitest/browser-playwright | 5.1.0 |
36
36
  | playwright | 1.64.1 (drives the installed Chrome; no browser is downloaded) |
37
37
  | @playwright/test | 1.64.1 |
38
- | universal-router | 10.0.3 (checked by hand with the example's tests, see step 5; the example project does not run it) |
38
+ | universal-router | 10.0.3 (checked by hand with the same tests as the Navigation API router, see step 5) |
39
39
 
40
- Verified on 2026-11-05 with Node 24 and Google Chrome 148, with the example project in the Nativele repository (https://github.com/Pixmi/nativele/tree/main/skills/nativele-architecture/example; it is not in the npm package), which CI runs again when the example or the library changes and once a month. The router relies on the Navigation API and `URLPattern`, verified in Chrome only. With another major version of any tool above, treat every step as unverified.
40
+ Verified on 2026-11-06 with Node 24 and Google Chrome 148, with the example project in the Nativele repository (https://github.com/Pixmi/nativele/tree/main/skills/nativele-architecture/example; it is not in the npm package). The router relies on the Navigation API and `URLPattern`, verified in Chrome only. With another major version of any tool above, treat every step as unverified.
41
41
 
42
42
  ## Set-up
43
43
 
@@ -56,7 +56,7 @@ Verified on 2026-11-05 with Node 24 and Google Chrome 148, with the example proj
56
56
 
57
57
  2. **Scan one folder, `src`, everywhere.** In `vite.config.ts`, `tsDom({ dirs: ["src"] })`; in every script and command, `ts-dom check src` and `ts-dom doctor src`. One folder gives one `ts-dom-tags.ts` and one argument for every command (limitation 1). When the app used another folder before (`src/nv` from `nativele-vite`), delete the `ts-dom-tags.ts` left in it, and the folder itself once its components have moved (Troubleshooting).
58
58
 
59
- 3. **Set `tsconfig.json`'s `include`** to `["src", "e2e"]`: `src` covers the app, the tests next to the code and `ts-dom-tags.ts`, and `e2e` the end-to-end specs, so `ts-dom check src` type-checks all of them. Keep `"strict": true` from `nativele-vite`.
59
+ 3. **The tsconfig must cover `src` and `e2e`**: `src` holds the app, the tests next to the code and `ts-dom-tags.ts`, and `e2e` the end-to-end specs, so `ts-dom check src` type-checks all of them (`"include": ["src", "e2e"]` in the trial). Check and adjust it with the TypeScript Skill (`npx ts-dom skills nativele-typescript`).
60
60
 
61
61
  4. **Put the shell and the outlet in `index.html`**, and start the router from `src/main.ts`:
62
62
 
@@ -139,7 +139,7 @@ Verified on 2026-11-05 with Node 24 and Google Chrome 148, with the example proj
139
139
  }
140
140
  ```
141
141
 
142
- `router.resolve()` returns `undefined` when an action returns nothing, so test the match with `!= null`. With this router, the example's unit tests and its end-to-end tests pass except the one for a browser without the Navigation API (links are always handled by the click handler) and the one that calls `navigation.navigate()`, which passes when it clicks a link instead; its browser router tests use the Navigation API and do not apply.
142
+ `router.resolve()` returns `undefined` when an action returns nothing, so test the match with `!= null`. With this router, the same unit tests and end-to-end tests pass except the one for a browser without the Navigation API (links are always handled by the click handler) and the one that calls `navigation.navigate()`, which passes when it clicks a link instead; its browser router tests use the Navigation API and do not apply.
143
143
 
144
144
  6. **Write each page as a component** in its feature folder: route params arrive as string props (`props: { projectId: "" }`), the page shows what an unknown id means, and it binds its stores in `connected()`. Everything a page must keep after the user leaves goes into a store: leaving removes the page, which ends its `bind` subscriptions, and coming back creates a new element.
145
145
 
@@ -167,11 +167,11 @@ Verified on 2026-11-05 with Node 24 and Google Chrome 148, with the example proj
167
167
  npm pkg set scripts.verify="npm run build && ts-dom doctor src && npm test && npm run test:browser && npm run test:e2e"
168
168
  ```
169
169
 
170
- The build script is for a project whose type check is plain `tsc`: `ts-dom check` generates the files and type-checks. In a framework project, keep the framework's type check (`tsc -b`, `vue-tsc -b`, `svelte-check`) and put `ts-dom gen src &&` in front of it instead (the framework's Skill shows the script). Add `test-results` and `playwright-report` to `.gitignore`, next to `*.html.ts` and `ts-dom-tags.ts`.
170
+ The build script is for a project whose type check is plain `tsc`: `ts-dom check` generates the files and type-checks. In a framework project, keep the framework's type check (`tsc -b`, `vue-tsc -b`, `svelte-check`) and put `ts-dom gen src &&` in front of it instead (the framework's Skill shows the script). Add `test-results` and `playwright-report` to `.gitignore`, next to `*.html.ts` and `ts-dom-tags.ts`. CI runs these scripts from a clean checkout; what its job needs beyond them (dev dependencies, a Chromium-based browser for the browser and end-to-end tests) is in `npx ts-dom skills nativele-ci`.
171
171
 
172
172
  ## Example
173
173
 
174
- The example project is a project tracker: a projects list (`/`), a project page with its tasks (`/projects/:id`) and a settings page (`/settings`).
174
+ A project tracker: a projects list (`/`), a project page with its tasks (`/projects/:id`) and a settings page (`/settings`). Everything below is enough to build the same layout; the full project from the agent trial is in the repository only as a reference.
175
175
 
176
176
  ```
177
177
  index.html package.json tsconfig.json vite.config.ts playwright.config.ts
@@ -183,6 +183,16 @@ src/features/settings/settings-page.* settings-page.test.ts
183
183
  e2e/app.spec.ts
184
184
  ```
185
185
 
186
+ The small components the code below uses, by their interface (write them like any component, with the core Skill):
187
+
188
+ | Component | Folder | Props | Events |
189
+ |---|---|---|---|
190
+ | `text-entry` | `src/shared/` | `label`, `action` (strings) | `entry-submit` with `{ value: string }` |
191
+ | `progress-meter` | `src/shared/` | `done`, `total` (numbers) | none |
192
+ | `task-row` | `src/features/projects/` | `task` (a `Task`) | `task-toggle`, no data |
193
+ | `route-message` | `src/app/` | `heading`, `detail` (strings) | none |
194
+ | `app-nav` | `src/app/` | none; it binds `currentPath` and `preferences` to mark the current link and show the user name | none |
195
+
186
196
  The router (Navigation API and `URLPattern`, no dependency):
187
197
 
188
198
  ```ts
@@ -264,7 +274,7 @@ export const currentPath = createStore(location.pathname);
264
274
  A feature store and a page of that feature:
265
275
 
266
276
  ```ts
267
- // src/features/projects/projects-store.ts (abridged: the example adds addProject and more seed data)
277
+ // src/features/projects/projects-store.ts
268
278
  import { createStore } from "@nativele/ts-dom";
269
279
 
270
280
  export interface Project { id: string; name: string }
@@ -363,7 +373,7 @@ define("project-page", ProjectPage);
363
373
 
364
374
  `preferences` is the app-wide store (`createStore<Preferences>({ userName: "", showDone: true })` in `src/shared/stores/preferences.ts`), written by the settings page and read by the shell and this page. `text-entry` and `progress-meter` come from `src/shared/`; `task-row` is this feature's own item component. The page's template uses their tags, and the generated `project-page.html.ts` imports all three.
365
375
 
366
- The configuration (the browser project and the Playwright config follow `nativele-testing`, with this project's ports):
376
+ The configuration (the browser project and the Playwright config follow `nativele-testing`, with this project's ports; `CHROME_BIN` points them at a Chromium-based browser installed elsewhere, which a CI runner needs too, see `nativele-ci`):
367
377
 
368
378
  ```ts
369
379
  // vite.config.ts
@@ -474,14 +484,14 @@ test("each page module is loaded only when its route is first shown", async ({ p
474
484
 
475
485
  ## Known limitations
476
486
 
477
- 1. **Scan one folder.** Several folders work too (`dirs: ["src/shared", "src/features"]`): a template imports a child defined in any scanned folder, and a tag defined in two of them is `duplicate-define`. But every folder then needs to be named in `tsDom({ dirs })`, `ts-dom check`, `ts-dom doctor` and the tsconfig `include` alike, and a folder left out of one of them is silently not generated or not checked there. One folder, `src`, avoids that. A component outside every scanned folder is not imported automatically: import its module where it is used.
487
+ 1. **Scan one folder.** Several folders work too (`dirs: ["src/shared", "src/features"]`): a template imports a child defined in any scanned folder, and a tag defined in two of them is `duplicate-define`. But every folder then needs to be named in `tsDom({ dirs })`, `ts-dom check`, `ts-dom doctor` and the tsconfig `include` (`nativele-typescript`) alike, and a folder left out of one of them is silently not generated or not checked there. One folder, `src`, avoids that. A component outside every scanned folder is not imported automatically: import its module where it is used. A component from another package of a workspace is imported from that package, which exports its tag map; it is not scanned (`npx ts-dom skills nativele-monorepo`).
478
488
  2. **Browser support.** The router needs `URLPattern`: without it the app stops at start-up with `URLPattern is not defined`. Without the Navigation API, `startRouter` attaches nothing, every link loads a new document, and the router renders the route at start-up (verified in Chrome with `navigation` removed). Only Chrome 148 was verified; check your target browsers, or use a History API router (step 5).
479
- 3. **Keep the abort check.** Without `if (signal?.aborted) return;` after the import, a page module that loads slowly mounts over the page of a later navigation, and the address bar and the page disagree. The example's end-to-end test delays a chunk to prove the check, for both routers.
489
+ 3. **Keep the abort check.** Without `if (signal?.aborted) return;` after the import, a page module that loads slowly mounts over the page of a later navigation, and the address bar and the page disagree. To prove the check in a test, delay one page's chunk and navigate twice: the second page must stay.
480
490
  4. **Every app path must return `index.html`.** Deep links and reloads (and every link, without the Navigation API) ask the server for the page's path. `vite dev` and `vite preview` answer with `index.html`; a production server must be set up the same way (not verified here).
481
491
  5. **A page is a new element on every navigation.** Its `state` starts over, also on back and forward, and also between `/projects/p1` and `/projects/p2`. Keep what must survive in a store.
482
492
  6. **Feature stores live as long as the document.** A store created in a lazily loaded module is created on the first visit to the feature and kept until a reload; reset it in tests with `store.set(seed())` in `beforeEach`.
483
493
  7. **happy-dom has no Navigation API and no `moveBefore`.** A test that uses `navigation` fails there with `ReferenceError: navigation is not defined`, and a reordered list item runs `disconnected()` and loses focus where Chrome keeps both. Put them in `*.browser.test.ts`; the route table can still be tested in happy-dom through `show()`.
484
- 8. Not verified: routers other than the two in step 5, nested routes and layouts, route guards, query strings and hashes as props (the `URLPattern` here matches the path only), scroll restoration and focus after a navigation, server-side rendering, browsers other than Chrome, production servers, and oxlint with tests next to the components (the `ts-dom/unused-markup` rule counts only the component's own class, see `nativele-testing`).
494
+ 8. Not verified: routers other than the two in step 5, nested routes and layouts, route guards, query strings and hashes as props (the `URLPattern` here matches the path only), scroll restoration and focus after a navigation, server-side rendering, browsers other than Chrome, production servers, and a linter with tests next to the components (the `ts-dom/unused-markup` rule counts only the component's own class; how test files are configured in the linter is in `nativele-linting`).
485
495
 
486
496
  ## Verify
487
497
 
@@ -495,7 +505,7 @@ npm run test:browser # expect: every src/**/*.browser.test.ts passes in he
495
505
  npm run test:e2e # expect: every e2e spec passes against vite preview on the built app
496
506
  ```
497
507
 
498
- The example project's `npm run verify` runs exactly these, from a clean checkout with nothing generated; its output (the `info` lines left out):
508
+ From a clean checkout with nothing generated, as CI runs them (the job itself is covered by `nativele-ci`), the output of these commands for the project tracker (the `info` lines left out):
499
509
 
500
510
  ```
501
511
  ts-dom check: no issues
@@ -520,7 +530,7 @@ The end-to-end tests cover: a deep link, links without a new document and a stor
520
530
 
521
531
  | Symptom | Cause | Fix |
522
532
  |---|---|---|
523
- | A shared component's tag stays empty on a page, with no error from `check`, `doctor`, `lint` or the build | the component is defined outside every scanned folder, so the generated module does not import it | scan the folder that holds it (`tsDom({ dirs: ["src"] })`, `ts-dom check src`), or import the component's module yourself |
533
+ | A shared component's tag stays empty on a page, with no error from `check`, `doctor`, `lint` or the build | the component is defined outside every scanned folder, so the generated module does not import it | scan the folder that holds it (`tsDom({ dirs: ["src"] })`, `ts-dom check src`), or import the component's module yourself; from another workspace package, import that package (`nativele-monorepo`) |
524
534
  | `TS2307: Cannot find module './x.js'` in `src/nv/ts-dom-tags.ts` after moving components | a `ts-dom-tags.ts` left in the folder that was scanned before | delete it, and the old folder once it is empty; only `src/ts-dom-tags.ts` is generated now |
525
535
  | `[duplicate-define] the tag <x> is defined more than once` after merging folders into one scan | two features define the same tag | rename one; tags share one registry |
526
536
  | The page of an earlier link appears after a later one, and the address bar shows the later URL | the abort check after the import is missing | `if (signal?.aborted) return;` after `await` in `show()` |
@@ -539,3 +549,6 @@ The end-to-end tests cover: a deep link, links without a new document and a stor
539
549
  - Setting up Vite: `npx ts-dom skills nativele-vite`
540
550
  - State libraries through `bind`: `npx ts-dom skills nativele-state-management`
541
551
  - Writing the tests: `npx ts-dom skills nativele-testing`
552
+ - The tsconfig: https://github.com/Pixmi/nativele/blob/main/skills/nativele-typescript/SKILL.md
553
+ - CI: https://github.com/Pixmi/nativele/blob/main/skills/nativele-ci/SKILL.md
554
+ - Components shared between packages of a workspace: https://github.com/Pixmi/nativele/blob/main/skills/nativele-monorepo/SKILL.md
@@ -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 |
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 the library's own CI, which type-checks, lints and runs every example 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