templa-js 0.10.1 → 0.13.1
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/AGENTS.md +20 -7
- package/PLANNER.md +11 -9
- package/README.md +72 -7
- package/SECTION.md +37 -0
- package/bin/templa.js +195 -148
- package/examples/_partials/common-head.html +1 -1
- package/examples/_partials/common-header.html +4 -7
- package/examples/_partials/common-layout.html +1 -1
- package/examples/_partials/index-cta.html +1 -1
- package/examples/about.html +1 -1
- package/examples/css/style.css +1 -0
- package/examples/index.html +1 -1
- package/package.json +7 -2
- package/templa.js +213 -16
package/AGENTS.md
CHANGED
|
@@ -9,6 +9,7 @@ templa is a tiny HTML template loader giving you:
|
|
|
9
9
|
- `<template src="...">` to inline another HTML file
|
|
10
10
|
- `{{var}}` for variables; `<template if="key">` / `<template unless="key">` for existence-based conditionals
|
|
11
11
|
- `<slot>` and `<slot name="X">` for Web Components-style layouts
|
|
12
|
+
- Automatic active-nav marking: an `<a>` inside `<nav>` pointing at the current page gets `aria-current="page"` — style it with one CSS rule
|
|
12
13
|
- A build CLI (`npx templa-js build`) that statically expands all of the above
|
|
13
14
|
|
|
14
15
|
Same syntax works at runtime (browser) and at build time. Zero dependencies.
|
|
@@ -48,7 +49,7 @@ Lock down everything Phase 2 sub-agents will *consume*. **One agent in charge. N
|
|
|
48
49
|
|
|
49
50
|
> Need a structured way to design the skeleton from a brief? Use `PLANNER.md` — an instruction prompt that walks through the same checklist below and produces a `plan.md` for the project before any file is written.
|
|
50
51
|
|
|
51
|
-
1. **Design tokens in `src/css/style.css`** — base typography, color scale, spacing scale (`--space-1` … `--space-7`), radius / shadow values, and document chrome (`<header>`, `<footer>`, layout grid helpers). Per-section visual rules do NOT live here — they ship co-located inside each section's partial via `<style data-merge="css/style.css">`. The build merges them into `style.css` automatically.
|
|
52
|
+
1. **Design tokens in `src/css/style.css`** — base typography, color scale, spacing scale (`--space-1` … `--space-7`), radius / shadow values, and document chrome (`<header>`, `<footer>`, layout grid helpers). Per-section visual rules do NOT live here — they ship co-located inside each section's partial via `<style data-merge="css/style.css">`. The build merges them into `style.css` automatically. Keep the `:root` token block **first in the file**, closed with a `/* ── tokens end ── */` marker: sub-agents read only that block, so Phase 2's token cost stays constant however large the stylesheet grows.
|
|
52
53
|
|
|
53
54
|
2. **The five `common-*` templates in `src/_partials/`** — these are the project's chrome and shared section vocabulary. Lock them in Phase 1; sub-agents do not touch them.
|
|
54
55
|
|
|
@@ -56,7 +57,7 @@ Lock down everything Phase 2 sub-agents will *consume*. **One agent in charge. N
|
|
|
56
57
|
|---|---|
|
|
57
58
|
| `common-head.html` | Everything inside `<head>` except doctype/title — charset, viewport, font links, stylesheet link |
|
|
58
59
|
| `common-layout.html` | Body skeleton: invokes `common-header`, wraps a default `<slot>` in `<main>`, invokes `common-footer` |
|
|
59
|
-
| `common-header.html` | The site `<header>` element (brand + nav) |
|
|
60
|
+
| `common-header.html` | The site `<header>` element (brand + nav). The current page's link is auto-marked `aria-current="page"`; style it with a single `nav a[aria-current="page"]` rule — never enumerate pages in CSS and never thread a `page` variable |
|
|
60
61
|
| `common-footer.html` | The site `<footer>` element |
|
|
61
62
|
| `common-subhero.html` | Shared inner-page subhero, parameterized via `title` (and optionally `bg`) |
|
|
62
63
|
|
|
@@ -68,7 +69,7 @@ Lock down everything Phase 2 sub-agents will *consume*. **One agent in charge. N
|
|
|
68
69
|
|
|
69
70
|
Phase 1 is complete when:
|
|
70
71
|
|
|
71
|
-
- `npx templa-js
|
|
72
|
+
- `npx templa-js check` exits 0 — **this is the gate; do not dispatch Phase 2 sub-agents until it passes.** `check` runs the build pipeline without writing anything and exits 1 listing every problem (missing partial, unresolved `{{key}}`, page-level conditional, unclosed `<template>`). Phase 2 assumes a sound skeleton; starting parallel work on a broken one produces incoherent output that's hard to recover from.
|
|
72
73
|
- The output renders cohesively even with placeholder section content.
|
|
73
74
|
- Every section file the pages reference exists on disk.
|
|
74
75
|
|
|
@@ -77,8 +78,9 @@ Phase 1 is complete when:
|
|
|
77
78
|
With the skeleton locked, dispatch sub-agents to fill in each section. **One sub-agent per `_partials/[pagename]-[section].html` file, all running concurrently.**
|
|
78
79
|
|
|
79
80
|
A sub-agent's brief is exactly:
|
|
81
|
+
- `SECTION.md` (project root) as its only required reading — do **not** hand sub-agents this whole file; SECTION.md carries the same rules at a fraction of the tokens, and the savings multiply by the number of agents.
|
|
80
82
|
- The single file path it owns (e.g. `_partials/index-hero.html`).
|
|
81
|
-
- A 2–3 line description of what the section should contain.
|
|
83
|
+
- A 2–3 line description of what the section should contain, including the attribute keys it will receive (if any).
|
|
82
84
|
- The page's existing wireframe / surrounding context (so it knows what comes before and after, but not so it can edit those).
|
|
83
85
|
|
|
84
86
|
Sub-agents may:
|
|
@@ -128,8 +130,9 @@ src/
|
|
|
128
130
|
- Files and directories starting with `_` are partials and are **not copied** to the build output. Reference them with relative paths.
|
|
129
131
|
- Entry pages live at any non-underscore path under the source root.
|
|
130
132
|
- `<template src="X">` inside a partial is resolved relative to **the file containing the tag**, not the entry page. `common-layout.html` lives in `_partials/` so it references its siblings as `<template src="common-header.html">` (no `_partials/` prefix). Page entries live at `src/`, so they reference partials as `<template src="_partials/common-head.html">`.
|
|
133
|
+
- **Asset URLs in a partial are also partial-relative.** `src` (any element), `href` on `<link>`/`<use>`, `srcset`, `poster`, and CSS `url()` (in `<style>` blocks and `style=""`, including co-located `<style data-merge>`) are resolved relative to the partial and rewritten to root-absolute paths (so `../css/style.css` in `_partials/common-head.html` → `/css/style.css`), build and runtime alike. NOT rebased: `<a href>` (write nav links root-absolute like `/about.html`), `{{templated}}` URLs, `data-*`, `url(#id)`, and anything already absolute/scheme/hash — leave those as you mean them.
|
|
131
134
|
- Section file class names match the filename: `_partials/index-hero.html` styles `.index-hero`. This makes the cascade easy to audit and prevents class collisions across pages.
|
|
132
|
-
- The `data-merge` attribute on a section's `<style>` block is the **dist-relative path** to the linked stylesheet — e.g. `data-merge="css/style.css"` when the page
|
|
135
|
+
- The `data-merge` attribute on a section's `<style>` block is the **dist-relative path** to the linked stylesheet — e.g. `data-merge="css/style.css"` when the page loads `/css/style.css`. Mismatched paths cause merged styles to land in a file the page doesn't load.
|
|
133
136
|
|
|
134
137
|
---
|
|
135
138
|
|
|
@@ -269,7 +272,7 @@ When to use: any partial whose presence implies its own visual rules. Co-locatin
|
|
|
269
272
|
|
|
270
273
|
Conditionals can be nested. Resolution iterates until stable.
|
|
271
274
|
|
|
272
|
-
For **conditional attributes** (`disabled` on/off, `target="_blank"` only when external, etc.), templa core does not provide a built-in mechanism — that is intentional.
|
|
275
|
+
For **conditional attributes** (`disabled` on/off, `target="_blank"` only when external, etc.), templa core does not provide a built-in mechanism — that is intentional. When a project needs one, bind the attribute with Alpine (e.g. `:disabled="..."`) at runtime, or write the variants as two existence-based blocks. Keeping the core to existence-based block conditionals preserves the "templa looks like HTML the platform should ship" stance.
|
|
273
276
|
|
|
274
277
|
### Layouts and slots
|
|
275
278
|
|
|
@@ -337,11 +340,16 @@ For SEO-sensitive pages, always build before deploy. Crawlers other than Googleb
|
|
|
337
340
|
```bash
|
|
338
341
|
npx templa-js build # default: ./src → ./dist
|
|
339
342
|
npx templa-js build -i pages -o public
|
|
343
|
+
npx templa-js build --strict # exit 1 on any problem (CI / agent loops)
|
|
344
|
+
npx templa-js build --no-format # skip the prettier pass (faster iteration)
|
|
345
|
+
npx templa-js check # verify without writing; always strict
|
|
340
346
|
npx templa-js --help
|
|
341
347
|
```
|
|
342
348
|
|
|
343
349
|
The CLI walks every `.html` file in the source tree (skipping `_*` files and directories), expands templates and slots recursively, and writes the result to the output directory. Other files are copied as-is.
|
|
344
350
|
|
|
351
|
+
**Agents: gate on exit codes, not log output.** `check` (and `build --strict`) exit 1 when a partial is missing, a `{{key}}` survives into the output, a page-level `<template if/unless>` remains, or a `<template>` is left unclosed — each problem is listed with its page. A plain `build` only warns and still exits 0.
|
|
352
|
+
|
|
345
353
|
---
|
|
346
354
|
|
|
347
355
|
## Pitfalls — read before debugging
|
|
@@ -360,7 +368,7 @@ The CLI walks every `.html` file in the source tree (skipping `_*` files and dir
|
|
|
360
368
|
|
|
361
369
|
7. **`<title>` / `<meta og:*>` in a head partial when running in runtime mode.** SNS crawlers and Google's first wave don't run JS, so they will see no title and no description. Either build, or keep these tags inline in the page.
|
|
362
370
|
|
|
363
|
-
8. **
|
|
371
|
+
8. **Assuming the runtime needs `'unsafe-eval'`.** It doesn't — the runtime never calls `eval` or `new Function`, so a plain `script-src 'self'` CSP works. Don't add `'unsafe-eval'` for templa. If the page should ship with no runtime at all, build with `npx templa-js build` — the output is plain HTML with no template syntax left.
|
|
364
372
|
|
|
365
373
|
9. **`<slot name="head">` inside a body-fragment layout.** `<slot>` only fills where it sits in the DOM. To inject head content per-page, write it directly in the page's `<head>`.
|
|
366
374
|
|
|
@@ -396,6 +404,8 @@ The CLI walks every `.html` file in the source tree (skipping `_*` files and dir
|
|
|
396
404
|
|
|
397
405
|
14. **Page entry file edited during Phase 2.** Page files are part of the skeleton; only the orchestrator adds, removes, or reorders the `<template src>` lines. A sub-agent that wants to add a new section asks the orchestrator to wire it in, then writes the new partial.
|
|
398
406
|
|
|
407
|
+
15. **Hand-rolled active-nav state.** Don't thread a `page` variable through the layout or enumerate pages in CSS selectors. templa marks the current page's `<nav>` link with `aria-current="page"` automatically (build and runtime); one `nav a[aria-current="page"]` rule covers every page, present and future.
|
|
408
|
+
|
|
399
409
|
---
|
|
400
410
|
|
|
401
411
|
## Quick command reference
|
|
@@ -407,6 +417,9 @@ npm install templa-js
|
|
|
407
417
|
# Build
|
|
408
418
|
npx templa-js build -i ./src -o ./dist
|
|
409
419
|
|
|
420
|
+
# Verify without writing (exit 0 = sound; the Phase 1 → 2 gate)
|
|
421
|
+
npx templa-js check -i ./src
|
|
422
|
+
|
|
410
423
|
# Version
|
|
411
424
|
npx templa-js --version
|
|
412
425
|
|
package/PLANNER.md
CHANGED
|
@@ -122,13 +122,15 @@ Pick concrete values; do not stay vague. Provide all of:
|
|
|
122
122
|
|
|
123
123
|
If the brief was vague on aesthetics, infer from the project type and brand vibe and note explicitly that the values are an inference the user can override.
|
|
124
124
|
|
|
125
|
+
Place the `:root` token block first in `style.css`, closed with a `/* ── tokens end ── */` marker — section sub-agents read only that block.
|
|
126
|
+
|
|
125
127
|
### 6. Decide the `common-*` templates
|
|
126
128
|
|
|
127
129
|
(All paths below are project-root relative.)
|
|
128
130
|
|
|
129
|
-
- **`src/_partials/common-head.html`** — `<head>` chrome: charset, viewport, theme-color, font preconnect/links,
|
|
131
|
+
- **`src/_partials/common-head.html`** — `<head>` chrome: charset, viewport, theme-color, font preconnect/links, `../css/style.css` link (partial-relative → rewritten to `/css/style.css`). Title comes from the page's `<template src="…common-head.html" title="…">` attribute.
|
|
130
132
|
- **`src/_partials/common-layout.html`** — body fragment: invokes `common-header`, wraps a default `<slot>` in `<main>`, invokes `common-footer`.
|
|
131
|
-
- **`src/_partials/common-header.html`** — `<header>` element: brand + navigation anchors
|
|
133
|
+
- **`src/_partials/common-header.html`** — `<header>` element: brand + navigation anchors. The current page's link is auto-marked `aria-current="page"` by templa (build and runtime); style it with a single `nav a[aria-current="page"]` rule — no per-page selectors, no `page` variable.
|
|
132
134
|
- **`src/_partials/common-footer.html`** — `<footer>` element with copyright/social, identical on every page.
|
|
133
135
|
- **`src/_partials/common-subhero.html`** — `<section>` for inner-page headers, parameterized by `title` and optionally `bg`. Used by every page except the home page.
|
|
134
136
|
|
|
@@ -142,7 +144,7 @@ A complete listing of every file the skeleton will create, **paths relative to t
|
|
|
142
144
|
src/css/style.css // tokens + base + chrome (locked in Phase 1)
|
|
143
145
|
src/_partials/common-head.html // <head> chrome (charset, viewport, fonts, css link)
|
|
144
146
|
src/_partials/common-layout.html // body skeleton: header / main slot / footer
|
|
145
|
-
src/_partials/common-header.html // <header>: brand + nav
|
|
147
|
+
src/_partials/common-header.html // <header>: brand + nav (active link auto-marked aria-current)
|
|
146
148
|
src/_partials/common-footer.html // <footer>: copyright + socials
|
|
147
149
|
src/_partials/common-subhero.html // shared inner-page subhero (title, optional bg)
|
|
148
150
|
src/_partials/index-hero.html // index — landing hero (sub-agent owned)
|
|
@@ -161,7 +163,7 @@ Every `[pagename]-[sectionname].html` listed above is a unit of Phase 2 sub-agen
|
|
|
161
163
|
|
|
162
164
|
### 8. Sketch the section-fill dispatch
|
|
163
165
|
|
|
164
|
-
One sub-agent per `[pagename]-[sectionname].html` file.
|
|
166
|
+
One sub-agent per `[pagename]-[sectionname].html` file. Each sub-agent reads `SECTION.md` (project root) as its only required reading, plus a 2–3-line brief from you: what the section contains, which design tokens to reach for, any image/asset references, any Alpine.js bits to author inline.
|
|
165
167
|
|
|
166
168
|
The dispatch is genuinely parallel — none of the section files share file content, so sub-agents never collide.
|
|
167
169
|
|
|
@@ -179,10 +181,10 @@ Example:
|
|
|
179
181
|
End the plan with this line, verbatim:
|
|
180
182
|
|
|
181
183
|
```bash
|
|
182
|
-
npx templa-js
|
|
184
|
+
npx templa-js check -i ./src
|
|
183
185
|
```
|
|
184
186
|
|
|
185
|
-
This is the gate that separates skeleton from content fill. Content sub-agents must not be dispatched until this command
|
|
187
|
+
This is the gate that separates skeleton from content fill. `check` runs the build pipeline without writing anything and exits 1 listing every problem. Content sub-agents must not be dispatched until this command exits 0 against the placeholder skeleton.
|
|
186
188
|
|
|
187
189
|
## Output format — your reply takes this shape
|
|
188
190
|
|
|
@@ -250,9 +252,9 @@ Fonts: Inter (body), Lora (headings).
|
|
|
250
252
|
(Note: tokens inferred from the "warm, slow" vibe; override if you want.)
|
|
251
253
|
|
|
252
254
|
## 6. common-* templates
|
|
253
|
-
- src/_partials/common-head.html — charset, viewport, theme-color, fonts,
|
|
255
|
+
- src/_partials/common-head.html — charset, viewport, theme-color, fonts, ../css/style.css (→ /css/style.css)
|
|
254
256
|
- src/_partials/common-layout.html — header / <main><slot/></main> / footer
|
|
255
|
-
- src/_partials/common-header.html — brand + nav
|
|
257
|
+
- src/_partials/common-header.html — brand + nav (active link auto-marked aria-current)
|
|
256
258
|
- src/_partials/common-footer.html — copyright + socials
|
|
257
259
|
- src/_partials/common-subhero.html — inner-page subhero (title attribute)
|
|
258
260
|
|
|
@@ -269,7 +271,7 @@ Fonts: Inter (body), Lora (headings).
|
|
|
269
271
|
|
|
270
272
|
## 9. Build gate
|
|
271
273
|
```bash
|
|
272
|
-
npx templa-js
|
|
274
|
+
npx templa-js check -i ./src
|
|
273
275
|
```
|
|
274
276
|
|
|
275
277
|
## Open questions
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ It works in two modes:
|
|
|
42
42
|
- **Standard HTML** — uses the native `<template>` element, not custom tags
|
|
43
43
|
- **Tiny** — ~240 lines of source, ~3.5KB gzipped
|
|
44
44
|
- **HTML-native** — `{{var}}` for values; `<template if>` / `<template unless>` for conditionals
|
|
45
|
+
- **Active-nav aware** — `<nav>` links to the current page get `aria-current="page"` automatically
|
|
45
46
|
- **Recursive** — partials inside partials just work
|
|
46
47
|
- **Resource-aware** — waits for `<link rel="stylesheet">` and `<script src>` inside partials before resolving
|
|
47
48
|
|
|
@@ -71,11 +72,11 @@ Bootstrap a new project in the current directory:
|
|
|
71
72
|
|
|
72
73
|
```bash
|
|
73
74
|
npx templa-js init # minimal src/ tree
|
|
74
|
-
npx templa-js init --ai # also write
|
|
75
|
+
npx templa-js init --ai # also write the AI agent guides
|
|
75
76
|
npx templa-js init --force # overwrite existing files
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
The result is a buildable project: run `npx templa-js build` immediately afterwards and `dist/` will be produced. The `--ai` flag adds
|
|
79
|
+
The result is a buildable project: run `npx templa-js build` immediately afterwards and `dist/` will be produced. The `--ai` flag adds four project-root markdown files that brief AI coding agents on templa's conventions: `AGENTS.md` (orchestrator guide), `PLANNER.md` (skeleton planning prompt), `SECTION.md` (compact sub-agent brief), and `CLAUDE.md` (pointer so Claude Code finds the others).
|
|
79
80
|
|
|
80
81
|
## Usage
|
|
81
82
|
|
|
@@ -172,6 +173,36 @@ Rules:
|
|
|
172
173
|
|
|
173
174
|
This pattern works identically with the build CLI — `npx templa-js build` inlines the layout into the output and you can drop the script tags.
|
|
174
175
|
|
|
176
|
+
### Active navigation links
|
|
177
|
+
|
|
178
|
+
Any `<a>` inside `<nav>` whose `href` resolves to the current page is automatically marked with `aria-current="page"` — at build time (against the output path) and at runtime (against `location`). Style the active link with one CSS rule; no per-page selectors, no passing a `page` variable around:
|
|
179
|
+
|
|
180
|
+
```html
|
|
181
|
+
<!-- _partials/common-header.html -->
|
|
182
|
+
<style data-merge="css/style.css">
|
|
183
|
+
nav a[aria-current="page"] { font-weight: bold; }
|
|
184
|
+
</style>
|
|
185
|
+
<header>
|
|
186
|
+
<nav>
|
|
187
|
+
<a href="/">Home</a>
|
|
188
|
+
<a href="/about.html">About</a>
|
|
189
|
+
</nav>
|
|
190
|
+
</header>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Matching rules:
|
|
194
|
+
|
|
195
|
+
| Case | Behaviour |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `/` vs `index.html` | equivalent — a trailing `/index.html` normalizes to `/` |
|
|
198
|
+
| Pretty URLs | `/about` ≡ `/about.html` ≡ `/about/`, so `.html` hrefs still match on hosts that serve extensionless paths (Netlify, Vercel) and vice versa |
|
|
199
|
+
| Query strings and hashes | ignored; comparison is by path only |
|
|
200
|
+
| Pure-hash links (`#section`) | skipped, so same-page TOC links stay unmarked |
|
|
201
|
+
| External / cross-origin links | never match |
|
|
202
|
+
| Hand-written `aria-current` | respected — templa won't touch that link |
|
|
203
|
+
|
|
204
|
+
Links outside `<nav>` are never marked: per the ARIA definition, `aria-current="page"` identifies the current page *within a set of navigation links*. As a side effect, screen readers announce the active link correctly for free.
|
|
205
|
+
|
|
175
206
|
### Nested partials
|
|
176
207
|
|
|
177
208
|
Partials can include other partials. `templa` keeps expanding until no `<template src>` remains.
|
|
@@ -180,11 +211,30 @@ Partials can include other partials. `templa` keeps expanding until no `<templat
|
|
|
180
211
|
|
|
181
212
|
`templa.start()` kicks off head expansion **synchronously** so its fetches overlap with the rest of HTML parsing — critical when partials in `<head>` include `<link rel="stylesheet">`. Body expansion waits for `DOMContentLoaded`. The returned Promise resolves once both phases complete.
|
|
182
213
|
|
|
183
|
-
### Relative paths in
|
|
214
|
+
### Relative paths in partials
|
|
184
215
|
|
|
185
|
-
|
|
216
|
+
URLs authored **inside a partial are relative to that partial's own location**, not to the page that includes it — so a partial can live in `_partials/` and be shared by pages at any directory depth, resolving the same from every page.
|
|
186
217
|
|
|
187
|
-
|
|
218
|
+
This applies to `<template src>` **and** to asset URLs: `src` (on any element), `href` on `<link>`/`<use>`, `srcset`, `poster`, and **CSS `url()`** inside `<style>` blocks and `style=""` attributes (so co-located [`<style data-merge>`](#co-located-styles) keeps working). templa rewrites them to root-absolute paths during expansion (`../css/style.css` in `_partials/head.html` → `/css/style.css`), identically at build time and runtime.
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<!-- _partials/common-head.html -->
|
|
222
|
+
<link rel="stylesheet" href="../css/style.css" /> <!-- → /css/style.css -->
|
|
223
|
+
<style data-merge="css/style.css">
|
|
224
|
+
.hero { background: url(../img/hero.png); } <!-- → url(/img/hero.png) -->
|
|
225
|
+
</style>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Left untouched (write these as you mean them):
|
|
229
|
+
|
|
230
|
+
| Not rebased | Why / what to write |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `<a href="…">` | Link targets are pages, not co-located assets. Use **root-absolute** (`/about.html`) so a shared nav works from every page. |
|
|
233
|
+
| `#fragment`, `url(#id)`, `mailto:`/`tel:`/`https:`/`data:`, `//host`, `/already-absolute` | Already unambiguous. |
|
|
234
|
+
| `{{templated}}` URLs | Filled by `render` afterwards — you own the value (pass it root-absolute). |
|
|
235
|
+
| `data-*` attributes | Reserved as metadata. |
|
|
236
|
+
|
|
237
|
+
> **Note:** root-absolute output assumes the site is served from the domain root. At runtime any deploy sub-path is carried automatically via `location`; the static build assumes root (a configurable base path is not yet supported).
|
|
188
238
|
|
|
189
239
|
### Co-located styles
|
|
190
240
|
|
|
@@ -231,6 +281,8 @@ For pages that don't need a runtime at all, build with `npx templa-js build` —
|
|
|
231
281
|
|
|
232
282
|
- `{{key}}` HTML-escapes its value. If you need to inject HTML, use `{{{key}}}` and make sure the value is trusted.
|
|
233
283
|
- Original `<template src>` elements are removed from the DOM after expansion.
|
|
284
|
+
- `if` / `unless` are resolved against the data passed by the calling `<template src>`, so conditionals only make sense **inside a partial**. A bare `<template if="…">` written at the page top level has no calling data — it is left untouched (and a `<template>` renders its content inertly, i.e. invisibly). Both the build CLI and the runtime warn when they find one.
|
|
285
|
+
- Only the exact attribute names `if` / `unless` / `src` / `slot` are special. Framework directives that merely end in one of those (Alpine's `<template x-if>`, Vue's `<template v-if>`, any `data-*`) are left completely alone, so templa partials can host them.
|
|
234
286
|
- This project is in early development (0.0.x). API may change.
|
|
235
287
|
|
|
236
288
|
## Build (CLI)
|
|
@@ -268,9 +320,19 @@ Reference partials with a relative path:
|
|
|
268
320
|
|---|---|---|
|
|
269
321
|
| `-i <dir>` | `./src` | Source directory |
|
|
270
322
|
| `-o <dir>` | `./dist` | Output directory (cleared before each build) |
|
|
323
|
+
| `--strict` | off | Exit 1 on any problem: missing partial, unresolved `{{key}}` in output, page-level `<template if/unless>`, unclosed `<template>` |
|
|
324
|
+
| `--no-format` | off | Skip the automatic prettier pass on the output |
|
|
271
325
|
| `--version` | | Print version |
|
|
272
326
|
| `--help` | | Print usage |
|
|
273
327
|
|
|
328
|
+
### Check (dry run)
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
npx templa-js check -i ./src
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Runs the full build pipeline without writing anything and always behaves as `--strict`: exit 0 means the source is sound, exit 1 means problems (each one listed). Designed as a machine-readable gate for CI and AI agents — verify a skeleton before filling sections in parallel, or verify a single section after writing it.
|
|
335
|
+
|
|
274
336
|
### Build vs runtime
|
|
275
337
|
|
|
276
338
|
Both modes share the same template syntax, so a partial works in either:
|
|
@@ -303,10 +365,12 @@ Decision rule: templa for everything that can be resolved before the user clicks
|
|
|
303
365
|
|
|
304
366
|
## Working with AI agents
|
|
305
367
|
|
|
306
|
-
If you use Claude Code, Cursor, Aider, Copilot, or similar AI tools,
|
|
368
|
+
If you use Claude Code, Cursor, Aider, Copilot, or similar AI tools, `npx templa-js init --ai` drops four files that do most of the work:
|
|
307
369
|
|
|
308
|
-
- [`AGENTS.md`](./AGENTS.md) — file conventions, two-phase workflow, syntax, common pitfalls.
|
|
370
|
+
- [`AGENTS.md`](./AGENTS.md) — file conventions, two-phase workflow, syntax, common pitfalls. The orchestrating agent's source of truth.
|
|
309
371
|
- [`PLANNER.md`](./PLANNER.md) — an instruction prompt that turns a free-form site brief into a concrete `plan.md` (page set, wireframes, section list, design tokens, file inventory) before any file is written.
|
|
372
|
+
- [`SECTION.md`](./SECTION.md) — a compact brief for parallel section sub-agents. Hand each sub-agent this file instead of AGENTS.md: the same rules they need, at a fraction of the tokens per agent.
|
|
373
|
+
- `CLAUDE.md` — a generated pointer file, because Claude Code auto-loads CLAUDE.md (not AGENTS.md); Cursor/Codex-style tools read AGENTS.md directly.
|
|
310
374
|
|
|
311
375
|
## Philosophy
|
|
312
376
|
|
|
@@ -318,6 +382,7 @@ Concrete consequences of that stance:
|
|
|
318
382
|
|
|
319
383
|
- Attribute names follow the platform: `<template src>` mirrors `<img src>` / `<script src>` / `<iframe src>`. We deliberately don't use `data-src`. The bare `src` lets editors and IDEs treat it like a real file reference (path completion, jump-to-file, refactor-rename).
|
|
320
384
|
- Conditionals are written as `<template if="key">…</template>` and `<template unless="key">…</template>` — no `{{#if}}` Mustache block, no expressions, no helpers. They are existence-based only.
|
|
385
|
+
- Active nav links get `aria-current="page"` automatically. CSS Selectors Level 4 specced `:local-link` for exactly this and no browser shipped it; templa bridges the gap using the ARIA attribute HTML authors already write by hand.
|
|
321
386
|
- Co-located styles use `data-merge="style.css"` — a `data-*` attribute, because that is HTML's documented hook for component-private metadata.
|
|
322
387
|
- Anything beyond block-level conditionals (conditional attributes, dynamic class lists, loops) is out of scope for the core and lives in plugins.
|
|
323
388
|
|
package/SECTION.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SECTION.md — brief for section sub-agents
|
|
2
|
+
|
|
3
|
+
You are filling in ONE section of a templa site. You own exactly one file:
|
|
4
|
+
the `_partials/[page]-[section].html` you were assigned. This brief is all
|
|
5
|
+
you need — AGENTS.md (the full guide) is for the orchestrator, not you.
|
|
6
|
+
|
|
7
|
+
## Write
|
|
8
|
+
|
|
9
|
+
The complete HTML for your section, with its styles co-located on top:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<style data-merge="css/style.css">
|
|
13
|
+
.index-hero { padding: var(--space-6) 0; text-align: center; }
|
|
14
|
+
.index-hero h1 { margin: 0 0 var(--space-2); }
|
|
15
|
+
</style>
|
|
16
|
+
<section class="index-hero">
|
|
17
|
+
<h1>{{heading}}</h1>
|
|
18
|
+
</section>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
|
|
23
|
+
- **Root element class = your filename.** `index-hero.html` → `<section class="index-hero">`, and every CSS rule is scoped under that class.
|
|
24
|
+
- **Use the design tokens** from the `:root` block at the top of `css/style.css` (`var(--space-*)`, color and font tokens). No hardcoded colors or ad-hoc spacing values.
|
|
25
|
+
- **Dynamic values** arrive as attributes on the calling `<template>`: read them with `{{kebab-case-key}}` (HTML-escaped) or `{{{key}}}` (raw — trusted HTML only). Optional blocks: `<template if="key">…</template>` / `<template unless="key">…</template>` (existence-based, no expressions).
|
|
26
|
+
- **Interactivity** (toggles, accordions, tabs) is inline Alpine.js (`x-data` etc.) inside your section.
|
|
27
|
+
|
|
28
|
+
## Never
|
|
29
|
+
|
|
30
|
+
1. Edit any other file — not `css/style.css`, not `common-*.html`, not page entries, not other sections.
|
|
31
|
+
2. Reference another section with `<template src>` — sections are leaves, not composers.
|
|
32
|
+
3. Invent a new `common-*` template. If your section seems to need one, STOP and report back; the orchestrator decides.
|
|
33
|
+
4. Use a `{{key}}` the caller doesn't pass — only the keys listed in your assignment brief exist.
|
|
34
|
+
|
|
35
|
+
## Done when
|
|
36
|
+
|
|
37
|
+
Your file is self-contained and `npx templa-js check` exits 0 from the project root.
|