@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
@@ -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.