@techninja/clearstack 0.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/cli.js +62 -0
  4. package/docs/BACKEND_API_SPEC.md +281 -0
  5. package/docs/BUILD_LOG.md +193 -0
  6. package/docs/COMPONENT_PATTERNS.md +481 -0
  7. package/docs/CONVENTIONS.md +226 -0
  8. package/docs/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
  9. package/docs/JSDOC_TYPING.md +86 -0
  10. package/docs/QUICKSTART.md +190 -0
  11. package/docs/SERVER_AND_DEPS.md +163 -0
  12. package/docs/STATE_AND_ROUTING.md +363 -0
  13. package/docs/TESTING.md +268 -0
  14. package/docs/app-spec/ENTITIES.md +37 -0
  15. package/docs/app-spec/README.md +19 -0
  16. package/lib/check.js +115 -0
  17. package/lib/copy.js +43 -0
  18. package/lib/init.js +73 -0
  19. package/lib/package-gen.js +83 -0
  20. package/lib/update.js +73 -0
  21. package/package.json +69 -0
  22. package/templates/fullstack/data/seed.json +1 -0
  23. package/templates/fullstack/src/api/db.js +75 -0
  24. package/templates/fullstack/src/api/entities.js +114 -0
  25. package/templates/fullstack/src/api/events.js +35 -0
  26. package/templates/fullstack/src/api/schemas.js +104 -0
  27. package/templates/fullstack/src/api/validate.js +52 -0
  28. package/templates/fullstack/src/pages/home/home-view.js +19 -0
  29. package/templates/fullstack/src/router/index.js +16 -0
  30. package/templates/fullstack/src/server.js +46 -0
  31. package/templates/fullstack/src/store/AppState.js +33 -0
  32. package/templates/fullstack/src/store/UserPrefs.js +31 -0
  33. package/templates/fullstack/src/store/realtimeSync.js +54 -0
  34. package/templates/shared/.configs/.prettierrc +8 -0
  35. package/templates/shared/.configs/eslint.config.js +64 -0
  36. package/templates/shared/.configs/jsconfig.json +24 -0
  37. package/templates/shared/.configs/web-test-runner.config.js +8 -0
  38. package/templates/shared/.env +9 -0
  39. package/templates/shared/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  40. package/templates/shared/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
  41. package/templates/shared/.github/ISSUE_TEMPLATE/spec_correction.md +26 -0
  42. package/templates/shared/.github/pull_request_template.md +51 -0
  43. package/templates/shared/.github/workflows/spec.yml +46 -0
  44. package/templates/shared/README.md +22 -0
  45. package/templates/shared/docs/app-spec/README.md +40 -0
  46. package/templates/shared/docs/clearstack/BACKEND_API_SPEC.md +281 -0
  47. package/templates/shared/docs/clearstack/BUILD_LOG.md +193 -0
  48. package/templates/shared/docs/clearstack/COMPONENT_PATTERNS.md +481 -0
  49. package/templates/shared/docs/clearstack/CONVENTIONS.md +226 -0
  50. package/templates/shared/docs/clearstack/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
  51. package/templates/shared/docs/clearstack/JSDOC_TYPING.md +86 -0
  52. package/templates/shared/docs/clearstack/QUICKSTART.md +190 -0
  53. package/templates/shared/docs/clearstack/SERVER_AND_DEPS.md +163 -0
  54. package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +363 -0
  55. package/templates/shared/docs/clearstack/TESTING.md +268 -0
  56. package/templates/shared/public/index.html +26 -0
  57. package/templates/shared/scripts/build-icons.js +86 -0
  58. package/templates/shared/scripts/vendor-deps.js +25 -0
  59. package/templates/shared/src/components/atoms/app-badge/app-badge.css +4 -0
  60. package/templates/shared/src/components/atoms/app-badge/app-badge.js +23 -0
  61. package/templates/shared/src/components/atoms/app-badge/app-badge.test.js +26 -0
  62. package/templates/shared/src/components/atoms/app-badge/index.js +1 -0
  63. package/templates/shared/src/components/atoms/app-button/app-button.css +3 -0
  64. package/templates/shared/src/components/atoms/app-button/app-button.js +41 -0
  65. package/templates/shared/src/components/atoms/app-button/app-button.test.js +43 -0
  66. package/templates/shared/src/components/atoms/app-button/index.js +1 -0
  67. package/templates/shared/src/components/atoms/app-icon/app-icon.css +4 -0
  68. package/templates/shared/src/components/atoms/app-icon/app-icon.js +57 -0
  69. package/templates/shared/src/components/atoms/app-icon/app-icon.test.js +30 -0
  70. package/templates/shared/src/components/atoms/app-icon/index.js +1 -0
  71. package/templates/shared/src/components/atoms/theme-toggle/index.js +1 -0
  72. package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.css +10 -0
  73. package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.js +42 -0
  74. package/templates/shared/src/styles/buttons.css +79 -0
  75. package/templates/shared/src/styles/components.css +31 -0
  76. package/templates/shared/src/styles/forms.css +20 -0
  77. package/templates/shared/src/styles/reset.css +32 -0
  78. package/templates/shared/src/styles/shared.css +135 -0
  79. package/templates/shared/src/styles/tokens.css +65 -0
  80. package/templates/shared/src/utils/formatDate.js +41 -0
  81. package/templates/shared/src/utils/statusColors.js +60 -0
  82. package/templates/static/src/pages/home/home-view.js +38 -0
  83. package/templates/static/src/router/index.js +16 -0
  84. package/templates/static/src/store/AppState.js +26 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hybrids-spec contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Clearstack
2
+
3
+ A no-build web component framework specification — and its working proof — built entirely through LLM-human collaboration.
4
+
5
+ ## Why This Exists
6
+
7
+ Modern frontend tooling optimizes for machines: bundlers, transpilers, tree-shakers. The result is code that no human (or LLM) can read as-written in the browser. This project asks: **what if we optimized for comprehension instead?**
8
+
9
+ The core bet: if every file is small, explicit, and runs exactly as authored — with no build step between source and browser — then both humans and LLMs can reason about, generate, and maintain the codebase with dramatically less friction.
10
+
11
+ This entire repository — the specification, the file structure, every component, the server, the tests, and this README — was authored through iterative conversation between a human and an LLM. The spec was written first, then proven through implementation, then corrected where the implementation revealed gaps. The spec enforces itself: `npm run spec` checks every file against its own rules.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ npm install
17
+ npm start # http://localhost:3000
18
+ npm test # node + browser tests
19
+ npm run spec # interactive spec compliance checker
20
+ ```
21
+
22
+ ## What's In The Box
23
+
24
+ A project/task tracker that exercises every pattern in the spec: API-backed entities, localStorage-only state, realtime sync via SSE, schema-driven endpoints, and a full atomic design component hierarchy — all served as raw ES modules with zero build tools.
25
+
26
+ ## Specification
27
+
28
+ | Document | What It Covers |
29
+ |---|---|
30
+ | [FRONTEND_IMPLEMENTATION_RULES.md](./docs/FRONTEND_IMPLEMENTATION_RULES.md) | Philosophy, framework choice, project structure, atomic design |
31
+ | [COMPONENT_PATTERNS.md](./docs/COMPONENT_PATTERNS.md) | Authoring, light DOM, styling, layout engine, JSDoc typing |
32
+ | [STATE_AND_ROUTING.md](./docs/STATE_AND_ROUTING.md) | Store, routing, unified app state, realtime SSE sync |
33
+ | [CONVENTIONS.md](./docs/CONVENTIONS.md) | Naming rules, anti-patterns |
34
+ | [SERVER_AND_DEPS.md](./docs/SERVER_AND_DEPS.md) | Express server, import maps, vendor dependency loading |
35
+ | [BACKEND_API_SPEC.md](./docs/BACKEND_API_SPEC.md) | REST CRUD, JSON Schema via HEAD, entity management |
36
+ | [TESTING.md](./docs/TESTING.md) | Testing philosophy, tools, patterns, phase checkpoints |
37
+ | [BUILD_LOG.md](./docs/BUILD_LOG.md) | How this project was built — LLM-human collaboration proof |
38
+ | [QUICKSTART.md](./docs/QUICKSTART.md) | Scaffolder setup, development workflow, updating, compliance |
39
+
40
+ ## Start a New Project
41
+
42
+ The Clearstack scaffolder generates a spec-compliant project from templates and keeps it in sync as the spec evolves.
43
+
44
+ ```bash
45
+ npx clearstack init # interactive project scaffolder
46
+ npx clearstack update # sync spec docs + configs from upstream
47
+ npx clearstack check # run spec compliance checks
48
+ ```
49
+
50
+ Two modes: **fullstack** (Express + WebSocket + JSON DB + SSE) or **static** (localStorage, no server).
51
+
52
+ See [QUICKSTART.md](./docs/QUICKSTART.md) for the full walkthrough.
53
+
54
+ ## Rules That Matter
55
+
56
+ - **No build tools.** ES modules served directly to the browser.
57
+ - **≤150 lines per code file.** When it grows, it splits.
58
+ - **Light DOM by default.** Shared styles just work.
59
+ - **JSDoc over TypeScript.** Types without a compile step — validated by `tsc --checkJs`.
60
+ - **Test at the boundary.** Each phase passes before the next begins.
61
+ - **The spec checks itself.** `npm run spec:code` and `npm run spec:docs`.
62
+ - **Lint and format.** ESLint + Prettier, semicolons, 2-space indent.
63
+
64
+ ## Scripts
65
+
66
+ ```bash
67
+ npm start # Start server
68
+ npm run dev # Start with --watch
69
+ npm test # Node + browser tests
70
+ npm run lint # ESLint check
71
+ npm run lint:fix # ESLint auto-fix
72
+ npm run format # Prettier auto-format
73
+ npm run typecheck # JSDoc type validation via tsc
74
+ npm run spec # Interactive spec compliance checker
75
+ npm run spec:code # Check code files ≤150 lines
76
+ npm run spec:docs # Check doc files ≤500 lines
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * clearstack CLI — scaffold, update, and check spec-compliant projects.
5
+ * Usage:
6
+ * clearstack init [-y] [--mode fullstack|static] [--port 3000]
7
+ * clearstack update
8
+ * clearstack check [code|docs]
9
+ * clearstack → interactive menu
10
+ */
11
+
12
+ import { dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
16
+ const args = process.argv.slice(2);
17
+ const cmd = args.find((a) => !a.startsWith('-'));
18
+ const flags = Object.fromEntries(
19
+ args.filter((a) => a.startsWith('--')).map((a) => {
20
+ const [k, v] = a.slice(2).split('=');
21
+ return [k, v ?? true];
22
+ }),
23
+ );
24
+ const yes = args.includes('-y') || args.includes('--yes');
25
+
26
+ /** Show interactive menu. */
27
+ async function interactive() {
28
+ const { select } = await import('@inquirer/prompts');
29
+ const action = await select({
30
+ message: 'clearstack — what do you want to do?',
31
+ choices: [
32
+ { name: 'Initialize a new project', value: 'init' },
33
+ { name: 'Update spec docs + configs', value: 'update' },
34
+ { name: 'Run spec compliance check', value: 'check' },
35
+ ],
36
+ });
37
+ await run(action);
38
+ }
39
+
40
+ /**
41
+ * Run a subcommand.
42
+ * @param {string} action
43
+ */
44
+ async function run(action) {
45
+ if (action === 'init') {
46
+ const { init } = await import('../lib/init.js');
47
+ await init(PKG_ROOT, { yes, ...flags });
48
+ } else if (action === 'update') {
49
+ const { update } = await import('../lib/update.js');
50
+ await update(PKG_ROOT);
51
+ } else if (action === 'check') {
52
+ const sub = args.find((a) => a !== cmd && !a.startsWith('-'));
53
+ const { check } = await import('../lib/check.js');
54
+ await check(process.cwd(), sub);
55
+ } else {
56
+ console.log('Usage: clearstack [init|update|check] [-y]');
57
+ }
58
+ }
59
+
60
+ if (cmd) await run(cmd);
61
+ else if (yes) await run('check');
62
+ else await interactive();
@@ -0,0 +1,281 @@
1
+ # Backend API Specification
2
+ ## REST Endpoints, JSON Schema Discovery & Realtime Sync
3
+
4
+ > Defines the server-side data contract. The frontend consumes this API via
5
+ > `[store.connect]` storage connectors.
6
+ > See [STATE_AND_ROUTING.md](./STATE_AND_ROUTING.md) for how the frontend
7
+ > binds to these endpoints.
8
+
9
+ ---
10
+
11
+ ## Design Principles
12
+
13
+ - **Standard REST** — resources as nouns, HTTP verbs for actions.
14
+ - **JSON Schema via OPTIONS** — every entity endpoint exposes its schema and
15
+ allowed methods on `OPTIONS` requests, enabling automated form generation.
16
+ - **Field-level validation errors** — server returns `422` with per-field
17
+ error messages that map directly to form fields.
18
+ - **SSE for realtime** — a single `/api/events` stream pushes entity change
19
+ notifications to connected clients.
20
+ - **Stateless requests** — no server-side sessions. Auth via tokens if needed.
21
+
22
+ ---
23
+
24
+ ## URL Structure
25
+
26
+ ```
27
+ /api/:entity OPTIONS (schema + methods), GET (list), POST (create)
28
+ /api/:entity/:id OPTIONS (schema + methods), GET (read), PUT (update), DELETE (remove)
29
+ /api/events GET (SSE stream)
30
+ ```
31
+
32
+ All request/response bodies are `application/json`.
33
+
34
+ ---
35
+
36
+ ## OPTIONS — Schema & Capability Discovery
37
+
38
+ An `OPTIONS` request to any entity endpoint returns the JSON Schema and
39
+ allowed HTTP methods. This is the primary mechanism for schema-driven forms.
40
+
41
+ ### Example: `OPTIONS /api/projects`
42
+
43
+ ```json
44
+ {
45
+ "schema": {
46
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
47
+ "title": "Project",
48
+ "type": "object",
49
+ "required": ["name"],
50
+ "properties": { ... }
51
+ },
52
+ "methods": ["OPTIONS", "GET", "POST"]
53
+ }
54
+ ```
55
+
56
+ ### Example: `OPTIONS /api/projects/p1`
57
+
58
+ ```json
59
+ {
60
+ "schema": { ... },
61
+ "methods": ["OPTIONS", "GET", "PUT", "DELETE"]
62
+ }
63
+ ```
64
+
65
+ The `Allow` header is also set for HTTP compliance.
66
+ `GET /api/:entity?schema=true` is still supported as a convenience.
67
+
68
+ ### Schema-Driven Forms
69
+
70
+ The frontend can fetch the schema at runtime and generate form fields
71
+ automatically. The schema provides:
72
+
73
+ | JSON Schema keyword | Form behavior |
74
+ |---|---|
75
+ | `type: "string"` | `<input type="text">` |
76
+ | `format: "email"` | `<input type="email">` |
77
+ | `format: "date-time"` | `<input type="datetime-local">` |
78
+ | `enum: [...]` | `<select>` with options |
79
+ | `minLength` / `maxLength` | Validation constraints |
80
+ | `readOnly: true` | Field displayed but not editable |
81
+ | `required: [...]` | Native `required` attribute + visual indicator |
82
+
83
+ JSON Schema constraints map directly to HTML validation attributes:
84
+
85
+ | JSON Schema | HTML attribute |
86
+ |---|---|
87
+ | `minLength` | `minlength` |
88
+ | `maxLength` | `maxlength` |
89
+ | `minimum` | `min` |
90
+ | `maximum` | `max` |
91
+ | `pattern` | `pattern` |
92
+
93
+ The browser's native constraint validation API enforces these — no custom
94
+ JS validation needed for client-side checks.
95
+
96
+ ---
97
+
98
+ ## Server-Side Validation & Error Responses
99
+
100
+ Some validation can only happen server-side (uniqueness, business rules).
101
+ When validation fails, the server returns `422` with field-level errors:
102
+
103
+ ```json
104
+ {
105
+ "error": "Validation failed",
106
+ "fields": {
107
+ "name": "name is required",
108
+ "status": "Must be one of: active, archived"
109
+ }
110
+ }
111
+ ```
112
+
113
+ The `schema-form` component maps `fields` entries to the corresponding
114
+ `form-field` components, which display the error below the input.
115
+
116
+ ### Error Response Contract
117
+
118
+ | Status | Body | Meaning |
119
+ |---|---|---|
120
+ | `422` | `{ error, fields }` | Validation failed — `fields` maps names to messages |
121
+ | `404` | `{ error }` | Entity or collection not found |
122
+ | `201` | Entity JSON | Created successfully |
123
+ | `200` | Entity JSON | Updated successfully |
124
+ | `204` | Empty | Deleted successfully |
125
+
126
+ This allows a single generic `schema-form` component to render any entity's
127
+ create/edit form without entity-specific template code.
128
+
129
+ ---
130
+
131
+ ## CRUD Operations
132
+
133
+ ### List — `GET /api/:entity`
134
+
135
+ Query params for filtering, sorting, pagination:
136
+
137
+ | Param | Example | Purpose |
138
+ |---|---|---|
139
+ | `limit` | `?limit=20` | Page size (default: 20) |
140
+ | `offset` | `?offset=40` | Skip N records |
141
+ | `sort` | `?sort=-createdAt` | Sort field, `-` prefix for desc |
142
+ | `filter` | `?role=admin` | Field equality filter |
143
+
144
+ Response:
145
+
146
+ ```json
147
+ {
148
+ "data": [ { "id": "1", "firstName": "Jane", ... } ],
149
+ "total": 42,
150
+ "limit": 20,
151
+ "offset": 0
152
+ }
153
+ ```
154
+
155
+ ### Read — `GET /api/:entity/:id`
156
+
157
+ Returns a single entity object:
158
+
159
+ ```json
160
+ { "id": "1", "firstName": "Jane", "lastName": "Doe", "email": "jane@example.com" }
161
+ ```
162
+
163
+ Returns `404` if not found.
164
+
165
+ ### Create — `POST /api/:entity`
166
+
167
+ Request body: entity fields (without `id` or `readOnly` fields).
168
+ Response: the created entity with `id` and server-generated fields.
169
+ Status: `201 Created`.
170
+
171
+ ### Update — `PUT /api/:entity/:id`
172
+
173
+ Request body: full or partial entity fields.
174
+ Response: the updated entity.
175
+ Status: `200 OK`.
176
+
177
+ ### Delete — `DELETE /api/:entity/:id`
178
+
179
+ Response: `204 No Content`.
180
+
181
+ ---
182
+
183
+ ## Realtime Sync — SSE
184
+
185
+ ### Endpoint: `GET /api/events`
186
+
187
+ Returns a `text/event-stream` connection. The server pushes events when
188
+ entities are created, updated, or deleted.
189
+
190
+ ### Event Format
191
+
192
+ ```
193
+ event: update
194
+ data: {"type":"user","id":"1","action":"updated"}
195
+
196
+ event: update
197
+ data: {"type":"user","id":"3","action":"created"}
198
+
199
+ event: update
200
+ data: {"type":"user","id":"2","action":"deleted"}
201
+ ```
202
+
203
+ | Field | Value |
204
+ |---|---|
205
+ | `type` | Entity name (lowercase, singular): `user`, `post` |
206
+ | `id` | Entity ID that changed |
207
+ | `action` | `created`, `updated`, or `deleted` |
208
+
209
+ ### Frontend Integration
210
+
211
+ See [STATE_AND_ROUTING.md](./STATE_AND_ROUTING.md) — the `connectRealtime()`
212
+ utility listens to this stream and calls `store.clear()` on the relevant
213
+ model, triggering automatic re-fetch for any component displaying that data.
214
+
215
+ ---
216
+
217
+ ## Dummy Data
218
+
219
+ The server ships with in-memory dummy data for development. No database
220
+ required.
221
+
222
+ ### Data Structure (server-side)
223
+
224
+ ```javascript
225
+ /** @type {Map<string, Map<string, object>>} */
226
+ const db = new Map();
227
+
228
+ // Seed with dummy users
229
+ db.set('users', new Map([
230
+ ['1', { id: '1', firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com', role: 'admin', createdAt: '2024-01-15T09:00:00Z' }],
231
+ ['2', { id: '2', firstName: 'John', lastName: 'Smith', email: 'john@example.com', role: 'editor', createdAt: '2024-02-20T14:30:00Z' }],
232
+ ['3', { id: '3', firstName: 'Alex', lastName: 'Chen', email: 'alex@example.com', role: 'viewer', createdAt: '2024-03-10T11:15:00Z' }],
233
+ ]));
234
+ ```
235
+
236
+ ### Schema Registry (server-side)
237
+
238
+ Schemas are defined once and served via OPTIONS:
239
+
240
+ ```javascript
241
+ const schemas = new Map();
242
+ schemas.set('users', {
243
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
244
+ title: 'User',
245
+ type: 'object',
246
+ required: ['firstName', 'lastName', 'email'],
247
+ properties: { /* ... as above ... */ },
248
+ });
249
+ ```
250
+
251
+ ### Adding a New Entity
252
+
253
+ 1. Define the JSON Schema in the schema registry.
254
+ 2. Seed dummy data in the `db` Map.
255
+ 3. The generic CRUD router handles all operations automatically.
256
+ 4. Create a corresponding frontend store model in `src/store/`.
257
+
258
+ No entity-specific route handlers needed — the server uses a single generic
259
+ router that works for any entity registered in the schema map.
260
+
261
+ ---
262
+
263
+ ## Frontend ↔ Backend Contract
264
+
265
+ | Frontend (Hybrids Store) | Backend (Express) |
266
+ |---|---|
267
+ | `[store.connect].get(id)` | `GET /api/:entity/:id` |
268
+ | `[store.connect].set(id, values)` | `PUT /api/:entity/:id` |
269
+ | `[store.connect].list(params)` | `GET /api/:entity?...` |
270
+ | `store.set(model, null)` (delete) | `DELETE /api/:entity/:id` |
271
+ | Schema fetch for forms | `OPTIONS /api/:entity` |
272
+ | Schema fetch for item | `OPTIONS /api/:entity/:id` |
273
+ | Realtime invalidation | `GET /api/events` (SSE) |
274
+
275
+ This contract means adding a new entity type requires:
276
+ - One JSON Schema definition (server)
277
+ - One store model file (frontend)
278
+ - Dummy seed data (server, for dev)
279
+
280
+ Everything else — CRUD routes, form generation, cache invalidation — is
281
+ handled by the generic infrastructure.
@@ -0,0 +1,193 @@
1
+ # Build Log
2
+ ## How This Project Was Built
3
+
4
+ > This entire repository — specification, implementation, tests, and this
5
+ > document — was authored in a single continuous conversation between a human
6
+ > and an LLM (Amazon Q). No code was written outside this collaboration.
7
+
8
+ ---
9
+
10
+ ## Timeline
11
+
12
+ The project was built over ~1.5 days in a single LLM context window.
13
+ Each phase was implemented, tested, and verified before proceeding.
14
+
15
+ ### Phase 1: Specification
16
+ - Wrote the initial spec as one file, hit ~500 lines
17
+ - Split into 7 topic-specific documents (the spec eating its own dogfood)
18
+ - Chose Hybrids.js after reading its source and type definitions from node_modules
19
+
20
+ ### Phase 2: Infrastructure
21
+ - Express server, vendor-deps script, import maps, HTML shell
22
+ - JSON Schema registry with seed data
23
+ - Generic CRUD router — one router handles all entity types
24
+ - 15 server tests passing before writing any frontend code
25
+
26
+ ### Phase 3: Store Models & Utils
27
+ - Singleton models (AppState, UserPrefs) with localStorage connectors
28
+ - Enumerable models (ProjectModel, TaskModel) with API connectors
29
+ - Utility functions (formatDate, timeAgo, statusColors)
30
+ - 35 tests passing
31
+
32
+ ### Phase 4: Components (Atoms → Molecules → Organisms)
33
+ - Built bottom-up: app-button, app-badge, app-icon → task-card, project-card → task-list, project-header
34
+ - Browser tests via @web/test-runner + Playwright
35
+ - 63 tests passing
36
+
37
+ ### Phase 5: Pages, Router & Integration
38
+ - page-layout template, home-view, project-view, app-router
39
+ - SSE realtime sync wired at the router level
40
+ - SPA fallback for direct URL navigation
41
+
42
+ ### Phase 6: Schema-Driven Forms
43
+ - OPTIONS endpoint returns JSON Schema + form layout
44
+ - schema-form organism auto-generates fields from schema
45
+ - Native HTML validation from schema constraints
46
+ - Server-side validation returns 422 with per-field errors
47
+ - Form layout system with column grouping and action alignment
48
+
49
+ ### Phase 7: Polish & Tooling
50
+ - Dark mode via CSS custom property overrides
51
+ - Spec checker CLI with interactive menu
52
+ - ESLint + Prettier + tsc --checkJs for JSDoc type validation
53
+ - GitHub Actions CI pipeline
54
+ - File reorganization (docs/, .configs/, tests/)
55
+
56
+ ### Phase 8: Drag Reorder & Whiteboard
57
+ - Drag-to-reorder with visual gap indicator and backend persistence
58
+ - WebSocket server for real-time canvas collaboration
59
+ - SVG whiteboard with drawing tools (in progress)
60
+
61
+ ---
62
+
63
+ ## Discoveries & Corrections
64
+
65
+ The spec was written first, then corrected as implementation revealed gaps.
66
+ These are the significant corrections:
67
+
68
+ ### Light DOM breaks slots
69
+ - **Expected:** `<slot>` for content composition
70
+ - **Actual:** Hybrids throws on `<slot>` in light DOM
71
+ - **Fix:** Template functions instead of template components
72
+ - **Documented in:** COMPONENT_PATTERNS.md → Content Composition
73
+
74
+ ### Template components break host context
75
+ - **Expected:** Event handlers in content templates resolve to the page
76
+ - **Actual:** `host` resolves to the nearest hybrids component (the template)
77
+ - **Fix:** Use plain functions that return `html`, not `define()`'d components
78
+ - **Documented in:** COMPONENT_PATTERNS.md → Event Handler Host Context
79
+
80
+ ### Custom events don't bubble by default
81
+ - **Expected:** `dispatch(host, 'press')` reaches parent listeners
82
+ - **Actual:** Without `bubbles: true`, events stop at the dispatching element
83
+ - **Fix:** Always `dispatch(host, 'event', { bubbles: true })`
84
+ - **Documented in:** COMPONENT_PATTERNS.md → Custom Events Must Bubble
85
+
86
+ ### store.clear(Model) vs store.clear([Model])
87
+ - **Expected:** `store.clear(TaskModel)` refreshes task lists
88
+ - **Actual:** Only clears singular cache, not list cache
89
+ - **Fix:** Use `store.clear([TaskModel])` for list stores
90
+ - **Documented in:** STATE_AND_ROUTING.md → Store API Quick Reference
91
+
92
+ ### store.ready(list) doesn't guarantee item readiness
93
+ - **Expected:** If the list is ready, items are ready
94
+ - **Actual:** Individual items can be pending after cache clear
95
+ - **Fix:** Guard each item with `store.ready(task)` in map()
96
+ - **Documented in:** STATE_AND_ROUTING.md → Guarding List Item Access
97
+
98
+ ### localStorage connector must return {}, not undefined
99
+ - **Expected:** Returning undefined uses model defaults
100
+ - **Actual:** Hybrids treats undefined as a failed get → error state
101
+ - **Fix:** Return `{}` so hybrids merges with defaults
102
+ - **Documented in:** STATE_AND_ROUTING.md → localStorage Connector
103
+
104
+ ### Batch operations cause SSE storms
105
+ - **Expected:** Reordering N tasks sends N updates, UI handles it
106
+ - **Actual:** N SSE events → N store.clear() calls → cascading errors
107
+ - **Fix:** Debounce SSE handler per entity type (300ms)
108
+ - **Documented in:** STATE_AND_ROUTING.md → Debouncing Batch Operations
109
+
110
+ ### Server sort used string comparison for numbers
111
+ - **Expected:** sortOrder field sorts numerically
112
+ - **Actual:** `localeCompare` on numbers gives wrong order
113
+ - **Fix:** Detect numeric values and use `a - b` comparison
114
+
115
+ ### SVG transforms: two-group rotation approach
116
+ - **Expected:** Embed rotation in shapeTransform string
117
+ - **Actual:** Rotation center in local coords doesn't match screen position
118
+ - **Fix:** Outer `<g>` for rotation (screen-space center), inner `<g>` for translate+scale
119
+ - **Documented in:** COMPONENT_PATTERNS.md → Coordinate Transforms
120
+
121
+ ### Move after rotation: unrotate is wrong for translate
122
+ - **Expected:** Unrotate screen delta for the inner translate
123
+ - **Actual:** Causes magnified/skewed movement
124
+ - **Fix:** Move both rotation center AND inner translate by raw screen delta
125
+ - **Key insight:** `rotate(deg, cx, cy)` = `translate(cx,cy) rotate(deg) translate(-cx,-cy)`. Shifting cx,cy and translate by the same delta cancels out.
126
+
127
+ ### Resize after rotation: unrotate IS needed
128
+ - **Expected:** Same approach as move
129
+ - **Actual:** Handles are visually rotated, so screen drag doesn't align with object axes
130
+ - **Fix:** Unrotate resize deltas only, not move deltas
131
+
132
+ ### SVG innerHTML destroys event listeners
133
+ - **Expected:** Event listeners persist across renders
134
+ - **Actual:** `innerHTML` replaces the DOM, losing all listeners
135
+ - **Fix:** Re-bind mouse listeners in `observe`, attach keyboard to host element
136
+ - **Documented in:** COMPONENT_PATTERNS.md → SVG Content via innerHTML
137
+
138
+ ### Path d-string manipulation breaks arc commands
139
+ - **Expected:** Regex replace on coordinate pairs works for all paths
140
+ - **Actual:** Arc commands have flags (0/1) that get mangled
141
+ - **Fix:** Use `shapeTransform` for complex shapes, only rewrite d for M/L paths
142
+
143
+ ### Canvas pan offset not applied to drawing coordinates
144
+ - **Expected:** Drawing at the visual position works after panning
145
+ - **Actual:** Coordinates calculated from SVG rect, not accounting for pan
146
+ - **Fix:** Shared `canvasPos()` utility subtracts pan offset from all tools
147
+
148
+ ---
149
+
150
+ ## Metrics
151
+
152
+ | Metric | Value |
153
+ |---|---|
154
+ | Total source files | 108 |
155
+ | Utility modules | 25 |
156
+ | Component files | 13 |
157
+ | Style sheets | 6 |
158
+ | API modules | 6 |
159
+ | Page views | 2 |
160
+ | Test files | 14 |
161
+ | Node tests | 65 |
162
+ | Browser tests | 41 |
163
+ | Spec documents | 10 |
164
+ | Max lines per file | 150 (enforced) |
165
+ | Max lines per doc | 500 (enforced) |
166
+ | Automated checks | 7 (line counts, lint, format, types, tests) |
167
+ | Build phases | 8 + whiteboard |
168
+ | Bugs found & fixed | ~25 significant |
169
+ | Bugs requiring >4 iterations | 3 (drag reorder, event bubbling, SVG transforms) |
170
+ | External dependencies | 4 runtime (hybrids, express, ws, lucide-static) |
171
+ | Build tools | 0 |
172
+
173
+ ---
174
+
175
+ ## What This Proves
176
+
177
+ 1. **Small files work for LLMs.** Every file fits in a single read. No scrolling,
178
+ no "show me lines 200-300", no losing context.
179
+
180
+ 2. **The spec catches drift.** When implementation diverged from the spec
181
+ (e.g. using slots in light DOM), the spec checker or manual review caught
182
+ it, and both the code and spec were corrected.
183
+
184
+ 3. **Test-at-the-boundary works.** Each phase was tested before the next began.
185
+ The two hardest bugs (drag reorder, event bubbling) were in the last phases
186
+ where components composed deeply — exactly where integration tests matter.
187
+
188
+ 4. **No-build is viable.** The app loads in 0.17s LCP, handles real-time sync
189
+ across browsers, and every file is debuggable as-written in devtools.
190
+
191
+ 5. **The spec improves through implementation.** 9 significant corrections were
192
+ made to the spec based on what we learned building the proof. The spec is
193
+ now more accurate than if it had been written in isolation.