@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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/bin/cli.js +62 -0
- package/docs/BACKEND_API_SPEC.md +281 -0
- package/docs/BUILD_LOG.md +193 -0
- package/docs/COMPONENT_PATTERNS.md +481 -0
- package/docs/CONVENTIONS.md +226 -0
- package/docs/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
- package/docs/JSDOC_TYPING.md +86 -0
- package/docs/QUICKSTART.md +190 -0
- package/docs/SERVER_AND_DEPS.md +163 -0
- package/docs/STATE_AND_ROUTING.md +363 -0
- package/docs/TESTING.md +268 -0
- package/docs/app-spec/ENTITIES.md +37 -0
- package/docs/app-spec/README.md +19 -0
- package/lib/check.js +115 -0
- package/lib/copy.js +43 -0
- package/lib/init.js +73 -0
- package/lib/package-gen.js +83 -0
- package/lib/update.js +73 -0
- package/package.json +69 -0
- package/templates/fullstack/data/seed.json +1 -0
- package/templates/fullstack/src/api/db.js +75 -0
- package/templates/fullstack/src/api/entities.js +114 -0
- package/templates/fullstack/src/api/events.js +35 -0
- package/templates/fullstack/src/api/schemas.js +104 -0
- package/templates/fullstack/src/api/validate.js +52 -0
- package/templates/fullstack/src/pages/home/home-view.js +19 -0
- package/templates/fullstack/src/router/index.js +16 -0
- package/templates/fullstack/src/server.js +46 -0
- package/templates/fullstack/src/store/AppState.js +33 -0
- package/templates/fullstack/src/store/UserPrefs.js +31 -0
- package/templates/fullstack/src/store/realtimeSync.js +54 -0
- package/templates/shared/.configs/.prettierrc +8 -0
- package/templates/shared/.configs/eslint.config.js +64 -0
- package/templates/shared/.configs/jsconfig.json +24 -0
- package/templates/shared/.configs/web-test-runner.config.js +8 -0
- package/templates/shared/.env +9 -0
- package/templates/shared/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- package/templates/shared/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- package/templates/shared/.github/ISSUE_TEMPLATE/spec_correction.md +26 -0
- package/templates/shared/.github/pull_request_template.md +51 -0
- package/templates/shared/.github/workflows/spec.yml +46 -0
- package/templates/shared/README.md +22 -0
- package/templates/shared/docs/app-spec/README.md +40 -0
- package/templates/shared/docs/clearstack/BACKEND_API_SPEC.md +281 -0
- package/templates/shared/docs/clearstack/BUILD_LOG.md +193 -0
- package/templates/shared/docs/clearstack/COMPONENT_PATTERNS.md +481 -0
- package/templates/shared/docs/clearstack/CONVENTIONS.md +226 -0
- package/templates/shared/docs/clearstack/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
- package/templates/shared/docs/clearstack/JSDOC_TYPING.md +86 -0
- package/templates/shared/docs/clearstack/QUICKSTART.md +190 -0
- package/templates/shared/docs/clearstack/SERVER_AND_DEPS.md +163 -0
- package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +363 -0
- package/templates/shared/docs/clearstack/TESTING.md +268 -0
- package/templates/shared/public/index.html +26 -0
- package/templates/shared/scripts/build-icons.js +86 -0
- package/templates/shared/scripts/vendor-deps.js +25 -0
- package/templates/shared/src/components/atoms/app-badge/app-badge.css +4 -0
- package/templates/shared/src/components/atoms/app-badge/app-badge.js +23 -0
- package/templates/shared/src/components/atoms/app-badge/app-badge.test.js +26 -0
- package/templates/shared/src/components/atoms/app-badge/index.js +1 -0
- package/templates/shared/src/components/atoms/app-button/app-button.css +3 -0
- package/templates/shared/src/components/atoms/app-button/app-button.js +41 -0
- package/templates/shared/src/components/atoms/app-button/app-button.test.js +43 -0
- package/templates/shared/src/components/atoms/app-button/index.js +1 -0
- package/templates/shared/src/components/atoms/app-icon/app-icon.css +4 -0
- package/templates/shared/src/components/atoms/app-icon/app-icon.js +57 -0
- package/templates/shared/src/components/atoms/app-icon/app-icon.test.js +30 -0
- package/templates/shared/src/components/atoms/app-icon/index.js +1 -0
- package/templates/shared/src/components/atoms/theme-toggle/index.js +1 -0
- package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.css +10 -0
- package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.js +42 -0
- package/templates/shared/src/styles/buttons.css +79 -0
- package/templates/shared/src/styles/components.css +31 -0
- package/templates/shared/src/styles/forms.css +20 -0
- package/templates/shared/src/styles/reset.css +32 -0
- package/templates/shared/src/styles/shared.css +135 -0
- package/templates/shared/src/styles/tokens.css +65 -0
- package/templates/shared/src/utils/formatDate.js +41 -0
- package/templates/shared/src/utils/statusColors.js +60 -0
- package/templates/static/src/pages/home/home-view.js +38 -0
- package/templates/static/src/router/index.js +16 -0
- 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.
|