@orkestrel/scaffold 0.0.81 → 0.0.82

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 (35) hide show
  1. package/dist/bin/main.js +167 -13
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/AGENTS.md +5 -3
  4. package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +2 -2
  5. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +41 -38
  6. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +3 -3
  7. package/dist/host/agents/skills/orkestrel-journey/references/decide.md +3 -3
  8. package/dist/host/agents/skills/orkestrel-journey/references/layer.md +5 -5
  9. package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +3 -3
  10. package/dist/host/agents/skills/orkestrel-journey/references/styles.md +9 -9
  11. package/dist/host/claude/rules/application.md +20 -6
  12. package/dist/host/claude/rules/architecture.md +2 -2
  13. package/dist/host/claude/rules/browser.md +9 -0
  14. package/dist/host/claude/rules/documentation.md +2 -1
  15. package/dist/host/claude/rules/styles.md +35 -12
  16. package/dist/host/claude/rules/tests.md +15 -10
  17. package/dist/host/claude/rules/workspace.md +128 -98
  18. package/dist/host/claude/skills/orkestrel-journey/SKILL.md +1 -1
  19. package/dist/host/configs/helpers.ts +157 -9
  20. package/dist/host/configs/policy.ts +64 -61
  21. package/dist/host/dotfiles/oxlintrc.json +132 -16
  22. package/dist/host/dotfiles/prettierignore +1 -1
  23. package/dist/host/guides/README.md +9 -5
  24. package/dist/host/guides/scaffold.md +475 -115
  25. package/dist/host/manifest.json +26 -26
  26. package/dist/host/tests/config.test.ts +996 -39
  27. package/dist/host/tests/policy.test.ts +11 -0
  28. package/dist/host/tests/setupPolicy.ts +396 -22
  29. package/dist/src/core/index.cjs +1261 -159
  30. package/dist/src/core/index.cjs.map +1 -1
  31. package/dist/src/core/index.d.cts +297 -35
  32. package/dist/src/core/index.d.ts +297 -35
  33. package/dist/src/core/index.js +1241 -160
  34. package/dist/src/core/index.js.map +1 -1
  35. package/package.json +1 -1
@@ -16,14 +16,16 @@
16
16
  ## Project model
17
17
 
18
18
  ```text
19
- src/ published library: core, browser, server, optional styles
20
- app/ application: core, browser, server
19
+ src/ published library: core, browser, server, optional styles, extension faces
20
+ app/ application: core, browser, server, extension faces
21
21
  tests/ mirrors source; setup*.ts owns shared test infrastructure
22
22
  configs/ thin target wrappers around root Vite/TypeScript configuration
23
23
  ```
24
24
 
25
25
  - `core` is host-independent. Browser and server may import core; core imports neither; browser and server never import each other.
26
26
  - `app/core` is host-independent. `app/server` may import `app/core`, `src/core`, and `src/server`, never browser code. `app/browser` may import `app/core`, `src/core`, and `src/browser`, and reaches server behavior through shared contracts and transports only.
27
+ - The browser surface is `src/browser`, `app/browser`, the journey, and the showcase; the styles surface is `src/styles` and its themes. An extension adds a face to a surface: the `vue` browser extension adds `src/vue` and `app/vue`, and a named styles extension adds `src/<name>`.
28
+ - `src/vue` and each `src/<name>` may import `src/core` and `src/browser`. `app/vue` may import what `app/browser` may import, plus `app/browser` and `src/vue`. No extension face imports server code or another extension's face.
27
29
  - Published source never imports private app code.
28
30
  - Enforce boundaries with the toolchain (Oxlint import restrictions, scoped TypeScript projects, Vite graphs). Add no second parser for TypeScript, Oxlint, Vue, HTML, CSS, or Vite.
29
31
  - `tsconfig.json`, `vite.config.ts`, and each `*/types.ts` are their sources of truth. Keep structural files even when empty.
@@ -57,7 +59,7 @@ configs/ thin target wrappers around root Vite/TypeScript configuration
57
59
  - **Named discriminants.** Name the axis (`relationship`, `command`, `category`), never `kind` or `type`.
58
60
  - **Centralize by kind.** Types, constants, helpers, validators, parsers, factories, and errors live in their kind file. An implementation file holds one class plus imports.
59
61
  - **Export and test reusable logic.** Fold a trivial one-use helper into its caller or export it from its kind file and test it.
60
- - **No nested functions**, except an anonymous callback passed as an argument or returned as the result.
62
+ - **No nested functions**, except a callback passed as an argument or returned as the result; `.claude/rules/architecture.md` § Functions and orchestration bounds the literal positions it climbs.
61
63
  - **Functional core, imperative shell.** Pure exported leaves; stateful orchestration as class methods. A method never forwards 1:1 to a helper.
62
64
  - **No superfluous wrappers.** A wrapper adds a boundary, invariant, composition, translation, lifecycle, or materially narrower contract, or it goes.
63
65
  - **Minimal public API.** Create or substantively expand a capability with its first real consumer; this gate applies at creation, never later. Expose an existing reusable capability through its environment barrel regardless of consumer count. Remove a symbol only when the capability itself must not exist. Prefer one minimal interface and one shared engine, with a native backend override only for a faster path.
@@ -28,8 +28,8 @@ execution and must explain why sibling imports cannot work.
28
28
  | Defining recursive or compositional spine | Class method, after extracting its pure leaves |
29
29
  | Trivial and genuinely one-use | Inline it into the caller |
30
30
 
31
- Never move logic into a nested function to evade centralization. An anonymous callback
32
- passed directly to another operation stays a callback, not a hidden helper declaration.
31
+ Apply `.claude/rules/architecture.md` § Functions and orchestration to nested functions and
32
+ callback positions.
33
33
 
34
34
  ## Hunt the wrapper
35
35
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orkestrel-journey
3
- description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a surface is reachable by keyboard alone, proving what a screen refuses as well as what it does, proving the styles a browser actually resolved under each theme and viewport, driving a transition table through the interface and watching it run, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, routing a rendered question to an artifact a model can read, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
3
+ description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a screen is reachable by keyboard alone, proving what a screen refuses as well as what it does, proving the styles a browser actually resolved under each theme and viewport, driving a transition table through the interface and watching it run, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, routing a rendered question to an artifact a model can read, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
4
4
  ---
5
5
 
6
6
  # Prove an application through human journeys
@@ -23,25 +23,27 @@ each ruling was taken at.
23
23
 
24
24
  ## Declare the families
25
25
 
26
- Declare in the browser environment's `integration.test.ts` which families that surface carries, and
26
+ Declare in the browser environment's `integration.test.ts` which families that screen carries, and
27
27
  assert in the always-on proofs that every declared family is present. A declaration names which
28
- families a surface owes. It never switches what a declared family proves.
28
+ families a screen owes. It never switches what a declared family proves.
29
29
 
30
30
  | Family | Declared | Proves |
31
31
  | ---------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
32
32
  | Journey | Always | Each user intent reaches its outcome through the interface |
33
- | Refusal | Always | Each control the surface withholds, through one exact failure voice |
34
- | Matrix | Where the surface ships more than one variant | The values the browser resolved under each declared variant |
33
+ | Refusal | Always | Each control the screen withholds, through one exact failure voice |
34
+ | Matrix | Where the journey wrapper declares more than one variant | The values the browser resolved under each declared variant |
35
35
  | Statechart | Where a journey drives a transition of an entity carrying its own state and event vocabulary | Each declared transition, driven through the interface where it can be |
36
- | Transport | Where the surface persists or restarts | Persistence, restart, and storage failure through real implementations |
36
+ | Transport | Where the screen persists or restarts | Persistence, restart, and storage failure through real implementations |
37
37
  | Capture | Under the capture flag | The registry times the variants, each registered file written to disk |
38
38
 
39
- - Refuse a declaration that omits a family whose trigger the surface meets. Report the omission as a
39
+ - Prove Matrix in the arrival journey `scaffold new` seeds while its wrapper declares more than one
40
+ variant: read one resolved value under each declared variant.
41
+ - Refuse a declaration that omits a family whose trigger the screen meets. Report the omission as a
40
42
  scope finding and stop; never prove the remaining families around it.
41
43
  - Assert the declaration itself: a family listed with no proof, and a proof belonging to no listed
42
44
  family, each fail the run.
43
45
  - Bind the journey laws to every declared family, not to the journey family alone. A matrix reading
44
- and a transport assertion reach their surface through the same verbs a journey reaches it through.
46
+ and a transport assertion reach their screen through the same verbs a journey reaches it through.
45
47
  - Change route and theme through the interface in the matrix family and the transport family. A
46
48
  family that navigates by calling the application's own router proves the router, and says nothing
47
49
  about the screen it reads afterwards.
@@ -92,9 +94,10 @@ start, and let it choose the capture destination, the matrix row, and the statec
92
94
  - Run the axis with `npm run test:journey`, which runs that wrapper and joins the `test` chain. Set
93
95
  `CAPTURE` to `1` in your own shell and run `npm run test:journey` again to write the frames; the
94
96
  root configuration reads that variable and provides it as `capture`.
95
- - Keep the journeys in `tests/app/browser/integration.test.ts`. Each variant project collects that
96
- file alone, and the ordinary `app:browser` project excludes it while the axis is on, so a journey
97
- written anywhere else runs in no variant.
97
+ - Keep the journeys in `tests/app/browser/integration.test.ts`, and the journeys of the `vue`
98
+ extension's application in `tests/app/vue/integration.test.ts`, which `npm run test:journey:vue`
99
+ runs. Each variant project of a mode collects that file alone, and the application's ordinary
100
+ project excludes it while the axis is on, so a journey written anywhere else runs in no variant.
98
101
  - Loop every declared variant inside one run for the matrix family
99
102
  ([styles.md](references/styles.md) → Run per variant).
100
103
  - Render exactly one variant per run for the capture family
@@ -120,7 +123,7 @@ whose text names `vitest`, so a script naming another runner's configuration rai
120
123
  to `createPortfolio` when capture needs no additional synchronous document change. Never attach
121
124
  an asynchronous action to `CaptureVariant.apply` or re-resolve a covered theme control at capture
122
125
  time.
123
- - Where the surface has no theme control, prepare the theme through the attribute the surface
126
+ - Where the screen has no theme control, prepare the theme through the attribute the screen
124
127
  reads. Use the optional `CaptureVariant.apply` hook only for a synchronous document change,
125
128
  such as setting that attribute. Treat its `() => void` contract as synchronous: `createPortfolio`
126
129
  invokes the hook without awaiting a returned promise.
@@ -194,15 +197,15 @@ await PORTFOLIO.place('home')
194
197
  5. **Generate the portfolio from the acceptance journeys.** Place each registered state inside the
195
198
  journey that reaches it, and never register a state no journey reaches.
196
199
  6. **Commit a value through an act a person performs:** `pressKeys('{Enter}')` on the focused
197
- control, a Tab away, or a named button. Report a surface that commits on a timer, on an
198
- unpredictable event, or only after work the person cannot observe as a surface finding, and never
200
+ control, a Tab away, or a named button. Report a screen that commits on a timer, on an
201
+ unpredictable event, or only after work the person cannot observe as a screen finding, and never
199
202
  work around it in the layer.
200
203
  7. **Type only what a person would.** Journeys carry trusted input; adversarial payloads belong to
201
204
  the transport family and the parser suites.
202
205
  8. **Perform every interaction step unconditionally.** Never gate a step on whether the control it
203
206
  is about to drive exists or is reachable, and never branch a journey on `readRefusal`. Let the
204
207
  resolver's failure voice name what the interface withheld. A guarded step passes whether or not
205
- the control was there, so the run goes green on a surface that removed the control.
208
+ the control was there, so the run goes green on a screen that removed the control.
206
209
 
207
210
  ## Import the journey layer
208
211
 
@@ -231,7 +234,7 @@ placement and scope `.claude/rules/tests.md` fixes.
231
234
 
232
235
  - Enter through the real entry: mount the shipped root component with a real store and the route a
233
236
  person lands on, and let the application load itself.
234
- - Reach each surface's own controls through forward Tab traversal in at least one journey.
237
+ - Reach each screen's own controls through forward Tab traversal in at least one journey.
235
238
  - Type keystroke by keystroke where the keystrokes are the subject; fill in one operation where the
236
239
  text is only a payload the person pastes.
237
240
  - Poll every fact the application produces asynchronously until it converges. Never assert one from
@@ -243,38 +246,38 @@ placement and scope `.claude/rules/tests.md` fixes.
243
246
  sentence and carry the replaced one in `absent`, so the reading that resolves carries one and not
244
247
  the other ([layer.md](references/layer.md) → The waits).
245
248
  - Assert the state a control announces beside every drive that sets it, and on an unselected
246
- sibling. A control announcing state owes this assertion whether or not the surface carries the
249
+ sibling. A control announcing state owes this assertion whether or not the screen carries the
247
250
  statechart family.
248
251
  - After a confirmed destructive action, assert through trusted input that focus landed on a visible,
249
252
  announced location.
250
253
  - Assert the whole page's perception never matches the vocabulary the product does not speak —
251
254
  engine, schema, and implementation words the interface is supposed to translate.
252
- - Report a bare accessible name that answers for more than one reachable element on one screen as a surface
253
- finding, and target through role or region until the surface is fixed.
255
+ - Report a bare accessible name that answers for more than one reachable element on one screen as a screen
256
+ finding, and target through role or region until the screen is fixed.
254
257
 
255
- ### The intents every surface owes
258
+ ### The intents every screen owes
256
259
 
257
- Write a journey for each of the following wherever the surface has that state. Take the expected
260
+ Write a journey for each of the following wherever the screen has that state. Take the expected
258
261
  outcome from the product guide; this skill supplies the mechanism and invents no copy, no redirect,
259
262
  and no title scheme.
260
263
 
261
- | Intent | The journey proves |
262
- | ----------------- | ------------------------------------------------------------------------------- |
263
- | Arrival | The entry route renders its own screen, read through a named region |
264
- | An unknown route | What the application does with a route it does not carry, and what it says |
265
- | An empty result | What a query matching nothing renders, in the product's own words |
266
- | The document name | The title each screen publishes, asserted per screen |
267
- | A render failure | What a person reads when a component throws, rather than an unexplained surface |
264
+ | Intent | The journey proves |
265
+ | ----------------- | ------------------------------------------------------------------------------ |
266
+ | Arrival | The entry route renders its own screen, read through a named region |
267
+ | An unknown route | What the application does with a route it does not carry, and what it says |
268
+ | An empty result | What a query matching nothing renders, in the product's own words |
269
+ | The document name | The title each screen publishes, asserted per screen |
270
+ | A render failure | What a person reads when a component throws, rather than an unexplained screen |
268
271
 
269
272
  - Report a missing outcome as a product finding, with its evidence site, rather than inventing the
270
- copy the surface owes.
273
+ copy the screen owes.
271
274
  - Assert the title from `document.title` per screen, against the title the product guide names for
272
275
  that screen. Report a screen the guide gives no title as a product finding.
273
276
 
274
277
  ## Prove the refusals
275
278
 
276
- - Give every surface a refusal family: the controls a person must not reach in the state the
277
- journey has put the surface in.
279
+ - Give every screen a refusal family: the controls a person must not reach in the state the
280
+ journey has put the screen in.
278
281
  - Assert the exact failure voice the case means. Never write an assertion that accepts more than
279
282
  one voice ([layer.md](references/layer.md) → The failure voices).
280
283
  - Cover the restrictions the interface imposes on itself: a collapsed panel's field, a verb
@@ -296,11 +299,11 @@ and no title scheme.
296
299
  `QuotaExceededError`.
297
300
  - Prove the visible half in a journey: the failure sentence a person reads, and the retry control
298
301
  that clears it. A storage failure whose visible half is a control that silently does nothing is a
299
- surface finding.
302
+ screen finding.
300
303
  - Assert restart by starting a second session over the same store and polling the restored value.
301
304
  - Take a stalled read to the application's own asynchronous store contract, never to `Storage`.
302
305
  `Storage` is synchronous, so a hanging read is not expressible against it and a fixture that fakes
303
- one is proving a surface the application does not have.
306
+ one is proving a store contract the application does not have.
304
307
 
305
308
  ## Prove the styles
306
309
 
@@ -350,16 +353,16 @@ Completion requires all of:
350
353
 
351
354
  - every in-scope user intent reaching its outcome through the interface, with no step that reaches
352
355
  past it;
353
- - the intents every surface owes present wherever the surface has the state, each outcome taken from
356
+ - the intents every screen owes present wherever the screen has the state, each outcome taken from
354
357
  the product guide and every missing one reported as a product finding;
355
- - keyboard-only reachability proven on every surface the journeys cover;
356
- - a refusal family per surface, each asserting one exact failure voice;
358
+ - keyboard-only reachability proven on every screen the journeys cover;
359
+ - a refusal family per screen, each asserting one exact failure voice;
357
360
  - the transport family declared separately, driven through real implementations, and convergent;
358
361
  - the declared families each proven, and the declaration itself asserted;
359
362
  - the matrix family read once per declared variant, each style reading carrying its published
360
363
  control from [styles.md](references/styles.md) → The published controls in the same run, and the
361
364
  contrast reading's control straddling its declared bar;
362
- - the authored-class census and the `extractStyles` reading taken on the mounted surface, each
365
+ - the authored-class census and the `extractStyles` reading taken on the mounted screen, each
363
366
  reporting the population it walked;
364
367
  - the statechart table driven to a terminal status with no failed row, and the harness tally read
365
368
  from the object and from its markup;
@@ -378,5 +381,5 @@ block is the home of that limit and of the condition that reopens it. Cite that
378
381
  neither into a verdict.
379
382
 
380
383
  Report each journey by the intent it proves, the refusals it establishes, the states it placed, the
381
- variants it read, the statechart outcome it reached, and every surface finding the layer's refusals
384
+ variants it read, the statechart outcome it reached, and every screen finding the layer's refusals
382
385
  exposed.
@@ -51,12 +51,12 @@ The package already refuses these, so assert none of them again:
51
51
 
52
52
  Declare the state names once in the journey file, and build the portfolio from that declaration.
53
53
 
54
- - Name a state for its surface and its condition — `answer-partial`, `start-storage-failure`,
54
+ - Name a state for its screen and its condition — `answer-partial`, `start-storage-failure`,
55
55
  `case-delete-confirmation`.
56
56
  - Register the states the design work actually needs, and place every registered one. Never leave a
57
57
  registered state unplaced.
58
58
  - Place a capture state from inside the journey that reaches it, immediately after the assertion
59
- that proves the surface is in the condition that state names.
59
+ that proves the screen is in the condition that state names.
60
60
  - Record each placed name in the suite's own set in the same step that calls `place`. That set, not
61
61
  `placements`, is what the always-on placement proof reads.
62
62
 
@@ -117,7 +117,7 @@ after the click returns.
117
117
  - Take each frame after the paint settles. Reach for `waitForAnimations` on the element whose
118
118
  transition was running ([layer.md](layer.md) → The waits); a frame shot mid-transition pictures a
119
119
  state the interface never rests in.
120
- - Regenerate the whole matrix from the journeys after any surface change. Never judge a round
120
+ - Regenerate the whole matrix from the journeys after any screen change. Never judge a round
121
121
  against a portfolio that is part old and part new.
122
122
  - Route review of the portfolio to the `orkestrel-polish` campaign, which owns preflight,
123
123
  verdicts, and reconciliation. This reference owns only how the journeys generate it.
@@ -53,8 +53,8 @@ so a decision cites the exact file and reads it in one call.
53
53
 
54
54
  Compose each file from what the run already holds:
55
55
 
56
- - `describeTree` of the mounted surface, for the roles, names, and states a person meets.
57
- - `describeFocus` of the mounted surface, for the focus order the keyboard walk took.
56
+ - `describeTree` of the mounted screen, for the roles, names, and states a person meets.
57
+ - `describeFocus` of the mounted screen, for the focus order the keyboard walk took.
58
58
  - The resolved-style rows the matrix family read for that variant — the property, the element, and
59
59
  the value the browser returned.
60
60
  - The journal's `steps` and `output`, for what the run did and what the page said while it did it.
@@ -66,7 +66,7 @@ Rules the artifact obeys:
66
66
  combination nobody can reproduce.
67
67
  - Write one file per variant, never one file per journey. A decision is taken per variant, and a
68
68
  reader opening one file per journey pays a round trip per journey.
69
- - Regenerate the whole set after any surface change. Never judge a round against a set that is part
69
+ - Regenerate the whole set after any screen change. Never judge a round against a set that is part
70
70
  old and part new.
71
71
  - Keep it out of version control.
72
72
 
@@ -185,13 +185,13 @@ the capture voices in [captures.md](captures.md).
185
185
 
186
186
  - Report an absent control and a present-but-unreachable one as different findings: absence names
187
187
  a missing control, and unreachability names the interface gating one that exists.
188
- - Report ambiguity as a finding about the surface. Quote the match count from the message, and
188
+ - Report ambiguity as a finding about the screen. Quote the match count from the message, and
189
189
  re-target the journey by role or region.
190
190
  - Assert a `DOMException` on its `name` as well as its message. A withheld storage operation raises
191
191
  `SecurityError` and a write past the quota raises `QuotaExceededError`, which is the pair a denied
192
192
  or full origin raises.
193
193
  - Never assert a `could not be resolved` sentence. Each one is narrowing the resolver needs under
194
- `noUncheckedIndexedAccess`, and no surface reaches it.
194
+ `noUncheckedIndexedAccess`, and no screen reaches it.
195
195
  - Read a refused bound as a test defect rather than a finding. The wait family validates its budget
196
196
  and its interval and names the argument the caller passed, under the subject `Wait` or
197
197
  `Animation`.
@@ -211,7 +211,7 @@ the capture voices in [captures.md](captures.md).
211
211
  `fillAccessible` where the text is only a payload the person pastes.
212
212
  - Bring focus about first — through `traverseAccessible`, `clickAccessible`, or `typeAccessible` —
213
213
  and then press. The refusal is what `pressKeys` adds over the provider's own keyboard call: a key
214
- sent to the body reaches no control, and every assertion after it reads a surface the key never
214
+ sent to the body reaches no control, and every assertion after it reads a screen the key never
215
215
  touched.
216
216
  - Escape the sequence yourself where the sequence is the subject. Reach for `typeAccessible` where
217
217
  the text is the subject and the key syntax is in the way.
@@ -245,7 +245,7 @@ the capture voices in [captures.md](captures.md).
245
245
  - Pass `absent: true` to `waitForState` where the claim is that the state went away, and read the
246
246
  returned states rather than taking a second reading afterwards.
247
247
  - Take `waitForState` as the published replacement for a settle keyed to a framework's own class
248
- names. Where the control announces nothing, the finding is the surface's: give it `aria-expanded`,
248
+ names. Where the control announces nothing, the finding is the screen's: give it `aria-expanded`,
249
249
  `aria-pressed`, or `aria-busy` rather than reading the classes a stylesheet happens to use.
250
250
  - Take every style, contrast, and capture reading after `waitForAnimations` on the element whose
251
251
  paint was moving. A reading taken mid-transition reports an interpolated frame no state of the
@@ -274,7 +274,7 @@ the capture voices in [captures.md](captures.md).
274
274
  locators do not, because the platform exposes a native disclosure rather than a role.
275
275
  - Drive an ARIA disclosure as what it is: a button. Click it with `clickAccessible`, then settle it
276
276
  with `waitForState` on the state it announces.
277
- - Report a control that opens a panel and announces no state as a surface finding. Author
277
+ - Report a control that opens a panel and announces no state as a screen finding. Author
278
278
  `aria-expanded` on it rather than settling on the classes its framework toggles.
279
279
  - Read a native summary's expansion with `readStates`, which reads the parent `details` element's
280
280
  own `open` where the summary declares no `aria-expanded`.
@@ -36,7 +36,7 @@ import {
36
36
  `assert`. Each phase receives the context and the part of the transition it owns.
37
37
  - Put the scenarios and the table in the workspace's browser test setup module, and import them from
38
38
  there.
39
- - Declare a transition for every event the surface accepts in every state it accepts it, including
39
+ - Declare a transition for every event the screen accepts in every state it accepts it, including
40
40
  the event that leaves the state unchanged. A table that lists only the moves the happy path takes
41
41
  proves the happy path.
42
42
  - Write the phases as module functions the whole table shares. Each phase reads its subject from its
@@ -184,7 +184,7 @@ it('walks the disclosure statechart', async () => {
184
184
  - Drive `act` through the journey verbs — `clickAccessible`, `clickDisclosure`, `typeAccessible`,
185
185
  `traverseAccessible`, `pressKeys` — for every transition a person can cause.
186
186
  - Drive `act` through the entity's own API only where the transition is the entity's rather than the
187
- person's: a lifecycle event, a transport reply, a timer the surface owns.
187
+ person's: a lifecycle event, a transport reply, a timer the screen owns.
188
188
  - Say which door each row used, in the row's `name`. A table that mixes the doors silently reads as
189
189
  a set of user transitions and proves something else.
190
190
 
@@ -286,7 +286,7 @@ import it.
286
286
 
287
287
  - Name only the attribute contract such a page must honour: the names in `STATECHART_ATTRIBUTES`,
288
288
  the readings in `STATECHART_STATUSES`, and the tally on one root node.
289
- - Report the page itself as the repository owner's decision — which transitions a surface owes,
289
+ - Report the page itself as the repository owner's decision — which transitions a screen owes,
290
290
  where the page is linked, and whether it ships at all.
291
291
  - Route a person who must watch the widget move to the harness run's own frames and its written
292
292
  artifact ([decide.md](decide.md) → The harness run), and name a deep link only where the workspace
@@ -1,6 +1,6 @@
1
1
  # Proving what the browser resolved
2
2
 
3
- Prove a style from what the browser resolved on the mounted surface. The `enterprise-bootstrap`
3
+ Prove a style from what the browser resolved on the mounted screen. The `enterprise-bootstrap`
4
4
  skill's [instruments reference](../../enterprise-bootstrap/references/inspection.md) names each
5
5
  instrument's property, its population, and its coverage. Take those from there, the reading from
6
6
  here, and the control from the builder this layer publishes for it.
@@ -75,12 +75,12 @@ readings follow.
75
75
  reads the whole matrix, where the capture family renders one variant per run.
76
76
  - Compose each variant's `apply` in the test, and apply it with the variant's `width` and `height`
77
77
  before the readings. Take every reading for that variant before moving to the next.
78
- - Name the attribute the surface actually reads in `apply`; a Bootstrap surface switches on
78
+ - Name the attribute the screen actually reads in `apply`; a Bootstrap screen switches on
79
79
  `data-bs-theme`. An `apply` that sets another attribute leaves the run in the default theme, where
80
80
  every reading passes.
81
- - Reach for the application's own theme control where the surface ships one, and assert the state it
81
+ - Reach for the application's own theme control where the screen ships one, and assert the state it
82
82
  announces. Setting the attribute directly proves the stylesheet; driving the control proves the
83
- surface.
83
+ screen.
84
84
  - Assert that the run read every declared variant. A matrix that silently walked one variant reports
85
85
  a pass for the theme nobody exercised.
86
86
  - Report which variants a result covers beside it. A pairing that appears only in a state the run
@@ -102,7 +102,7 @@ provided.
102
102
  `pressKeys`, or a real click. Pass `worn` where the chrome is painted onto a second element such
103
103
  as a label. It reports `undefined` for a control not matching `:focus-visible`, for the browser's
104
104
  own automatic ring, and for a focus style that only repaints the fill — treat each as a finding
105
- about the surface rather than as a pass.
105
+ about the screen rather than as a pass.
106
106
  - Reach for `measureContrast`, `measureLuminance`, `blendColor`, `readLayers`, and `readBackdrop`
107
107
  only where the composite itself is the subject. Never re-derive `readContrast` from them.
108
108
 
@@ -117,7 +117,7 @@ Take each reading's control from the builder this layer publishes for it.
117
117
  | `extractStyles` | `buildEscapes(permitted)` | An inline declaration, an embedded `<style>` element, and the sheet the id exempts |
118
118
  | `readCensus` | `buildCensus()` | An HTML token and an SVG token, neither declared by any loaded stylesheet |
119
119
 
120
- - Append each control's `root` to the same surface root the reading walks, take the reading, and
120
+ - Append each control's `root` to the same screen root the reading walks, take the reading, and
121
121
  remove it afterwards. Every builder returns detached nodes and mounts nothing, so where the
122
122
  control is read is the caller's decision.
123
123
  - Assert on the fields the builder returns rather than on a token or a selector written down in the
@@ -134,12 +134,12 @@ Take each reading's control from the builder this layer publishes for it.
134
134
  Take the property, the population, and the coverage from the instruments reference → Authored class
135
135
  in the shipped cascade. This is the reading.
136
136
 
137
- - Read the census with `readCensus(root)`, which walks the mounted surface, reports `elements` as
137
+ - Read the census with `readCensus(root)`, which walks the mounted screen, reports `elements` as
138
138
  the population it read, lists every `tokens` value the markup carries, and lists as `undeclared`
139
139
  the tokens no loaded stylesheet declares. It refuses a walk that read no element.
140
140
  - Assert on `elements` as well as on `undeclared`. An empty walk reports no undeclared token, and so
141
141
  does markup whose every class the cascade declares.
142
- - Take `root` from the mounted surface, so the census covers what rendered rather than what a
142
+ - Take `root` from the mounted screen, so the census covers what rendered rather than what a
143
143
  template file spells.
144
144
  - Read `readClasses` and `readCascade` directly only where one side of the difference is the
145
145
  subject. `readCensus` is the reading, and re-deriving it drops the population it reports.
@@ -152,7 +152,7 @@ Take the property, the population, the named exemptions, and the coverage from t
152
152
  reference → Style escapes. This is the reading.
153
153
 
154
154
  - Read escapes with `extractStyles(root)`, which returns the markup of every hit it found.
155
- - Take the reading before any journey drives the surface, because the population is the undriven
155
+ - Take the reading before any journey drives the screen, because the population is the undriven
156
156
  tree.
157
157
  - Append `buildEscapes(permitted).root` to that same `root`, so the control reaches the reading
158
158
  through `extractStyles`.
@@ -15,13 +15,25 @@ paths:
15
15
  are first-class.
16
16
  - The CLI names the independent selections `--src` and `--app`.
17
17
  `--surfaces` is not an alias and must fail as an unknown option.
18
+ - `new` selects the styles surface with `--styles`, its themes target with `--themes` (requires
19
+ `--styles`), the showcase with `--showcase` (requires `--app browser`), and extensions with
20
+ `--extend <surface:name,…>`. Each `--extend` entry is `browser:vue`, applied to every selected
21
+ browser axis, or `styles:<name>`, which requires `--styles`. `--app browser` implies the journey.
18
22
  - Core-only, browser-only, and server-only applications are valid. A combined
19
23
  browser+server application includes app/core for shared contracts.
20
24
  - Every selected environment has an `index.ts` barrel. `main.ts` is an executable
21
25
  entry and never owns reusable declarations.
22
26
  - app/core is host-independent and check/test-only.
23
- - app/browser uses app/core contracts, Vue 3 when selected, an `index.html`
24
- entry, `vue-tsc`, and real Chromium tests.
27
+ - app/browser is framework-independent: it uses app/core contracts, an
28
+ `index.html` entry, a `main.ts` that renders through the DOM, `check:app:browser`
29
+ through `tsc`, and real Chromium tests.
30
+ - The Vue application lives in app/vue: `main.ts`, `index.html`, `App.vue`,
31
+ `check:app:vue` through `vue-tsc`, `dev:vue`, and real Chromium tests.
32
+ - When a target's app/browser holds a `.vue` file, move it to app/vue. The
33
+ generator's `repair` raises a blocking question naming that move until it lands.
34
+ - The showcase builds one page per application, app/browser and each app-side
35
+ browser extension, into root `showcase/` with the final-page stamp;
36
+ `.claude/rules/workspace.md` § Build outputs owns its build.
25
37
  - app/server uses app/core contracts, parses environment values before binding,
26
38
  defaults to loopback, emits `dist/app/server/main.cjs`, and keeps only
27
39
  `node:*` external.
@@ -36,8 +48,9 @@ paths:
36
48
  generated-consumer tests exercise those real configurations.
37
49
  - Generated consumers must pass lint, scoped typechecking, production builds, and
38
50
  real integration tests.
39
- - Scoped checks include `.ts`, `.tsx`, `.mts`, and `.cts`. Vue SFCs and CSS
40
- belong to browser environments; SCSS requires an authorized compiler dependency.
51
+ - Include `.ts`, `.tsx`, `.mts`, and `.cts` in scoped checks. Publish only TypeScript from
52
+ `src/vue`, and put Vue SFCs in `app/vue`. Put CSS in browser code; compile SCSS through the
53
+ `sass` dependency the styles surface declares.
41
54
  - Published `src` environments never import private `app` modules. Src core is
42
55
  host-independent; src browser/server may import src core but never one
43
56
  another's implementation. Apply the same environment law to
@@ -45,8 +58,9 @@ paths:
45
58
  is its core API.
46
59
  - App-only manifests are unscoped and `private: true`, with no `main`,
47
60
  `module`, `types`, export map, or publish configuration. Mixed manifests
48
- publish only `dist/src`, and Vue remains development-only because app output
49
- is never published.
61
+ publish only `dist/src` and the SCSS sources a stylesheet export names, and
62
+ never app output; `vue` stays a development dependency apart from the optional
63
+ peer `.claude/rules/browser.md` admits.
50
64
  - Give app/server process signals to a tested, explicitly stoppable,
51
65
  generation-safe runner whose stale failures cannot release a newer run.
52
66
  - Return the runner from convenience startup, so normal cleanup cannot be hidden.
@@ -49,7 +49,7 @@ Use only the centralized files an environment needs.
49
49
  - Extract local declarations by kind. “Only used here” and “not exported” are not exemptions.
50
50
  - Every declaration in a centralized file is exported. Fold away a trivial single-use declaration or export/test it; never leave it hidden.
51
51
  - The only permitted non-exported module-scope declarations are in a runtime entrypoint that must be self-contained and cannot import siblings, such as raw source loaded in a worker. Explain that necessity in a comment.
52
- - A runtime entry—`src/bin/main.ts`, `app/browser/main.ts`, `app/server/main.ts`—is a fixed name, not a centralized kind file. Both the data rule and the function rule reach it, so it declares no module-scope constant and no module-scope function: it imports what it needs and runs. The preceding self-contained exception covers only an entrypoint that cannot import siblings.
52
+ - Treat `src/bin/main.ts`, `app/browser/main.ts`, `app/vue/main.ts`, and `app/server/main.ts` as fixed runtime entries. Declare no module-scope constant or function in them; import what they need and run. Apply the preceding self-contained exception only to an entrypoint that cannot import siblings.
53
53
  - Perform a cleanup pass after implementation: no stray implementation-file declarations, non-exported/wrong-kind centralized declarations, prohibited nested declarations, duplicate implementations, compatibility aliases, superfluous wrappers, stale imports/barrel rows, or untested extracted functions.
54
54
 
55
55
  ## Kind purity
@@ -166,7 +166,7 @@ A wrapper survives only when it adds a real boundary, invariant, composition, tr
166
166
 
167
167
  - Never declare or assign a function inside another function or method.
168
168
  - This bans local `function`, `function*`, and `const fn = () => ...`, regardless of caller count.
169
- - The only in-body function expressions allowed are an anonymous callback passed directly as an argument and an anonymous function returned directly as the result (the factory/combinator pattern).
169
+ - Admit a named or anonymous function expression passed as a call or constructor argument, returned as a result, or used as an arrow body, through parentheses, non-computed object-literal property values, and array elements. Admit methods, getters, and setters of such an object literal. Refuse a climb out of a method or accessor body, or through a spread, computed key, class field, assignment, or local binding.
170
170
  - Instance-bound work that reaches state or sibling methods is a method, not a free function.
171
171
 
172
172
  Separate these roles:
@@ -1,13 +1,22 @@
1
1
  ---
2
2
  paths:
3
3
  - 'src/browser/**/*.ts'
4
+ - 'src/vue/**/*.ts'
4
5
  - 'app/browser/**/*.{ts,vue}'
6
+ - 'app/vue/**/*.{ts,vue}'
5
7
  - 'tests/{src,app}/browser/**/*'
8
+ - 'tests/{src,app}/vue/**/*'
6
9
  - 'tests/setupBrowser.ts'
7
10
  ---
8
11
 
9
12
  # Vue and browser rules
10
13
 
14
+ - Publish only TypeScript composables and contracts from `src/vue`; put Vue single-file components
15
+ in `app/vue`. Keep `.vue` files and `vue` imports out of `src/browser` and `app/browser`.
16
+ Apply `AGENTS.md` § Project model to imports from each Vue face.
17
+ - Declare `vue` in the manifest as an optional peer only for the `./vue` export. Until that
18
+ declaration exists, the `./vue` build refuses `vue`, `vue/*`, and `@vue/*`; after it, the build
19
+ externalizes `vue` and its subpaths and still refuses `@vue/*`.
11
20
  - Never use Vue `$emit`.
12
21
  - Coordinate reactivity through props, controllers, stores, services, and composables.
13
22
  - A composable exposes readonly refs plus methods; consumers never mutate returned refs directly:
@@ -23,7 +23,8 @@ Documentation is an enforced contract, not explanatory decoration. The Writing r
23
23
  - `AGENTS.md` and its linked rules are the sole convention source. Do not create competing instruction copies in guides.
24
24
  - `guides/README.md` is the map: maintain both a concept index and a directory index. The concept index runs `spec ↔ source ↔ tests ↔ showcase` minus every column whose subject this workspace lacks, so an app-only workspace that publishes no library and builds no showcase still owes a full index over the columns it has.
25
25
  - Where the repository keeps one, `ROADMAP.md` is the sequenced plan of record. Each chunk reaches green before the next.
26
- - A showcase is executable proof of public API. A missing demonstration is a missing feature, detectable by parity.
26
+ - Demonstrate public API in application source, prove its journeys there, and rebuild the selected showcase pages for publication.
27
+ - Name each `showcase/<application>.html` page that demonstrates the row in the concept index's showcase column; name `browser.html` for the base modes.
27
28
  - An integration surface's guide documents the validated hookup for each supported client: the exact commands run, the authentication and approval model that client needs, and the honest limit wherever a client cannot reach part of the surface.
28
29
 
29
30
  ## Parity
@@ -2,7 +2,9 @@
2
2
  paths:
3
3
  - '**/*.{scss,css}'
4
4
  - 'src/styles/**/*'
5
+ - 'src/*/sheet.ts'
5
6
  - 'tests/setupStyles.ts'
7
+ - 'tests/setupStyles.test.ts'
6
8
  - 'tests/setupBrowser.ts'
7
9
  ---
8
10
 
@@ -12,19 +14,24 @@ SCSS mirrors TypeScript centralization. Concrete token prefixes are project-spec
12
14
 
13
15
  ## Centralized files
14
16
 
15
- | File | Sole responsibility |
16
- | -------------- | ------------------------------------------------------------- |
17
- | `_mixins.scss` | `@function` values and `@mixin` declaration emitters |
18
- | `_tokens.scss` | `:root` public custom-property tokens and cascade-layer order |
19
- | `_theme.scss` | Token overrides under theme selectors |
20
- | `index.scss` | Sole compilation barrel |
17
+ | File | Sole responsibility |
18
+ | ------------------- | ------------------------------------------------------------- |
19
+ | `_mixins.scss` | `@function` values and `@mixin` declaration emitters |
20
+ | `_tokens.scss` | `:root` public custom-property tokens and cascade-layer order |
21
+ | `_theme.scss` | Token overrides under theme selectors |
22
+ | `_reset.scss` | The face's reset declarations, when that face owns a reset |
23
+ | `themes/index.scss` | Barrel of named theme packs, compiled into its own sheet |
24
+ | `index.scss` | Sole compilation barrel |
21
25
 
26
+ - Apply this table and the folder barrels in § Folders to every sheet face: `src/styles` and each
27
+ `src/<name>` styles extension. `.claude/rules/workspace.md` § Environments fixes the `sheet.ts`
28
+ entry.
22
29
  - `_mixins.scss` emits no top-level CSS.
23
30
  - Consumers load it with `@use '../mixins' as *`.
24
31
  - Never load `mixins` from `index.scss`.
25
- - `index.scss` is the sole compilation barrel; it loads `tokens`, `theme`, and output partials with `@use`.
32
+ - `index.scss` is the sole compilation barrel of its sheet; it loads `tokens`, `theme` where `_theme.scss` exists, and the output partials with `@use`. Never load `themes/` from `index.scss`.
26
33
  - `_tokens.scss` is the token source of truth. Adding a token is allowed; rename/removal is breaking.
27
- - `_theme.scss` only retunes tokens under selectors such as `[data-theme='…']`.
34
+ - `_theme.scss` and each `themes/` pack only retune tokens under theme selectors such as `[data-theme='…']`.
28
35
  - Component partials never override global tokens.
29
36
 
30
37
  ## Sass mechanisms
@@ -39,8 +46,11 @@ SCSS mirrors TypeScript centralization. Concrete token prefixes are project-spec
39
46
  - Check `_tokens.scss` before inventing a token.
40
47
  - Put global tokens in `_tokens.scss`; put truly component-scoped custom properties on the component selector.
41
48
  - Never bury tokens in unrelated partials.
42
- - Never use literal colors. Use `var(--token)` or `color-mix()` over tokens. The one file a
43
- literal color may appear in is `_tokens.scss`, where the token itself is declared.
49
+ - Never use literal colors outside a pinned recreation. Use `var(--token)` or `color-mix()` over
50
+ tokens. The one file a literal color may appear in is `_tokens.scss`, where the token itself is
51
+ declared. A face whose contract is the exact recreation of a pinned external artifact keeps
52
+ the literals, declarations, and order the pin declares, and records tokenization and
53
+ accessibility additions in its separate authored face.
44
54
  - Never repeat per-color/per-variant blocks; drive shared structure with one `@each` over a shared list.
45
55
  - If a pattern appears in at least two partials, move it to `_mixins.scss`.
46
56
  - Treat a declaration block two partials share because each records an external value as a
@@ -50,8 +60,21 @@ SCSS mirrors TypeScript centralization. Concrete token prefixes are project-spec
50
60
  - Never `@extend` across partials; share through tokens/mixins.
51
61
  - Never declare a `transition:` without `prefers-reduced-motion: reduce`. Use the project transition mixin, which emits both.
52
62
  - Animations include `@include reduced-motion { animation: none }`.
53
- - Never wrap rules in a foreign cascade layer. Each partial uses its folder's own layer.
54
- - Declare cascade-layer order once in the consumer entry before `@import 'tailwindcss'`, so utilities win predictably.
63
+ - Never wrap rules in a foreign cascade layer. Each partial uses its folder's own layer. A sheet
64
+ that recreates an external framework instead writes every normal declaration into one layer named
65
+ for that framework and every `!important` declaration outside every layer.
66
+ - Declare cascade-layer order once in the consumer entry before `@import 'tailwindcss'`, so utilities win predictably. When a package publishes several sheets, open every published sheet with the same full order statement, so the order holds whichever sheet loads first.
67
+ - Open `themes/index.scss` with `@use '../tokens'`, whose first emitted rule is the order statement, then `@use 'default'`; Sass refuses a `@use` after another rule, so never write the statement there literally.
68
+
69
+ ## Folders
70
+
71
+ - Give each folder an `_index.scss` barrel that loads its partials with `@use`; keep the barrel when the folder is empty.
72
+ - Put a rule that styles one element in `elements/`, a class skin that applies with no script running in `components/`, and a class that sets one property in `utilities/`.
73
+ - Add another folder only for a job `elements/`, `components/`, and `utilities/` do not hold, and give it its own barrel and its own layer.
74
+
75
+ ## Proofs
76
+
77
+ - Declare every CSSOM instrument a sheet proof reads in `tests/setupStyles.ts`, never in a test file. `tests/setupStyles.test.ts` proves each instrument under the root setup mirror in `.claude/rules/tests.md`.
55
78
 
56
79
  ## Naming
57
80