@homepages/template-kit 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +61 -17
- package/dist/base.css +9 -0
- package/dist/cli/check/config.js +37 -0
- package/dist/cli/check/css.js +117 -0
- package/dist/cli/check/diagnostics.js +32 -0
- package/dist/cli/check/index.js +87 -0
- package/dist/cli/check/loader.js +142 -0
- package/dist/cli/check/paths.js +15 -0
- package/dist/cli/check/relativize.js +18 -0
- package/dist/cli/check/render-invariants.js +24 -0
- package/dist/cli/check/resolve-tool.js +38 -0
- package/dist/cli/check/stages/deps.js +337 -0
- package/dist/cli/check/stages/lint.js +46 -0
- package/dist/cli/check/stages/render.js +101 -0
- package/dist/cli/check/stages/size.js +158 -0
- package/dist/cli/check/stages/tree.js +207 -0
- package/dist/cli/check/stages/typecheck.js +46 -0
- package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +18 -0
- package/dist/cli/check/stages/validate/facts.js +18 -0
- package/dist/cli/check/stages/validate/formatter-contract.js +14 -0
- package/dist/cli/check/stages/validate/group-contract.js +21 -0
- package/dist/cli/check/stages/validate/index.js +66 -0
- package/dist/cli/check/stages/validate/list-scalar-contract.js +22 -0
- package/dist/cli/check/stages/validate/nav-contract.js +93 -0
- package/dist/cli/check/stages/validate/nested-slot-contract.js +12 -0
- package/dist/cli/check/stages/validate/object-list-contract.js +31 -0
- package/dist/cli/check/stages/validate/orchestrator.js +241 -0
- package/dist/cli/check/stages/validate/produced-by-contract.js +18 -0
- package/dist/cli/check/stages/validate/row-contract.js +31 -0
- package/dist/cli/check/stages/validate/select-contract.js +13 -0
- package/dist/cli/check/stages/validate/source-contract.js +21 -0
- package/dist/cli/check/stages/validate/variant-contract.js +15 -0
- package/dist/cli/check/workspace.js +86 -0
- package/dist/cli/theme/generate.js +43 -0
- package/dist/cli/theme/index.js +33 -0
- package/dist/cli/theme/load-theme.js +69 -0
- package/dist/cli.js +22 -4
- package/dist/contracts/fill-treatments.js +47 -0
- package/dist/design-system/theme.d.ts +30 -300
- package/dist/design-system/theme.js +112 -90
- package/dist/eslint/rules/no-hex.js +78 -6
- package/dist/eslint.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/package.js +1 -1
- package/dist/primitives/Image.js +1 -1
- package/dist/schema/fill-spec.d.ts +80 -590
- package/dist/schema/fixture-schema.d.ts +5 -81
- package/dist/schema/manifest.d.ts +39 -475
- package/dist/schema/resolve-section-ref.js +10 -0
- package/dist/schema/rows.js +10 -0
- package/dist/schema/section-nav.js +9 -0
- package/dist/schema/section-schema.d.ts +51 -437
- package/dist/schema/slot-types.d.ts +12 -16
- package/dist/styles.css +31 -88
- package/docs/INDEX.md +4 -3
- package/docs/check.md +121 -0
- package/docs/eslint.md +18 -8
- package/docs/llms.txt +21 -9
- package/docs/primitives.md +5 -0
- package/docs/rules/INDEX.md +11 -4
- package/docs/rules/bundle-binary-asset.md +102 -0
- package/docs/rules/fixtures-invalid.md +96 -0
- package/docs/rules/license-denied.md +11 -3
- package/docs/rules/manifest-invalid.md +99 -0
- package/docs/rules/no-hex.md +76 -10
- package/docs/rules/no-templates.md +87 -0
- package/docs/rules/parse-error.md +76 -0
- package/docs/rules/schema-invalid.md +96 -0
- package/docs/rules/size-section-css.md +2 -2
- package/docs/theme-and-css.md +155 -90
- package/package.json +4 -9
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/fixtures-invalid authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, ../schema-system.md, schema-invalid.md]
|
|
5
|
+
updated: 2026-07-14
|
|
6
|
+
---
|
|
7
|
+
# template-kit/fixtures-invalid
|
|
8
|
+
|
|
9
|
+
> `fixtures.ts` is the one contract file nothing else parses for you. This is that parse.
|
|
10
|
+
|
|
11
|
+
## Rule
|
|
12
|
+
|
|
13
|
+
The default export of `fixtures.ts` must satisfy the fixture-module shape:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
{
|
|
17
|
+
base: {
|
|
18
|
+
slots: { /* every required slot, with a valid value */ },
|
|
19
|
+
options: { /* optional */ },
|
|
20
|
+
anchors: { /* optional */ },
|
|
21
|
+
variants: { /* optional */ },
|
|
22
|
+
},
|
|
23
|
+
option_matrix: [ /* optional: { label?, options } entries */ ],
|
|
24
|
+
states: { /* optional: named scenario → { label?, slots?, options?, anchors?, variants? } */ },
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`base` is required and must be an object holding a `slots` record — every other key is
|
|
29
|
+
optional. Each violation names the exact path inside the module that's wrong (`"base"`,
|
|
30
|
+
`"base.slots"`, `"states.empty-address"`, …) and what shape was expected there instead.
|
|
31
|
+
|
|
32
|
+
## Reason
|
|
33
|
+
|
|
34
|
+
`schema.ts` and `fill-spec.ts` are both Zod-parsed on the way into every other check, so a
|
|
35
|
+
malformed one is caught immediately. `fixtures.ts` used to be the exception: nothing
|
|
36
|
+
touched it until the render stage read it directly, so a wrong shape there surfaced as a
|
|
37
|
+
raw crash mid-render, blamed on whatever line happened to dereference the missing field —
|
|
38
|
+
never on the fixture that was actually wrong.
|
|
39
|
+
|
|
40
|
+
This gate exists to move that failure to where the mistake was made, with a message about
|
|
41
|
+
the fixture module instead of a stack trace from three files away.
|
|
42
|
+
|
|
43
|
+
## Fix
|
|
44
|
+
|
|
45
|
+
Read the message's path — it points at the exact key that's wrong — and bring that key's
|
|
46
|
+
shape in line with what's documented above.
|
|
47
|
+
|
|
48
|
+
### Before
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// sections/hero/fixtures.ts
|
|
52
|
+
import type { FixtureModule } from "@homepages/template-kit";
|
|
53
|
+
|
|
54
|
+
const fixtures: FixtureModule = {
|
|
55
|
+
base: {
|
|
56
|
+
slots: { headline: "A mansard Victorian in the heart of Jamaica Plain" },
|
|
57
|
+
},
|
|
58
|
+
states: {
|
|
59
|
+
"no-photo": { slots: { hero_image: null }, label: 12 },
|
|
60
|
+
// ^ label must be a string
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default fixtures;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
Section "hero": fixtures.ts — "states.no-photo.label": Expected string, received number.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### After
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// sections/hero/fixtures.ts
|
|
75
|
+
import type { FixtureModule } from "@homepages/template-kit";
|
|
76
|
+
|
|
77
|
+
const fixtures: FixtureModule = {
|
|
78
|
+
base: {
|
|
79
|
+
slots: { headline: "A mansard Victorian in the heart of Jamaica Plain" },
|
|
80
|
+
},
|
|
81
|
+
states: {
|
|
82
|
+
"no-photo": { slots: { hero_image: null }, label: "No hero photo" },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default fixtures;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## See also
|
|
90
|
+
|
|
91
|
+
- [The schema system](../schema-system.md) — `fixtures.ts`'s place among the section's
|
|
92
|
+
four contract files.
|
|
93
|
+
- [`schema-invalid`](schema-invalid.md) — the sibling gate on `schema.ts` and
|
|
94
|
+
`fill-spec.ts`.
|
|
95
|
+
- [`bundle-incomplete`](bundle-incomplete.md) — reported instead of this id when
|
|
96
|
+
`fixtures.ts` is missing outright, rather than present and wrong.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/license-denied authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, audit-severity.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/license-denied
|
|
8
8
|
|
|
@@ -31,8 +31,16 @@ Two cases people expect to slide through, and neither does:
|
|
|
31
31
|
- **A missing `license` field is a denial**, not a pass. An unlicensed package is not a
|
|
32
32
|
permissive one — by default, no rights are granted at all.
|
|
33
33
|
|
|
34
|
-
**Scope: the production tree only.**
|
|
35
|
-
|
|
34
|
+
**Scope: the production tree only.** "Production tree" means exactly what
|
|
35
|
+
`npm ci --omit=dev` installs. `devDependencies` are not evaluated — your bundler, your
|
|
36
|
+
test runner, and your formatter do not ship to anyone.
|
|
37
|
+
|
|
38
|
+
**An optional peer you have anyway counts.** If a production package declares an
|
|
39
|
+
optional `peerDependency` and your workspace has that package for any reason (even as a
|
|
40
|
+
`devDependency`), npm resolves the peer and keeps it in the `--omit=dev` install — so it
|
|
41
|
+
is in the production tree and its license is checked, transitively. An optional peer you
|
|
42
|
+
do **not** have is never installed and is not evaluated. Run `npm ci --omit=dev` and look
|
|
43
|
+
at the resulting `node_modules` if you want the exact set the check sees.
|
|
36
44
|
|
|
37
45
|
**There is no per-package override.** No allowlist entry you can add, no waiver comment. By
|
|
38
46
|
design.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/manifest-invalid authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, ../schema-system.md, schema-invalid.md, no-templates.md]
|
|
5
|
+
updated: 2026-07-14
|
|
6
|
+
---
|
|
7
|
+
# template-kit/manifest-invalid
|
|
8
|
+
|
|
9
|
+
> `manifest.json` is the one file that says which sections make a page, and in what
|
|
10
|
+
> order. Every check on it lands under this id.
|
|
11
|
+
|
|
12
|
+
## Rule
|
|
13
|
+
|
|
14
|
+
A template folder (`templates/<key>/`) must have a `manifest.json` at its root, and that
|
|
15
|
+
file must:
|
|
16
|
+
|
|
17
|
+
- **exist** — a template folder with none at all reports this once, naming the missing
|
|
18
|
+
file;
|
|
19
|
+
- **parse as JSON, and satisfy the manifest shape** — `key`, `name`, `deliverable_type`,
|
|
20
|
+
and an ordered `sections` array of section instances (`section`, `instance_id`,
|
|
21
|
+
`options`, `locked`, `required`, `reorderable`);
|
|
22
|
+
- **agree with its own folder** — the manifest's `key` must match the template folder
|
|
23
|
+
name it lives in;
|
|
24
|
+
- **reference real sections** — every `sections[].section` must resolve to a section this
|
|
25
|
+
workspace actually has;
|
|
26
|
+
- **override only what's real and overridable** — an instance's `options` may only name an
|
|
27
|
+
option the target section declares, only when that option is not
|
|
28
|
+
`template_overridable: false`, and only with a value that actually differs from the
|
|
29
|
+
section's own default;
|
|
30
|
+
- **satisfy the whole-composition nav contract** — the cross-section rules that need the
|
|
31
|
+
full, resolved section set rather than any one section in isolation.
|
|
32
|
+
|
|
33
|
+
Every violation collects into the same run; a manifest with several wrong references
|
|
34
|
+
reports all of them together.
|
|
35
|
+
|
|
36
|
+
## Reason
|
|
37
|
+
|
|
38
|
+
A section's own four files describe that section in isolation — they say nothing about
|
|
39
|
+
which page it appears on, in what order, or which of its options a specific template has
|
|
40
|
+
chosen to lock in. `manifest.json` is where that composition is declared, and it is the
|
|
41
|
+
only place a downstream consumer (the publisher, the fill pipeline, the editor) can go to
|
|
42
|
+
learn a template's actual shape. A manifest that references a section that doesn't exist,
|
|
43
|
+
or restates a default as if it were an override, is a template whose real composition
|
|
44
|
+
nobody can trust the manifest to describe.
|
|
45
|
+
|
|
46
|
+
## Fix
|
|
47
|
+
|
|
48
|
+
Read the message: it names the template, the section instance, and what's wrong.
|
|
49
|
+
Missing entirely, add one. Otherwise, fix the field the message points at.
|
|
50
|
+
|
|
51
|
+
### Before
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
templates/acme-modern/
|
|
55
|
+
manifest.json
|
|
56
|
+
sections/
|
|
57
|
+
hero/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
// templates/acme-modern/manifest.json
|
|
62
|
+
{
|
|
63
|
+
"key": "acme-classic",
|
|
64
|
+
"name": "Acme Classic",
|
|
65
|
+
"deliverable_type": "single_property_website",
|
|
66
|
+
"sections": [
|
|
67
|
+
{ "section": "hero", "instance_id": "hero-1", "options": {}, "locked": [], "required": true, "reorderable": false }
|
|
68
|
+
],
|
|
69
|
+
"reconcile": []
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
Template "acme-modern": manifest key "acme-classic" does not match the template folder name "acme-modern".
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### After
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
// templates/acme-modern/manifest.json
|
|
81
|
+
{
|
|
82
|
+
"key": "acme-modern",
|
|
83
|
+
"name": "Acme Modern",
|
|
84
|
+
"deliverable_type": "single_property_website",
|
|
85
|
+
"sections": [
|
|
86
|
+
{ "section": "hero", "instance_id": "hero-1", "options": {}, "locked": [], "required": true, "reorderable": false }
|
|
87
|
+
],
|
|
88
|
+
"reconcile": []
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## See also
|
|
93
|
+
|
|
94
|
+
- [The schema system](../schema-system.md#manifestjson--composing-sections-into-a-template) —
|
|
95
|
+
what a manifest declares and the type it's validated against.
|
|
96
|
+
- [`schema-invalid`](schema-invalid.md) — the same kind of cross-reference check, one
|
|
97
|
+
level down, on a single section's own contract files.
|
|
98
|
+
- [`no-templates`](no-templates.md) — reported instead when the workspace has no
|
|
99
|
+
`templates/<key>/` folder to check at all.
|
package/docs/rules/no-hex.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/no-hex authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, server-vs-client.md, ../theme-and-css.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/no-hex
|
|
8
8
|
|
|
@@ -11,11 +11,20 @@ updated: 2026-07-14
|
|
|
11
11
|
|
|
12
12
|
## Rule
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This rule ships as a **warning**, not an error: a section spending theme tokens is
|
|
15
|
+
recommended, not gated. A hard-coded colour squiggles in your editor, but it does not
|
|
16
|
+
fail `template-kit check` (the check gates on errors only, so a warning never reaches it)
|
|
17
|
+
and so does not block ingest either. The guidance below is still the right thing to do —
|
|
18
|
+
it only changes what the linter does with a violation, not whether the violation is
|
|
19
|
+
worth fixing.
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
The rule flags two shapes:
|
|
22
|
+
|
|
23
|
+
- **A CSS colour function that bakes a colour**, anywhere in a string or template
|
|
24
|
+
literal: `oklch(…)`, `oklab(…)`, `rgb(…)`, `rgba(…)`, `hsl(…)`, `hsla(…)`,
|
|
25
|
+
`color-mix(…)`. No context test — nobody spells an anchor id `rgb(...)`. **One
|
|
26
|
+
carve-out:** a `color-mix()` whose colour arguments are all theme-derived is legal —
|
|
27
|
+
see below.
|
|
19
28
|
- **A hex literal** (3, 4, 6, or 8 digits) **in a colour context**. A colour context is
|
|
20
29
|
a Tailwind arbitrary-value bracket that resolves to a colour:
|
|
21
30
|
- a colour property — `[color:#fff]`, `[background:#fff]`, `[background-color:#fff]`,
|
|
@@ -35,6 +44,40 @@ anchor ids that happen to be spelled from hex digits.
|
|
|
35
44
|
Every hex-shaped token in a string is checked, not just the first — an out-of-context
|
|
36
45
|
token early in a `className` cannot shadow a genuine colour later in it.
|
|
37
46
|
|
|
47
|
+
### The `color-mix()` carve-out
|
|
48
|
+
|
|
49
|
+
A `color-mix()` carries **no palette of its own** when every one of its colour arguments
|
|
50
|
+
is theme-derived — a `var(--…)` reference, `transparent`, `currentColor`, or a nested
|
|
51
|
+
`color-mix()` judged on these same terms. It composes a colour out of the theme the
|
|
52
|
+
section was handed, so it is as theme-agnostic as `bg-surface` and cannot break when
|
|
53
|
+
another template reuses the section. It is legal:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
hover:bg-[color-mix(in_srgb,var(--tr-color-surface)_92%,var(--tr-color-ink))]
|
|
57
|
+
bg-[color-mix(in_srgb,var(--tr-color-ink)_50%,transparent)] ← a translucent tint:
|
|
58
|
+
overlay, scrim, hover wash
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A mix weight (`92%`) is not a colour; only the colour arguments are tested.
|
|
62
|
+
|
|
63
|
+
`transparent` and `currentColor` are the only palette-free keywords: `transparent` names
|
|
64
|
+
the *absence* of a colour, and `currentColor` defers to the inherited (theme-driven)
|
|
65
|
+
`color`. Every other CSS named colour (`white`, `black`, `rebeccapurple`, …) is a baked
|
|
66
|
+
palette exactly as a hex is. So these report:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
color-mix(in srgb, var(--tr-color-primary) 80%, #fff) ← a hex literal
|
|
70
|
+
color-mix(in srgb, var(--tr-color-primary) 80%, white) ← a CSS named colour
|
|
71
|
+
color-mix(in srgb, var(--tr-color-accent, #fff) 80%, …) ← a var FALLBACK is a baked
|
|
72
|
+
colour the moment the
|
|
73
|
+
property is undefined
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The carve-out is `color-mix()` **only** — it is the one colour function that *composes* a
|
|
77
|
+
colour rather than stating one. The other six take raw channel values, so they bake a
|
|
78
|
+
colour by construction: `rgb(0 0 0)`, `oklch(0.7 0.1 20)`, and even
|
|
79
|
+
`rgb(var(--r) var(--g) var(--b))` report.
|
|
80
|
+
|
|
38
81
|
## Reason
|
|
39
82
|
|
|
40
83
|
A section carries **zero palette of its own**. It is theme-agnostic, and must look
|
|
@@ -49,10 +92,14 @@ not a palette boundary.
|
|
|
49
92
|
|
|
50
93
|
## Fix
|
|
51
94
|
|
|
52
|
-
Use
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
instead of in one section.
|
|
95
|
+
Use one of your theme's colour utilities. If the value is genuinely brand decoration with
|
|
96
|
+
no token yet, add it to `colors` in the **template's** `theme.ts` — the key `lagoon`
|
|
97
|
+
compiles to `--tr-color-lagoon` and the `bg-lagoon` / `text-lagoon` utilities — which
|
|
98
|
+
keeps the value in one place instead of in one section.
|
|
99
|
+
|
|
100
|
+
If you need a colour the theme does not name — a hover, active, or pressed step — derive
|
|
101
|
+
it from the tokens the theme *does* name with `color-mix()`, rather than reaching for a
|
|
102
|
+
literal to lighten or darken with.
|
|
56
103
|
|
|
57
104
|
### Before
|
|
58
105
|
|
|
@@ -90,8 +137,27 @@ export function Renderer({ slots }: Props) {
|
|
|
90
137
|
}
|
|
91
138
|
```
|
|
92
139
|
|
|
140
|
+
### A state colour, derived
|
|
141
|
+
|
|
142
|
+
Lightening a hover step by mixing in a hard-coded white is both the violation and the
|
|
143
|
+
bug: under a dark theme it lightens a button the theme wanted dark. Mix toward a colour
|
|
144
|
+
the **theme** names, and the step follows whatever theme the section is handed.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
// Before — `#fff` is a palette this section invented.
|
|
148
|
+
<button className="bg-primary hover:bg-[color-mix(in_srgb,var(--tr-color-primary)_80%,#fff)]">
|
|
149
|
+
{slots.cta}
|
|
150
|
+
</button>
|
|
151
|
+
|
|
152
|
+
// After — every colour argument is a theme var.
|
|
153
|
+
<button className="bg-primary hover:bg-[color-mix(in_srgb,var(--tr-color-primary)_80%,var(--tr-color-surface))]">
|
|
154
|
+
{slots.cta}
|
|
155
|
+
</button>
|
|
156
|
+
```
|
|
157
|
+
|
|
93
158
|
## See also
|
|
94
159
|
|
|
95
|
-
- [Theme and CSS](../theme-and-css.md#
|
|
160
|
+
- [Theme and CSS](../theme-and-css.md#tokentheme--your-templates-design-system) — the
|
|
161
|
+
token system, and how a `colors` entry becomes a utility.
|
|
96
162
|
- [Server vs client](server-vs-client.md) — why the other bans stop at the hydration
|
|
97
163
|
boundary and this one does not.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/no-templates authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, manifest-invalid.md]
|
|
5
|
+
updated: 2026-07-14
|
|
6
|
+
---
|
|
7
|
+
# template-kit/no-templates
|
|
8
|
+
|
|
9
|
+
> A run that gates nothing must never report success. This is what a workspace with no
|
|
10
|
+
> template to check reports instead of a silent pass.
|
|
11
|
+
|
|
12
|
+
## Rule
|
|
13
|
+
|
|
14
|
+
`templates/` must hold at least one template folder — for the run you asked for:
|
|
15
|
+
|
|
16
|
+
- `template-kit check` (no argument, from the workspace root): `templates/` has no
|
|
17
|
+
folder in it at all.
|
|
18
|
+
- `template-kit check --all`: same — no template folder exists to check.
|
|
19
|
+
- `template-kit check <name>` (or a bare `check` run from inside a template's own
|
|
20
|
+
folder): that one named template does not exist. (A folder that exists but is
|
|
21
|
+
missing its `manifest.json` is a different, more specific failure —
|
|
22
|
+
[`manifest-invalid`](manifest-invalid.md) — because a folder under `templates/` is a
|
|
23
|
+
template whatever else is wrong with it.)
|
|
24
|
+
|
|
25
|
+
This is checked once per run, scoped to the workspace, not to any one template.
|
|
26
|
+
|
|
27
|
+
## Reason
|
|
28
|
+
|
|
29
|
+
`every()` over an empty set is vacuously true. A `check` run that selected zero templates
|
|
30
|
+
and reported "check passed" would exit 0 having run not one gate — indistinguishable from
|
|
31
|
+
a workspace that passed every rule, when in truth no rule ran at all. Anything that
|
|
32
|
+
re-runs this command on your behalf (an ingest pipeline, a CI step) would accept that
|
|
33
|
+
silence as a clean submission.
|
|
34
|
+
|
|
35
|
+
Reporting a diagnostic instead means "nothing to check" is a failure your run surfaces,
|
|
36
|
+
not a false green light.
|
|
37
|
+
|
|
38
|
+
## Fix
|
|
39
|
+
|
|
40
|
+
Add a template: `templates/<key>/` with a `manifest.json` declaring its section
|
|
41
|
+
composition, and a `sections/` folder holding the sections it composes. If you meant to
|
|
42
|
+
check a specific template and named it wrong, or ran `check` from a folder that isn't a
|
|
43
|
+
template folder, correct the name or your working directory instead.
|
|
44
|
+
|
|
45
|
+
### Before
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
my-workspace/
|
|
49
|
+
package.json
|
|
50
|
+
package-lock.json
|
|
51
|
+
templates/ ← empty
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
$ template-kit check
|
|
56
|
+
✗ workspace — 1 problem(s)
|
|
57
|
+
[template-kit/no-templates] This workspace has no template to check: templates/ holds no template folder.
|
|
58
|
+
fix: Add a template: templates/<key>/ with a manifest.json declaring its section composition and a sections/ folder holding the sections it composes.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### After
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
my-workspace/
|
|
65
|
+
package.json
|
|
66
|
+
package-lock.json
|
|
67
|
+
templates/
|
|
68
|
+
acme-modern/
|
|
69
|
+
manifest.json
|
|
70
|
+
sections/
|
|
71
|
+
hero/
|
|
72
|
+
Renderer.tsx
|
|
73
|
+
schema.ts
|
|
74
|
+
fill-spec.ts
|
|
75
|
+
fixtures.ts
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
$ template-kit check
|
|
80
|
+
✓ acme-modern
|
|
81
|
+
check passed
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## See also
|
|
85
|
+
|
|
86
|
+
- [`manifest-invalid`](manifest-invalid.md) — the check that takes over once a template
|
|
87
|
+
folder exists, on whether its `manifest.json` is present and correct.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/parse-error authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, typecheck.md]
|
|
5
|
+
updated: 2026-07-14
|
|
6
|
+
---
|
|
7
|
+
# template-kit/parse-error
|
|
8
|
+
|
|
9
|
+
> A file so broken it never became an AST. Every other rule needs one; this is what's
|
|
10
|
+
> reported when there isn't one to have an opinion about.
|
|
11
|
+
|
|
12
|
+
## Rule
|
|
13
|
+
|
|
14
|
+
Every other lint rule runs against a parsed file — an AST it can walk and ask questions
|
|
15
|
+
of. When a `.ts`/`.tsx` file cannot even be parsed (invalid syntax: an unclosed brace, a
|
|
16
|
+
stray token, a JSX tag that never closes), there is no AST for any rule to run against,
|
|
17
|
+
and no rule id of its own to report. This id is what's reported in that one case instead:
|
|
18
|
+
the parser's own message, relocated to the file and line it names.
|
|
19
|
+
|
|
20
|
+
This is the only diagnostic under this id — it is not a rule you can violate by writing
|
|
21
|
+
working code a particular way, the way `no-hex` or `no-inline-style` are. It exists so
|
|
22
|
+
that "the file didn't parse" is a rule-id and a fix hint, not silence or a raw crash.
|
|
23
|
+
|
|
24
|
+
## Reason
|
|
25
|
+
|
|
26
|
+
A stage never aborts the run — an author fixing one file's problem must see every other
|
|
27
|
+
file's problems in the same run, not discover them one release at a time. A parser
|
|
28
|
+
exception is the one case where that promise is hardest to keep: there is genuinely
|
|
29
|
+
nothing else to check about a file that doesn't parse. Reporting it under its own id,
|
|
30
|
+
rather than dropping it or letting it throw, keeps that promise intact.
|
|
31
|
+
|
|
32
|
+
You may see the **same file** flagged here and under
|
|
33
|
+
[`typecheck`](typecheck.md): the lint stage and the typecheck stage each parse the
|
|
34
|
+
workspace independently, and a syntax error fails both parsers, so both stages report it,
|
|
35
|
+
each in their own words. Fixing the syntax error clears both.
|
|
36
|
+
|
|
37
|
+
## Fix
|
|
38
|
+
|
|
39
|
+
Read the message — it is the parser's own error, naming the file and the line. Fix the
|
|
40
|
+
syntax at that location.
|
|
41
|
+
|
|
42
|
+
### Before
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// sections/hero/Renderer.tsx
|
|
46
|
+
export function Renderer() {
|
|
47
|
+
return (
|
|
48
|
+
<Section>
|
|
49
|
+
<h1>{headline}</h1>
|
|
50
|
+
</Section
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
sections/hero/Renderer.tsx:6
|
|
57
|
+
Parsing error: '>' expected.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### After
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// sections/hero/Renderer.tsx
|
|
64
|
+
export function Renderer() {
|
|
65
|
+
return (
|
|
66
|
+
<Section>
|
|
67
|
+
<h1>{headline}</h1>
|
|
68
|
+
</Section>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## See also
|
|
74
|
+
|
|
75
|
+
- [`typecheck`](typecheck.md) — the sibling failure one layer up: a file that parses but
|
|
76
|
+
does not type-check.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/schema-invalid authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, ../schema-system.md, fixtures-invalid.md, manifest-invalid.md]
|
|
5
|
+
updated: 2026-07-14
|
|
6
|
+
---
|
|
7
|
+
# template-kit/schema-invalid
|
|
8
|
+
|
|
9
|
+
> `schema.ts` and `fill-spec.ts` are a matched pair. This is every check that makes sure
|
|
10
|
+
> they actually agree.
|
|
11
|
+
|
|
12
|
+
## Rule
|
|
13
|
+
|
|
14
|
+
A section's `schema.ts` and `fill-spec.ts` must, together:
|
|
15
|
+
|
|
16
|
+
- **parse** — each is `as const satisfies` its shape (`SectionSchemaShape`,
|
|
17
|
+
`FillSpecShape`); a value that doesn't satisfy it fails here with the parse error;
|
|
18
|
+
- **cross-reference correctly** — every `fill-spec.ts` decision produces a slot that
|
|
19
|
+
actually exists in `schema.ts`, every `reads` entry names a real, author-declarable
|
|
20
|
+
facts key, every option's `affects_decisions`/`affects_slots` names a real decision or
|
|
21
|
+
slot;
|
|
22
|
+
- **satisfy the closed per-type contracts** — an `object_list` slot's `item_order`
|
|
23
|
+
covers every field, a `select` slot's cases match its transform, a group's members are
|
|
24
|
+
slots that exist, and the rest of the per-slot-type rules the schema system documents.
|
|
25
|
+
|
|
26
|
+
One violation is one diagnostic; a section with several wrong things reports all of them
|
|
27
|
+
in the same run, not one per fix.
|
|
28
|
+
|
|
29
|
+
This is the id you also see when a section **cannot be built or parsed at all** — a
|
|
30
|
+
syntax error in `schema.ts`, a missing export, an import that doesn't resolve. The
|
|
31
|
+
message in that case is the underlying build error, relocated to the file it points at.
|
|
32
|
+
|
|
33
|
+
## Reason
|
|
34
|
+
|
|
35
|
+
`fill-spec.ts` is not free-standing — every decision it declares exists to fill a slot
|
|
36
|
+
`schema.ts` declared, and every fact it reads must be one the platform actually exposes.
|
|
37
|
+
A decision that produces a slot no schema declares, or reads a fact that doesn't exist,
|
|
38
|
+
is not a style problem: the fill pipeline would either drop the output on the floor or
|
|
39
|
+
crash trying to read a fact key that isn't there. Catching the mismatch here, at author
|
|
40
|
+
time, is the difference between a red check and a listing that silently fails to fill.
|
|
41
|
+
|
|
42
|
+
## Fix
|
|
43
|
+
|
|
44
|
+
Read the message: it names the section, the decision or slot in question, and what's
|
|
45
|
+
wrong with it. Fix whichever side is actually stale — the schema, or the fill-spec.
|
|
46
|
+
|
|
47
|
+
### Before
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// sections/facts/schema.ts
|
|
51
|
+
export const schema = {
|
|
52
|
+
meta: { displayName: "Facts" },
|
|
53
|
+
slots: {
|
|
54
|
+
year_built: { type: "text", required: false, produced_by: "year_built_text" },
|
|
55
|
+
},
|
|
56
|
+
options: {},
|
|
57
|
+
} as const satisfies SectionSchemaShape;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// sections/facts/fill-spec.ts
|
|
62
|
+
export const fillSpec = {
|
|
63
|
+
reads: [],
|
|
64
|
+
decisions: [
|
|
65
|
+
{ id: "year_built_text", type: "text-block", produces: ["year_built_summary"], length_cap: 20 },
|
|
66
|
+
// ^ typo — schema.ts declares "year_built"
|
|
67
|
+
],
|
|
68
|
+
} as const satisfies FillSpecShape;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Section "facts": fill-spec decision "year_built_text" produces unknown slot "year_built_summary".
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### After
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// sections/facts/fill-spec.ts
|
|
79
|
+
export const fillSpec = {
|
|
80
|
+
reads: [],
|
|
81
|
+
decisions: [
|
|
82
|
+
{ id: "year_built_text", type: "text-block", produces: ["year_built"], length_cap: 20 },
|
|
83
|
+
],
|
|
84
|
+
} as const satisfies FillSpecShape;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## See also
|
|
88
|
+
|
|
89
|
+
- [The schema system](../schema-system.md) — the full authoring contract for `schema.ts`
|
|
90
|
+
and `fill-spec.ts`.
|
|
91
|
+
- [`fixtures-invalid`](fixtures-invalid.md) — the sibling gate on the third contract
|
|
92
|
+
file, `fixtures.ts`.
|
|
93
|
+
- [`manifest-invalid`](manifest-invalid.md) — the same kind of cross-reference check, one
|
|
94
|
+
level up, on a template composing several sections.
|
|
95
|
+
- [`bundle-incomplete`](bundle-incomplete.md) — reported instead of this id when a
|
|
96
|
+
contract file is missing outright, rather than present and wrong.
|
|
@@ -46,8 +46,8 @@ Three moves, in order of how often they work:
|
|
|
46
46
|
stylesheet plus opt-in modules, *and* a fat "everything" bundle that is the one you get
|
|
47
47
|
from copying the quickstart. Take the core, plus only the modules you use.
|
|
48
48
|
2. **Cut the webfont.** If the package's stylesheet drags in a font for its icons, and you
|
|
49
|
-
are using three of those icons, replace them with inline SVG — or with a family the
|
|
50
|
-
theme already declares (`--tr-font
|
|
49
|
+
are using three of those icons, replace them with inline SVG — or with a family the
|
|
50
|
+
template's theme already declares in `fonts` (`--tr-font-<name>`), which costs nothing
|
|
51
51
|
because it is already loaded.
|
|
52
52
|
3. **Drop the package and hand-write the residue.** If you use one component from a large
|
|
53
53
|
widget library, its stylesheet is paying for components you never render.
|