create-koppajs 1.1.0 → 1.2.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 (27) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +119 -122
  3. package/bin/create-koppajs.js +158 -13
  4. package/package.json +2 -1
  5. package/template-overlays/router/ARCHITECTURE.md +86 -0
  6. package/template-overlays/router/CHANGELOG.md +44 -0
  7. package/template-overlays/router/DEVELOPMENT_RULES.md +57 -0
  8. package/template-overlays/router/README.md +243 -0
  9. package/template-overlays/router/ROADMAP.md +34 -0
  10. package/template-overlays/router/TESTING_STRATEGY.md +67 -0
  11. package/template-overlays/router/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  12. package/template-overlays/router/docs/architecture/module-boundaries.md +39 -0
  13. package/template-overlays/router/docs/meta/maintenance.md +38 -0
  14. package/template-overlays/router/docs/specs/README.md +19 -0
  15. package/template-overlays/router/docs/specs/app-bootstrap.md +42 -0
  16. package/template-overlays/router/docs/specs/router-navigation.md +41 -0
  17. package/template-overlays/router/index.html +14 -0
  18. package/template-overlays/router/package.json +74 -0
  19. package/template-overlays/router/pnpm-lock.yaml +3793 -0
  20. package/template-overlays/router/src/app-view.kpa +128 -0
  21. package/template-overlays/router/src/home-page.kpa +100 -0
  22. package/template-overlays/router/src/main.ts +89 -0
  23. package/template-overlays/router/src/not-found-page.kpa +69 -0
  24. package/template-overlays/router/src/router-page.kpa +102 -0
  25. package/template-overlays/router/src/style.css +51 -0
  26. package/template-overlays/router/tests/e2e/app.spec.ts +38 -0
  27. package/template-overlays/router/tests/integration/main-bootstrap.test.ts +150 -0
package/CHANGELOG.md CHANGED
@@ -16,6 +16,28 @@ _No unreleased changes yet._
16
16
 
17
17
  ---
18
18
 
19
+ ## [1.2.0] — Starter Variants And Router Overlay
20
+
21
+ **2026-03-26**
22
+
23
+ ### Added
24
+
25
+ - added an opt-in `router` starter that layers `@koppajs/koppajs-router`, a
26
+ two-page navigation flow, and an explicit fallback route on top of the
27
+ minimal baseline
28
+ - added overlay-based starter support through `template-overlays/` so future
29
+ starter variants can replace only the files that actually differ
30
+
31
+ ### Changed
32
+
33
+ - extended the CLI contract with `--template` and `--router`, plus interactive
34
+ starter selection in TTY runs when no template flag is provided
35
+ - expanded smoke, unit, and generated-template build coverage to validate both
36
+ the default minimal starter and the opt-in router starter
37
+ - updated architecture docs, specs, and ADRs to reflect multi-starter support
38
+
39
+ ---
40
+
19
41
  ## [1.1.0] — Starter & Release Baseline Upgrade
20
42
 
21
43
  **2026-03-17**
package/README.md CHANGED
@@ -1,71 +1,89 @@
1
- <a id="readme-top"></a>
1
+ # create-koppajs
2
+
3
+ `create-koppajs` is the official KoppaJS CLI scaffolder. It creates a new
4
+ project by copying the versioned base starter in `template/`, optionally
5
+ applying a supported starter overlay from `template-overlays/`, and patching a
6
+ small, explicit set of identity files.
7
+
8
+ ## Purpose
9
+
10
+ This repository exists to do one job well:
11
+
12
+ - create a fresh project directory
13
+ - copy the current supported KoppaJS starter baseline
14
+ - optionally add a supported starter variant such as `router`
15
+ - preserve a stable, inspectable bootstrap path for new KoppaJS applications
16
+
17
+ It is not a runtime package and it does not own application behavior after
18
+ generation.
19
+
20
+ ## Repository Classification
21
+
22
+ - Repo type: CLI scaffolding package with a bundled starter family
23
+ - Runtime responsibility: one-shot filesystem scaffolding through
24
+ `bin/create-koppajs.js`
25
+ - Build-time responsibility: publish the starter assets, protect the contract,
26
+ and validate tagged releases
27
+ - UI surface: none at the repository root; the generated starter owns the UI
28
+ - Maturity level: stable, contract-governed, maintenance-first
29
+
30
+ ## Ownership Boundaries
31
+
32
+ - `bin/create-koppajs.js` owns argument parsing, prompting, validation, starter
33
+ selection, template copy, placeholder patching, and next-step output.
34
+ - `template/` owns the default `minimal` starter baseline.
35
+ - `template-overlays/` owns the files that differ for opt-in starter variants.
36
+ - `scripts/` and `.github/workflows/` own repository-quality and release
37
+ verification.
38
+ - Root governance files own the repository doctrine and must stay aligned with
39
+ code and workflows.
40
+
41
+ The root package must not take on runtime concerns that belong in generated
42
+ applications, and generated applications must not depend on unpublished root
43
+ files after scaffold completion.
44
+
45
+ ## Public Contract
46
+
47
+ The stable public contract of this repository is:
48
+
49
+ - the `create-koppajs` command and its `--help` / `--version` flags
50
+ - the optional project-name argument and prompt fallback when omitted
51
+ - the optional `--template <name>` and `--router` starter-selection flags
52
+ - the interactive starter-template prompt when no template flag is provided in
53
+ an interactive terminal
54
+ - rejection of invalid project names, invalid template names, and non-empty
55
+ target directories
56
+ - recursive copying of the bundled `template/` directory plus any selected
57
+ overlay
58
+ - restoration of publish-safe dotfiles and dotdirectories during copy
59
+ - patching of generated `package.json`, `README.md`, `CHANGELOG.md`, and
60
+ `RELEASE.md`
61
+ - the generated starter baselines defined by `template/` and
62
+ `template-overlays/`
63
+ - the npm package payload: `bin/`, `template/`, `template-overlays/`,
64
+ `README.md`, `CHANGELOG.md`, and `LICENSE`
65
+
66
+ The governing specs for that contract are:
67
+
68
+ - [docs/specs/cli-scaffolding.md](./docs/specs/cli-scaffolding.md)
69
+ - [docs/specs/template-starter-contract.md](./docs/specs/template-starter-contract.md)
2
70
 
3
- <div align="center">
4
- <img src="https://public-assets-1b57ca06-687a-4142-a525-0635f7649a5c.s3.eu-central-1.amazonaws.com/koppajs/koppajs-logo-text-900x226.png" width="500" alt="KoppaJS Logo">
5
- </div>
6
-
7
- <br>
8
-
9
- <div align="center">
10
- <a href="https://www.npmjs.com/package/create-koppajs"><img src="https://img.shields.io/npm/v/create-koppajs?style=flat-square" alt="npm version"></a>
11
- <a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License"></a>
12
- </div>
13
-
14
- <br>
15
-
16
- <div align="center">
17
- <h1 align="center">create-koppajs</h1>
18
- <h3 align="center">Scaffold a new KoppaJS project in seconds</h3>
19
- <p align="center">
20
- <i>The fastest way to start building with KoppaJS.</i>
21
- </p>
22
- </div>
23
-
24
- <br>
25
-
26
- <div align="center">
27
- <p align="center">
28
- <a href="https://github.com/koppajs/koppajs-documentation">Documentation</a>
29
- &middot;
30
- <a href="https://github.com/koppajs/koppajs-core">KoppaJS Core</a>
31
- &middot;
32
- <a href="https://github.com/koppajs/koppajs-vite-plugin">Vite Plugin</a>
33
- &middot;
34
- <a href="https://github.com/koppajs/create-koppajs/issues">Issues</a>
35
- </p>
36
- </div>
37
-
38
- <br>
39
-
40
- <details>
41
- <summary>Table of Contents</summary>
42
- <ol>
43
- <li><a href="#what-is-this">What is this?</a></li>
44
- <li><a href="#usage">Usage</a></li>
45
- <li><a href="#what-gets-generated">What gets generated</a></li>
46
- <li><a href="#requirements">Requirements</a></li>
47
- <li><a href="#release--governance">Release & Governance</a></li>
48
- <li><a href="#community--contribution">Community & Contribution</a></li>
49
- <li><a href="#license">License</a></li>
50
- </ol>
51
- </details>
52
-
53
- ---
54
-
55
- ## What is this?
56
-
57
- `create-koppajs` is the **official project scaffolder** for KoppaJS.
71
+ ## Usage
58
72
 
59
- It creates a ready-to-run starter project with a single command — no configuration, no dependencies to install first, no boilerplate to write by hand.
73
+ Default starter:
60
74
 
61
- ---
75
+ ```bash
76
+ pnpm create koppajs my-app
77
+ ```
62
78
 
63
- ## Usage
79
+ Router starter:
64
80
 
65
81
  ```bash
66
- pnpm create koppajs my-app
82
+ pnpm create koppajs my-app --template router
67
83
  ```
68
84
 
85
+ Alternative entrypoints:
86
+
69
87
  ```bash
70
88
  npm create koppajs my-app
71
89
  ```
@@ -74,7 +92,11 @@ npm create koppajs my-app
74
92
  npx create-koppajs my-app
75
93
  ```
76
94
 
77
- Then:
95
+ If the target directory name is omitted, the CLI prompts for one. If no
96
+ template flag is provided in an interactive terminal, the CLI also prompts for
97
+ starter selection. Non-interactive runs default to `minimal`.
98
+
99
+ After generation:
78
100
 
79
101
  ```bash
80
102
  cd my-app
@@ -82,88 +104,63 @@ pnpm install
82
104
  pnpm dev
83
105
  ```
84
106
 
85
- If you omit the project name, the CLI will prompt you for one.
86
-
87
- ---
88
-
89
- ## What gets generated
90
-
91
- ```
92
- my-app/
93
- ├── .github/
94
- ├── .husky/
95
- ├── docs/
96
- ├── tests/
97
- ├── CHANGELOG.md
98
- ├── RELEASE.md
99
- ├── AI_CONSTITUTION.md
100
- ├── ARCHITECTURE.md
101
- ├── package.json
102
- ├── .gitignore
103
- ├── README.md
104
- ├── vite.config.mjs
105
- ├── vitest.config.mjs
106
- ├── playwright.config.ts
107
- ├── tsconfig.json
108
- ├── pnpm-lock.yaml
109
- ├── LICENSE
110
- ├── public/
111
- │ └── favicon.svg
112
- └── src/
113
- ├── main.ts
114
- ├── style.css
115
- ├── app-view.kpa
116
- └── counter-component.kpa
117
- ```
118
-
119
- - **Vite** as dev server and bundler
120
- - **TypeScript**, **ESLint**, **Prettier**, **Vitest**, and **Playwright**
121
- - **GitHub workflows**, **Husky**, **lint-staged**, and **Conventional Commits**
122
- - **Meta-layer docs**, ADRs, specs, and a release baseline
123
- - **Two sample components** (app view + counter) aligned with the current
124
- `koppajs-example` starter
107
+ ## Requirements
125
108
 
126
- ---
109
+ - for `create-koppajs`: Node.js `>=20`
110
+ - for generated starter projects: pnpm `>=10` and a starter-supported Node.js
111
+ line, currently `20.19+`, `22.13+`, or `24+`
127
112
 
128
- ## Requirements
113
+ ## Generated Starters
129
114
 
130
- - `create-koppajs`: Node.js >= 20
131
- - Generated starter workflow: pnpm >= 10 plus a starter-supported Node.js line
132
- (currently 20.19+, 22.13+, or 24+)
115
+ The generated project includes one of two supported starters:
133
116
 
134
- ---
117
+ - `minimal` by default: a small KoppaJS application built on Vite and
118
+ TypeScript
119
+ - `router` as opt-in: the same baseline plus `@koppajs/koppajs-router`, a
120
+ simple two-page navigation flow, and an explicit fallback route
135
121
 
136
- ## Release & Governance
122
+ Every starter also includes:
137
123
 
138
- The scaffolded starter now ships with the same quality and governance baseline
139
- as the current official `koppajs-example`, adapted for generated projects:
124
+ - quality tooling through ESLint, Prettier, Vitest, and Playwright
125
+ - local workflow guards through Husky, lint-staged, and commitlint
126
+ - starter governance files, ADR/spec structure, and release-process documents
127
+ - GitHub workflows for CI and tagged releases
140
128
 
141
- - explicit meta docs and ADR/spec structure
142
- - tag-driven `CHANGELOG.md` plus `RELEASE.md`
143
- - Conventional Commit enforcement via `commitlint`
144
- - a generated-project CI/release baseline under `.github/workflows`
129
+ The root repository treats those starters as versioned product surface, not
130
+ test data.
145
131
 
146
- ---
132
+ ## Ecosystem Fit
147
133
 
148
- ## Community & Contribution
134
+ `create-koppajs` is the canonical entry point for starting a new KoppaJS
135
+ application. It complements:
149
136
 
150
- Issues and pull requests are welcome:
137
+ - `@koppajs/koppajs-core` for runtime behavior
138
+ - `@koppajs/koppajs-router` for optional route orchestration in scaffolded apps
139
+ - `@koppajs/koppajs-vite-plugin` for build integration
140
+ - the maintained KoppaJS starter conventions reflected in `template/` and
141
+ `template-overlays/`
151
142
 
152
- https://github.com/koppajs/create-koppajs/issues
143
+ The repository stays intentionally narrow so the CLI, starter contract, and
144
+ governance baseline can evolve together without hidden behavior.
153
145
 
154
- ## Governance & Architecture
146
+ ## Governance
155
147
 
156
- Repository design and maintenance rules live in the meta layer:
148
+ The root meta layer defines how this repository changes:
157
149
 
158
150
  - [AI_CONSTITUTION.md](./AI_CONSTITUTION.md)
159
151
  - [ARCHITECTURE.md](./ARCHITECTURE.md)
152
+ - [DECISION_HIERARCHY.md](./DECISION_HIERARCHY.md)
160
153
  - [DEVELOPMENT_RULES.md](./DEVELOPMENT_RULES.md)
161
154
  - [TESTING_STRATEGY.md](./TESTING_STRATEGY.md)
162
155
  - [RELEASE.md](./RELEASE.md)
156
+ - [ROADMAP.md](./ROADMAP.md)
163
157
  - [docs/meta/README.md](./docs/meta/README.md)
158
+ - [docs/architecture/README.md](./docs/architecture/README.md)
159
+ - [docs/quality/README.md](./docs/quality/README.md)
164
160
 
165
- ---
161
+ Tagged releases are documented in [CHANGELOG.md](./CHANGELOG.md). Contributor
162
+ workflow rules live in [CONTRIBUTING.md](./CONTRIBUTING.md).
166
163
 
167
164
  ## License
168
165
 
169
- Apache-2.0 — © 2026 KoppaJS, Bastian Bensch
166
+ Apache License 2.0 — © 2026 KoppaJS, Bastian Bensch
@@ -8,17 +8,90 @@ import { createInterface } from "node:readline";
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
  export const TEMPLATE_DIR = join(__dirname, "..", "template");
11
+ export const TEMPLATE_OVERLAY_DIRS = Object.freeze({
12
+ minimal: null,
13
+ router: join(__dirname, "..", "template-overlays", "router"),
14
+ });
15
+ export const DEFAULT_TEMPLATE = "minimal";
11
16
  const CLI_PKG = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
12
17
 
13
18
  // ── Args ────────────────────────────────────────────────────────────
14
19
 
15
20
  export function parseArgs(argv) {
16
21
  const raw = argv.slice(2);
17
- return {
22
+ const parsed = {
18
23
  help: raw.includes("--help") || raw.includes("-h"),
19
24
  version: raw.includes("--version") || raw.includes("-v"),
20
- projectName: raw.find((a) => !a.startsWith("-")) || null,
25
+ projectName: null,
26
+ templateName: null,
27
+ optionError: null,
21
28
  };
29
+
30
+ const setTemplateName = (templateName) => {
31
+ if (!templateName) {
32
+ parsed.optionError = "Option --template requires a value.";
33
+ return false;
34
+ }
35
+
36
+ if (parsed.templateName && parsed.templateName !== templateName) {
37
+ parsed.optionError = "Choose either --router or one --template value.";
38
+ return false;
39
+ }
40
+
41
+ parsed.templateName = templateName;
42
+ return true;
43
+ };
44
+
45
+ for (let index = 0; index < raw.length; index++) {
46
+ const arg = raw[index];
47
+
48
+ if (arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v") {
49
+ continue;
50
+ }
51
+
52
+ if (arg === "--router") {
53
+ if (!setTemplateName("router")) {
54
+ break;
55
+ }
56
+ continue;
57
+ }
58
+
59
+ if (arg === "--template" || arg === "-t") {
60
+ const templateName = raw[index + 1];
61
+
62
+ if (!templateName || templateName.startsWith("-")) {
63
+ parsed.optionError = "Option --template requires a value.";
64
+ break;
65
+ }
66
+
67
+ if (!setTemplateName(templateName)) {
68
+ break;
69
+ }
70
+
71
+ index++;
72
+ continue;
73
+ }
74
+
75
+ if (arg.startsWith("--template=")) {
76
+ if (!setTemplateName(arg.slice("--template=".length))) {
77
+ break;
78
+ }
79
+ continue;
80
+ }
81
+
82
+ if (arg.startsWith("-t=")) {
83
+ if (!setTemplateName(arg.slice("-t=".length))) {
84
+ break;
85
+ }
86
+ continue;
87
+ }
88
+
89
+ if (!arg.startsWith("-") && parsed.projectName === null) {
90
+ parsed.projectName = arg;
91
+ }
92
+ }
93
+
94
+ return parsed;
22
95
  }
23
96
 
24
97
  // ── Help / Version ──────────────────────────────────────────────────
@@ -35,11 +108,14 @@ export function printHelp() {
35
108
  npx create-koppajs [project-name]
36
109
 
37
110
  Options:
38
- --help, -h Show this help message
39
- --version, -v Show version number
111
+ --help, -h Show this help message
112
+ --version, -v Show version number
113
+ --template, -t <name> Starter template: minimal | router
114
+ --router Shortcut for --template router
40
115
 
41
116
  Example:
42
117
  pnpm create koppajs my-app
118
+ pnpm create koppajs my-app --template router
43
119
  `);
44
120
  }
45
121
 
@@ -49,14 +125,14 @@ export function printVersion() {
49
125
 
50
126
  // ── Prompt ──────────────────────────────────────────────────────────
51
127
 
52
- export function promptProjectName() {
128
+ function promptLine(question, closeErrorMessage, input = process.stdin, output = process.stdout) {
53
129
  return new Promise((res, rej) => {
54
- const rl = createInterface({ input: process.stdin, output: process.stdout });
130
+ const rl = createInterface({ input, output });
55
131
  let answered = false;
56
132
  rl.on("close", () => {
57
- if (!answered) rej(new Error("Input closed before a project name was provided."));
133
+ if (!answered) rej(new Error(closeErrorMessage));
58
134
  });
59
- rl.question(" Project name: ", (answer) => {
135
+ rl.question(question, (answer) => {
60
136
  answered = true;
61
137
  rl.close();
62
138
  res(answer.trim());
@@ -64,6 +140,34 @@ export function promptProjectName() {
64
140
  });
65
141
  }
66
142
 
143
+ export function promptProjectName(input = process.stdin, output = process.stdout) {
144
+ return promptLine(
145
+ " Project name: ",
146
+ "Input closed before a project name was provided.",
147
+ input,
148
+ output,
149
+ );
150
+ }
151
+
152
+ export async function promptStarterTemplate(input = process.stdin, output = process.stdout) {
153
+ const answer = (await promptLine(
154
+ ` Starter template (minimal/router) [${DEFAULT_TEMPLATE}]: `,
155
+ "Input closed before a starter template was provided.",
156
+ input,
157
+ output,
158
+ )).toLowerCase();
159
+
160
+ if (answer === "" || answer === "m") {
161
+ return DEFAULT_TEMPLATE;
162
+ }
163
+
164
+ if (answer === "r") {
165
+ return "router";
166
+ }
167
+
168
+ return answer;
169
+ }
170
+
67
171
  // ── Validation ──────────────────────────────────────────────────────
68
172
 
69
173
  export function validateProjectName(name) {
@@ -78,6 +182,16 @@ export function validateProjectName(name) {
78
182
  }
79
183
  }
80
184
 
185
+ export function validateStarterTemplate(templateName) {
186
+ if (Object.hasOwn(TEMPLATE_OVERLAY_DIRS, templateName)) {
187
+ return;
188
+ }
189
+
190
+ throw new Error(
191
+ `Unknown starter template "${templateName}". Supported templates: ${Object.keys(TEMPLATE_OVERLAY_DIRS).join(", ")}.`,
192
+ );
193
+ }
194
+
81
195
  // ── Target directory ────────────────────────────────────────────────
82
196
 
83
197
  export function ensureTargetDir(targetPath) {
@@ -115,6 +229,16 @@ export function copyDirRecursive(src, dest) {
115
229
  }
116
230
  }
117
231
 
232
+ export function copyStarterTemplate(templateName, dest) {
233
+ copyDirRecursive(TEMPLATE_DIR, dest);
234
+
235
+ const overlayDir = TEMPLATE_OVERLAY_DIRS[templateName];
236
+
237
+ if (overlayDir) {
238
+ copyDirRecursive(overlayDir, dest);
239
+ }
240
+ }
241
+
118
242
  // ── Patch package.json ──────────────────────────────────────────────
119
243
 
120
244
  export function patchPackageJson(destDir, projectName) {
@@ -156,8 +280,17 @@ export function printNextSteps(projectName) {
156
280
 
157
281
  // ── Main ────────────────────────────────────────────────────────────
158
282
 
159
- export async function runCli(argv = process.argv, cwd = process.cwd()) {
160
- const { help, version, projectName: argName } = parseArgs(argv);
283
+ export function shouldPromptForTemplateSelection(input = process.stdin, output = process.stdout) {
284
+ return Boolean(input.isTTY && output.isTTY);
285
+ }
286
+
287
+ export async function runCli(
288
+ argv = process.argv,
289
+ cwd = process.cwd(),
290
+ io = { input: process.stdin, output: process.stdout },
291
+ ) {
292
+ const { help, version, projectName: argName, templateName: argTemplateName, optionError } =
293
+ parseArgs(argv);
161
294
 
162
295
  if (help) {
163
296
  printHelp();
@@ -169,17 +302,29 @@ export async function runCli(argv = process.argv, cwd = process.cwd()) {
169
302
  return 0;
170
303
  }
171
304
 
172
- const projectName = argName || (await promptProjectName());
305
+ if (optionError) {
306
+ throw new Error(optionError);
307
+ }
308
+
309
+ const projectName = argName || (await promptProjectName(io.input, io.output));
173
310
 
174
311
  validateProjectName(projectName);
175
312
 
313
+ const templateName =
314
+ argTemplateName ||
315
+ (shouldPromptForTemplateSelection(io.input, io.output)
316
+ ? await promptStarterTemplate(io.input, io.output)
317
+ : DEFAULT_TEMPLATE);
318
+
319
+ validateStarterTemplate(templateName);
320
+
176
321
  const targetDir = resolve(cwd, projectName);
177
322
 
178
323
  ensureTargetDir(targetDir);
179
324
 
180
- console.log(`\n Scaffolding KoppaJS project: ${projectName}\n`);
325
+ console.log(`\n Scaffolding KoppaJS project: ${projectName} (${templateName} starter)\n`);
181
326
 
182
- copyDirRecursive(TEMPLATE_DIR, targetDir);
327
+ copyStarterTemplate(templateName, targetDir);
183
328
  patchPackageJson(targetDir, projectName);
184
329
  patchReadme(targetDir, projectName);
185
330
  patchChangelog(targetDir, projectName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-koppajs",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Scaffold a new KoppaJS project in seconds.",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "bin",
12
12
  "template",
13
+ "template-overlays",
13
14
  "CHANGELOG.md",
14
15
  "README.md",
15
16
  "LICENSE"
@@ -0,0 +1,86 @@
1
+ # Architecture
2
+
3
+ This repository is a small KoppaJS router starter. It demonstrates a clear
4
+ bootstrap path: HTML shell, TypeScript entrypoint, one root app shell
5
+ component, one router instance, two primary routes, and one explicit not-found
6
+ route. The repository also carries the same quality and release automation
7
+ baseline as the minimal starter so the example remains production-like.
8
+
9
+ ## System overview
10
+
11
+ - `index.html` provides the document shell, loads global CSS, declares the
12
+ `<app-view>` root element, and imports the TypeScript entrypoint.
13
+ - `src/main.ts` registers local components with `Core.take(...)`, calls
14
+ `Core()` once, waits for the route outlet to exist, and initializes one
15
+ `KoppajsRouter` instance.
16
+ - `src/app-view.kpa` is the root shell. It renders the hero area, the primary
17
+ navigation, and the `#app-outlet` container that receives route content.
18
+ - `src/home-page.kpa` is the default route and composes `counter-component`.
19
+ - `src/router-page.kpa` is the second route and explains the router wiring.
20
+ - `src/not-found-page.kpa` is the explicit catch-all route.
21
+ - `src/counter-component.kpa` remains the example local-state component.
22
+ - `src/style.css` defines global tokens, base layout, and the background.
23
+ - `tests/integration/` verifies bootstrap wiring and router startup.
24
+ - `tests/e2e/` verifies the user-visible route flow and counter behavior.
25
+
26
+ More detailed boundaries live in [docs/architecture/module-boundaries.md](./docs/architecture/module-boundaries.md).
27
+
28
+ ## Runtime flow
29
+
30
+ 1. The browser loads [`index.html`](./index.html).
31
+ 2. The document loads [`src/style.css`](./src/style.css) and [`src/main.ts`](./src/main.ts).
32
+ 3. [`src/main.ts`](./src/main.ts) registers `app-view`, `home-page`,
33
+ `router-page`, `not-found-page`, and `counter-component`, then calls
34
+ `Core()`.
35
+ 4. KoppaJS instantiates `<app-view>`.
36
+ 5. [`src/app-view.kpa`](./src/app-view.kpa) renders the app shell, navigation,
37
+ and `#app-outlet`.
38
+ 6. `src/main.ts` initializes `KoppajsRouter` with the route table and outlet.
39
+ 7. The router renders the matching route component into `#app-outlet`,
40
+ synchronizes active links, and updates the current browser URL.
41
+
42
+ ## Quality automation layer
43
+
44
+ - `eslint.config.mjs` lint-checks TypeScript source plus tooling files.
45
+ - `prettier.config.mjs` and `.editorconfig` keep supported text files consistent.
46
+ - `.npmrc` enforces the declared engine floor during installs.
47
+ - `.husky/pre-commit` runs `lint-staged`.
48
+ - `.husky/commit-msg` validates commit headers with `commitlint`.
49
+ - `.github/workflows/ci.yml` runs the full repository validation flow on GitHub.
50
+ - `.github/workflows/release.yml` runs tagged release validation and creates GitHub Releases.
51
+
52
+ ## Module responsibilities
53
+
54
+ | Module | Responsibility | Must not do |
55
+ | --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------ |
56
+ | `index.html` | Declare the root tag and static assets | Hold feature logic or router setup |
57
+ | `src/main.ts` | Bootstrap the app, register components, and start one router | Accumulate page copy or unrelated UI state |
58
+ | `src/app-view.kpa` | Render the shared shell, nav, and router outlet | Own route resolution or register components |
59
+ | `src/home-page.kpa` | Render the landing route and compose the counter example | Reach into router internals or mutate global DOM state |
60
+ | `src/router-page.kpa` | Render the second route and explain the router baseline | Own bootstrap logic or global navigation state |
61
+ | `src/not-found-page.kpa` | Render the explicit fallback route | Replace route matching or bootstrap responsibilities |
62
+ | `src/counter-component.kpa` | Demonstrate local interactive state | Reach into route orchestration or app-shell concerns |
63
+ | `src/style.css` | Hold global tokens and truly global base styles | Duplicate component-local visuals |
64
+ | `tests/integration/` | Verify bootstrap and router-start boundaries | Duplicate full browser smoke expectations |
65
+ | `tests/e2e/` | Verify visible navigation and counter behavior in a real browser | Assert brittle implementation details |
66
+
67
+ ## Invariants
68
+
69
+ - There is exactly one bootstrap entrypoint.
70
+ - The root tag in `index.html` must match a component registered in `src/main.ts`.
71
+ - `Core()` is called once from `src/main.ts`.
72
+ - The starter owns exactly one router instance.
73
+ - The route table contains an explicit `*` fallback route.
74
+ - Route rendering happens through `#app-outlet`.
75
+ - Official releases require matching versions across `package.json`,
76
+ `CHANGELOG.md`, and `vX.Y.Z` tags.
77
+
78
+ ## Extension guidance
79
+
80
+ - Add new route components under `src/` and register them from `src/main.ts`.
81
+ - Keep route definitions in one obvious place.
82
+ - Extract reusable route helpers into `.ts` modules only when logic becomes
83
+ shared or branch-heavy.
84
+ - Add a spec before changing visible navigation behavior.
85
+ - Add an ADR before changing the routing model, adding data fetching,
86
+ persistence, or another major subsystem.