@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,174 @@
1
+ ---
2
+ name: nativele-linting
3
+ description: "Bring the Nativele (@nativele/ts-dom) template lint rules into a project's existing linter (oxlint, Vite+ vp lint, or ESLint flat config) without replacing it: which rules exist and where they run (ts-dom lint, ts-dom check, the ts-dom JS plugin), choosing groups, single rule ids, severities and ignore lists, and keeping generated files and test folders out. Use when a project with @nativele/ts-dom has oxlint or eslint, when ts-dom doctor reports oxlint-plugin-missing, or when a ts-dom lint finding needs a decision."
4
+ metadata:
5
+ tier: official
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
+ tools: "oxlint@1.91.0 vite-plus@1.5.0 eslint@10.14.0 eslint@9.39.4 typescript-eslint@8.72.1 typescript@7.0.2 typescript@6.0.3"
9
+ author: "Nativele maintainers"
10
+ detects: "oxlint eslint"
11
+ ---
12
+
13
+ # Nativele linting
14
+
15
+ ## When to use
16
+
17
+ - The project depends on `@nativele/ts-dom` and lints with oxlint (`.oxlintrc.json`), Vite+ (`vp lint`, a `lint` block in `vite.config.ts`) or ESLint (`eslint.config.*`), and the Nativele rules should run in that linter, in the editor and in the project's lint command.
18
+ - `ts-dom doctor` reports `oxlint-plugin-missing`, or a lint finding (`unused-ref`, `img-alt`, `unknown-prop`, ...) needs a decision: fix it, give the rule another severity, or ignore an id.
19
+ - Generated files (`*.html.ts`, `ts-dom-tags.ts`) show up in the linter's output, or a template used only by tests is reported.
20
+ - This Skill owns the lint configuration as far as Nativele is concerned: `.oxlintrc.json`, the `lint` block of `vite.config.ts`, and `eslint.config.*`. It does not own the rest of `vite.config.ts` (the build: `nativele-vite`), `tsconfig*.json` (`nativele-typescript`), formatting (`nativele-formatting`), the components themselves (`nativele-core`), tests (`nativele-testing`) or CI (`nativele-ci`). It keeps the project's linter, plugins and rules; it never replaces a linter, adds a second one, or rewrites a config.
21
+
22
+ ## Versions
23
+
24
+ | Tool | Verified version |
25
+ |---|---|
26
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
27
+ | oxlint | 1.91.0 (`.oxlintrc.json`) |
28
+ | vite-plus | 1.5.0 (`vp lint`, which runs oxlint 1.91.0, with the `lint` block in `vite.config.ts`) |
29
+ | eslint | 10.14.0 and 9.39.4 (flat config `eslint.config.js`) |
30
+ | typescript-eslint | 8.72.1 (the parser for `.ts` files in ESLint; it requires `typescript` below 6.1, so the ESLint projects used 6.0.3) |
31
+ | typescript | 7.0.2 (oxlint and Vite+ projects), 6.0.3 (ESLint projects) |
32
+
33
+ Verified on 2026-11-06 in fresh projects built from the packed library, with a fixture that has an unused `data-ref`, a template no file imports, an `<img>` without `alt`, a misspelled child prop, a missing handler and a test-only component. With a different major version of oxlint or ESLint, or a legacy `.eslintrc*` config, treat every step as unverified.
34
+
35
+ ## Set-up
36
+
37
+ Check first, then change only what the task needs. Keep the user's plugins, rules, categories, ignore patterns, key order and formatting.
38
+
39
+ 1. **Find the linter the project actually uses.** Look for `.oxlintrc.json` (or `.oxlintrc.jsonc`), a `lint:` block in `vite.config.ts` (Vite+), `eslint.config.js`/`.mjs`/`.ts`, and the `lint` script in `package.json`. Use the one the `lint` script runs. If there is none, do not add a linter: `ts-dom check` (which `nativele-core` already runs) and `ts-dom lint` give the same findings without one. Other linters were not tried; for them, run `npx ts-dom lint <dir>` as its own step and leave their config alone.
40
+ 2. **See what is reported today.** Run `npx ts-dom lint <dir> --a11y` with the folders the project passes to `ts-dom check`, and the project's own lint command. The first shows every Nativele finding; the second shows what the linter already reports, so a new finding after the change can be told apart from an old one. Run `npx ts-dom doctor <dir>`: `oxlint-plugin-missing` names the config file that lacks the plugin.
41
+ 3. **Load the plugin in that config, next to what is there.**
42
+ - oxlint (`.oxlintrc.json`) and Vite+ (`lint` in `vite.config.ts`, same keys): add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` (create the array if missing; keep other entries). The plain string `"@nativele/ts-dom/oxlint"` also works. Do not touch `plugins` (the built-in ones) or `categories`.
43
+ - ESLint (flat config): `import tsDom from "@nativele/ts-dom/oxlint"` and add a config object `{ plugins: { "ts-dom": tsDom }, rules: { ... } }` to the exported array. The same module is a valid ESLint plugin. It needs the TypeScript parser the project already uses for `.ts` files (verified with `typescript-eslint`).
44
+ - Loading the plugin enables nothing: with `jsPlugins` set and no `ts-dom/*` rule, nothing is reported and doctor is satisfied. Step 4 decides the rules.
45
+ 4. **Choose rules and severities** (see "Rules, groups and severities" below). The baseline is `"ts-dom/unused-markup": "error"`. Add `"ts-dom/data-flow"` and `"ts-dom/a11y"` as `"warn"` when the user wants them; ask before turning on a11y in an existing project with many templates, and show the count from step 2.
46
+ 5. **Keep generated files out of the linter.** The rules run on the component files, never on `*.html.ts` or `ts-dom-tags.ts`, but the project's own rules do. oxlint and `vp lint` skip what `.gitignore` lists, so when `.gitignore` has `*.html.ts` and `ts-dom-tags.ts` (as the setup guide recommends), nothing is needed. Otherwise, and always with ESLint (it does not read `.gitignore`), add `**/*.html.ts` and `**/ts-dom-tags.ts` to the existing ignore list: `ignorePatterns` for oxlint and Vite+, `globalIgnores([...])` from `eslint/config` for ESLint. Never fix a finding inside a generated file: it is rewritten on the next `ts-dom gen`.
47
+ 6. **Test folders.** Access from a test never counts as use (Known limitations 3), and the rules only report a template in the file that imports it. Turn `ts-dom/unused-markup` off only for files that import a template themselves (a test-only host component): an `overrides` entry `{ "files": ["test/**"], "rules": { "ts-dom/unused-markup": "off" } }` in oxlint, or `{ files: ["test/**"], rules: { "ts-dom/unused-markup": "off" } }` in ESLint, with the project's own test globs. Do not lower `unused-ref` for component files to make room for tests; a ref that only a test reads is removed and the test queries the DOM (the testing Skill owns how).
48
+ 7. **State what other areas need, and leave the how to them.** The lint script belongs to the build owner and the CI steps to `nativele-ci`: the project's lint command covers the Nativele rules, except `no-importer` (Known limitations 1), which `ts-dom check` reports. The type check order (generated files before `tsc`) is a requirement `nativele-typescript` states; the script that runs it belongs to the build owner. Formatting templates, and keeping the formatter off the generated files, belongs to `nativele-formatting`.
49
+ 8. **Run Verify.**
50
+
51
+ ### What runs where
52
+
53
+ | | `ts-dom lint [dirs]` | `ts-dom check [dirs]` | The `ts-dom` JS plugin in oxlint, `vp lint` or ESLint |
54
+ |---|---|---|---|
55
+ | unused-markup (errors) | all 7 ids, including `no-importer` | all 7 ids, including `no-importer` | 6 ids; `no-importer` does not exist here |
56
+ | data-flow (warnings) | always | always | when `ts-dom/data-flow` or a single id is on |
57
+ | a11y (warnings) | with `--a11y` | with `--a11y` | when `ts-dom/a11y` or a single id is on |
58
+ | Also runs | nothing else; needs no generated files and writes nothing | codegen (writes the generated files unless `--no-write`) and `tsc` | the project's other rules |
59
+ | Severity and ignore | fixed: unused-markup is an error, the rest warnings; no per-id switch (filter `--json` if needed) | the same | per rule in the config, `ignore` on the three groups |
60
+ | Where a finding points | `src/nv/card.html:3:3 error [unused-ref] ...` | the same | the template's `import` line in the `.ts` file, with the HTML location first: `src/nv/card.ts:2:1: error ts-dom(unused-markup): ./card.html.js:3:3 [unused-ref] ...` |
61
+ | Exit code | 1 on an error (also with `--json`); 1 on a warning too with `--strict`; 0 otherwise | the same | the linter's own (oxlint and ESLint: 1 on an error, 0 on warnings only) |
62
+ | `--json` | `{ "issues": [{ file, rule, severity, message, line, col }] }`; data-flow child issues add `tag`, `attrs`, `events` | `{ "issues": [...] }` | the linter's own format |
63
+
64
+ `ts-dom lint` and `ts-dom check` scan only the folders passed to them, and `--include`/`--exclude` do not filter their lint findings. Neither reads the linter's config, so a severity or ignore chosen there does not change them (Known limitations 2).
65
+
66
+ ### Rules, groups and severities
67
+
68
+ - Three groups: `ts-dom/unused-markup`, `ts-dom/data-flow`, `ts-dom/a11y`. A group reports all of its ids; `["warn", { "ignore": ["img-alt"] }]` drops ids from it. `npx ts-dom rules` lists every id by group, and `npx ts-dom rules --explain=<id>` says why it exists.
69
+ - Every id is also a rule of its own (`ts-dom/unused-ref`, `ts-dom/img-alt`, `ts-dom/unknown-prop`, ...; 38 rules in all), for a severity per id. A single-id rule takes no options.
70
+ - To give one id another severity, ignore it in its group and enable it alone: `"ts-dom/unused-markup": ["error", { "ignore": ["unused-ref"] }], "ts-dom/unused-ref": "warn"`. A group and one of its ids both on report the finding twice.
71
+ - Choose by what each id costs when it is missed: unused-markup ids are dead or broken markup (blank text, an empty list, a handler that does not exist) and stay errors. data-flow ids are design advice plus child contract checks (`unknown-prop`, `invalid-prop-value`, `unknown-event`), warnings by default; raise the contract ids to `"error"` singly if the user wants. a11y ids are static hints, warnings, and `ignore` takes the ids that do not apply (for example `aria-idref` when labels point outside the template).
72
+ - An `ignore` id that does not exist is silently accepted: check every id against `npx ts-dom rules` before writing it.
73
+
74
+ ## Example
75
+
76
+ An oxlint project, before:
77
+
78
+ ```json
79
+ {
80
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
81
+ "plugins": ["typescript", "unicorn"],
82
+ "rules": {
83
+ "no-console": "warn",
84
+ "eqeqeq": "error"
85
+ }
86
+ }
87
+ ```
88
+
89
+ `.gitignore` already lists `node_modules`, `*.html.ts` and `ts-dom-tags.ts`; tests in `test/` include a host component that imports its own template. After (only `jsPlugins`, three rules and `overrides` added):
90
+
91
+ ```json
92
+ {
93
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
94
+ "plugins": ["typescript", "unicorn"],
95
+ "jsPlugins": [{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }],
96
+ "rules": {
97
+ "no-console": "warn",
98
+ "eqeqeq": "error",
99
+ "ts-dom/unused-markup": "error",
100
+ "ts-dom/a11y": ["warn", { "ignore": [] }],
101
+ "ts-dom/data-flow": ["warn", { "ignore": [] }]
102
+ },
103
+ "overrides": [{ "files": ["test/**"], "rules": { "ts-dom/unused-markup": "off" } }]
104
+ }
105
+ ```
106
+
107
+ With Vite+, the same keys go into the existing `lint` block of `vite.config.ts` (`jsPlugins`, the `ts-dom/*` rules, and `ignorePatterns: ["**/*.html.ts", "**/ts-dom-tags.ts"]` only if `.gitignore` does not list them); the `plugins: [tsDom()]` entry and the rest of the file belong to the build.
108
+
109
+ An ESLint project (flat config), after; the lines added are the `globalIgnores`, the `tsDom` import and the last two objects:
110
+
111
+ ```js
112
+ import { globalIgnores } from "eslint/config";
113
+ import tseslint from "typescript-eslint";
114
+ import tsDom from "@nativele/ts-dom/oxlint";
115
+
116
+ export default tseslint.config(
117
+ globalIgnores(["**/*.html.ts", "**/ts-dom-tags.ts"]),
118
+ tseslint.configs.recommended,
119
+ {
120
+ plugins: { "ts-dom": tsDom },
121
+ rules: { "ts-dom/unused-markup": "error", "ts-dom/data-flow": "warn" },
122
+ },
123
+ { files: ["test/**"], rules: { "ts-dom/unused-markup": "off" } },
124
+ );
125
+ ```
126
+
127
+ Without `globalIgnores`, `tseslint.configs.recommended` reports `@typescript-eslint/no-empty-object-type` errors in the generated `*.html.ts` files.
128
+
129
+ ## Known limitations
130
+
131
+ 1. **`no-importer` runs only in `ts-dom lint` and `ts-dom check`.** The plugin checks a template from the file that imports it, so a template nobody imports is invisible to it, and `"ts-dom/no-importer"` fails with `Rule 'no-importer' not found in plugin 'ts-dom'`. Keep `ts-dom check` (or `ts-dom lint`) in the project's checks; a lint command alone does not cover it.
132
+ 2. **The CLI ignores the linter's choices.** `ts-dom lint` and `ts-dom check` always report unused-markup as errors and have no ignore option: an id set to `"warn"` or ignored in the linter still fails `ts-dom check`. Fix the finding rather than relax the linter when the project runs both; relax only what the user accepts in both places (or filter `--json` in the user's own script).
133
+ 3. **Use from outside the class does not count.** `unused-markup` compares a template with the body of `class X extends Component(tpl)`; `el.refs.x` in a test, in another component or in a script outside the class is not use, in any of the three tools. Turning the rule off for test folders silences only templates those test files import.
134
+ 4. **Child contracts are read from the working directory.** `unknown-prop`, `invalid-prop-value` and `unknown-event` need the child component's `define()`; the plugin scans for them from the folder the linter runs in. Run from a folder that does not contain the child (oxlint started in `test/` on `src`), the `unknown-prop` finding disappeared without a message. Run the linter from the project folder.
135
+ 5. **Disable comments hide the whole template.** Every finding is reported on the `import` line, so `// oxlint-disable-next-line ts-dom/unused-markup` (or `eslint-disable`) above it silences every unused-markup finding of that template, while `ts-dom check` keeps reporting them. Do not use disable comments for template findings; fix the template or use `ignore` for an a11y or data-flow id the user accepts.
136
+ 6. **Doctor reads only `.oxlintrc.json`, `.oxlintrc.jsonc`, `oxlintrc.json`, `oxlint.config.ts`, `oxlint.config.mts` and the `lint` block of `vite.config.*`.** It does not look at ESLint configs at all. Doctor also counts the plugin as configured when `jsPlugins` names it, even with no `ts-dom/*` rule on. Check the config yourself, and confirm with a finding (Verify).
137
+ 7. **No fix for findings in the HTML.** The only suggestion is `missing-handler`, which adds an empty method to the class (`oxlint --fix-suggestions` wrote `save(_e: Event): void {}`); the user's own rules (for example `@typescript-eslint/no-empty-function` in typescript-eslint's `strict`) may then report that empty method, which the developer fills in. An unused `data-ref` or a11y finding is fixed by editing the `.html`.
138
+ 8. **Not verified:** the editor integration (the Oxc and ESLint VS Code extensions), whether the plugin picks up a child's new prop in a long-running editor session without a restart, legacy `.eslintrc*`, oxlint's JavaScript or TypeScript config files, and linters other than oxlint and ESLint.
139
+
140
+ ## Verify
141
+
142
+ ```sh
143
+ npx ts-dom lint src/nv --a11y # expect: ts-dom lint: no issues (or only findings the user accepted)
144
+ npx oxlint # or: npx vp lint / npx eslint . ; expect: exit code 0 and no ts-dom(...) or generated-file findings
145
+ npx ts-dom doctor src/nv # expect: no oxlint-plugin-missing
146
+ npx ts-dom check src/nv # expect: ts-dom check: no issues
147
+ ```
148
+
149
+ Then prove the linter runs the rules: add `<span data-ref="probe"></span>` to a template, run the project's lint command, and expect `error ts-dom(unused-markup): ./<name>.html.js:<line>:<col> [unused-ref] data-ref="probe" is never used by the component class.` on the template's import line (ESLint prints the same message under the `.ts` file); exit code 1. Remove it afterwards. If a11y or data-flow is on, an `<img>` without `alt` gives `[img-alt]`, and a misspelled attribute on a child tag gives `[unknown-prop]`, both as warnings.
150
+
151
+ ## Troubleshooting
152
+
153
+ | Symptom | Cause | Fix |
154
+ |---|---|---|
155
+ | `x Plugin 'ts-dom' not found` | a `ts-dom/*` rule without the `jsPlugins` entry | add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` |
156
+ | `x Failed to load JS plugin: @nativele/ts-dom/oxlint-plugin` ... `is not exported` | a wrong specifier | `@nativele/ts-dom/oxlint` |
157
+ | `x Rule 'no-importer' not found in plugin 'ts-dom'` | `no-importer` has no rule in the plugin | remove it; `ts-dom check` reports it (Known limitations 1) |
158
+ | `Error: Rule 'ts-dom/unused-ref' does not accept options` | `ignore` on a single-id rule | `ignore` only on the three groups |
159
+ | `Unexpected property "ignor". Expected properties: "ignore".` | a misspelled option key | `ignore` |
160
+ | An ignored id is still reported | the id is misspelled (accepted silently), or it is ignored in the group but also enabled alone | check the id with `npx ts-dom rules`; one place per id |
161
+ | Every finding appears twice, once as `ts-dom(unused-markup)` and once as `ts-dom(unused-ref)` | the group and a single id are both on | ignore the id in the group, or drop one of them |
162
+ | The linter is clean but `ts-dom check` fails with `[unused-ref]` or `[no-importer]` | Known limitations 1 and 2 | fix the template; the CLI does not read the linter's config |
163
+ | `@typescript-eslint/no-empty-object-type`, `sort-keys`, `import(newline-after-import)` or similar in `*.html.ts` | the project's own rules run on generated files | ignore `**/*.html.ts` and `**/ts-dom-tags.ts` in the linter (step 5); never edit the generated files |
164
+ | oxlint reports thousands of findings in `node_modules` | the project has no `.gitignore` listing `node_modules` (oxlint linted `node_modules` then, and stopped once `.gitignore` listed it) | add `node_modules` to `.gitignore` or `ignorePatterns`, as the user prefers |
165
+ | `[unused-ref]` for a ref a test reads | test access is not use (Known limitations 3) | remove the ref and query the DOM in the test |
166
+ | `[unknown-prop]` missing for a child tag | the linter ran from a folder without the child's `define()` (Known limitations 4) | run it from the project folder |
167
+
168
+ ## See also
169
+
170
+ - Lint settings, the oxlint plugin, the editor and the `ts-dom lint` flags: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#lint-optional
171
+ - What each tool checks, `ts-dom check` and `ts-dom doctor`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#checks
172
+ - The a11y ids and what each checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#aria--accessibility-checks
173
+ - The data-flow rules and what to do instead of passing through layers: https://github.com/Pixmi/nativele/blob/main/docs/components.md#avoiding-layer-by-layer-passing-lint-ts-domdata-flow
174
+ - How Skills share a project, and who owns which configuration: https://github.com/Pixmi/nativele/blob/main/skills/README.md#how-skills-work-together
@@ -0,0 +1,298 @@
1
+ ---
2
+ name: nativele-monorepo
3
+ description: "Use Nativele across the packages of a workspace (npm or pnpm workspaces, optionally with a task runner such as turbo): a package of shared components used by one or more apps, where each package generates and checks its own components, how an app gets their modules and their tag types, where @nativele/ts-dom and typescript must be installed, and which package must be generated or built before another type-checks. Use when a repository has several package.json files and Nativele components in one of them are used by another."
4
+ metadata:
5
+ tier: official
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
+ tools: "npm@11.19.1 pnpm@12.16.1 turbo@2.11.10 vite@8.3.6 typescript@7.0.2 create-vite@9.3.4 vue-tsc@3.3.14"
9
+ author: "Nativele maintainers"
10
+ detects: "turbo nx lerna"
11
+ ---
12
+
13
+ # Nativele in a monorepo
14
+
15
+ ## When to use
16
+
17
+ - The repository is a workspace (a root `package.json` with `workspaces`, or a `pnpm-workspace.yaml`), and Nativele components live in a package that another package uses: a component package (`packages/ui`) consumed by one or more apps (`apps/web`), or several packages that each have components.
18
+ - Signals: more than one `package.json` that lists `@nativele/ts-dom`; an app that depends on a workspace package (`"@acme/ui": "workspace:*"` or a version) whose source has `define()` calls; `turbo`, `nx` or `lerna` in the root `package.json`, which is what `ts-dom doctor` matches (it reads only dependencies, and a workspace is declared by `workspaces` or `pnpm-workspace.yaml`, not by a dependency).
19
+ - This Skill owns the workspace side of Nativele: where each package's `ts-dom` commands run and on which folder, how a component package exposes its modules and tag types, which package declares `@nativele/ts-dom` and `typescript`, and the order in which packages must be generated or built. It keeps the user's package manager, workspace layout and task runner. It does not own `tsconfig*.json` (`nativele-typescript`), the build tool and its config (`nativele-vite` or the framework's Skill), lint, CI, or the task runner's config; it states what they must do and leaves the how to them.
20
+ - Not for one package with several folders: scan one folder, as the [architecture guide](https://github.com/Pixmi/nativele/blob/main/docs/architecture.md#generated-files-checks-and-tsconfig) says. Not for setting a workspace up from nothing.
21
+
22
+ ## Versions
23
+
24
+ | Tool | Verified version |
25
+ |---|---|
26
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
27
+ | npm (workspaces) | 11.19.1, on Node 24.22.2 |
28
+ | pnpm (workspaces) | 12.16.1 |
29
+ | turbo | 2.11.10 (with npm workspaces) |
30
+ | vite | 8.3.6 |
31
+ | typescript | 7.0.2 (and 6.0.3 where hoisting put it, see Known limitations) |
32
+ | create-vite (`vanilla-ts`, the app) | 9.3.4 |
33
+ | vue-tsc | 3.3.14 (only to see which package's checker `ts-dom check` runs) |
34
+
35
+ Verified on 2026-11-06 in Google Chrome 148 with a component package `packages/ui` and a Vite app `apps/web` from `create-vite`, built once before Nativele was added, in an npm workspace and a pnpm workspace: the component package exported as TypeScript source and as built JavaScript with declarations, and the built one also packed and installed into a separate app. Not verified: yarn and Bun workspaces, nx, lerna, TypeScript project references across packages (`tsc -b` with `composite`), and apps with a UI framework consuming the package. With a different major version of npm, pnpm or turbo, treat every step as unverified.
36
+
37
+ ## Set-up
38
+
39
+ Check first, then change only what fails. Use the workspace's own package manager and commands for every install and script; never add or switch one.
40
+
41
+ 1. **Read the workspace.** The root `package.json` (`workspaces`, `packageManager`, scripts), `pnpm-workspace.yaml`, the lockfile (which package manager), and a task runner's config (`turbo.json`, `nx.json`). For each package: does its `package.json` list `@nativele/ts-dom` and `typescript`, where are its templates (the folder with `.html` and `define()` files), which workspace packages does it depend on, and what is its `exports`.
42
+ When a component that already lives in an app moves into the package, keep its tag name, and remove what pointed to its old place in the app: the old folder in the app's `tsDom({ dirs })` and in its `ts-dom` scripts (point them at the folder that holds the app's own components; a folder that no longer exists fails with `dir-not-found`, and the app's own templates are then not generated), the import in the app's entry, and the `ts-dom-tags.ts` left in the old folder.
43
+ 2. **Check the copies of the library:** `npm ls @nativele/ts-dom` or `pnpm why -r @nativele/ts-dom`. Expect one version (npm prints the others as `deduped`; pnpm prints `Found 1 version of @nativele/ts-dom`). Two versions work, but ship the runtime twice (Known limitations 6).
44
+ 3. **Declare the dependencies in every package that needs them.** Each package resolves `@nativele/ts-dom`, `typescript` and the `ts-dom` command from its own folder, so relying on another package's hoisted copy breaks when the layout changes (Known limitations 7):
45
+
46
+ | Package | `@nativele/ts-dom` | `typescript` |
47
+ |---|---|---|
48
+ | component package | `peerDependencies` with the range the apps use (`"^0.6.0"`), and the same version in `devDependencies` for its own `ts-dom` and tests | `devDependencies` |
49
+ | app | `dependencies` | `devDependencies` |
50
+
51
+ The peer lets an app outside the workspace install the package without a second copy (verified: a packed component package installed into a separate app gave one `@nativele/ts-dom`, `deduped`). Inside the workspace the package's own `devDependencies` entry satisfies its peer, so the peer does not stop a second version there; step 2 does. Install with the workspace's command (`npm i -D -w @acme/ui @nativele/ts-dom typescript`, `pnpm --filter @acme/ui add -D @nativele/ts-dom typescript`), then add the peer to that `package.json` (`npm pkg set "peerDependencies.@nativele/ts-dom=^0.6.0" -w @acme/ui`, or by editing the file). The app depends on the component package the way the workspace already links its packages (`"workspace:*"` in pnpm, a version in npm).
52
+ 4. **Each package scans only its own folder, and its `ts-dom` commands run in that package.** In `packages/ui`: `ts-dom check src`; in `apps/web`: `ts-dom check src`, and `tsDom()` keeps the app's own `dirs`. From the root, run them through the workspace (`npm exec -w web -- ts-dom check src`, `pnpm --filter web exec ts-dom check src`, `pnpm -r exec ts-dom check src`, or each package's script). Do not give one command the folders of several packages, and do not add another package's folder to `tsDom({ dirs })` (Known limitations 4).
53
+ 5. **The component package exposes its components through one entry module** (its `exports`):
54
+ - it imports or re-exports every module that calls `define()`, so importing the package registers the tags;
55
+ - it has the line `export type {} from "./ts-dom-tags.js";`, which brings the package's generated tag map into every program that imports the package, so consumers get the props, events and list item types of its tags (Known limitations 2); the component classes it exports must be `export class`, as in [Composition](https://github.com/Pixmi/nativele/blob/main/docs/components.md#composition-using-multiple-components-together);
56
+ - its `package.json` has no `"sideEffects": false`. If the project wants `sideEffects`, list the entry module and every module that calls `define()` (Known limitations 9).
57
+ 6. **Source or built.** Both were verified inside a workspace with Vite; which one is the user's choice, and the build belongs to the build owner:
58
+ - Source: `"exports": { ".": "./src/index.ts" }`. The app's bundler compiles the package, and the app's type check reads the package's source with the app's tsconfig, so the package's generated files must exist before the app type-checks or builds.
59
+ - Built: the package's build writes JavaScript and declarations (`tsc` with `declaration` and `outDir: "dist"` was verified), and `"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } }`. The generated templates are compiled into `dist` (`x.html.js`, `x.html.d.ts`, `ts-dom-tags.d.ts`), the declaration of the entry keeps `export type {} from "./ts-dom-tags.js"`, and a consumer runs no `ts-dom` on the package. A package published outside the workspace must be built: the consumer's `ts-dom` does not generate inside `node_modules`, and `npm pack` leaves out the git-ignored generated files, so a source package installed from a tarball failed to resolve its `./x.html.js` imports. Ship `dist` (`"files": ["dist"]`); the templates and generated sources are not needed.
60
+ The tsconfig for the package's build belongs to the TypeScript Skill (`nativele-typescript`); what this Skill needs from it is that it includes the folder with `ts-dom-tags.ts` and emits declarations.
61
+ 7. **The app imports the package once for its side effect** in its entry module (`import "@acme/ui";`), and the types it needs (`import type { UiButton, Tag } from "@acme/ui";`). An object or array prop such as `badge` cannot be written as an attribute in the app's template; set it from TypeScript (`this.refs.badge.badge = {...}` in `render`, or the element's property). A template in the app that uses `<ui-button>` gets no import of it from `ts-dom gen`, because the tag is not defined in the app's folder ([architecture guide](https://github.com/Pixmi/nativele/blob/main/docs/architecture.md#generated-files-checks-and-tsconfig)).
62
+ 8. **State the order to the owner of the scripts or the task runner.** The component package's `ts-dom gen` (source) or its build (built) must finish before the app's `ts-dom check`, `tsc` or `vite build`. What was observed with each tool, for the owner to use:
63
+ - `npm run <script> --workspaces` runs the packages in the order of the `workspaces` array, not by dependency: `["packages/*", "apps/*"]` ran `packages/ui` first, `["apps/*", "packages/*"]` ran the app first. Do not reorder an array that lists apps first (the order is the team's, and other scripts may depend on it); name the packages in order instead: `npm run build -w @acme/ui && npm run build -w web`.
64
+ - `pnpm -r run <script>` ran `packages/ui` first although `pnpm-workspace.yaml` listed `apps/*` first, because the app depends on it.
65
+ - turbo with `"dependsOn": ["^build"]` ran the package first. Its `outputs` must contain what the next package reads: `dist/**` for a built package, and for a source package also the generated files (`"src/**/*.html.ts"`, `"src/**/ts-dom-tags.ts"`); with only `dist/**`, a cache hit on a clean checkout restored nothing and the app's check failed with `TS2307: Cannot find module './ui-button.html.js'` inside `packages/ui`.
66
+ Each package's own script still type-checks after generating (`ts-dom check src && ...`), as the Vite Skill and the TypeScript Skill say; doctor's `typecheck-before-gen` reads the `.gitignore` files from the package up to the workspace root, so a root-only `.gitignore` counts.
67
+ 9. **During development**, the app's dev server does not regenerate another package's templates. For a source package, run `ts-dom gen --watch src` in the component package next to the app's `vite`: the app's dev server then reloads the page when the package's generated file changes. A built package needs its own watch build, which is the build owner's.
68
+ 10. **Skills and doctor.** `ts-dom skills add` copies into the folder it runs in (`.claude/skills/` or `.agents/skills/`), and `ts-dom doctor` looks for copies in the folder it runs in and the folders above it up to the workspace root (a `package.json` with `workspaces`, a `pnpm-workspace.yaml`, else the git root). Copy the Skills where the agent is started (usually the root); doctor in each package then sees them there.
69
+ 11. **Run Verify.**
70
+
71
+ ## Example
72
+
73
+ npm workspaces; with pnpm, the root has `pnpm-workspace.yaml` (`packages: ["apps/*", "packages/*"]`), the app depends on `"@acme/ui": "workspace:*"`, and the root script is `pnpm -r run build`. The app is `create-vite`'s `vanilla-ts` template with its `tsconfig.json` and `.gitignore` unchanged; its starter files (`counter.ts`, `assets`, `public`) were removed.
74
+
75
+ ```json
76
+ // package.json (root)
77
+ {
78
+ "name": "shop",
79
+ "private": true,
80
+ "workspaces": ["packages/*", "apps/*"],
81
+ "scripts": { "build": "npm run build --workspaces" }
82
+ }
83
+ ```
84
+
85
+ ```gitignore
86
+ # .gitignore (root)
87
+ node_modules
88
+ *.html.ts
89
+ ts-dom-tags.ts
90
+ ```
91
+
92
+ ```json
93
+ // packages/ui/package.json
94
+ {
95
+ "name": "@acme/ui",
96
+ "version": "0.0.0",
97
+ "private": true,
98
+ "type": "module",
99
+ "exports": { ".": "./src/index.ts" },
100
+ "scripts": { "build": "ts-dom check src" },
101
+ "peerDependencies": { "@nativele/ts-dom": "^0.6.0" },
102
+ "devDependencies": { "@nativele/ts-dom": "^0.6.0", "typescript": "^7.0.2" }
103
+ }
104
+ ```
105
+
106
+ ```json
107
+ // packages/ui/tsconfig.json (owned by TypeScript; shown as verified)
108
+ {
109
+ "compilerOptions": {
110
+ "target": "es2023",
111
+ "module": "esnext",
112
+ "moduleResolution": "bundler",
113
+ "lib": ["ES2023", "DOM"],
114
+ "strict": true,
115
+ "verbatimModuleSyntax": true,
116
+ "skipLibCheck": true,
117
+ "noEmit": true
118
+ },
119
+ "include": ["src"]
120
+ }
121
+ ```
122
+
123
+ ```html
124
+ <!-- packages/ui/src/ui-button.html -->
125
+ <button type="button" data-on-click="press">{{ label }}</button>
126
+ ```
127
+
128
+ ```ts
129
+ // packages/ui/src/ui-button.ts
130
+ import { Component, define, event } from "@nativele/ts-dom";
131
+ import tpl from "./ui-button.html.js";
132
+
133
+ export class UiButton extends Component(tpl, {
134
+ props: { label: "" },
135
+ emits: { "ui-press": event<{ label: string }>() },
136
+ }) {
137
+ press(): void {
138
+ this.emit("ui-press", { label: this.props.label });
139
+ }
140
+ }
141
+ define("ui-button", UiButton);
142
+ ```
143
+
144
+ ```html
145
+ <!-- packages/ui/src/ui-tag.html -->
146
+ <span>{{ text }}</span>
147
+ ```
148
+
149
+ ```ts
150
+ // packages/ui/src/ui-tag.ts
151
+ import { Component, define } from "@nativele/ts-dom";
152
+ import tpl from "./ui-tag.html.js";
153
+
154
+ export interface Tag {
155
+ id: string;
156
+ label: string;
157
+ }
158
+
159
+ export class UiTag extends Component(tpl, {
160
+ props: { tag: { id: "", label: "" } as Tag },
161
+ derive: { text: (s) => s.tag.label },
162
+ }) {}
163
+ define("ui-tag", UiTag);
164
+ ```
165
+
166
+ ```ts
167
+ // packages/ui/src/index.ts
168
+ export type {} from "./ts-dom-tags.js";
169
+ export { UiButton } from "./ui-button.js";
170
+ export { UiTag, type Tag } from "./ui-tag.js";
171
+ ```
172
+
173
+ ```json
174
+ // apps/web/package.json (the parts that change)
175
+ {
176
+ "scripts": { "build": "ts-dom check src && vite build" },
177
+ "dependencies": { "@acme/ui": "^0.0.0", "@nativele/ts-dom": "^0.6.0" },
178
+ "devDependencies": { "typescript": "~7.0.2", "vite": "^8.3.6" }
179
+ }
180
+ ```
181
+
182
+ ```ts
183
+ // apps/web/vite.config.ts
184
+ import { defineConfig } from "vite";
185
+ import { tsDom } from "@nativele/ts-dom/vite";
186
+
187
+ export default defineConfig({
188
+ plugins: [tsDom()],
189
+ });
190
+ ```
191
+
192
+ ```ts
193
+ // apps/web/src/main.ts (index.html has <app-shell></app-shell> in its body)
194
+ import "./style.css";
195
+ import "@acme/ui";
196
+ import "./app-shell.ts";
197
+ ```
198
+
199
+ ```html
200
+ <!-- apps/web/src/app-shell.html -->
201
+ <p>{{ message }}</p>
202
+ <ui-button data-ref="add" label="Add tag" data-on-ui-press="onAdd"></ui-button>
203
+ <div role="list">
204
+ <template data-each="tags" data-key="id" data-prop="tag">
205
+ <ui-tag role="listitem"></ui-tag>
206
+ </template>
207
+ </div>
208
+ ```
209
+
210
+ ```ts
211
+ // apps/web/src/app-shell.ts
212
+ import { Component, define } from "@nativele/ts-dom";
213
+ import type { EmitEvent } from "@nativele/ts-dom";
214
+ import type { Tag, UiButton } from "@acme/ui";
215
+ import tpl from "./app-shell.html.js";
216
+
217
+ export class AppShell extends Component(tpl, {
218
+ state: { message: "No tags yet", tags: [] as Tag[] },
219
+ }) {
220
+ onAdd(e: Event): void {
221
+ const { label } = (e as EmitEvent<UiButton, "ui-press">).detail;
222
+ const n = this.state.tags.length + 1;
223
+ this.set({ message: `${label}: ${n}`, tags: [...this.state.tags, { id: `t${n}`, label: `tag ${n}` }] });
224
+ }
225
+ override render(v: { tags: readonly Tag[] }): void {
226
+ this.refs.add.label = v.tags.length ? "Add another" : "Add tag";
227
+ this.lists.tags.items = v.tags;
228
+ }
229
+ }
230
+ define("app-shell", AppShell);
231
+ ```
232
+
233
+ `this.refs.add` is `UiButton` and `this.lists.tags.items` takes `Tag` items only because the package's tag map reaches the app through `export type {} from "./ts-dom-tags.js"`.
234
+
235
+ ## Known limitations
236
+
237
+ 1. **A tag from another package is neither imported nor defined for you.** The app's generated `x.html.ts` imports only components defined in the app's scanned folder. With only `import type` from the package, the build succeeds and the page shows the tags as empty, never-upgraded elements (`customElements.get("ui-button")` is `undefined`), with no error in the console. Import the package for its side effect (step 7).
238
+ 2. **The package's `ts-dom-tags.ts` does not reach consumers by itself**: nothing imports it. Without step 5's line, the app's `refs` of the package's tags are plain `HTMLElement` (`TS2339: Property 'label' does not exist on type 'HTMLElement'`), and a keyed list of the package's item component takes any object type with the key field but rejects items typed by an interface (`Index signature for type 'string' is missing in type 'Tag'`). `import type {} from "./ts-dom-tags.js"` and `/// <reference path="./ts-dom-tags.ts" />` work for a source package, but both are dropped from the emitted `index.d.ts` of a built one; `export type {} from` is kept there and erased from the JavaScript. `import "./ts-dom-tags.js"` is kept too, but as a runtime import of an empty module.
239
+ 3. **Lint does not see another package's components.** With the tag map in place, `tsc` checks their prop types (`TS2322: Type 'number' is not assignable to type 'string'`), list item types and `EmitEvent` details, but a misspelled attribute (`lable="Save"`) or event (`data-on-ui-pres`) on the package's tag is not reported: `unknown-prop` and `unknown-event` read only the scanned folders.
240
+ 4. **One `ts-dom` run over several packages' folders breaks package boundaries.** `ts-dom gen packages/ui/src apps/web/src` from the root writes `import "../../../packages/ui/src/ui-button.js"` into the app's generated files, bypassing the package's `exports`; the app's own run (and `tsDom()` at build start) writes the files without it, so `ts-dom gen --check` from either side reports the other's files as `stale-generated`. With a built package, an app bundled from those files contained both `packages/ui/dist/ui-button.js` and `packages/ui/src/ui-button.ts`. `tsDom({ dirs: ["src", "../../packages/ui/src"] })` does the same. From the root, `ts-dom check` also needs a root `tsconfig.json` (without one: `tsc-failed`; `ts-dom doctor`: `no-tsconfig`), and uses what resolves from the root: its `typescript`, and `vue-tsc` only if the root `package.json` lists it (`ts-dom check` picks the checker by the `package.json` of the folder it runs in: in a pnpm workspace it ran `vue-tsc` in the app that listed it and `tsc` in the component package).
241
+ 5. **A tag defined in two packages fails silently at runtime.** `define()` skips a tag that is already registered, so the module loaded first wins and the other class is never used, with no error. Per package, `ts-dom check` reports it only when both tag maps are in one program, as `duplicate-define` (from tsc's `TS2717` in `ts-dom-tags.ts`; its `Fix:` says to rename the tag). A run over both folders reports `duplicate-define`, with the problems of limitation 4. Give each package's new tags a prefix of their own; a component moved in from an app keeps its tag (step 1), because the pages and tests that use it name that tag.
242
+ 6. **Two versions of `@nativele/ts-dom` work but ship twice.** With the component package on another version than the app, props, events, a keyed list of the package's item component and a store created in the package and bound in the app all worked, and the minified bundle grew from 18.20 kB to 34.29 kB. No `ts-dom` command reports it, and inside the workspace the peer does not prevent it (step 3); step 2 does.
243
+ 7. **Hoisting hides missing dependencies until an install changes it.** In an npm workspace, a component package that lists neither `@nativele/ts-dom` nor `typescript` still generates, checks and builds through the root's hoisted copies, and doctor reports nothing. When an install moves `typescript` into another package's `node_modules` (adding and removing `vue-tsc` in the app does), `ts-dom check src` in the package stops with `error [tsc-not-found]`. A hoisted `typescript` at the root can also differ from the app's own (6.0.3 against `~7.0.2`), so `npx tsc` in the app and `ts-dom check` from the root run different compilers. In pnpm the same package fails at once: `Command "ts-dom" not found`, and the app's check reports `TS2307: Cannot find module '@nativele/ts-dom'` in the package's files. Step 3 fixes all of these.
244
+ 8. **`ts-dom doctor` reads the folder it runs in** for its `package.json`, `tsconfig.json` and Vite config, and the folders up to the workspace root for `.gitignore` and Skill copies. At the root without folders it reports `dir-not-found` for `src`. Run it in each package with that package's folder, and check the script order yourself (step 8).
245
+ 9. **`"sideEffects": false` drops the components from a production build.** With it in the component package, `vite build` removed `import "@acme/ui"` and every `define()`, and no tag was registered. A list must name the entry module too: a list of only the modules that call `define()` still dropped them, while `["./src/index.ts", "./src/ui-*.ts"]` and `["**/*.ts"]` kept them.
246
+ 10. **The app's dev server does not watch another package's templates** (step 9).
247
+
248
+ ## Verify
249
+
250
+ At the root:
251
+
252
+ ```sh
253
+ npm ls @nativele/ts-dom # pnpm: pnpm why -r @nativele/ts-dom; expect one version
254
+ ```
255
+
256
+ In each package that has components (here `packages/ui`, then `apps/web`; pnpm: `pnpm exec ts-dom ...`):
257
+
258
+ ```sh
259
+ npx ts-dom doctor src # expect: no error; tsconfig-* findings go to the TypeScript Skill; skill-available is info
260
+ npx ts-dom check src # expect: ts-dom check: no issues (never tsc-not-found)
261
+ ```
262
+
263
+ From the root, the build through the workspace on a clean checkout (no `*.html.ts`, no `ts-dom-tags.ts`, no `dist`):
264
+
265
+ ```sh
266
+ npm run build # pnpm: pnpm run build; expect: packages/ui's script before apps/web's, each "ts-dom check: no issues", then vite's "built in"
267
+ ```
268
+
269
+ Then prove the types cross the package boundary: in the app's `render`, write `this.refs.add.label = v.tags.length;` and expect `TS2322: Type 'number' is not assignable to type 'string'` from `npx ts-dom check src`; write `this.lists.tags.items = v.tags.map((t) => ({ id: t.id, name: t.label }));` and expect a `TS2322` that ends with `Property 'label' is missing in type '{ id: string; name: string; }'`. Undo both. In the browser (`vite preview`), `customElements.get("ui-button")` and `customElements.get("ui-tag")` are defined, clicking the button changes its label to "Add another" and adds a `<ui-tag>` per click, and the console has no errors.
270
+
271
+ ## Troubleshooting
272
+
273
+ | Symptom | Cause | Fix |
274
+ |---|---|---|
275
+ | The package's tags render nothing, no console error, `customElements.get(tag)` is `undefined` | the app imports the package only as types, or `"sideEffects": false` dropped it from the build | `import "@acme/ui"` in the app's entry (step 7); remove `sideEffects: false` or list the entry and `define()` modules |
276
+ | `TS2339: Property 'label' does not exist on type 'HTMLElement'` on a ref of the package's tag | the package's tag map does not reach the app | `export type {} from "./ts-dom-tags.js"` in the package's entry; for a built package, rebuild it |
277
+ | `TS2307: Cannot find module './ui-button.html.js'` in `../../packages/ui/src/...` during the app's check | the source package was not generated first (clean checkout, wrong order, or a task runner cache without the generated files in `outputs`) | run the package's `ts-dom gen src` first (step 8) |
278
+ | `TS2307: Cannot find module '@acme/ui'` and `TS2882` for `import "@acme/ui"` | the built package's `dist` does not exist yet | build the package first (step 8) |
279
+ | `Index signature for type 'string' is missing in type 'Tag'` on a keyed list of the package's item component | the package's tag map does not reach the app (limitation 2) | as above |
280
+ | `Could not resolve './ui-tag.html.js' in node_modules/@acme/ui/src/ui-tag.ts`, and `TS2307` for it | a source package installed from a tarball or a registry: `npm pack` left out the git-ignored generated files, and the app's `ts-dom` does not generate inside `node_modules` | publish the package built (step 6) |
281
+ | `TS2307: Cannot find module '@nativele/ts-dom'` in the package's files (pnpm) | the component package does not list it | step 3 |
282
+ | `pnpm exec ts-dom ...` stops with `Command "ts-dom" not found`, and `npx ts-dom` asks the registry for an unrelated package named `ts-dom` (`404`) | the package where it runs does not list `@nativele/ts-dom` | step 3 |
283
+ | `error [tsc-not-found]` and exit code 1 | `typescript` is not resolvable from that package (it was only hoisted) | `typescript` in the package's `devDependencies` |
284
+ | `stale-generated` for the app's files right after a run elsewhere | one run covered several packages' folders (limitation 4) | run each package on its own folder; regenerate with `ts-dom gen src` in the app |
285
+ | `error [duplicate-define]` mentioning `TS2717` | two packages define the same tag (limitation 5) | rename one tag |
286
+ | `tsc-failed`: `vue-tsc did not check the types` in one package only | `ts-dom check` runs `vue-tsc` where that package lists it; with TypeScript 7 it fails | see the TypeScript Skill (`nativele-typescript`) |
287
+ | `npm i -D -w @acme/ui ...` reports success but the package's `package.json` is unchanged | npm 11.19.1 did not save to a workspace folder created after the last install | run `npm i` at the root once, then the command again |
288
+ | pnpm prints `Agent skills awaiting approval: @nativele/ts-dom` on install | a pnpm 12 permissions message; `ts-dom skills` and `ts-dom skills add` worked without approving | the user decides with `pnpm approve` |
289
+
290
+ ## See also
291
+
292
+ - One scanned folder, and components outside the scanned folders: https://github.com/Pixmi/nativele/blob/main/docs/architecture.md#generated-files-checks-and-tsconfig
293
+ - How a template brings in child components, and `ts-dom-tags.ts`: https://github.com/Pixmi/nativele/blob/main/docs/components.md#composition-using-multiple-components-together
294
+ - Scanning several folders, the generated files, `ts-dom gen --check` and `--watch`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#installation-and-setup
295
+ - What `ts-dom check` and `ts-dom doctor` check, and how `check` picks `vue-tsc`: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md#checks
296
+ - The tsconfig of each package: https://github.com/Pixmi/nativele/blob/main/skills/nativele-typescript/SKILL.md
297
+ - The Vite plugin, `dirs` and the build script: https://github.com/Pixmi/nativele/blob/main/skills/nativele-vite/SKILL.md
298
+ - 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-react
3
3
  description: "Use Nativele web components inside a React 19 app: passing props, receiving events, children and slots, lifecycle, and TSX types. Use when a project depends on react and @nativele/ts-dom."
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: "react@19.3.1 react-dom@19.3.1 @types/react@19.3.0 @types/react-dom@19.3.0 vite@8.3.5 @vitejs/plugin-react@6.2.1 typescript@7.0.2"
9
9
  author: "Nativele maintainers"
10
10
  detects: "react"
@@ -24,20 +24,20 @@ Nativele components are native custom elements. React renders them like any othe
24
24
 
25
25
  | Tool | Verified version |
26
26
  |---|---|
27
- | @nativele/ts-dom | 0.5.1 (the last agent trial) |
27
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
28
28
  | react, react-dom | 19.3.1 |
29
29
  | @types/react, @types/react-dom | 19.3.0 |
30
30
  | vite | 8.3.5 |
31
31
  | @vitejs/plugin-react | 6.2.1 |
32
32
  | typescript | 7.0.2 |
33
33
 
34
- Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-react/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. With a different major version of React, treat every step as unverified.
34
+ Verified on 2026-11-06 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-react/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. With a different major version of React, treat every step 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`. Write them with the core Skill: `npx ts-dom skills add nativele-core`.
39
39
  2. Install: `npm i @nativele/ts-dom`.
40
- 3. Set `"strict": true` in the tsconfig whose `include` covers that folder (`tsconfig.app.json` in Vite's React template, which leaves it off); Nativele's type checks depend on it. Other configs, such as the one for `vite.config.ts`, stay as they are.
40
+ 3. The tsconfig whose `include` covers that folder (`tsconfig.app.json` in Vite's React template, which does not set `strict`) must meet Nativele's requirements: check and adjust it with the TypeScript Skill (`npx ts-dom skills nativele-typescript`).
41
41
  4. Add the Nativele Vite plugin next to the React plugin, pointing at that folder:
42
42
 
43
43
  ```ts
@@ -51,7 +51,7 @@ Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nati
51
51
 
52
52
  Pass the same folder to every `ts-dom` command (`npx ts-dom check src/nv`), so there is one `ts-dom-tags.ts`, in `src/nv`.
53
53
 
54
- 5. If the project lints with oxlint (`.oxlintrc.json`, as in Vite's React template), load the ts-dom rules there: add `{ "name": "ts-dom", "specifier": "@nativele/ts-dom/oxlint" }` to `jsPlugins` and `"ts-dom/unused-markup": "error"` to `rules`.
54
+ 5. If the project lints (Vite's React template has oxlint), the ts-dom rules go into its linter: follow the linting Skill (`npx ts-dom skills nativele-linting`).
55
55
 
56
56
  6. Import every Nativele component module in the entry file, before React renders anything:
57
57
 
@@ -89,7 +89,7 @@ Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nati
89
89
 
90
90
  The component class must be exported and passed to `define()` in a folder that `ts-dom gen` scans, so it appears in `ts-dom-tags.ts`.
91
91
 
92
- The generated files are not committed, so on a clean checkout the type check in `npm run build` runs before anything has written them and fails with `TS2307: Cannot find module './x.html.js'`. Generate them first: `"build": "ts-dom gen src/nv && tsc -b && vite build"` (the template's script with `ts-dom gen src/nv &&` in front).
92
+ The generated files are not committed, so on a clean checkout the type check in `npm run build` runs before anything has written them and fails with `TS2307: Cannot find module './x.html.js'` (why: `nativele-typescript`). Generate them first: `"build": "ts-dom gen src/nv && tsc -b && vite build"` (the template's script with `ts-dom gen src/nv &&` in front). CI starts from such a checkout; the rest of what its job needs is in `npx ts-dom skills nativele-ci`.
93
93
 
94
94
  ## Example
95
95
 
@@ -187,7 +187,7 @@ npx tsc --noEmit -p tsconfig.app.json # expect: no errors (keep one @ts-expect
187
187
  npm run build # expect: ✓ built (it runs ts-dom gen, tsc -b and vite build)
188
188
  ```
189
189
 
190
- Pass `tsc` the tsconfig from step 3. A root `tsconfig.json` that only lists `references` (as in Vite's template) checks nothing itself, so `tsc --noEmit` without `-p` passes even with type errors.
190
+ Pass `tsc` the tsconfig from step 3: the template's root `tsconfig.json` only lists `references`, so `tsc --noEmit` without `-p` checks nothing (`nativele-typescript`).
191
191
 
192
192
  In the browser, render a component with an object prop and an event handler, and check that the element has no `item` attribute (`el.getAttributeNames()`), that `el.item` is the object, and that the handler fires once per click. A reference implementation of these checks is the [`verify.mjs`](https://github.com/Pixmi/nativele/blob/main/skills/nativele-react/example/verify.mjs) of the trial's example project, which does them with Playwright, plus children that React reorders, adds and removes inside a `shadow: true` component and inside stable wrappers in a Light DOM component. A Light DOM component has no `<slot>` elements once rendered; check where the children ended up with `el.assignedNodes(name)`, where `""` is the default slot.
193
193
 
@@ -195,7 +195,7 @@ In the browser, render a component with an object prop and an event handler, and
195
195
 
196
196
  | Symptom | Cause | Fix |
197
197
  |---|---|---|
198
- | `TS2307: Cannot find module './x.html.js'` from `npm run build` on a clean checkout | `tsc -b` runs before the generated files exist | start the script with `ts-dom gen src/nv &&` |
198
+ | `TS2307: Cannot find module './x.html.js'` from `npm run build` on a clean checkout | `tsc -b` runs before the generated files exist | start the script with `ts-dom gen src/nv &&`; for CI, `nativele-ci` |
199
199
  | An object prop shows `[object Object]` or is missing | the component was rendered before `define()` ran | import the component module in the entry file before rendering |
200
200
  | The event handler never runs | the prop name is camelCased (`onItemSelect`) | use `on` plus the exact event name (`onitem-select`) |
201
201
  | `insertBefore`/`removeChild` errors, the React root disappears | React children changed inside a Light DOM component with slots | use `shadow: true`, or stable wrapper elements per slot |
@@ -207,3 +207,6 @@ In the browser, render a component with an object prop and an event handler, and
207
207
  - Components, props and events: https://github.com/Pixmi/nativele/blob/main/docs/components.md
208
208
  - Slots, Light DOM and Shadow DOM: https://github.com/Pixmi/nativele/blob/main/docs/components.md#light-default-and-shadow
209
209
  - Setup and checks: https://github.com/Pixmi/nativele/blob/main/docs/tooling.md
210
+ - The tsconfig: https://github.com/Pixmi/nativele/blob/main/skills/nativele-typescript/SKILL.md
211
+ - The ts-dom rules in the project's linter: https://github.com/Pixmi/nativele/blob/main/skills/nativele-linting/SKILL.md
212
+ - CI: https://github.com/Pixmi/nativele/blob/main/skills/nativele-ci/SKILL.md
@@ -3,8 +3,8 @@ name: nativele-state-management
3
3
  description: "Decide where state lives in an app built with Nativele (component state, a shared createStore, or an existing Zustand, Redux Toolkit or nanostores store) and connect external stores to this.bind with a small adapter: selectors that limit re-rendering, actions and dispatch from event handlers, unsubscribing when a component is removed. Use when a project depends on @nativele/ts-dom and on zustand, @reduxjs/toolkit, redux or nanostores, or when components need to share data."
4
4
  metadata:
5
5
  tier: official
6
- nativele: ">=0.5.2 <0.6.0"
7
- verified: "2026-11-05"
6
+ nativele: ">=0.6.0 <0.7.0"
7
+ verified: "2026-11-06"
8
8
  tools: "zustand@5.0.16 @reduxjs/toolkit@2.14.0 redux@5.0.1 nanostores@1.5.5 vite@8.3.6 typescript@7.0.2 create-vite@9.3.4"
9
9
  author: "Nativele maintainers"
10
10
  detects: "zustand @reduxjs/toolkit redux nanostores"
@@ -24,7 +24,7 @@ metadata:
24
24
 
25
25
  | Tool | Verified version |
26
26
  |---|---|
27
- | @nativele/ts-dom | 0.5.2 (the last agent trial) |
27
+ | @nativele/ts-dom | 0.6.0 (the last agent trial) |
28
28
  | zustand (`zustand/vanilla`) | 5.0.16 |
29
29
  | @reduxjs/toolkit | 2.14.0 |
30
30
  | redux | 5.0.1 (installed by `@reduxjs/toolkit`; a `legacy_createStore` store with the adapter was checked by hand with `tsc` and Node) |
@@ -33,7 +33,7 @@ metadata:
33
33
  | typescript | 7.0.2 |
34
34
  | create-vite (`vanilla-ts` template, the starting point) | 9.3.4 |
35
35
 
36
- Verified on 2026-11-05 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-state-management/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. With a different major version of a state library, treat the adapter and every limitation about that library as unverified.
36
+ Verified on 2026-11-06 in Google Chrome 148 with the example project in the Nativele repository (`skills/nativele-state-management/example`). With a different major version of a state library, treat the adapter and every limitation about that library as unverified.
37
37
 
38
38
  ## Set-up
39
39