generator-white-label 8.0.0 → 10.0.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 (109) hide show
  1. package/CLI.md +16 -20
  2. package/EXISTING_APPLICATIONS.md +155 -0
  3. package/PACKAGE_MANAGERS.md +6 -4
  4. package/README.md +136 -126
  5. package/app/404.ts +28 -0
  6. package/app/README.md +45 -7
  7. package/{scaffold/no-jsx/app → app}/assets/script/index.ts +1 -0
  8. package/app/assets/script/tasks/TaskApplication.ts +11 -11
  9. package/app/assets/script/tasks/TaskMediator.ts +6 -11
  10. package/app/assets/script/tasks/TaskRouter.ts +1 -1
  11. package/{scaffold/no-jsx/app → app}/assets/script/tasks/TaskView.ts +1 -1
  12. package/app/assets/view/CodeBlock.ts +15 -0
  13. package/app/assets/view/examples/tasks/TaskExample.ts +50 -0
  14. package/app/assets/view/examples/tasks/task-state.ts +1 -1
  15. package/app/assets/view/layout/SiteFooter.ts +11 -0
  16. package/app/assets/view/layout/SiteHeader.ts +17 -0
  17. package/app/assets/view/page-data.ts +2 -2
  18. package/app/assets/view/sections/GettingStartedSection.ts +26 -0
  19. package/app/assets/view/sections/HeroSection.ts +15 -0
  20. package/app/assets/view/sections/LiveExampleSection.ts +24 -0
  21. package/app/assets/view/sections/PackageDocsSection.ts +69 -0
  22. package/app/assets/view/sections/{SourceGuideSection.tsx → SourceGuideSection.ts} +11 -16
  23. package/app/index.ts +50 -0
  24. package/cli/index.ts +6 -42
  25. package/dist/app/404.d.ts +1 -1
  26. package/dist/app/404.js +18 -2
  27. package/dist/app/404.js.map +1 -1
  28. package/dist/app/assets/script/index.d.ts +1 -6
  29. package/dist/app/assets/script/index.js +1 -6
  30. package/dist/app/assets/script/index.js.map +1 -1
  31. package/dist/app/assets/script/tasks/TaskApplication.js +11 -11
  32. package/dist/app/assets/script/tasks/TaskApplication.js.map +1 -1
  33. package/dist/app/assets/script/tasks/TaskMediator.d.ts +5 -10
  34. package/dist/app/assets/script/tasks/TaskMediator.js.map +1 -1
  35. package/dist/app/assets/script/tasks/TaskRouter.js +1 -1
  36. package/dist/app/assets/script/tasks/TaskRouter.js.map +1 -1
  37. package/dist/app/assets/script/tasks/TaskView.d.ts +1 -6
  38. package/dist/app/assets/script/tasks/TaskView.js +2 -8
  39. package/dist/app/assets/script/tasks/TaskView.js.map +1 -1
  40. package/dist/app/assets/view/CodeBlock.d.ts +3 -3
  41. package/dist/app/assets/view/CodeBlock.js +4 -2
  42. package/dist/app/assets/view/CodeBlock.js.map +1 -1
  43. package/dist/app/assets/view/examples/tasks/TaskExample.d.ts +2 -8
  44. package/dist/app/assets/view/examples/tasks/TaskExample.js +41 -9
  45. package/dist/app/assets/view/examples/tasks/TaskExample.js.map +1 -1
  46. package/dist/app/assets/view/examples/tasks/task-state.d.ts +1 -1
  47. package/dist/app/assets/view/examples/tasks/task-state.js.map +1 -1
  48. package/dist/app/assets/view/layout/SiteFooter.d.ts +2 -2
  49. package/dist/app/assets/view/layout/SiteFooter.js +8 -3
  50. package/dist/app/assets/view/layout/SiteFooter.js.map +1 -1
  51. package/dist/app/assets/view/layout/SiteHeader.d.ts +2 -8
  52. package/dist/app/assets/view/layout/SiteHeader.js +14 -9
  53. package/dist/app/assets/view/layout/SiteHeader.js.map +1 -1
  54. package/dist/app/assets/view/page-data.d.ts +2 -2
  55. package/dist/app/assets/view/page-data.js.map +1 -1
  56. package/dist/app/assets/view/sections/GettingStartedSection.d.ts +1 -1
  57. package/dist/app/assets/view/sections/GettingStartedSection.js +21 -6
  58. package/dist/app/assets/view/sections/GettingStartedSection.js.map +1 -1
  59. package/dist/app/assets/view/sections/HeroSection.d.ts +2 -8
  60. package/dist/app/assets/view/sections/HeroSection.js +12 -9
  61. package/dist/app/assets/view/sections/HeroSection.js.map +1 -1
  62. package/dist/app/assets/view/sections/LiveExampleSection.d.ts +2 -6
  63. package/dist/app/assets/view/sections/LiveExampleSection.js +17 -7
  64. package/dist/app/assets/view/sections/LiveExampleSection.js.map +1 -1
  65. package/dist/app/assets/view/sections/PackageDocsSection.d.ts +1 -1
  66. package/dist/app/assets/view/sections/PackageDocsSection.js +57 -20
  67. package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -1
  68. package/dist/app/assets/view/sections/SourceGuideSection.d.ts +2 -7
  69. package/dist/app/assets/view/sections/SourceGuideSection.js +20 -8
  70. package/dist/app/assets/view/sections/SourceGuideSection.js.map +1 -1
  71. package/dist/app/index.d.ts +2 -8
  72. package/dist/app/index.js +36 -11
  73. package/dist/app/index.js.map +1 -1
  74. package/dist/cli/index.d.ts +0 -3
  75. package/dist/cli/index.js +6 -37
  76. package/dist/cli/index.js.map +1 -1
  77. package/dist/scaffold/index.d.ts +2 -13
  78. package/dist/scaffold/index.js +36 -56
  79. package/dist/scaffold/index.js.map +1 -1
  80. package/dist/scripts/build.js +97 -28
  81. package/dist/scripts/build.js.map +1 -1
  82. package/dist/server/handler.d.ts +7 -0
  83. package/dist/server/handler.js +60 -0
  84. package/dist/server/handler.js.map +1 -0
  85. package/package.json +25 -24
  86. package/pnpm-workspace.yaml +0 -1
  87. package/scaffold/AGENTS.md +23 -0
  88. package/scaffold/index.ts +32 -60
  89. package/scripts/build.ts +90 -30
  90. package/server/handler.ts +2 -10
  91. package/template-test/site.test.js +3 -1
  92. package/tsconfig.site.json +1 -4
  93. package/app/404.tsx +0 -28
  94. package/app/assets/script/index.tsx +0 -13
  95. package/app/assets/script/tasks/TaskView.tsx +0 -24
  96. package/app/assets/view/CodeBlock.tsx +0 -17
  97. package/app/assets/view/examples/tasks/TaskExample.tsx +0 -63
  98. package/app/assets/view/layout/SiteFooter.tsx +0 -11
  99. package/app/assets/view/layout/SiteHeader.tsx +0 -23
  100. package/app/assets/view/sections/GettingStartedSection.tsx +0 -27
  101. package/app/assets/view/sections/HeroSection.tsx +0 -21
  102. package/app/assets/view/sections/LiveExampleSection.tsx +0 -29
  103. package/app/assets/view/sections/PackageDocsSection.tsx +0 -69
  104. package/app/index.tsx +0 -54
  105. package/scaffold/no-jsx/README.md +0 -47
  106. package/scaffold/no-jsx/app/404.ts +0 -10
  107. package/scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts +0 -39
  108. package/scaffold/no-jsx/app/index.ts +0 -58
  109. package/tsconfig.site.no-jsx.json +0 -24
package/CLI.md CHANGED
@@ -7,7 +7,7 @@ CLI ──────> createProject() ──────> project
7
7
  Node API ─┘
8
8
  ```
9
9
 
10
- `createProject()` owns the scaffold. The CLI translates command-line input into that API.
10
+ `createProject()` owns the canonical tagged-template scaffold, including destination safety. The CLI translates command-line input into that API; it does not maintain a second generation path.
11
11
 
12
12
  ## Create a project
13
13
 
@@ -25,7 +25,7 @@ yarn dlx generator-white-label create my-project
25
25
  pnpm dlx generator-white-label create my-project
26
26
  ```
27
27
 
28
- The CLI creates into a new or existing **empty** directory. It refuses a non-empty destination so a mistyped path cannot overwrite existing project files.
28
+ Project creation accepts a missing or existing **empty** directory. A non-empty destination is rejected before scaffold files are copied or written.
29
29
 
30
30
  Then install and test the generated project with npm, Yarn, or pnpm:
31
31
 
@@ -36,23 +36,21 @@ npm install && npm test
36
36
  # or: pnpm install && pnpm test
37
37
  ```
38
38
 
39
- When run interactively, the CLI asks whether page and view templates should use JSX/TSX. JSX is optional: choose **Yes** for White Label's first-party JSX syntax such as `<section>...</section>`, or **No** for plain TypeScript functions that return HTML strings. Both choices produce the same working starter application and features.
39
+ The generated project uses ordinary `.ts` files and White Label View's first-party tagged HTML templates:
40
40
 
41
- Choose **No** when you want plain string templates or plan to use a third-party renderer. Install that renderer in the generated application and call it from the View `template` function; White Label does not require an adapter. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested third-party engines and rendering contract.
41
+ ```ts
42
+ import {html} from 'white-label-view/html';
42
43
 
43
- For scripts and non-interactive use, choose explicitly:
44
-
45
- ```sh
46
- white-label create my-project --jsx
47
- white-label create my-project --no-jsx
48
-
49
- npx generator-white-label create my-project --jsx
50
- npx generator-white-label create my-project --no-jsx
44
+ export default function Page(data: {title: string}) {
45
+ return html`<main><h1>${data.title}</h1></main>`;
46
+ }
51
47
  ```
52
48
 
53
- The same `--jsx` and `--no-jsx` flags work through `yarn dlx` and `pnpm dlx`.
49
+ Dynamic text and quoted-attribute values are escaped by the tagged-template runtime. Use `attributes()` for conditional/opening-tag attributes and reserve `unsafeHTML()` for application-owned content that is already trusted or sanitized.
54
50
 
55
- If no choice can be asked interactively and neither flag is supplied, JSX remains the default for backward compatibility.
51
+ There is no generator renderer prompt and no `--jsx` or `--no-jsx` flag. If a project prefers JSX, Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, or another renderer, add it as an application dependency and return its rendered output from View's `template` function. JSX remains a tested third-party option rather than a generator-owned runtime.
52
+
53
+ See [Template engines](https://whitelabeljs.org/docs/view/#template-engines) for tested integrations and trust boundaries.
56
54
 
57
55
  Run `white-label --help` for usage. See [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md) for package-manager compatibility details.
58
56
 
@@ -62,21 +60,19 @@ Run `white-label --help` for usage. See [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS
62
60
  import {createProject} from 'generator-white-label';
63
61
 
64
62
  await createProject({
65
- destination: '/absolute/path/to/my-project',
66
- jsx: false
63
+ destination: '/absolute/path/to/my-project'
67
64
  });
68
65
  ```
69
66
 
70
- Set `jsx: true` for JSX/TSX templates or `jsx: false` for plain TypeScript and HTML strings. Use `jsx: false` as the starting point when another template engine should own rendering. Omitting `jsx` defaults to `true`.
71
-
72
- `createProject()` is the lower-level programmatic API and does not apply the CLI's non-empty-directory guard. Applications using it directly own destination-policy decisions.
67
+ `createProject()` uses the same destination-safety rule as the CLI: missing and empty directories are allowed; non-empty directories are rejected before project content is written.
73
68
 
74
69
  ## Read the implementation
75
70
 
76
71
  The source is intentionally small enough to teach the design:
77
72
 
78
- - `scaffold/index.ts` — project creation
73
+ - `scaffold/index.ts` — project creation and destination-safety boundary
79
74
  - `cli/index.ts` — command-line adapter
75
+ - `app/` — canonical TypeScript/tagged-template starter
80
76
  - `test/` — executable contracts
81
77
 
82
78
  Creation behavior belongs in one place. Future integrations should call `createProject()` rather than copy templates or reimplement generation logic.
@@ -0,0 +1,155 @@
1
+ # Adopt White Label in an existing server-rendered application
2
+
3
+ White Label does not require a rewrite. Existing applications should install only the runtime packages a feature needs and integrate them at explicit boundaries.
4
+
5
+ The **generator is intentionally for new projects** and refuses to scaffold over a non-empty destination. If an application already exists—SFCC/SFRA, a CMS, Rails, PHP, Java, .NET, another server-rendered stack, or a long-lived frontend—do not regenerate the host application. Add White Label to one feature or DOM region at a time.
6
+
7
+ This is one of the strongest White Label use cases: modern TypeScript structure and lifecycle where it helps, while the existing platform keeps ownership of routing, business rules, initial HTML, and the rest of the page.
8
+
9
+ ## When this pattern fits
10
+
11
+ Incremental adoption is a good fit when:
12
+
13
+ - the server or CMS already renders meaningful HTML;
14
+ - public URLs, SEO, accessibility, or no-JavaScript behavior matter;
15
+ - a rewrite would be disproportionate to the feature being added;
16
+ - teams want observable state, lifecycle, application events, or URL-backed interaction without introducing a full frontend framework;
17
+ - multiple teams/features need clear ownership boundaries on the same page;
18
+ - commerce/account/content flows must keep authoritative business logic on the server.
19
+
20
+ It is less compelling for experiences that are intentionally fully client-owned and deeply stateful across most of the viewport, such as editors, design tools, complex collaborative workspaces, or other applications where a component framework already provides valuable shared conventions.
21
+
22
+ ## Install only the needed primitives
23
+
24
+ Start with the actual responsibility the feature needs:
25
+
26
+ ```sh
27
+ npm install white-label-view white-label-model
28
+ ```
29
+
30
+ Add Router or Mediator only when the feature genuinely needs URL state or cross-module events:
31
+
32
+ ```sh
33
+ npm install white-label-router white-label-mediator
34
+ ```
35
+
36
+ White Label packages do not require the generator at runtime and do not require one another unless the application chooses to compose them.
37
+
38
+ A useful rule is:
39
+
40
+ ```text
41
+ existing platform owns the page
42
+ +
43
+ White Label owns a deliberate feature boundary
44
+ ```
45
+
46
+ ## Adopt existing server-rendered markup
47
+
48
+ `white-label-view` can own lifecycle around an element that already exists instead of replacing the host application's rendering system.
49
+
50
+ ```ts
51
+ import {Model} from 'white-label-model';
52
+ import View from 'white-label-view';
53
+
54
+ const status = document.querySelector<HTMLElement>('[data-filter-status]')!;
55
+ const filters = new Model({color: ''});
56
+
57
+ const statusView = new View({
58
+ parentElement: status.parentElement!,
59
+ element: status,
60
+ model: filters,
61
+ update(element, data) {
62
+ const state = data as {color: string};
63
+ element.textContent = state.color
64
+ ? `Color filter: ${state.color}`
65
+ : 'No color filter selected';
66
+ return true;
67
+ }
68
+ }).initialize();
69
+ ```
70
+
71
+ No client template is required when an attached existing element can be updated in place. The host remains responsible for the initial HTML; View owns only the adopted root and its lifecycle.
72
+
73
+ Keep ownership narrow. Unrelated server-rendered siblings, headers, forms, navigation, and page chrome should remain outside the View unless the feature genuinely owns them.
74
+
75
+ `destroy()` removes the root the View owns. If the host application expects that element to remain after enhancement teardown, choose an ownership boundary that can be removed safely rather than treating host-owned markup as disposable.
76
+
77
+ ## Scope progressive routing to one enhanced region
78
+
79
+ By default, Router listens for opted-in `data-pushstate` links across the document. An embedded or incrementally adopted feature can limit that interception to its own DOM region:
80
+
81
+ ```ts
82
+ import Router from 'white-label-router';
83
+
84
+ const refinementArea = document.querySelector<HTMLElement>('[data-refinements]')!;
85
+ const router = new Router();
86
+
87
+ router.navigationRoot = refinementArea;
88
+ router.routes = {
89
+ '/search': (_scope, location) => {
90
+ filters.update({color: location.data.query.prefv1 || ''});
91
+ }
92
+ };
93
+ router.initialize();
94
+ ```
95
+
96
+ Only eligible links whose click event reaches that navigation root are enhanced by that Router. Other page links retain the host application's normal behavior. Multiple independently owned regions can therefore coexist without every Router claiming document-wide link handling.
97
+
98
+ `navigationRoot` controls click-listener ownership. It is deliberately separate from `router.scope`, which remains the value passed to route handlers.
99
+
100
+ ## Add state and events only where needed
101
+
102
+ A feature does not need the full White Label stack.
103
+
104
+ Use `white-label-model` when the feature needs observable state. Load data through the application's existing fetch/client/service layer, validate it at that boundary when appropriate, then apply it to Model. Model should not replace a backend API, persistence layer, or framework store solely for architectural symmetry.
105
+
106
+ Use `white-label-mediator` when separate modules need to exchange intent without importing one another. Direct function calls are clearer when there is already an ownership relationship. Avoid turning a mediator into an unstructured global event namespace.
107
+
108
+ ## Preserve the host application's server contract
109
+
110
+ White Label should not duplicate or bypass server-owned responsibilities. Keep these in the existing platform when it already owns them:
111
+
112
+ - authentication and authorization;
113
+ - pricing, inventory, tax, promotions, checkout, and other authoritative business rules;
114
+ - canonical URLs and directly requestable public routes;
115
+ - initial semantic content and crawlable navigation;
116
+ - CSRF/session protections and trusted API boundaries;
117
+ - CMS/content governance and server-side personalization rules;
118
+ - persistence, queues, retries, and cross-process workflows.
119
+
120
+ Use White Label where a client-side state, lifecycle, event, or URL boundary adds value. Normal server navigation is still the right choice when enhancement does not materially improve the interaction.
121
+
122
+ ## SEO and accessibility boundary
123
+
124
+ For public content, keep the important information in the initial response. Progressive enhancement should improve interaction rather than become a prerequisite for discovery or comprehension.
125
+
126
+ Prefer:
127
+
128
+ - real `href` values and directly requestable routes;
129
+ - semantic headings, forms, controls, landmarks, and status regions in initial HTML;
130
+ - server/static ownership of canonical URLs, titles, descriptions, robots directives, and structured data;
131
+ - deliberate focus handling after meaningful client-side navigation or content replacement;
132
+ - client behavior that preserves keyboard, context-menu, modified-click, and no-JavaScript behavior.
133
+
134
+ Do not treat source-level metadata as proof of search-engine indexing. Validate deployed behavior and indexing with actual production/search-console evidence.
135
+
136
+ ## Teardown
137
+
138
+ Long-lived shells and dynamically mounted regions should release the objects they own:
139
+
140
+ ```ts
141
+ statusView.destroy();
142
+ router.destroy();
143
+ filters.destroy();
144
+ ```
145
+
146
+ Also destroy a feature-owned Mediator when that event boundary leaves the application lifecycle.
147
+
148
+ ## Commerce and SFCC example
149
+
150
+ The White Label demo repository contains a concrete Salesforce B2C Commerce / SFRA example using the same incremental boundary:
151
+
152
+ - [SFCC / SFRA progressive enhancement recipe](https://github.com/bshack/white-label-demo-site/blob/main/docs/recipes/sfcc-sfra-progressive-enhancement.md)
153
+ - [Incremental JavaScript for server-rendered applications](https://whitelabeljs.org/guides/incremental-javascript-for-server-rendered-apps/)
154
+
155
+ The same pattern applies to other server-rendered stacks: keep the server as the source of truth, then adopt only the client-side regions that need richer behavior.
@@ -10,9 +10,9 @@ yarn dlx generator-white-label create my-project
10
10
  pnpm dlx generator-white-label create my-project
11
11
  ```
12
12
 
13
- The same `--jsx` and `--no-jsx` options are available through every invocation path. JSX is optional: use `--jsx` for the first-party White Label JSX runtime, or `--no-jsx` for plain TypeScript templates and as the starting point for a third-party template engine.
13
+ Every invocation creates the same canonical TypeScript/tagged-template starter. There are no renderer-selection flags.
14
14
 
15
- See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for tested third-party renderers and the View rendering contract.
15
+ White Label View remains template-engine agnostic. Projects that prefer JSX or a third-party renderer can install it after generation and return its rendered output from View's `template` function. See [Template engines](https://whitelabeljs.org/docs/view/#template-engines) for tested integrations and trust boundaries.
16
16
 
17
17
  ## Work with a generated project
18
18
 
@@ -26,10 +26,12 @@ pnpm install && pnpm test
26
26
 
27
27
  Generated manifests require the supported Node.js runtime but do not require a specific package manager. Nested scripts use Node's `--run` support instead of invoking npm internally.
28
28
 
29
- Generated projects use exact npm versions for the four first-party White Label packages. They also carry narrow install-script approvals for the dependencies that actually need lifecycle builds: `esbuild`, `@parcel/watcher`, and `white-label-view`. npm uses pinned `allowScripts` entries, Yarn uses pinned `dependenciesMeta` build permissions, and pnpm uses `onlyBuiltDependencies`.
29
+ Generated projects use exact npm versions for the four first-party White Label packages. Install/build lifecycle approvals are limited to dependencies that need native or generated artifacts during installation: `esbuild` and `@parcel/watcher`. npm uses pinned `allowScripts` entries, Yarn uses pinned `dependenciesMeta` build permissions, and pnpm uses `onlyBuiltDependencies`. Published White Label runtime packages include their built output and do not receive lifecycle-script approval in generated projects.
30
30
 
31
- Yarn's package-age security gate is left enabled for ordinary dependencies. The generated `.yarnrc.yml` preapproves only `white-label-mediator`, `white-label-model`, `white-label-router`, and `white-label-view` so a newly published White Label release can be installed immediately without disabling Yarn's protection for the rest of the dependency graph.
31
+ Yarn's package-age security gate is left enabled for ordinary dependencies. The generated `.yarnrc.yml` preapproves only `white-label-mediator`, `white-label-model`, `white-label-router`, and `white-label-view` so a newly published coordinated White Label release can be installed immediately without disabling Yarn's protection for the rest of the dependency graph. Package-age preapproval does not grant lifecycle-script execution.
32
32
 
33
33
  ## Repository development
34
34
 
35
35
  The committed `package-lock.json` remains the generator repository's canonical dependency lockfile and npm remains the maintenance/audit path used by primary CI. Compatibility CI packs the real generator artifact and verifies its API and CLI under npm, Yarn, and pnpm. Alternate lockfiles are not committed merely for compatibility testing.
36
+
37
+ Generator 10 depends on `white-label-view@7`. View 7's implementation is merged but not yet published, so coordinated CI temporarily builds and injects the exact merged View 7 revision for integration testing. The release lockfile remains registry-based; do not replace it with a Git dependency or treat the temporary packed artifact as a published dependency. Normal registry installation remains a release gate until View 7 is published.
package/README.md CHANGED
@@ -1,16 +1,30 @@
1
1
  # generator-white-label
2
2
 
3
- `generator-white-label` is a framework-independent TypeScript project generator for small, accessible, SEO-friendly sites with progressive enhancement, composable White Label primitives, and a provider-neutral Node.js function example.
3
+ `generator-white-label` is a framework-independent TypeScript project generator for small, accessible, SEO-friendly, HTML-first sites with progressive enhancement, composable White Label primitives, first-party tagged HTML templates, and a provider-neutral Node.js function example.
4
4
 
5
5
  [Documentation](https://whitelabeljs.org/docs/generator/) · [API reference](https://whitelabeljs.org/api/#generator) · [Demo site](https://whitelabeljs.org/)
6
6
 
7
- The project is also its own teaching tool. The landing page, README, source, and tests are meant to be read together:
7
+ White Label does not prescribe a framework. It provides focused pieces that can be composed where useful and omitted where they are not.
8
8
 
9
- ```text
10
- understand → see → build → verify
11
- ```
9
+ ## Where the generator fits
12
10
 
13
- White Label does not prescribe a framework. It provides focused pieces that can be composed where useful and omitted where they are not.
11
+ Use the generator when starting a **new** site or small web application and you want:
12
+
13
+ - meaningful static HTML before client JavaScript runs;
14
+ - progressive enhancement instead of an SPA requirement;
15
+ - explicit Model/View/Mediator/Router boundaries;
16
+ - ordinary TypeScript with readable HTML-shaped templates;
17
+ - accessibility and search-oriented defaults;
18
+ - a small codebase that remains easy for humans and coding agents to inspect;
19
+ - provider-neutral Web `Request`/`Response` serverless composition.
20
+
21
+ The generated project is especially well suited to public sites, documentation/content experiences, agency or multi-client work, small product/account surfaces, and teams that want application structure without handing architecture to a full framework.
22
+
23
+ ### Already have an application?
24
+
25
+ Do **not** use the generator to layer a scaffold over it. The generator intentionally rejects a non-empty destination.
26
+
27
+ Existing server-rendered, CMS, commerce, Rails/PHP/Java/.NET, or long-lived frontend applications should install the individual White Label runtime packages they need and adopt them incrementally. See [`EXISTING_APPLICATIONS.md`](EXISTING_APPLICATIONS.md) and the public [incremental server-rendered application guide](https://whitelabeljs.org/guides/incremental-javascript-for-server-rendered-apps/).
14
28
 
15
29
  ## The idea
16
30
 
@@ -30,21 +44,61 @@ URL / user action
30
44
  DOM
31
45
  ```
32
46
 
33
- These are responsibilities, not mandatory layers. Static content can stay plain JSX or plain TypeScript HTML strings. A feature that does not need routing does not need a router.
47
+ These are responsibilities, not mandatory layers. Static content can stay a plain TypeScript function that returns tagged HTML. A feature that does not need routing does not need a router; a feature that only needs local state can use Model by itself.
34
48
 
35
49
  - [`white-label-router`](https://github.com/bshack/white-label-router) turns location into application intent.
36
50
  - [`white-label-mediator`](https://github.com/bshack/white-label-mediator) coordinates application events.
37
51
  - [`white-label-model`](https://github.com/bshack/white-label-model) owns observable state.
38
52
  - [`white-label-view`](https://github.com/bshack/white-label-view) owns rendering and DOM lifecycle.
39
- - [`white-label-view/jsx-runtime`](https://github.com/bshack/white-label-view) provides optional escaped JSX without React or another template engine.
53
+ - `white-label-view/html` provides the first-party tagged-template helpers used by the generator.
40
54
 
41
55
  The goal is simple boundaries with explicit composition.
42
56
 
57
+ ## Tagged HTML templates
58
+
59
+ Generated pages and View templates are ordinary `.ts` functions:
60
+
61
+ ```ts
62
+ import {html} from 'white-label-view/html';
63
+
64
+ export default function Page(data: {title: string}) {
65
+ return html`
66
+ <main>
67
+ <h1>${data.title}</h1>
68
+ </main>
69
+ `;
70
+ }
71
+ ```
72
+
73
+ Normal text and quoted-attribute interpolations are escaped. Tagged markup composes without double escaping, so small view functions can remain reusable without introducing a component runtime.
74
+
75
+ Conditional or grouped opening-tag attributes use `attributes()`:
76
+
77
+ ```ts
78
+ import {attributes, html} from 'white-label-view/html';
79
+
80
+ const input = html`
81
+ <input ${attributes({
82
+ type: 'checkbox',
83
+ checked: complete,
84
+ 'data-task-id': taskId
85
+ })}>
86
+ `;
87
+ ```
88
+
89
+ `unsafeHTML()` is deliberately explicit. Use it only for application-owned content that is already trusted or sanitized, such as controlled JSON-LD after applying the application's serialization policy. It is not a sanitizer and it is not a replacement for contextual URL/CSS validation.
90
+
91
+ The tagged-template runtime rejects interpolation in ambiguous or dangerous contexts such as script/style bodies, comments, tag names, and unquoted attribute positions rather than pretending generic escaping makes those contexts safe.
92
+
93
+ White Label View remains template-engine agnostic. JSX, Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, and other renderers can remain application dependencies and return their rendered output from View's `template` function. JSX is a tested third-party option rather than a White Label-owned runtime.
94
+
95
+ See [Template engines](https://whitelabeljs.org/docs/view/#template-engines) for tested integrations and trust boundaries.
96
+
43
97
  ## Learn from the generated application
44
98
 
45
- The generated landing page demonstrates the same architecture it documents.
99
+ The project is also a teaching tool. The generated landing page demonstrates the same architecture its source and tests document.
46
100
 
47
- Its static sections use either JSX functions or equivalent plain TypeScript HTML-string functions, depending on the generator choice. The task example then shows all four packages working together:
101
+ The task example shows all four runtime packages working together:
48
102
 
49
103
  ```text
50
104
  TaskRouter
@@ -54,33 +108,19 @@ TaskMediator
54
108
  TaskModel
55
109
  ↓ observable state
56
110
  TaskView
57
- ↓ render
111
+ ↓ tagged HTML render
58
112
  DOM
59
113
  ```
60
114
 
61
- Both generated variants provide the same application behavior and progressive enhancement. The only difference is template syntax.
62
-
63
- ## Read the source
64
-
65
- For the default JSX scaffold, a useful reading order is:
115
+ A useful reading order is:
66
116
 
67
- 1. [`app/index.tsx`](app/index.tsx) — page composition.
68
- 2. [`app/assets/view/sections/LiveExampleSection.tsx`](app/assets/view/sections/LiveExampleSection.tsx) — static composition around an interactive feature.
69
- 3. [`app/assets/view/examples/tasks/TaskExample.tsx`](app/assets/view/examples/tasks/TaskExample.tsx) — shared JSX rendering.
117
+ 1. [`app/index.ts`](app/index.ts) — page composition.
118
+ 2. [`app/assets/view/sections/LiveExampleSection.ts`](app/assets/view/sections/LiveExampleSection.ts) — static composition around an interactive feature.
119
+ 3. [`app/assets/view/examples/tasks/TaskExample.ts`](app/assets/view/examples/tasks/TaskExample.ts) — shared tagged-template rendering.
70
120
  4. [`app/assets/script/tasks/TaskApplication.ts`](app/assets/script/tasks/TaskApplication.ts) — explicit dependency wiring.
71
121
  5. `TaskRouter`, `TaskMediator`, `TaskModel`, and `TaskView` — one responsibility at a time.
72
- 6. [`server/handler.ts`](server/handler.ts) — provider-neutral `Request`/`Response` serverless composition.
73
- 7. [`test/app.test.js`](test/app.test.js) and [`template-test/site.test.js`](template-test/site.test.js) — browser and generated-project contracts.
74
-
75
- The `--no-jsx` scaffold mirrors the same structure with `.ts` files and HTML-string render functions.
76
-
77
- Comments focus on why boundaries exist instead of narrating obvious TypeScript.
78
-
79
- When documenting public APIs, examples use comments where they clarify intent, lifecycle, side effects, or non-obvious behavior. Public method documentation should also state the return value—including meaningful boolean/status values, chaining returns, `undefined`, promises, and relevant thrown/rejected errors.
80
-
81
- A useful rule when extending the project is:
82
-
83
- > Introduce an abstraction when it gives a concern a clear home, not merely to create another layer.
122
+ 6. [`server/handler.ts`](server/handler.ts) — provider-neutral `Request`/`Response` composition.
123
+ 7. Tests — executable contracts for browser and generated-project behavior.
84
124
 
85
125
  ## Create a project
86
126
 
@@ -88,23 +128,13 @@ Requirement:
88
128
 
89
129
  - Node.js `^22.18.0` or `>=24.11.0`
90
130
 
91
- Install the CLI globally with your preferred package manager, or run the package directly.
92
-
93
- With npm:
131
+ Run the package directly:
94
132
 
95
133
  ```sh
96
134
  npx generator-white-label create my-project
97
- ```
98
-
99
- With Yarn:
100
-
101
- ```sh
135
+ # or
102
136
  yarn dlx generator-white-label create my-project
103
- ```
104
-
105
- With pnpm:
106
-
107
- ```sh
137
+ # or
108
138
  pnpm dlx generator-white-label create my-project
109
139
  ```
110
140
 
@@ -115,7 +145,7 @@ npm install --global generator-white-label
115
145
  white-label create my-project
116
146
  ```
117
147
 
118
- After generation, use npm, Yarn, or pnpm consistently within the project:
148
+ After generation, use one package manager consistently:
119
149
 
120
150
  ```sh
121
151
  cd my-project
@@ -124,25 +154,48 @@ npm install && npm test
124
154
  # or: pnpm install && pnpm test
125
155
  ```
126
156
 
127
- Interactive creation asks whether templates should use JSX/TSX. JSX is optional: choose **Yes** for White Label's first-party JSX syntax such as `<section>...</section>`, or **No** for plain TypeScript functions that return HTML strings. Both choices generate the same functional starter application.
157
+ There is one canonical scaffold. The CLI has no renderer prompt and no `--jsx`/`--no-jsx` mode switch.
128
158
 
129
- Choose the no-JSX path when another template engine should own rendering. Install that engine in the generated application and call it from the View `template` function; no White Label adapter is required. White Label View currently tests Handlebars, Eta, EJS, Mustache, Nunjucks, and Pug in both browser and server rendering. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for tested versions, examples, the rendering contract, and security guidance.
159
+ The generator refuses to layer a scaffold over an existing non-empty destination. This protection is enforced by the shared `createProject()` engine for both CLI and programmatic use. An existing empty directory is allowed; a missing directory is created.
130
160
 
131
- For explicit or non-interactive use:
161
+ See [`CLI.md`](CLI.md), [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md), and [`EXISTING_APPLICATIONS.md`](EXISTING_APPLICATIONS.md).
132
162
 
133
- ```sh
134
- white-label create my-project --jsx
135
- white-label create my-project --no-jsx
163
+ ## Progressive enhancement and SEO
164
+
165
+ The generated project renders meaningful HTML during the build. Interactive task filters are real links first and are enhanced with History API navigation after initialization.
136
166
 
137
- npx generator-white-label create my-project --jsx
138
- npx generator-white-label create my-project --no-jsx
167
+ ```text
168
+ HTML owns semantics.
169
+ JavaScript enhances behavior.
139
170
  ```
140
171
 
141
- The same `--jsx` and `--no-jsx` options work through `yarn dlx` and `pnpm dlx`.
172
+ Public content should remain readable, navigable, and crawlable without executing the enhancement bundle. Generated pages include canonical URLs, page titles/descriptions, robots metadata, semantic navigation, JSON-LD, and sitemap-oriented output; deployment still needs to serve the generated files correctly.
173
+
174
+ Do not treat metadata in source code as proof of search-engine indexing. Verify deployed behavior and indexing with actual production/search-console evidence.
175
+
176
+ ## A simple View click event
177
+
178
+ Use View lifecycle hooks to add and remove browser listeners with the same callback reference:
179
+
180
+ ```ts
181
+ import View from 'white-label-view';
182
+
183
+ class ButtonView extends View {
184
+ handleClick = () => console.log('Clicked');
142
185
 
143
- If no interactive answer is available and neither flag is supplied, JSX is the default for backward compatibility.
186
+ addListeners() {
187
+ this.element.addEventListener('click', this.handleClick);
188
+ return this;
189
+ }
144
190
 
145
- See [`CLI.md`](CLI.md) for the CLI contract and [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md) for package-manager compatibility details.
191
+ removeListeners() {
192
+ this.element.removeEventListener('click', this.handleClick);
193
+ return this;
194
+ }
195
+ }
196
+ ```
197
+
198
+ `addListeners()` runs when the View mounts. `removeListeners()` runs before replacement or destruction, so listener ownership remains explicit.
146
199
 
147
200
  ## Programmatic API
148
201
 
@@ -151,92 +204,45 @@ Project creation has one implementation:
151
204
  ```js
152
205
  import {createProject} from 'generator-white-label';
153
206
 
154
- // Resolves after the scaffold files and package manifest have been written.
155
207
  await createProject({
156
- destination: new URL('./my-project', import.meta.url).pathname,
157
- jsx: false
208
+ destination: new URL('./my-project', import.meta.url).pathname
158
209
  });
159
210
  ```
160
211
 
161
- Set `jsx: true` for JSX/TSX templates or `jsx: false` for plain TypeScript and HTML strings. Use `jsx: false` as the starting point for a third-party template engine. Omitting `jsx` defaults to `true`.
212
+ `createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject. If the destination exists and contains files, it rejects before copying or writing project content.
213
+
214
+ `createProject()` is the canonical creation API. The CLI and future integrations are adapters around it rather than separate generation systems. Keeping overwrite policy here prevents adapters from bypassing the same safety boundary.
215
+
216
+ ## Serverless and function runtimes
162
217
 
163
- `createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject the promise instead of returning a status value.
218
+ Every generated project includes `server/handler.ts`, a provider-neutral example built around Web `Request` and `Response`. It composes Router, Mediator, Model, and `white-label-view/server` without Express or a provider SDK.
164
219
 
165
- `createProject()` is the canonical creation API. The CLI and future integrations are adapters around it rather than separate generation systems.
220
+ Mutable White Label instances are created inside `handleRequest()`. Serverless hosts can reuse a warm process for many requests, so mutable module-level state can leak request data or listeners. Immutable configuration can still live at module scope when its lifetime is intentionally process-wide.
166
221
 
167
- This is the same design principle used throughout White Label: one responsibility, one implementation, explicit adapters at environment boundaries.
222
+ Cloud-specific adapters should stay at the boundary: translate a provider request into a Web `Request` when needed, call `handleRequest()`, then translate the returned `Response` back only if required.
223
+
224
+ Generated-project tests exercise sequential warm invocations, concurrent requests, request-data escaping, execution without browser globals, and a browser/Web-target bundle smoke test. They also enforce serverless composition size/import regression budgets. These are regression guards, not universal latency guarantees.
225
+
226
+ The Web-target smoke test does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. Published runtime packages document Node as their supported server runtime; verify a specific target before deployment.
168
227
 
169
228
  ## Source layout
170
229
 
171
230
  | Path | Purpose |
172
231
  | --- | --- |
173
- | `scaffold/index.ts` | Canonical `createProject()` implementation |
174
- | `scaffold/no-jsx/` | Plain-TypeScript template equivalents |
232
+ | `scaffold/index.ts` | Canonical `createProject()` implementation and destination-safety boundary |
175
233
  | `cli/index.ts` | First-party command-line adapter |
176
- | `app/*.tsx` | Default JSX top-level static pages |
177
- | `app/assets/view/` | Default JSX views and page sections |
234
+ | `app/*.ts` | Static pages using tagged HTML templates |
235
+ | `app/assets/view/` | Reusable tagged-template views and sections |
178
236
  | `app/assets/script/tasks/` | Model/View/Mediator/Router example |
179
- | `server/handler.ts` | Provider-neutral serverless `Request` → `Response` example |
180
- | `scripts/build.ts` | Static rendering and asset build for `.ts` and `.tsx` pages |
237
+ | `server/handler.ts` | Provider-neutral serverless Request → Response example |
238
+ | `scripts/build.ts` | Static rendering and asset build |
181
239
  | `test/` | Executable contracts and integration tests |
182
- | `template-test/` | Generated-project validation, including serverless portability budgets |
183
-
184
- No React, Bootstrap, Sass, Eta, Handlebars, Mustache, Nunjucks, Pug, web fonts, cloud SDK, or browser-side template framework is required. Third-party template engines and provider adapters remain optional application dependencies.
240
+ | `template-test/` | Generated-project validation |
185
241
 
186
- ## Template choice: JSX or no JSX
187
-
188
- JSX is optional. When enabled, TypeScript uses the White Label JSX runtime:
189
-
190
- ```json
191
- {
192
- "compilerOptions": {
193
- "jsx": "react-jsx",
194
- "jsxImportSource": "white-label-view"
195
- }
196
- }
197
- ```
198
-
199
- A page remains an ordinary function:
200
-
201
- ```tsx
202
- export default function Page(data: Record<string, unknown>) {
203
- return <main><h1>{String(data.title)}</h1></main>;
204
- }
205
- ```
206
-
207
- With `--no-jsx`, the equivalent page is ordinary TypeScript returning an HTML string instead. The application architecture and generated features remain the same. That no-JSX scaffold is also the intended starting point when Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, or another renderer should remain the project's template convention.
208
-
209
- For larger pages, compose focused views instead of growing one renderer indefinitely.
210
-
211
- JSX expressions are escaped by default. In plain-TypeScript templates or third-party engines, applications own the engine's escaping and raw-output configuration. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested matrix and trust boundaries.
212
-
213
- ## Progressive enhancement
214
-
215
- The generated project renders meaningful HTML during the build. Interactive task filters are real links first and are enhanced with History API navigation after initialization.
216
-
217
- The principle is intentional:
218
-
219
- ```text
220
- HTML owns semantics.
221
- JavaScript enhances behavior.
222
- ```
223
-
224
- ## Serverless and function runtimes
225
-
226
- Every generated project includes `server/handler.ts`, a provider-neutral example built around the Web `Request` and `Response` APIs. It composes Router, Mediator, Model, and `white-label-view/server` without Express or a provider SDK.
227
-
228
- Mutable White Label instances are created inside `handleRequest()`. This is intentional: serverless hosts can reuse a warm process for many requests, so mutable module-level Model/View/Router/Mediator instances can leak request data or listeners. Immutable configuration can still be shared at module scope when its lifetime is intentionally process-wide.
229
-
230
- Cloud-specific adapters should stay at the boundary. Translate an AWS/Vercel/Netlify/Azure/etc. request into a Web `Request` when necessary, call `handleRequest()`, and translate the returned `Response` back only if the provider requires it.
231
-
232
- Generated-project tests exercise sequential warm invocations, concurrent requests, request-data escaping, execution without browser globals, and a browser/Web-target bundle smoke test. They also enforce a 100 kB minified serverless-composition bundle budget and a 750 ms fresh-process handler-import budget. These are regression guards, not universal latency guarantees.
233
-
234
- The Web-target bundle smoke test catches unresolved Node built-ins, but it does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. The published runtime packages still document Node as their supported server runtime; verify a specific edge provider before deployment.
242
+ No React, Bootstrap, Sass, Eta, Handlebars, Mustache, Nunjucks, Pug, web font, cloud SDK, or browser-side template framework is required. Optional renderers/providers remain application dependencies.
235
243
 
236
244
  ## Build and verify
237
245
 
238
- The repository keeps npm as its canonical maintenance/audit path and committed lockfile. Generated projects support npm, Yarn, and pnpm.
239
-
240
246
  Development build:
241
247
 
242
248
  ```sh
@@ -263,13 +269,17 @@ npm run audit
263
269
  npm pack --dry-run
264
270
  ```
265
271
 
266
- Tests are part of the documentation. They demonstrate intended contracts while protecting behavior. Executable project source is held to 100% statement, branch, function, and line coverage.
272
+ Tests are part of the documentation. Executable project source is held to 100% statement, branch, function, and line coverage. CI checks supported Node versions, packed CLI/programmatic installation, production builds, and generated-project compatibility across npm, Yarn, and pnpm.
273
+
274
+ ## Coordinated runtime release
275
+
276
+ Generator 10 targets the published White Label runtime line: `white-label-mediator@5.0.0`, `white-label-model@7.0.1`, `white-label-router@6.1.0`, and `white-label-view@7.0.0`. Release and CI verification use those registry packages directly.
267
277
 
268
278
  ## White Label ecosystem
269
279
 
270
280
  - [`white-label-model`](https://github.com/bshack/white-label-model) — observable state.
271
- - [`white-label-view`](https://github.com/bshack/white-label-view) — rendering, DOM lifecycle, optional JSX, and a template-engine-agnostic rendering contract.
281
+ - [`white-label-view`](https://github.com/bshack/white-label-view) — rendering, existing-DOM lifecycle, tagged HTML templates, server rendering, and a template-engine-agnostic contract.
272
282
  - [`white-label-mediator`](https://github.com/bshack/white-label-mediator) — application events.
273
- - [`white-label-router`](https://github.com/bshack/white-label-router) — routing and URL state.
283
+ - [`white-label-router`](https://github.com/bshack/white-label-router) — progressive routing and URL state.
274
284
 
275
285
  The generated project imports the real packages rather than reproducing their behavior locally. That makes it both an example and an ecosystem integration test.