create-koppajs 1.0.1 → 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 (80) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +166 -131
  3. package/bin/create-koppajs.js +346 -175
  4. package/package.json +55 -34
  5. package/template/AI_CONSTITUTION.md +50 -0
  6. package/template/ARCHITECTURE.md +86 -0
  7. package/template/CHANGELOG.md +34 -0
  8. package/template/CONTRIBUTING.md +92 -0
  9. package/template/DECISION_HIERARCHY.md +32 -0
  10. package/template/DEVELOPMENT_RULES.md +57 -0
  11. package/template/LICENSE +1 -1
  12. package/template/README.md +241 -49
  13. package/template/RELEASE.md +230 -0
  14. package/template/ROADMAP.md +34 -0
  15. package/template/TESTING_STRATEGY.md +93 -0
  16. package/template/_editorconfig +12 -0
  17. package/template/_gitattributes +1 -0
  18. package/template/_github/instructions/ai-workflow.md +33 -0
  19. package/template/_github/workflows/ci.yml +38 -0
  20. package/template/_github/workflows/release.yml +58 -0
  21. package/template/_gitignore +5 -0
  22. package/template/_husky/commit-msg +8 -0
  23. package/template/_husky/pre-commit +1 -0
  24. package/template/_npmrc +1 -0
  25. package/template/_prettierignore +7 -0
  26. package/template/commitlint.config.mjs +6 -0
  27. package/template/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  28. package/template/docs/adr/0002-adopt-a-living-meta-layer.md +30 -0
  29. package/template/docs/adr/0003-normalize-kpa-plugin-output.md +24 -0
  30. package/template/docs/adr/0004-adopt-an-automated-quality-baseline.md +31 -0
  31. package/template/docs/adr/0005-adopt-a-tag-driven-release-baseline.md +45 -0
  32. package/template/docs/adr/0006-adopt-commit-message-conventions.md +39 -0
  33. package/template/docs/adr/README.md +37 -0
  34. package/template/docs/adr/TEMPLATE.md +18 -0
  35. package/template/docs/architecture/module-boundaries.md +48 -0
  36. package/template/docs/meta/maintenance.md +40 -0
  37. package/template/docs/quality/quality-gates.md +39 -0
  38. package/template/docs/specs/README.md +36 -0
  39. package/template/docs/specs/TEMPLATE.md +34 -0
  40. package/template/docs/specs/app-bootstrap.md +46 -0
  41. package/template/docs/specs/counter-component.md +48 -0
  42. package/template/docs/specs/quality-workflow.md +62 -0
  43. package/template/eslint.config.mjs +54 -0
  44. package/template/package.json +57 -6
  45. package/template/playwright.config.ts +31 -0
  46. package/template/pnpm-lock.yaml +3784 -0
  47. package/template/prettier.config.mjs +6 -0
  48. package/template/src/app-view.kpa +35 -36
  49. package/template/src/counter-component.kpa +87 -87
  50. package/template/src/style.css +5 -5
  51. package/template/tests/e2e/app.spec.ts +18 -0
  52. package/template/tests/integration/main-bootstrap.test.ts +33 -0
  53. package/template/tests/unit/normalize-kpa-module-export.test.ts +46 -0
  54. package/template/tsconfig.json +13 -2
  55. package/template/vite.config.d.mts +7 -0
  56. package/template/vite.config.mjs +39 -4
  57. package/template/vitest.config.mjs +19 -0
  58. package/template-overlays/router/ARCHITECTURE.md +86 -0
  59. package/template-overlays/router/CHANGELOG.md +44 -0
  60. package/template-overlays/router/DEVELOPMENT_RULES.md +57 -0
  61. package/template-overlays/router/README.md +243 -0
  62. package/template-overlays/router/ROADMAP.md +34 -0
  63. package/template-overlays/router/TESTING_STRATEGY.md +67 -0
  64. package/template-overlays/router/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  65. package/template-overlays/router/docs/architecture/module-boundaries.md +39 -0
  66. package/template-overlays/router/docs/meta/maintenance.md +38 -0
  67. package/template-overlays/router/docs/specs/README.md +19 -0
  68. package/template-overlays/router/docs/specs/app-bootstrap.md +42 -0
  69. package/template-overlays/router/docs/specs/router-navigation.md +41 -0
  70. package/template-overlays/router/index.html +14 -0
  71. package/template-overlays/router/package.json +74 -0
  72. package/template-overlays/router/pnpm-lock.yaml +3793 -0
  73. package/template-overlays/router/src/app-view.kpa +128 -0
  74. package/template-overlays/router/src/home-page.kpa +100 -0
  75. package/template-overlays/router/src/main.ts +89 -0
  76. package/template-overlays/router/src/not-found-page.kpa +69 -0
  77. package/template-overlays/router/src/router-page.kpa +102 -0
  78. package/template-overlays/router/src/style.css +51 -0
  79. package/template-overlays/router/tests/e2e/app.spec.ts +38 -0
  80. package/template-overlays/router/tests/integration/main-bootstrap.test.ts +150 -0
@@ -0,0 +1,6 @@
1
+ export default {
2
+ semi: true,
3
+ singleQuote: false,
4
+ tabWidth: 2,
5
+ trailingComma: "all",
6
+ };
@@ -1,36 +1,35 @@
1
- [template]
2
- <div class="app-root">
3
- <img src="https://public-assets-1b57ca06-687a-4142-a525-0635f7649a5c.s3.eu-central-1.amazonaws.com/koppajs/koppajs-logo-text-900x226.png" width="420" alt="KoppaJS Logo" class="logo">
4
- <p class="subtitle">Minimal Starter</p>
5
- <counter-component></counter-component>
6
- </div>
7
- [/template]
8
-
9
- [css]
10
- .app-root {
11
- display: flex;
12
- flex-direction: column;
13
- align-items: center;
14
- justify-content: center;
15
- min-height: 100vh;
16
- background: #0f1117;
17
- font-family: system-ui, -apple-system, sans-serif;
18
- padding: 2rem;
19
- }
20
-
21
- .logo {
22
- margin-bottom: .9rem;
23
- }
24
-
25
- .subtitle {
26
- color: #64dce8;
27
- font-size: 1rem;
28
- letter-spacing: 0.25em;
29
- text-transform: uppercase;
30
- margin: 0 0 1.5rem 0;
31
- opacity: 0.8;
32
- }
33
- [/css]
34
-
35
-
36
-
1
+ [template]
2
+ <div class="app-root">
3
+ <img src="https://public-assets-1b57ca06-687a-4142-a525-0635f7649a5c.s3.eu-central-1.amazonaws.com/koppajs/koppajs-logo-text-900x226.png" width="420" alt="KoppaJS Logo" class="logo">
4
+ <p class="subtitle">Minimal Starter</p>
5
+ <counter-component></counter-component>
6
+ </div>
7
+ [/template]
8
+
9
+ [css]
10
+ .app-root {
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: center;
14
+ justify-content: center;
15
+ min-height: 100vh;
16
+ background: #0f1117;
17
+ font-family: system-ui, -apple-system, sans-serif;
18
+ padding: 2rem;
19
+ }
20
+
21
+ .logo {
22
+ margin-bottom: .9rem;
23
+ }
24
+
25
+ .subtitle {
26
+ color: #64dce8;
27
+ font-size: 1rem;
28
+ letter-spacing: 0.25em;
29
+ text-transform: uppercase;
30
+ margin: 0 0 1.5rem 0;
31
+ opacity: 0.8;
32
+ }
33
+ [/css]
34
+
35
+
@@ -1,87 +1,87 @@
1
- [template]
2
- <div class="counter-card">
3
- <span class="counter-label">Counter</span>
4
- <p class="counter-value">{{ count }}</p>
5
- <div class="counter-buttons">
6
- <button onClick="decrement">−</button>
7
- <button onClick="increment">+</button>
8
- </div>
9
- </div>
10
- [/template]
11
-
12
- [ts]
13
- return {
14
- state: { count: 0 },
15
- methods: {
16
- increment() {
17
- this.count++;
18
- },
19
- decrement() {
20
- this.count--;
21
- },
22
- },
23
- };
24
- [/ts]
25
-
26
- [css]
27
- .counter-card {
28
- background: #1a1d2e;
29
- border: 1px solid #2a2d3e;
30
- border-radius: 1.2rem;
31
- padding: 2.5rem 3rem;
32
- display: flex;
33
- flex-direction: column;
34
- align-items: center;
35
- gap: 1.2rem;
36
- min-width: 260px;
37
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 48px rgba(100, 220, 232, 0.05);
38
- }
39
-
40
- .counter-label {
41
- font-size: 0.85rem;
42
- color: #64dce8;
43
- text-transform: uppercase;
44
- letter-spacing: 0.2em;
45
- opacity: 0.7;
46
- }
47
-
48
- .counter-value {
49
- font-size: 3rem;
50
- font-weight: 700;
51
- color: #e8eaf0;
52
- margin: 0;
53
- font-variant-numeric: tabular-nums;
54
- }
55
-
56
- .counter-buttons {
57
- display: flex;
58
- gap: 1rem;
59
- }
60
-
61
- button {
62
- background: transparent;
63
- color: #64dce8;
64
- border: 1px solid #64dce8;
65
- border-radius: 0.6rem;
66
- padding: 0.6rem 1.5rem;
67
- font-size: 1.3rem;
68
- font-weight: 600;
69
- cursor: pointer;
70
- transition: background 0.35s cubic-bezier(0.4, 0, 0.2, 1),
71
- color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
72
- transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
73
- border-color 0.35s cubic-bezier(0.4, 0, 0.2, 1);
74
- }
75
-
76
- button:hover {
77
- background: rgba(100, 220, 232, 0.12);
78
- color: #8ff0f8;
79
- border-color: #8ff0f8;
80
- }
81
-
82
- button:active {
83
- background: #64dce8;
84
- color: #0f1117;
85
- transform: scale(0.93);
86
- }
87
- [/css]
1
+ [template]
2
+ <div class="counter-card">
3
+ <span class="counter-label">Counter</span>
4
+ <p class="counter-value" role="status" aria-live="polite">{{ count }}</p>
5
+ <div class="counter-buttons">
6
+ <button type="button" aria-label="Decrement counter" onClick="decrement">−</button>
7
+ <button type="button" aria-label="Increment counter" onClick="increment">+</button>
8
+ </div>
9
+ </div>
10
+ [/template]
11
+
12
+ [ts]
13
+ return {
14
+ state: { count: 0 },
15
+ methods: {
16
+ increment() {
17
+ this.count++;
18
+ },
19
+ decrement() {
20
+ this.count--;
21
+ },
22
+ },
23
+ };
24
+ [/ts]
25
+
26
+ [css]
27
+ .counter-card {
28
+ background: #1a1d2e;
29
+ border: 1px solid #2a2d3e;
30
+ border-radius: 1.2rem;
31
+ padding: 2.5rem 3rem;
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ gap: 1.2rem;
36
+ min-width: 260px;
37
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 48px rgba(100, 220, 232, 0.05);
38
+ }
39
+
40
+ .counter-label {
41
+ font-size: 0.85rem;
42
+ color: #64dce8;
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.2em;
45
+ opacity: 0.7;
46
+ }
47
+
48
+ .counter-value {
49
+ font-size: 3rem;
50
+ font-weight: 700;
51
+ color: #e8eaf0;
52
+ margin: 0;
53
+ font-variant-numeric: tabular-nums;
54
+ }
55
+
56
+ .counter-buttons {
57
+ display: flex;
58
+ gap: 1rem;
59
+ }
60
+
61
+ button {
62
+ background: transparent;
63
+ color: #64dce8;
64
+ border: 1px solid #64dce8;
65
+ border-radius: 0.6rem;
66
+ padding: 0.6rem 1.5rem;
67
+ font-size: 1.3rem;
68
+ font-weight: 600;
69
+ cursor: pointer;
70
+ transition: background 0.35s cubic-bezier(0.4, 0, 0.2, 1),
71
+ color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
72
+ transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
73
+ border-color 0.35s cubic-bezier(0.4, 0, 0.2, 1);
74
+ }
75
+
76
+ button:hover {
77
+ background: rgba(100, 220, 232, 0.12);
78
+ color: #8ff0f8;
79
+ border-color: #8ff0f8;
80
+ }
81
+
82
+ button:active {
83
+ background: #64dce8;
84
+ color: #0f1117;
85
+ transform: scale(0.93);
86
+ }
87
+ [/css]
@@ -1,5 +1,5 @@
1
- * {
2
- box-sizing: border-box;
3
- margin: 0;
4
- padding: 0;
5
- }
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("renders the starter and updates the counter", async ({ page }) => {
4
+ await page.goto("/");
5
+
6
+ await expect(page.getByText("Minimal Starter")).toBeVisible();
7
+
8
+ const counterValue = page.getByRole("status");
9
+
10
+ await expect(counterValue).toHaveText("0");
11
+
12
+ await page.getByRole("button", { name: "Increment counter" }).click();
13
+ await expect(counterValue).toHaveText("1");
14
+
15
+ await page.getByRole("button", { name: "Decrement counter" }).click();
16
+ await page.getByRole("button", { name: "Decrement counter" }).click();
17
+ await expect(counterValue).toHaveText("-1");
18
+ });
@@ -0,0 +1,33 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const { core, take } = vi.hoisted(() => {
4
+ const take = vi.fn();
5
+ const core = Object.assign(vi.fn(), { take });
6
+
7
+ return { core, take };
8
+ });
9
+
10
+ vi.mock("@koppajs/koppajs-core", () => ({
11
+ Core: core,
12
+ }));
13
+
14
+ describe("main bootstrap", () => {
15
+ afterEach(() => {
16
+ core.mockClear();
17
+ take.mockClear();
18
+ vi.resetModules();
19
+ });
20
+
21
+ it("registers the root components and boots the app once", async () => {
22
+ await import("../../src/main");
23
+
24
+ expect(take).toHaveBeenCalledTimes(2);
25
+ expect(take).toHaveBeenNthCalledWith(1, expect.anything(), "app-view");
26
+ expect(take).toHaveBeenNthCalledWith(
27
+ 2,
28
+ expect.anything(),
29
+ "counter-component",
30
+ );
31
+ expect(core).toHaveBeenCalledTimes(1);
32
+ });
33
+ });
@@ -0,0 +1,46 @@
1
+ import type { Plugin } from "vite";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { normalizeKpaModuleExport } from "../../vite.config.mjs";
5
+
6
+ async function transformWith(plugin: Plugin, code: string, id: string) {
7
+ const transform = plugin.transform;
8
+
9
+ if (!transform) {
10
+ return null;
11
+ }
12
+
13
+ if (typeof transform === "function") {
14
+ return transform.call({} as never, code, id);
15
+ }
16
+
17
+ return transform.handler.call({} as never, code, id);
18
+ }
19
+
20
+ describe("normalizeKpaModuleExport", () => {
21
+ it("wraps raw KPA output in an ES module export", async () => {
22
+ const plugin = normalizeKpaModuleExport();
23
+
24
+ await expect(
25
+ transformWith(plugin, '{ template: "<div></div>" }', "/src/app-view.kpa"),
26
+ ).resolves.toEqual({
27
+ code: 'export default { template: "<div></div>" };',
28
+ map: null,
29
+ });
30
+ });
31
+
32
+ it("ignores non-KPA files and already exported modules", async () => {
33
+ const plugin = normalizeKpaModuleExport();
34
+
35
+ await expect(
36
+ transformWith(plugin, "const count = 0;", "/src/main.ts"),
37
+ ).resolves.toBeNull();
38
+ await expect(
39
+ transformWith(
40
+ plugin,
41
+ "export default { template: '<div></div>' };",
42
+ "/src/app-view.kpa",
43
+ ),
44
+ ).resolves.toBeNull();
45
+ });
46
+ });
@@ -10,6 +10,17 @@
10
10
  "forceConsistentCasingInFileNames": true,
11
11
  "noEmit": true
12
12
  },
13
- "include": ["src/**/*.ts"],
14
- "exclude": ["node_modules", "dist"]
13
+ "include": [
14
+ "src/**/*.ts",
15
+ "tests/**/*.ts",
16
+ "playwright.config.ts",
17
+ "vite.config.d.mts"
18
+ ],
19
+ "exclude": [
20
+ "coverage",
21
+ "dist",
22
+ "node_modules",
23
+ "playwright-report",
24
+ "test-results"
25
+ ]
15
26
  }
@@ -0,0 +1,7 @@
1
+ import type { Plugin, UserConfig } from "vite";
2
+
3
+ export declare function normalizeKpaModuleExport(): Plugin;
4
+
5
+ declare const config: UserConfig;
6
+
7
+ export default config;
@@ -1,10 +1,45 @@
1
- import { defineConfig } from "vite";
2
1
  import koppaPlugin from "@koppajs/koppajs-vite-plugin";
2
+ import { defineConfig } from "vite";
3
+
4
+ /**
5
+ * Wrap raw `.kpa` object output in a valid ES module export.
6
+ *
7
+ * @returns {import("vite").Plugin}
8
+ */
9
+ export function normalizeKpaModuleExport() {
10
+ return {
11
+ name: "normalize-kpa-module-export",
12
+ enforce: "post",
13
+ /**
14
+ * @param {string} code
15
+ * @param {string} id
16
+ */
17
+ transform(code, id) {
18
+ const cleanId = id.split("?")[0];
19
+
20
+ if (!cleanId.endsWith(".kpa")) {
21
+ return null;
22
+ }
23
+
24
+ const trimmed = code.trim();
25
+
26
+ if (!trimmed.startsWith("{") || trimmed.startsWith("export default")) {
27
+ return null;
28
+ }
29
+
30
+ return {
31
+ code: `export default ${trimmed};`,
32
+ map: null,
33
+ };
34
+ },
35
+ };
36
+ }
3
37
 
4
38
  export default defineConfig({
5
39
  plugins: [
6
40
  koppaPlugin({
7
- tsconfigFile: "./tsconfig.json"
8
- })
9
- ]
41
+ tsconfigFile: "./tsconfig.json",
42
+ }),
43
+ normalizeKpaModuleExport(),
44
+ ],
10
45
  });
@@ -0,0 +1,19 @@
1
+ import { defineConfig, mergeConfig } from "vitest/config";
2
+
3
+ import viteConfig from "./vite.config.mjs";
4
+
5
+ export default mergeConfig(
6
+ viteConfig,
7
+ defineConfig({
8
+ test: {
9
+ include: ["tests/**/*.test.ts"],
10
+ coverage: {
11
+ provider: "v8",
12
+ reporter: ["text", "html"],
13
+ reportsDirectory: "./coverage",
14
+ include: ["src/**/*.ts"],
15
+ exclude: ["playwright.config.ts", "tests/**/*.ts", "vitest.config.mjs"],
16
+ },
17
+ },
18
+ }),
19
+ );
@@ -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.
@@ -0,0 +1,44 @@
1
+ # Change Log
2
+
3
+ All notable changes to **__PROJECT_NAME__** are documented in this file.
4
+
5
+ This project uses a **manual, tag-driven release process**.
6
+ Only tagged versions represent official releases.
7
+
8
+ This changelog documents **intentional milestones and guarantees**,
9
+ not every internal refactor.
10
+
11
+ ---
12
+
13
+ ## [Unreleased]
14
+
15
+ _No unreleased changes yet._
16
+
17
+ ---
18
+
19
+ ## [1.0.0] — Initial Starter Baseline
20
+
21
+ **2026-03-17**
22
+
23
+ ### Added
24
+
25
+ - a small KoppaJS router app shell with `app-view`, `home-page`,
26
+ `router-page`, `not-found-page`, and `counter-component`
27
+ - route-based rendering through `@koppajs/koppajs-router`
28
+ - ESLint, Prettier, Vitest, Playwright, Husky, lint-staged, and commitlint
29
+ - starter governance docs, ADRs, specs, and release workflow files
30
+
31
+ ---
32
+
33
+ ## Versioning Policy
34
+
35
+ - Semantic Versioning (SemVer) is followed pragmatically
36
+ - **Breaking changes** include:
37
+ - public runtime behavior changes
38
+ - route structure changes
39
+ - release workflow changes
40
+
41
+ ---
42
+
43
+ _This changelog documents intent.
44
+ If something is not written here, it is not guaranteed._
@@ -0,0 +1,57 @@
1
+ # Development Rules
2
+
3
+ ## Scope
4
+
5
+ These rules describe how code and documentation are expected to evolve in this repository.
6
+
7
+ ## Source layout rules
8
+
9
+ - Keep `index.html` as a static shell with asset references and the single root element.
10
+ - Keep `src/main.ts` limited to bootstrap concerns: imports, `Core.take(...)`
11
+ registrations, route definitions, and initialization of the single router instance.
12
+ - Use `.kpa` files for UI composition, local component state, and component-scoped CSS.
13
+ - Keep the route outlet inside `app-view.kpa` and keep route rendering consumer-owned.
14
+ - If logic becomes reusable, asynchronous, or branch-heavy, move it into `.ts` modules under `src/`.
15
+ - Keep `public/` for static assets only.
16
+
17
+ ## Naming conventions
18
+
19
+ - Custom element names must be kebab-case.
20
+ - Root-level application components should use `app-` prefixes when they represent app shells or app-wide structure.
21
+ - Route component filenames should match their registered component tags or route purpose.
22
+ - New files and folders should use descriptive names over abbreviations.
23
+
24
+ ## Dependency rules
25
+
26
+ - Runtime dependencies must be justified by a concrete need in a spec or ADR.
27
+ - Prefer browser APIs and KoppaJS primitives before adding helper libraries.
28
+ - Keep this repository wired to published npm packages unless there is an explicit ADR stating otherwise.
29
+ - Build tooling changes must update contributor docs and architecture docs in the same change.
30
+ - Quality tooling must stay proportionate to the starter.
31
+
32
+ ## Coding patterns
33
+
34
+ - Favor simple, local state over premature abstraction.
35
+ - Keep route definitions obvious and close to router initialization.
36
+ - Avoid hidden side effects during import.
37
+ - Keep CSS local to components unless the style truly applies application-wide.
38
+ - Preserve strict TypeScript settings; do not weaken `tsconfig.json` to work around errors.
39
+ - Give interactive controls stable accessible names so smoke tests can target public semantics instead of brittle selectors.
40
+
41
+ ## Forbidden without a spec and ADR
42
+
43
+ - Replacing the starter's single-router model with a second navigation system
44
+ - Adding global state containers
45
+ - Adding network or persistence layers
46
+ - Introducing SSR, multi-page bootstraps, or multiple root entries
47
+ - Switching dependency sourcing from npm packages to local monorepo links
48
+ - Expanding the starter into a feature-rich demo application
49
+ - Adding heavyweight hooks that run the entire suite on every commit
50
+
51
+ ## Documentation obligations
52
+
53
+ - Update [ARCHITECTURE.md](./ARCHITECTURE.md) when source layout or runtime flow changes.
54
+ - Update [TESTING_STRATEGY.md](./TESTING_STRATEGY.md) when quality gates or tooling change.
55
+ - Update or create a spec in [docs/specs](./docs/specs) for user-visible changes.
56
+ - Add an ADR in [docs/adr](./docs/adr) for durable technical decisions.
57
+ - Update [docs/quality/quality-gates.md](./docs/quality/quality-gates.md) when scripts, hooks, CI checks, or browser-smoke expectations change.