@nifrajs/web-vanilla 2.1.0 → 2.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @nifrajs/web-vanilla
2
2
 
3
- Zero-framework render adapter for @nifrajs/web — auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.
3
+ Zero-framework render adapter for @nifrajs/web - auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.
4
4
 
5
- Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework — one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
5
+ Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
6
6
 
7
7
  ## Install
8
8
 
@@ -19,7 +19,7 @@ MIT
19
19
 
20
20
  ## For AI agents
21
21
 
22
- Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
22
+ Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
23
23
  one cheap read instead of the whole corpus. For the wider framework: the repo's
24
24
  [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
25
25
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
package/dist/compose.d.ts CHANGED
@@ -2,8 +2,8 @@ import type { RenderProps } from "@nifrajs/web";
2
2
  import type { Template } from "./html.js";
3
3
  /**
4
4
  * A vanilla "component": a plain function from props to a {@link Template}. The page (innermost
5
- * chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` — the
6
- * already-rendered inner fragment — plus the same render props, mirroring the React/Preact
5
+ * chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` - the
6
+ * already-rendered inner fragment - plus the same render props, mirroring the React/Preact
7
7
  * adapters' `children` contract.
8
8
  */
9
9
  export type VanillaComponent = (props: RenderProps & {
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,KAAK,QAAQ,CAAA;AAEzF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,CAO/E"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,KAAK,QAAQ,CAAA;AAEzF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,CAc/E"}
package/dist/compose.js CHANGED
@@ -6,7 +6,14 @@ export function compose(chain, props) {
6
6
  const last = chain.length - 1;
7
7
  let node = chain[last](props);
8
8
  for (let i = last - 1; i >= 0; i--) {
9
- node = chain[i]({ ...props, children: node });
9
+ // Each layout receives its own loader data at its own index. Layouts are the chain's leading
10
+ // prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
11
+ // boundary marker, the page) reads `undefined` and is unaffected.
12
+ node = chain[i]({
13
+ ...props,
14
+ data: props.layoutData?.[i] ?? null,
15
+ children: node,
16
+ });
10
17
  }
11
18
  return node;
12
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAI,KAAK,CAAC,IAAI,CAAsB,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,GAAI,KAAK,CAAC,CAAC,CAAsB,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAI,KAAK,CAAC,IAAI,CAAsB,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,6FAA6F;QAC7F,mGAAmG;QACnG,kEAAkE;QAClE,IAAI,GAAI,KAAK,CAAC,CAAC,CAAsB,CAAC;YACpC,GAAG,KAAK;YACR,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;YACnC,QAAQ,EAAE,IAAI;SACf,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
package/dist/html.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The tagged-template HTML engine — `html` builds an escaped-by-construction {@link Template}.
2
+ * The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
3
3
  *
4
4
  * Security model (the part that matters): every interpolated value is HTML-escaped by default.
5
- * The ONLY ways to emit unescaped markup are (1) nesting another `html` template — already escaped
6
- * by construction — and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
5
+ * The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
6
+ * by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
7
7
  * The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
8
- * unquoted attributes or inline scripts are NOT safe by design — the docs say "always quote".
8
+ * unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
9
9
  */
10
10
  /** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
11
11
  export declare class RawHtml {
@@ -13,12 +13,12 @@ export declare class RawHtml {
13
13
  constructor(value: string);
14
14
  }
15
15
  /**
16
- * Mark a string as trusted, pre-escaped markup — it is emitted verbatim. The deliberate escape
16
+ * Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
17
17
  * hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
18
18
  * React's dangerouslySetInnerHTML, without the JSX.
19
19
  */
20
20
  export declare function raw(trusted: string): RawHtml;
21
- /** A rendered HTML fragment — what `html` returns and components produce. Stringified once. */
21
+ /** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
22
22
  export declare class Template {
23
23
  /** The final markup. Built eagerly at tag time (interpolations are already values by then). */
24
24
  readonly html: string;
package/dist/html.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The tagged-template HTML engine — `html` builds an escaped-by-construction {@link Template}.
2
+ * The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
3
3
  *
4
4
  * Security model (the part that matters): every interpolated value is HTML-escaped by default.
5
- * The ONLY ways to emit unescaped markup are (1) nesting another `html` template — already escaped
6
- * by construction — and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
5
+ * The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
6
+ * by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
7
7
  * The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
8
- * unquoted attributes or inline scripts are NOT safe by design — the docs say "always quote".
8
+ * unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
9
9
  */
10
10
  /** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
11
11
  export class RawHtml {
@@ -15,7 +15,7 @@ export class RawHtml {
15
15
  }
16
16
  }
17
17
  /**
18
- * Mark a string as trusted, pre-escaped markup — it is emitted verbatim. The deliberate escape
18
+ * Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
19
19
  * hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
20
20
  * React's dangerouslySetInnerHTML, without the JSX.
21
21
  */
@@ -24,7 +24,7 @@ export function raw(trusted) {
24
24
  }
25
25
  const ESCAPE_RE = /[&<>"']/;
26
26
  const escapeHtml = (s) => {
27
- // Fast path: most text has nothing to escape — one regex probe, zero allocation.
27
+ // Fast path: most text has nothing to escape - one regex probe, zero allocation.
28
28
  if (!ESCAPE_RE.test(s))
29
29
  return s;
30
30
  let out = "";
@@ -55,7 +55,7 @@ const escapeHtml = (s) => {
55
55
  }
56
56
  return out + s.slice(last);
57
57
  };
58
- /** A rendered HTML fragment — what `html` returns and components produce. Stringified once. */
58
+ /** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
59
59
  export class Template {
60
60
  /** The final markup. Built eagerly at tag time (interpolations are already values by then). */
61
61
  html;
@@ -85,8 +85,8 @@ function renderValue(value) {
85
85
  out += renderValue(item);
86
86
  return out;
87
87
  }
88
- // Objects/functions are a bug at the call site — render loudly rather than "[object Object]".
89
- throw new TypeError(`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} — interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`);
88
+ // Objects/functions are a bug at the call site - render loudly rather than "[object Object]".
89
+ throw new TypeError(`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} - interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`);
90
90
  }
91
91
  /** The tag: `` html`<p>${user.name}</p>` `` → an escaped {@link Template}. */
92
92
  export function html(strings, ...values) {
package/dist/index.d.ts CHANGED
@@ -1,26 +1,26 @@
1
1
  /**
2
- * @nifrajs/web-vanilla — the zero-framework render adapter for @nifrajs/web.
2
+ * @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
3
3
  *
4
4
  * Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
5
5
  * **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
6
- * docs, comparison tables) where HTML is the product — interactivity comes from
6
+ * docs, comparison tables) where HTML is the product - interactivity comes from
7
7
  * `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
8
8
  *
9
- * Routes using this adapter are server-rendered documents, not hydrated apps — set
9
+ * Routes using this adapter are server-rendered documents, not hydrated apps - set
10
10
  * `export const hydrate = false` on the route (there is no client runtime to hydrate with);
11
11
  * `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
12
- * management, and streaming all work unchanged — they live in @nifrajs/web, not the view layer.
12
+ * management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
13
13
  *
14
14
  * import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
15
15
  *
16
16
  * export default function Hotels({ data }) {
17
17
  * const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
18
- * return html`<ul>${hotels.map((h) => html`<li>${h.name} — ₹${h.pricePaise / 100}</li>`)}</ul>`
18
+ * return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
19
19
  * }
20
20
  */
21
21
  import type { RenderAdapter } from "@nifrajs/web";
22
22
  export { compose, type VanillaComponent } from "./compose.js";
23
23
  export { type HtmlValue, html, RawHtml, raw, Template } from "./html.js";
24
- /** The zero-framework server render adapter — pass to @nifrajs/web's `renderPage`/`createWebApp`. */
24
+ /** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
25
25
  export declare const vanillaAdapter: RenderAdapter;
26
26
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,36 +1,36 @@
1
1
  /**
2
- * @nifrajs/web-vanilla — the zero-framework render adapter for @nifrajs/web.
2
+ * @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
3
3
  *
4
4
  * Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
5
5
  * **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
6
- * docs, comparison tables) where HTML is the product — interactivity comes from
6
+ * docs, comparison tables) where HTML is the product - interactivity comes from
7
7
  * `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
8
8
  *
9
- * Routes using this adapter are server-rendered documents, not hydrated apps — set
9
+ * Routes using this adapter are server-rendered documents, not hydrated apps - set
10
10
  * `export const hydrate = false` on the route (there is no client runtime to hydrate with);
11
11
  * `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
12
- * management, and streaming all work unchanged — they live in @nifrajs/web, not the view layer.
12
+ * management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
13
13
  *
14
14
  * import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
15
15
  *
16
16
  * export default function Hotels({ data }) {
17
17
  * const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
18
- * return html`<ul>${hotels.map((h) => html`<li>${h.name} — ₹${h.pricePaise / 100}</li>`)}</ul>`
18
+ * return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
19
19
  * }
20
20
  */
21
21
  import { compose } from "./compose.js";
22
22
  export { compose } from "./compose.js";
23
23
  export { html, RawHtml, raw, Template } from "./html.js";
24
24
  const ENCODER = new TextEncoder();
25
- /** The zero-framework server render adapter — pass to @nifrajs/web's `renderPage`/`createWebApp`. */
25
+ /** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
26
26
  export const vanillaAdapter = {
27
- // One-pass string render — renderPage's buffered fast path uses this for every non-deferred
27
+ // One-pass string render - renderPage's buffered fast path uses this for every non-deferred
28
28
  // page, which for vanilla routes is the norm.
29
29
  renderToString(chain, props) {
30
30
  return compose(chain, props).html;
31
31
  },
32
32
  // Deferred pages need the streaming seam; a synchronous renderer satisfies it with a one-chunk
33
- // stream (the shell IS the document — vanilla has no Suspense boundaries of its own; @nifrajs/web's
33
+ // stream (the shell IS the document - vanilla has no Suspense boundaries of its own; @nifrajs/web's
34
34
  // defer()/<no-js fallback> machinery handles progressive data above this seam).
35
35
  renderToStream(chain, props) {
36
36
  const body = compose(chain, props).html;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nifrajs/web-vanilla",
3
- "version": "2.1.0",
4
- "description": "Zero-framework render adapter for @nifrajs/web — auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.",
3
+ "version": "2.3.0",
4
+ "description": "Zero-framework render adapter for @nifrajs/web - auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.",
5
5
  "keywords": [
6
6
  "bun",
7
7
  "typescript",
@@ -33,7 +33,7 @@
33
33
  "typecheck": "tsc --noEmit"
34
34
  },
35
35
  "peerDependencies": {
36
- "@nifrajs/web": "^2.1.0"
36
+ "@nifrajs/web": "^2.3.0"
37
37
  },
38
38
  "license": "MIT",
39
39
  "publishConfig": {
package/src/compose.ts CHANGED
@@ -3,8 +3,8 @@ import type { Template } from "./html.ts"
3
3
 
4
4
  /**
5
5
  * A vanilla "component": a plain function from props to a {@link Template}. The page (innermost
6
- * chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` — the
7
- * already-rendered inner fragment — plus the same render props, mirroring the React/Preact
6
+ * chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` - the
7
+ * already-rendered inner fragment - plus the same render props, mirroring the React/Preact
8
8
  * adapters' `children` contract.
9
9
  */
10
10
  export type VanillaComponent = (props: RenderProps & { children?: Template }) => Template
@@ -17,7 +17,14 @@ export function compose(chain: readonly unknown[], props: RenderProps): Template
17
17
  const last = chain.length - 1
18
18
  let node = (chain[last] as VanillaComponent)(props)
19
19
  for (let i = last - 1; i >= 0; i--) {
20
- node = (chain[i] as VanillaComponent)({ ...props, children: node })
20
+ // Each layout receives its own loader data at its own index. Layouts are the chain's leading
21
+ // prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
22
+ // boundary marker, the page) reads `undefined` and is unaffected.
23
+ node = (chain[i] as VanillaComponent)({
24
+ ...props,
25
+ data: props.layoutData?.[i] ?? null,
26
+ children: node,
27
+ })
21
28
  }
22
29
  return node
23
30
  }
package/src/html.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The tagged-template HTML engine — `html` builds an escaped-by-construction {@link Template}.
2
+ * The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
3
3
  *
4
4
  * Security model (the part that matters): every interpolated value is HTML-escaped by default.
5
- * The ONLY ways to emit unescaped markup are (1) nesting another `html` template — already escaped
6
- * by construction — and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
5
+ * The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
6
+ * by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
7
7
  * The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
8
- * unquoted attributes or inline scripts are NOT safe by design — the docs say "always quote".
8
+ * unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
9
9
  */
10
10
 
11
11
  /** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
@@ -17,7 +17,7 @@ export class RawHtml {
17
17
  }
18
18
 
19
19
  /**
20
- * Mark a string as trusted, pre-escaped markup — it is emitted verbatim. The deliberate escape
20
+ * Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
21
21
  * hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
22
22
  * React's dangerouslySetInnerHTML, without the JSX.
23
23
  */
@@ -27,7 +27,7 @@ export function raw(trusted: string): RawHtml {
27
27
 
28
28
  const ESCAPE_RE = /[&<>"']/
29
29
  const escapeHtml = (s: string): string => {
30
- // Fast path: most text has nothing to escape — one regex probe, zero allocation.
30
+ // Fast path: most text has nothing to escape - one regex probe, zero allocation.
31
31
  if (!ESCAPE_RE.test(s)) return s
32
32
  let out = ""
33
33
  let last = 0
@@ -58,7 +58,7 @@ const escapeHtml = (s: string): string => {
58
58
  return out + s.slice(last)
59
59
  }
60
60
 
61
- /** A rendered HTML fragment — what `html` returns and components produce. Stringified once. */
61
+ /** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
62
62
  export class Template {
63
63
  /** The final markup. Built eagerly at tag time (interpolations are already values by then). */
64
64
  readonly html: string
@@ -95,9 +95,9 @@ function renderValue(value: HtmlValue): string {
95
95
  for (const item of value) out += renderValue(item)
96
96
  return out
97
97
  }
98
- // Objects/functions are a bug at the call site — render loudly rather than "[object Object]".
98
+ // Objects/functions are a bug at the call site - render loudly rather than "[object Object]".
99
99
  throw new TypeError(
100
- `[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} — interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`,
100
+ `[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} - interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`,
101
101
  )
102
102
  }
103
103
 
package/src/index.ts CHANGED
@@ -1,21 +1,21 @@
1
1
  /**
2
- * @nifrajs/web-vanilla — the zero-framework render adapter for @nifrajs/web.
2
+ * @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
3
3
  *
4
4
  * Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
5
5
  * **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
6
- * docs, comparison tables) where HTML is the product — interactivity comes from
6
+ * docs, comparison tables) where HTML is the product - interactivity comes from
7
7
  * `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
8
8
  *
9
- * Routes using this adapter are server-rendered documents, not hydrated apps — set
9
+ * Routes using this adapter are server-rendered documents, not hydrated apps - set
10
10
  * `export const hydrate = false` on the route (there is no client runtime to hydrate with);
11
11
  * `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
12
- * management, and streaming all work unchanged — they live in @nifrajs/web, not the view layer.
12
+ * management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
13
13
  *
14
14
  * import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
15
15
  *
16
16
  * export default function Hotels({ data }) {
17
17
  * const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
18
- * return html`<ul>${hotels.map((h) => html`<li>${h.name} — ₹${h.pricePaise / 100}</li>`)}</ul>`
18
+ * return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
19
19
  * }
20
20
  */
21
21
 
@@ -27,15 +27,15 @@ export { type HtmlValue, html, RawHtml, raw, Template } from "./html.ts"
27
27
 
28
28
  const ENCODER = new TextEncoder()
29
29
 
30
- /** The zero-framework server render adapter — pass to @nifrajs/web's `renderPage`/`createWebApp`. */
30
+ /** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
31
31
  export const vanillaAdapter: RenderAdapter = {
32
- // One-pass string render — renderPage's buffered fast path uses this for every non-deferred
32
+ // One-pass string render - renderPage's buffered fast path uses this for every non-deferred
33
33
  // page, which for vanilla routes is the norm.
34
34
  renderToString(chain: readonly unknown[], props: RenderProps): string {
35
35
  return compose(chain, props).html
36
36
  },
37
37
  // Deferred pages need the streaming seam; a synchronous renderer satisfies it with a one-chunk
38
- // stream (the shell IS the document — vanilla has no Suspense boundaries of its own; @nifrajs/web's
38
+ // stream (the shell IS the document - vanilla has no Suspense boundaries of its own; @nifrajs/web's
39
39
  // defer()/<no-js fallback> machinery handles progressive data above this seam).
40
40
  renderToStream(chain: readonly unknown[], props: RenderProps): ReadableStream<Uint8Array> {
41
41
  const body = compose(chain, props).html