@techninja/clearstack 0.2.16 → 0.2.19

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 (38) hide show
  1. package/docs/BACKEND_API_SPEC.md +85 -48
  2. package/docs/BUILD_LOG.md +42 -19
  3. package/docs/COMPONENT_PATTERNS.md +57 -51
  4. package/docs/CONVENTIONS.md +43 -31
  5. package/docs/FRONTEND_IMPLEMENTATION_RULES.md +57 -58
  6. package/docs/JSDOC_TYPING.md +1 -0
  7. package/docs/QUICKSTART.md +20 -18
  8. package/docs/SERVER_AND_DEPS.md +28 -29
  9. package/docs/STATE_AND_ROUTING.md +53 -52
  10. package/docs/TESTING.md +38 -37
  11. package/docs/app-spec/ENTITIES.md +16 -16
  12. package/docs/app-spec/README.md +4 -4
  13. package/lib/check.js +42 -77
  14. package/lib/package-gen.js +3 -0
  15. package/lib/spec-utils.js +109 -0
  16. package/package.json +5 -2
  17. package/templates/fullstack/data/seed.json +1 -1
  18. package/templates/shared/.configs/.markdownlint.jsonc +9 -0
  19. package/templates/shared/.configs/.stylelintrc.json +16 -0
  20. package/templates/shared/.configs/jsconfig.json +2 -9
  21. package/templates/shared/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  22. package/templates/shared/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  23. package/templates/shared/.github/ISSUE_TEMPLATE/spec_correction.md +1 -1
  24. package/templates/shared/.github/pull_request_template.md +3 -0
  25. package/templates/shared/.github/workflows/spec.yml +3 -3
  26. package/templates/shared/docs/app-spec/README.md +8 -8
  27. package/templates/shared/docs/clearstack/BACKEND_API_SPEC.md +85 -48
  28. package/templates/shared/docs/clearstack/BUILD_LOG.md +42 -19
  29. package/templates/shared/docs/clearstack/COMPONENT_PATTERNS.md +57 -51
  30. package/templates/shared/docs/clearstack/CONVENTIONS.md +43 -31
  31. package/templates/shared/docs/clearstack/FRONTEND_IMPLEMENTATION_RULES.md +57 -58
  32. package/templates/shared/docs/clearstack/JSDOC_TYPING.md +1 -0
  33. package/templates/shared/docs/clearstack/QUICKSTART.md +20 -18
  34. package/templates/shared/docs/clearstack/SERVER_AND_DEPS.md +28 -29
  35. package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +53 -52
  36. package/templates/shared/docs/clearstack/TESTING.md +38 -37
  37. package/templates/shared/src/public/index.html +23 -23
  38. package/templates/shared/src/styles/shared.css +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techninja/clearstack",
3
- "version": "0.2.16",
3
+ "version": "0.2.19",
4
4
  "type": "module",
5
5
  "description": "A no-build web component framework specification — scaffold, validate, and evolve spec-compliant projects",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "test": "node --test tests/*.test.js src/utils/*.test.js src/store/*.test.js",
24
24
  "spec": "node --env-file=.env scripts/spec.js",
25
25
  "lint": "eslint --config .configs/eslint.config.js . --fix",
26
- "format": "prettier --config .configs/.prettierrc --write src scripts tests",
26
+ "format": "prettier --config .configs/.prettierrc --write src scripts tests templates/**/*.md templates/**/*.html templates/**/*.css templates/**/*.json",
27
27
  "typecheck": "tsc --project .configs/jsconfig.json",
28
28
  "release": "node scripts/release.js",
29
29
  "prepublishOnly": "node scripts/sync-docs.js"
@@ -54,8 +54,11 @@
54
54
  "express": "^5.2.1",
55
55
  "hybrids": "^9.1.22",
56
56
  "lucide-static": "^1.7.0",
57
+ "markdownlint-cli2": "^0.21.0",
57
58
  "playwright": "^1.50.0",
58
59
  "prettier": "^3.8.1",
60
+ "stylelint": "^17.6.0",
61
+ "stylelint-config-standard": "^40.0.0",
59
62
  "typescript": "^6.0.2",
60
63
  "ws": "^8.20.0"
61
64
  }
@@ -1 +1 @@
1
- {"example": {}}
1
+ { "example": {} }
@@ -0,0 +1,9 @@
1
+ {
2
+ "default": true,
3
+ "MD013": false,
4
+ "MD024": { "siblings_only": true },
5
+ "MD033": false,
6
+ "MD040": false,
7
+ "MD041": false,
8
+ "MD060": false,
9
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "stylelint-config-standard",
3
+ "rules": {
4
+ "import-notation": "string",
5
+ "selector-class-pattern": null,
6
+ "custom-property-pattern": null,
7
+ "no-descending-specificity": null,
8
+ "rule-empty-line-before": [
9
+ "always",
10
+ {
11
+ "except": ["first-nested"],
12
+ "ignore": ["after-comment"]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -13,13 +13,6 @@
13
13
  "hybrids": ["../node_modules/hybrids/types/index.d.ts"]
14
14
  }
15
15
  },
16
- "include": [
17
- "../src/**/*.js",
18
- "../scripts/**/*.js"
19
- ],
20
- "exclude": [
21
- "../node_modules",
22
- "../src/public/vendor",
23
- "../**/*.test.js"
24
- ]
16
+ "include": ["../src/**/*.js", "../scripts/**/*.js"],
17
+ "exclude": ["../node_modules", "../src/public/vendor", "../**/*.test.js"]
25
18
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: Bug Report
3
3
  about: Something isn't working as expected
4
- title: "[Bug] "
4
+ title: '[Bug] '
5
5
  labels: bug
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: Feature Request
3
3
  about: Suggest a new feature or improvement
4
- title: "[Feature] "
4
+ title: '[Feature] '
5
5
  labels: enhancement
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: Spec Correction
3
3
  about: The spec says one thing but implementation reveals another
4
- title: "[Spec] "
4
+ title: '[Spec] '
5
5
  labels: spec
6
6
  ---
7
7
 
@@ -11,12 +11,15 @@
11
11
  <!-- List the key changes. Group by category if helpful. -->
12
12
 
13
13
  ### Added
14
+
14
15
  -
15
16
 
16
17
  ### Changed
18
+
17
19
  -
18
20
 
19
21
  ### Fixed
22
+
20
23
  -
21
24
 
22
25
  ## Spec Compliance
@@ -12,11 +12,11 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@v5
15
+ - uses: actions/checkout@v6
16
16
 
17
- - uses: actions/setup-node@v5
17
+ - uses: actions/setup-node@v6
18
18
  with:
19
- node-version: 20
19
+ node-version: 24
20
20
  cache: npm
21
21
 
22
22
  - run: npm ci
@@ -12,14 +12,14 @@ and architectural decisions live.
12
12
 
13
13
  ## What to Document
14
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 |
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
23
 
24
24
  ## Rules
25
25
 
@@ -1,4 +1,5 @@
1
1
  # Backend API Specification
2
+
2
3
  ## REST Endpoints, JSON Schema Discovery & Realtime Sync
3
4
 
4
5
  > Defines the server-side data contract. The frontend consumes this API via
@@ -70,25 +71,25 @@ The `Allow` header is also set for HTTP compliance.
70
71
  The frontend can fetch the schema at runtime and generate form fields
71
72
  automatically. The schema provides:
72
73
 
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 |
74
+ | JSON Schema keyword | Form behavior |
75
+ | ------------------------- | ---------------------------------------------- |
76
+ | `type: "string"` | `<input type="text">` |
77
+ | `format: "email"` | `<input type="email">` |
78
+ | `format: "date-time"` | `<input type="datetime-local">` |
79
+ | `enum: [...]` | `<select>` with options |
80
+ | `minLength` / `maxLength` | Validation constraints |
81
+ | `readOnly: true` | Field displayed but not editable |
82
+ | `required: [...]` | Native `required` attribute + visual indicator |
82
83
 
83
84
  JSON Schema constraints map directly to HTML validation attributes:
84
85
 
85
86
  | JSON Schema | HTML attribute |
86
- |---|---|
87
- | `minLength` | `minlength` |
88
- | `maxLength` | `maxlength` |
89
- | `minimum` | `min` |
90
- | `maximum` | `max` |
91
- | `pattern` | `pattern` |
87
+ | ----------- | -------------- |
88
+ | `minLength` | `minlength` |
89
+ | `maxLength` | `maxlength` |
90
+ | `minimum` | `min` |
91
+ | `maximum` | `max` |
92
+ | `pattern` | `pattern` |
92
93
 
93
94
  The browser's native constraint validation API enforces these — no custom
94
95
  JS validation needed for client-side checks.
@@ -115,13 +116,13 @@ The `schema-form` component maps `fields` entries to the corresponding
115
116
 
116
117
  ### Error Response Contract
117
118
 
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 |
119
+ | Status | Body | Meaning |
120
+ | ------ | ------------------- | --------------------------------------------------- |
121
+ | `422` | `{ error, fields }` | Validation failed — `fields` maps names to messages |
122
+ | `404` | `{ error }` | Entity or collection not found |
123
+ | `201` | Entity JSON | Created successfully |
124
+ | `200` | Entity JSON | Updated successfully |
125
+ | `204` | Empty | Deleted successfully |
125
126
 
126
127
  This allows a single generic `schema-form` component to render any entity's
127
128
  create/edit form without entity-specific template code.
@@ -134,12 +135,12 @@ create/edit form without entity-specific template code.
134
135
 
135
136
  Query params for filtering, sorting, pagination:
136
137
 
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 |
138
+ | Param | Example | Purpose |
139
+ | -------- | ------------------ | ------------------------------- |
140
+ | `limit` | `?limit=20` | Page size (default: 20) |
141
+ | `offset` | `?offset=40` | Skip N records |
142
+ | `sort` | `?sort=-createdAt` | Sort field, `-` prefix for desc |
143
+ | `filter` | `?role=admin` | Field equality filter |
143
144
 
144
145
  Response:
145
146
 
@@ -200,11 +201,11 @@ event: update
200
201
  data: {"type":"user","id":"2","action":"deleted"}
201
202
  ```
202
203
 
203
- | Field | Value |
204
- |---|---|
205
- | `type` | Entity name (lowercase, singular): `user`, `post` |
206
- | `id` | Entity ID that changed |
207
- | `action` | `created`, `updated`, or `deleted` |
204
+ | Field | Value |
205
+ | -------- | ------------------------------------------------- |
206
+ | `type` | Entity name (lowercase, singular): `user`, `post` |
207
+ | `id` | Entity ID that changed |
208
+ | `action` | `created`, `updated`, or `deleted` |
208
209
 
209
210
  ### Frontend Integration
210
211
 
@@ -226,11 +227,44 @@ required.
226
227
  const db = new Map();
227
228
 
228
229
  // 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
- ]));
230
+ db.set(
231
+ 'users',
232
+ new Map([
233
+ [
234
+ '1',
235
+ {
236
+ id: '1',
237
+ firstName: 'Jane',
238
+ lastName: 'Doe',
239
+ email: 'jane@example.com',
240
+ role: 'admin',
241
+ createdAt: '2024-01-15T09:00:00Z',
242
+ },
243
+ ],
244
+ [
245
+ '2',
246
+ {
247
+ id: '2',
248
+ firstName: 'John',
249
+ lastName: 'Smith',
250
+ email: 'john@example.com',
251
+ role: 'editor',
252
+ createdAt: '2024-02-20T14:30:00Z',
253
+ },
254
+ ],
255
+ [
256
+ '3',
257
+ {
258
+ id: '3',
259
+ firstName: 'Alex',
260
+ lastName: 'Chen',
261
+ email: 'alex@example.com',
262
+ role: 'viewer',
263
+ createdAt: '2024-03-10T11:15:00Z',
264
+ },
265
+ ],
266
+ ]),
267
+ );
234
268
  ```
235
269
 
236
270
  ### Schema Registry (server-side)
@@ -244,7 +278,9 @@ schemas.set('users', {
244
278
  title: 'User',
245
279
  type: 'object',
246
280
  required: ['firstName', 'lastName', 'email'],
247
- properties: { /* ... as above ... */ },
281
+ properties: {
282
+ /* ... as above ... */
283
+ },
248
284
  });
249
285
  ```
250
286
 
@@ -262,17 +298,18 @@ router that works for any entity registered in the schema map.
262
298
 
263
299
  ## Frontend ↔ Backend Contract
264
300
 
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) |
301
+ | Frontend (Hybrids Store) | Backend (Express) |
302
+ | --------------------------------- | -------------------------- |
303
+ | `[store.connect].get(id)` | `GET /api/:entity/:id` |
304
+ | `[store.connect].set(id, values)` | `PUT /api/:entity/:id` |
305
+ | `[store.connect].list(params)` | `GET /api/:entity?...` |
306
+ | `store.set(model, null)` (delete) | `DELETE /api/:entity/:id` |
307
+ | Schema fetch for forms | `OPTIONS /api/:entity` |
308
+ | Schema fetch for item | `OPTIONS /api/:entity/:id` |
309
+ | Realtime invalidation | `GET /api/events` (SSE) |
274
310
 
275
311
  This contract means adding a new entity type requires:
312
+
276
313
  - One JSON Schema definition (server)
277
314
  - One store model file (frontend)
278
315
  - Dummy seed data (server, for dev)
@@ -1,4 +1,5 @@
1
1
  # Build Log
2
+
2
3
  ## How This Project Was Built
3
4
 
4
5
  > This entire repository — specification, implementation, tests, and this
@@ -13,33 +14,39 @@ The project was built over ~1.5 days in a single LLM context window.
13
14
  Each phase was implemented, tested, and verified before proceeding.
14
15
 
15
16
  ### Phase 1: Specification
17
+
16
18
  - Wrote the initial spec as one file, hit ~500 lines
17
19
  - Split into 7 topic-specific documents (the spec eating its own dogfood)
18
20
  - Chose Hybrids.js after reading its source and type definitions from node_modules
19
21
 
20
22
  ### Phase 2: Infrastructure
23
+
21
24
  - Express server, vendor-deps script, import maps, HTML shell
22
25
  - JSON Schema registry with seed data
23
26
  - Generic CRUD router — one router handles all entity types
24
27
  - 15 server tests passing before writing any frontend code
25
28
 
26
29
  ### Phase 3: Store Models & Utils
30
+
27
31
  - Singleton models (AppState, UserPrefs) with localStorage connectors
28
32
  - Enumerable models (ProjectModel, TaskModel) with API connectors
29
33
  - Utility functions (formatDate, timeAgo, statusColors)
30
34
  - 35 tests passing
31
35
 
32
36
  ### Phase 4: Components (Atoms → Molecules → Organisms)
37
+
33
38
  - Built bottom-up: app-button, app-badge, app-icon → task-card, project-card → task-list, project-header
34
39
  - Browser tests via @web/test-runner + Playwright
35
40
  - 63 tests passing
36
41
 
37
42
  ### Phase 5: Pages, Router & Integration
43
+
38
44
  - page-layout template, home-view, project-view, app-router
39
45
  - SSE realtime sync wired at the router level
40
46
  - SPA fallback for direct URL navigation
41
47
 
42
48
  ### Phase 6: Schema-Driven Forms
49
+
43
50
  - OPTIONS endpoint returns JSON Schema + form layout
44
51
  - schema-form organism auto-generates fields from schema
45
52
  - Native HTML validation from schema constraints
@@ -47,6 +54,7 @@ Each phase was implemented, tested, and verified before proceeding.
47
54
  - Form layout system with column grouping and action alignment
48
55
 
49
56
  ### Phase 7: Polish & Tooling
57
+
50
58
  - Dark mode via CSS custom property overrides
51
59
  - Spec checker CLI with interactive menu
52
60
  - ESLint + Prettier + tsc --checkJs for JSDoc type validation
@@ -54,6 +62,7 @@ Each phase was implemented, tested, and verified before proceeding.
54
62
  - File reorganization (docs/, .configs/, tests/)
55
63
 
56
64
  ### Phase 8: Drag Reorder & Whiteboard
65
+
57
66
  - Drag-to-reorder with visual gap indicator and backend persistence
58
67
  - WebSocket server for real-time canvas collaboration
59
68
  - SVG whiteboard with drawing tools (in progress)
@@ -66,81 +75,95 @@ The spec was written first, then corrected as implementation revealed gaps.
66
75
  These are the significant corrections:
67
76
 
68
77
  ### Light DOM breaks slots
78
+
69
79
  - **Expected:** `<slot>` for content composition
70
80
  - **Actual:** Hybrids throws on `<slot>` in light DOM
71
81
  - **Fix:** Template functions instead of template components
72
82
  - **Documented in:** COMPONENT_PATTERNS.md → Content Composition
73
83
 
74
84
  ### Template components break host context
85
+
75
86
  - **Expected:** Event handlers in content templates resolve to the page
76
87
  - **Actual:** `host` resolves to the nearest hybrids component (the template)
77
88
  - **Fix:** Use plain functions that return `html`, not `define()`'d components
78
89
  - **Documented in:** COMPONENT_PATTERNS.md → Event Handler Host Context
79
90
 
80
91
  ### Custom events don't bubble by default
92
+
81
93
  - **Expected:** `dispatch(host, 'press')` reaches parent listeners
82
94
  - **Actual:** Without `bubbles: true`, events stop at the dispatching element
83
95
  - **Fix:** Always `dispatch(host, 'event', { bubbles: true })`
84
96
  - **Documented in:** COMPONENT_PATTERNS.md → Custom Events Must Bubble
85
97
 
86
98
  ### store.clear(Model) vs store.clear([Model])
99
+
87
100
  - **Expected:** `store.clear(TaskModel)` refreshes task lists
88
101
  - **Actual:** Only clears singular cache, not list cache
89
102
  - **Fix:** Use `store.clear([TaskModel])` for list stores
90
103
  - **Documented in:** STATE_AND_ROUTING.md → Store API Quick Reference
91
104
 
92
105
  ### store.ready(list) doesn't guarantee item readiness
106
+
93
107
  - **Expected:** If the list is ready, items are ready
94
108
  - **Actual:** Individual items can be pending after cache clear
95
109
  - **Fix:** Guard each item with `store.ready(task)` in map()
96
110
  - **Documented in:** STATE_AND_ROUTING.md → Guarding List Item Access
97
111
 
98
112
  ### localStorage connector must return {}, not undefined
113
+
99
114
  - **Expected:** Returning undefined uses model defaults
100
115
  - **Actual:** Hybrids treats undefined as a failed get → error state
101
116
  - **Fix:** Return `{}` so hybrids merges with defaults
102
117
  - **Documented in:** STATE_AND_ROUTING.md → localStorage Connector
103
118
 
104
119
  ### Batch operations cause SSE storms
120
+
105
121
  - **Expected:** Reordering N tasks sends N updates, UI handles it
106
122
  - **Actual:** N SSE events → N store.clear() calls → cascading errors
107
123
  - **Fix:** Debounce SSE handler per entity type (300ms)
108
124
  - **Documented in:** STATE_AND_ROUTING.md → Debouncing Batch Operations
109
125
 
110
126
  ### Server sort used string comparison for numbers
127
+
111
128
  - **Expected:** sortOrder field sorts numerically
112
129
  - **Actual:** `localeCompare` on numbers gives wrong order
113
130
  - **Fix:** Detect numeric values and use `a - b` comparison
114
131
 
115
132
  ### SVG transforms: two-group rotation approach
133
+
116
134
  - **Expected:** Embed rotation in shapeTransform string
117
135
  - **Actual:** Rotation center in local coords doesn't match screen position
118
136
  - **Fix:** Outer `<g>` for rotation (screen-space center), inner `<g>` for translate+scale
119
137
  - **Documented in:** COMPONENT_PATTERNS.md → Coordinate Transforms
120
138
 
121
139
  ### Move after rotation: unrotate is wrong for translate
140
+
122
141
  - **Expected:** Unrotate screen delta for the inner translate
123
142
  - **Actual:** Causes magnified/skewed movement
124
143
  - **Fix:** Move both rotation center AND inner translate by raw screen delta
125
144
  - **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
145
 
127
146
  ### Resize after rotation: unrotate IS needed
147
+
128
148
  - **Expected:** Same approach as move
129
149
  - **Actual:** Handles are visually rotated, so screen drag doesn't align with object axes
130
150
  - **Fix:** Unrotate resize deltas only, not move deltas
131
151
 
132
152
  ### SVG innerHTML destroys event listeners
153
+
133
154
  - **Expected:** Event listeners persist across renders
134
155
  - **Actual:** `innerHTML` replaces the DOM, losing all listeners
135
156
  - **Fix:** Re-bind mouse listeners in `observe`, attach keyboard to host element
136
157
  - **Documented in:** COMPONENT_PATTERNS.md → SVG Content via innerHTML
137
158
 
138
159
  ### Path d-string manipulation breaks arc commands
160
+
139
161
  - **Expected:** Regex replace on coordinate pairs works for all paths
140
162
  - **Actual:** Arc commands have flags (0/1) that get mangled
141
163
  - **Fix:** Use `shapeTransform` for complex shapes, only rewrite d for M/L paths
142
164
 
143
165
  ### Canvas pan offset not applied to drawing coordinates
166
+
144
167
  - **Expected:** Drawing at the visual position works after panning
145
168
  - **Actual:** Coordinates calculated from SVG rect, not accounting for pan
146
169
  - **Fix:** Shared `canvasPos()` utility subtracts pan offset from all tools
@@ -149,26 +172,26 @@ These are the significant corrections:
149
172
 
150
173
  ## Metrics
151
174
 
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 |
175
+ | Metric | Value |
176
+ | ---------------------------- | ------------------------------------------------ |
177
+ | Total source files | 108 |
178
+ | Utility modules | 25 |
179
+ | Component files | 13 |
180
+ | Style sheets | 6 |
181
+ | API modules | 6 |
182
+ | Page views | 2 |
183
+ | Test files | 14 |
184
+ | Node tests | 65 |
185
+ | Browser tests | 41 |
186
+ | Spec documents | 10 |
187
+ | Max lines per file | 150 (enforced) |
188
+ | Max lines per doc | 500 (enforced) |
189
+ | Automated checks | 7 (line counts, lint, format, types, tests) |
190
+ | Build phases | 8 + whiteboard |
191
+ | Bugs found & fixed | ~25 significant |
169
192
  | 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 |
193
+ | External dependencies | 4 runtime (hybrids, express, ws, lucide-static) |
194
+ | Build tools | 0 |
172
195
 
173
196
  ---
174
197