@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
> {{description}}
|
|
4
|
+
|
|
5
|
+
Built with the [Clearstack](https://github.com/techninja/clearstack) no-build web component specification.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev # Start dev server
|
|
12
|
+
npm test # Run tests
|
|
13
|
+
npm run spec # Spec compliance checker
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Specification
|
|
17
|
+
|
|
18
|
+
See `docs/` for the full specification this project follows.
|
|
19
|
+
|
|
20
|
+
## License
|
|
21
|
+
|
|
22
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Application-Specific Specifications
|
|
2
|
+
|
|
3
|
+
> This directory is yours. The upstream spec will never touch it.
|
|
4
|
+
|
|
5
|
+
## What Goes Here
|
|
6
|
+
|
|
7
|
+
Files in `docs/clearstack/` are managed by the upstream package.
|
|
8
|
+
Running `npx clearstack update` may overwrite them with newer versions.
|
|
9
|
+
**This `docs/app-spec/` directory is excluded from upstream updates** —
|
|
10
|
+
it's where your project's own conventions, domain-specific patterns,
|
|
11
|
+
and architectural decisions live.
|
|
12
|
+
|
|
13
|
+
## What to Document
|
|
14
|
+
|
|
15
|
+
| Document | Example content |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `ENTITIES.md` | Your domain entities, their relationships, field descriptions |
|
|
18
|
+
| `PATTERNS.md` | Project-specific component patterns, naming overrides |
|
|
19
|
+
| `API.md` | Custom API endpoints beyond the generic CRUD |
|
|
20
|
+
| `DEPLOYMENT.md` | How this project is built, deployed, and monitored |
|
|
21
|
+
| `DECISIONS.md` | Architecture Decision Records — why you chose X over Y |
|
|
22
|
+
| `OVERRIDES.md` | Where your project intentionally deviates from the base spec and why |
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- **One topic per file.** Same as the base spec — split when it grows.
|
|
27
|
+
- **Link to the base spec** when extending it, not duplicating it.
|
|
28
|
+
- **Document deviations explicitly.** If you override a base spec convention,
|
|
29
|
+
say which one and why. Future you (or your LLM) will thank you.
|
|
30
|
+
- **Keep it current.** If a pattern changes, update the doc in the same PR.
|
|
31
|
+
|
|
32
|
+
## How Updates Work
|
|
33
|
+
|
|
34
|
+
When you run `npx clearstack update`:
|
|
35
|
+
|
|
36
|
+
1. Files in `docs/clearstack/` are compared against the upstream package
|
|
37
|
+
2. Changed files are overwritten — review with `git diff docs/`
|
|
38
|
+
3. Files in `.configs/` are also synced to latest standards
|
|
39
|
+
4. **`docs/app-spec/` is never touched** — your files are safe
|
|
40
|
+
5. The `docs/clearstack/.specversion` file tracks which upstream version you synced from
|
|
@@ -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.
|