@orkestrel/scaffold 0.0.35 → 0.0.37

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.
@@ -112,6 +112,8 @@ Variants: `.accordion-flush` (edge-to-edge, no outer borders); omit `data-bs-par
112
112
 
113
113
  `role="alert"` announces immediately when the element is injected into the DOM — right for errors and warnings. For calm status messages injected dynamically, prefer a polite live region (`role="status"`). Anything that _looks_ like an alert carries the alert role: styling and semantics disagree the moment a notice wears `.alert` chrome with no role, and an accessibility snapshot is what catches it. When to use alert vs toast vs banner: [bootstrap-reference.md](bootstrap-reference.md) → Feedback discipline.
114
114
 
115
+ An alert is a subtle fill — apply the subtle-fill degradation rule to everything inside it ([SKILL.md](../SKILL.md) → Surfaces, color, contrast).
116
+
115
117
  ### Badge
116
118
 
117
119
  ```html
@@ -186,6 +188,8 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
186
188
 
187
189
  Icon-only buttons need `aria-label` and a ≥24×24 px target (WCAG 2.2) — `btn-sm` icon clusters in toolbars are the common violation; pad rather than shrink.
188
190
 
191
+ Choose the variant by contrast rather than by taste ([SKILL.md](../SKILL.md) → Hierarchy & actions).
192
+
189
193
  ### Button Group
190
194
 
191
195
  ```html
@@ -400,6 +404,29 @@ Multiple targets: give each panel `.multi-collapse` and point separate triggers
400
404
 
401
405
  ### Modal
402
406
 
407
+ **Build a blocking dialog on the native `<dialog>`.** `showModal()` brings focus containment, Esc, an inert background, and top-layer stacking from the platform — nothing to construct, nothing to dispose when the view unmounts, and no JS instance for a virtual-DOM framework to fight with over the same nodes. Leave the element itself unpainted and put Bootstrap chrome inside it:
408
+
409
+ ```html
410
+ <dialog class="p-0 border-0 bg-transparent" role="alertdialog" aria-labelledby="confirmHeading">
411
+ <div class="card shadow">
412
+ <div class="card-header">
413
+ <h2 id="confirmHeading" class="h5 mb-0">Delete invoice</h2>
414
+ </div>
415
+ <div class="card-body">
416
+ <p class="card-text mb-0">This permanently deletes INV-1042.</p>
417
+ </div>
418
+ <div class="card-footer d-flex flex-wrap justify-content-end gap-2">
419
+ <button type="button" class="btn btn-secondary">Keep</button>
420
+ <button type="button" class="btn btn-danger">Delete invoice</button>
421
+ </div>
422
+ </div>
423
+ </dialog>
424
+ ```
425
+
426
+ `role="alertdialog"` for a destructive confirm, `role="dialog"` otherwise; `aria-labelledby` points at the heading. The element's own `close` event is where the host clears the state that opened it, so Esc and the buttons all close by one path. The scrim is the UA's `::backdrop`, which no Bootstrap class touches — restyling it is a rung-4 decision.
427
+
428
+ Bootstrap's `.modal` is the answer when the project already drives its dialogs through Bootstrap's JS:
429
+
403
430
  ```html
404
431
  <div
405
432
  class="modal fade"
@@ -467,7 +494,7 @@ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, clos
467
494
  </ul>
468
495
  <form class="d-flex" role="search">
469
496
  <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
470
- <button class="btn btn-outline-success" type="submit">Search</button>
497
+ <button class="btn btn-success" type="submit">Search</button>
471
498
  </form>
472
499
  </div>
473
500
  </div>
@@ -941,7 +968,7 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
941
968
  <span class="bi bi-circle fs-6 lh-1 text-body-secondary" role="img" aria-label="Not started"></span>
942
969
  ```
943
970
 
944
- - **Color from the emphasis tokens.** `text-*-emphasis` is the mode-adaptive tier built for marks on subtle surfaces; the plain `text-*` colors are tuned for light and thin out in dark. Measure every mark at **≥ 3:1** against the surface it sits on, **in both themes**, against the compiled cascade — a skin's token values are its own.
971
+ - **Color from the emphasis tokens.** `text-*-emphasis` is the mode-adaptive tier built for marks on subtle surfaces; the plain `text-*` colors are tuned for light and thin out in dark. On a filled surface — `.active`, `.bg-primary`, `text-bg-*` — drop the tone class instead and let the fill's contrast color take the glyph ([Selection fills](#selection-fills)). Measure every mark at **≥ 3:1** against the surface it sits on, **in both themes**, against the compiled cascade — a skin's token values are its own.
945
972
  - **Filled and hollow say different things** — done vs pending, live vs idle — so pair glyphs that share one advance width (a filled/hollow pair from the same icon family). Mixed widths make a column of marks jitter row to row.
946
973
  - **Size with `fs-*` _and_ `lh-1`.** A glyph inherits the row's line-height, so an `fs-*` bump without `lh-1` grows the line box and pushes the row taller than its neighbors.
947
974
  - Give the mark an accessible name (`role="img"` + `aria-label`, or a `.visually-hidden` word next to an `aria-hidden` glyph) — a mark whose only meaning is its color and shape is color-only status.
@@ -961,7 +988,7 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
961
988
  | Long documentation in-product | `accordion` or scrollable `modal` | One infinite tinted card stack |
962
989
  | Dense data | `table` + `table-responsive` (+ `table-sm` when appropriate) | Non-semantic grids of text |
963
990
  | Choosing a form value | `form-select` (or native input) | A `dropdown` menu posing as an input |
964
- | Confirmations / focused tasks | `modal` with header, body, footer actions | Nested modals |
991
+ | Confirmations / focused tasks | native `<dialog>` with header, body, footer actions | Nested modals |
965
992
  | Secondary filters on small screens | `offcanvas` | Permanent wide sidebars that crush content |
966
993
  | Transient success feedback | `toast` | `alert()`; toasts for errors |
967
994
  | Loading a known layout | `placeholder` skeleton | Layout-collapsing centered spinner |
@@ -977,7 +1004,7 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
977
1004
 
978
1005
  A selected row, pill, or filter chip repaints everything inside it — marks included. Two traps, both invisible until the selected state is captured in both themes:
979
1006
 
980
- - **A mark on an active fill of the same family disappears.** `.active` on a `list-group-item`, `nav-pill`, or `page-item` sets the item's own color, and a `text-bg-primary`-family mark inside it inherits or loses to that fill — the mark is there in the markup and gone on screen. `text-body-emphasis` (or an outline glyph that keeps its own token) survives the fill; verify by capturing the selected row, not by reading the class list.
1007
+ - **A mark on an active fill of the same family disappears.** `.active` on a `list-group-item`, `nav-pill`, or `page-item` sets the item's own color, and a `text-bg-primary`-family mark inside it inherits or loses to that fill — present in the markup, gone on screen. Carry no tone class inside the fill ([SKILL.md](../SKILL.md) Surfaces, color, contrast). Verify by capturing the selected row, not by reading the class list.
981
1008
  - **`btn-check` filter labels invert in dark.** A `btn-outline-secondary` label reads as "chosen" in light and as "muted" in dark, because the checked fill and the surface swap relative weight. Give chosen filters an accent variant (a real theme color) rather than the neutral outline, so "chosen" reads the same way in both modes.
982
1009
 
983
1010
  Exactly one item in a selection carries `aria-current` — the visual fill and the announced state must be the same item.
@@ -4,56 +4,126 @@
4
4
  > process, and copy guidance — use when setting visual direction.
5
5
  > Operate layer: [SKILL.md](../SKILL.md).
6
6
 
7
- Approach this as the design lead at a small studio known for giving every client a visual identity that could not be mistaken for anyone else's. This client has already rejected proposals that felt templated, and is paying for a distinctive point of view: make deliberate, opinionated choices about palette, typography, and layout that are specific to this brief, and take one real aesthetic risk you can justify.
7
+ Give the surface a visual identity that could not be mistaken for anyone else's, and reject any
8
+ direction that reads as templated. Make deliberate, opinionated choices about palette, typography,
9
+ and layout that are specific to this brief, and take one real aesthetic risk you can justify.
8
10
 
9
11
  ## Ground it in the subject
10
12
 
11
- If the brief does not pin down what the product or subject is, pin it yourself before designing: name one concrete subject, its audience, and the page's single job, and state your choice. If there's any information in your memory about the human's preferences, context about what they're building, or designs you've made before — use that as a hint. The subject's own world, its materials, instruments, artifacts, and vernacular, is where distinctive choices come from. Build with the brief's real content and subject matter throughout.
13
+ Pin the subject before designing whenever the brief leaves it open: name one concrete subject, its
14
+ audience, and the page's single job, and state the choice. Use what you know of the user's
15
+ preferences, of what they are building, and of designs you have made for them before as hints, never
16
+ as templates. Draw the distinctive choices from the subject's own world — its materials,
17
+ instruments, artifacts, and vernacular. Build with the brief's real content and subject matter
18
+ throughout.
12
19
 
13
20
  ## Design principles
14
21
 
15
- For web designs, the hero is a thesis. Open with the most characteristic thing in the subject's world, in whatever form makes sense for it: a headline, an image, an animation, a live demo, an interactive moment. Be deliberate with your choice: a big number with a small label, supporting stats, and a gradient accent is the template answeronly use it if that's truly the best option.
22
+ Open a web design's hero with the subject's thesis the one claim the page makescarried by the
23
+ most characteristic thing in the subject's world, in whatever form suits it: a headline, an image,
24
+ an animation, a live demo, an interactive moment. Choose that opening deliberately — a big number
25
+ with a small label, supporting stats, and a gradient accent is the template answer, so take it only
26
+ where it is genuinely the best option.
16
27
 
17
- Typography carries the personality of the page. Pair the display and body faces deliberately, not the same families you would reach for on any other project, and set a clear type scale with intentional weights, widths, and spacing. Make the type treatment itself a memorable part of the design, not a neutral delivery vehicle for the content.
28
+ Set the typography as a decision rather than a default. Pair the display and body faces
29
+ deliberately, and not the families you would reach for on any other project. Set a clear type scale
30
+ with intentional weights, widths, and spacing. Make the type treatment one of the things the design
31
+ is remembered by.
18
32
 
19
- Structure is information. Structural devices — numbering, eyebrows, dividers, labels — should encode something true about the content, not decorate it. Many generic designs use numbered markers (01 / 02 / 03), but that's only appropriate if the content actually is a sequence, like a real process or a typed timeline where order carries information the reader needs. Question whether choices like numbered markers actually make sense before incorporating them.
33
+ Make every structural device — numbering, eyebrows, dividers, labels — encode something true about
34
+ the content rather than decorate it. Use numbered markers (01 / 02 / 03) only where the content is
35
+ a sequence: a real process, or a typed timeline whose order carries information the reader needs.
36
+ Before adding a device, check that it encodes something the reader needs.
20
37
 
21
- Leverage motion deliberately. Think about where and whether animation can serve the subject: a page-load sequence, a scroll-triggered reveal, hover micro-interactions, ambient atmosphere. An orchestrated moment usually lands harder than scattered effects; choose what the direction calls for. Sometimes less is more — extra animation is one of the fastest ways to make a design feel AI-generated.
38
+ Decide where and whether animation serves the subject: a page-load sequence, a scroll-triggered
39
+ reveal, hover micro-interactions, ambient atmosphere. Prefer one orchestrated moment to scattered
40
+ effects, and follow the direction where it calls for something else. Cut animation the direction
41
+ does not need — extra animation is one of the fastest ways to make a design read as AI-generated.
22
42
 
23
- Match complexity to the vision. Maximalist directions need elaborate execution; minimal directions need precision in spacing, type, and detail. Elegance is executing the chosen vision well.
43
+ Match complexity to the vision. Maximalist directions need elaborate execution; minimal directions
44
+ need precision in spacing, type, and detail.
24
45
 
25
- Consider written content carefully. A design brief often contains no real content, and it's up to you to come up with copy. Copy can make a design feel as templated as the layout itself. See the writing section below.
46
+ Write the copy yourself when the brief supplies none, and treat it as design material: templated
47
+ copy makes a surface read as templated as a templated layout does. Follow the writing rules below.
26
48
 
27
49
  ## Where the signature lives in product UI
28
50
 
29
- The same craft applies to dense, authenticated tools but the signature moves. In an admin screen or dashboard, the data is the content and must stay quiet, legible, and fast to scan; spending the aesthetic risk on the table itself taxes every user on every visit. Put the point of view in the chrome instead: the navigation and header treatment, the type pairing, the empty states, the way status and density are handled. A distinctive product UI is one whose _frame_ could not be mistaken for another product while its data surfaces stay disciplined and conventional enough to disappear into use.
51
+ Apply the same craft to dense, authenticated tools, and move the signature. In an admin screen or
52
+ dashboard the data is the content: keep it quiet, legible, and fast to scan, and never spend the
53
+ aesthetic risk on the table itself, which adds scan time for every user on every visit. Put the
54
+ point of view in the chrome — the navigation and header treatment, the type pairing, the empty
55
+ states, the handling of status and density. Make the frame impossible to mistake for another
56
+ product's, and keep the data surfaces disciplined and conventional enough to read without effort.
30
57
 
31
58
  ## Process: brainstorm, explore, plan, critique, build, critique again
32
59
 
33
- For calibration: AI-generated design right now clusters around three looks: (1) a warm cream background (near #F4F1EA) with a high-contrast serif display and a terracotta accent; (2) a near-black background with a single bright acid-green or vermilion accent; (3) a broadsheet-style layout with hairline rules, zero border-radius, and dense newspaper-like columns. All three are legitimate for some briefs, but they are defaults rather than choices, and they appear regardless of subject. Where the brief pins down a visual direction, follow it exactly — the brief's own words always win, including when it asks for one of these looks. Where it leaves an axis free, don't spend that freedom on one of these defaults. Just like a hired human designer, there's a careful balance between doing what you're good at and taking each project as a chance to experiment and learn.
34
-
35
- Work in two passes. First, brainstorm a short design plan based on the human's design brief: create a compact token system with color, type, layout, and signature. Color: describe the palette as 4–6 named hex values. Type: the typefaces for 2+ roles (a characterful display face used with restraint, a complementary body face, and a utility face for captions or data if needed). Layout: a layout concept, using one-sentence prose descriptions and ASCII wireframes to ideate and compare. Signature: the single unique element this page will be remembered by, embodying the brief in an appropriate way.
36
-
37
- Then review that plan against the brief before building: if any part of it reads like the generic default you would produce for any similar page (work through a similar prompt to see if you arrive somewhere similar) rather than a choice made for this specific brief — revise that part, and say what you changed and why. Only after you've confirmed the relative uniqueness of your design plan should you start to write the code, following the revised plan exactly and deriving every color and type decision from it.
38
-
39
- When writing the code, be careful structuring your CSS selector specificities. It's easy to generate classes that cancel each other out (especially a type-based selector like `.section` against an element-based selector like `.cta`), and paddings/margins between sections are where it happens most.
40
-
41
- Do a lot of this planning and iteration in your thinking, and only show ideas to the user when you have higher confidence they'll delight.
60
+ Calibrate against the three looks AI-generated design currently clusters around: (1) a warm cream
61
+ background (near #F4F1EA) with a high-contrast serif display and a terracotta accent; (2) a
62
+ near-black background with a single bright acid-green or vermilion accent; (3) a broadsheet-style
63
+ layout with hairline rules, zero border-radius, and dense newspaper-like columns. All three are
64
+ legitimate for some briefs; they are defaults rather than choices, and they appear regardless of
65
+ subject. Follow the brief exactly where it pins a visual direction — the brief's own words always
66
+ win, including when they ask for one of these looks. Where the brief leaves an axis free, spend that
67
+ freedom somewhere other than these defaults. Balance the moves you have already proven against
68
+ experimenting where the brief invites it.
69
+
70
+ Work in two passes. First, brainstorm a short design plan from the brief: a compact token system
71
+ with color, type, layout, and signature. Color: describe the palette as 4–6 named hex values. Type:
72
+ name the typefaces for 2+ roles — a characterful display face used with restraint, a complementary
73
+ body face, and a utility face for captions or data where one is needed. Layout: state a layout
74
+ concept, using one-sentence prose descriptions and ASCII wireframes to ideate and compare.
75
+ Signature: name the single element this page will be remembered by, embodying the brief.
76
+
77
+ Then review that plan against the brief before building. Where a part of it reads like the generic
78
+ default you would produce for any similar page — work through a similar prompt and see whether you
79
+ arrive somewhere similar — revise that part, and say what you changed and why. Start writing code
80
+ only once the plan is specific to this brief, then follow the revised plan exactly and derive every
81
+ color and type decision from it.
82
+
83
+ Structure your CSS selector specificities deliberately when writing the code. Classes cancel each
84
+ other out easily, especially a type-based selector like `.section` against an element-based selector
85
+ like `.cta`, and the padding and margin between sections is where it happens most.
86
+
87
+ Do this planning and iteration in your thinking. Show the user a direction only once it satisfies
88
+ the brief and the quality floor below.
42
89
 
43
90
  ## Restraint and self-critique
44
91
 
45
- Spend your boldness in one place. Let the signature element be the one memorable thing, keep everything around it quiet and disciplined, and cut any decoration that does not serve the brief. Not taking a risk can be a risk itself! Build to a quality floor without announcing it: responsive down to mobile, visible keyboard focus, reduced motion respected. Critique your own work as you build, taking screenshots if your environment supports it — a picture is worth 1000 tokens, and it is the only thing that can tell you whether the design you wrote is the design that rendered. Look at both themes and both the wide and the narrow viewport; a treatment that only exists in the markup is not a treatment yet. Consider Chanel's advice: before leaving the house, look in the mirror and remove one accessory. Human creators have memory and always try to do something new; if you have a place to jot down notes about what you've tried, it will help future passes.
46
-
47
- ## More on writing in design
48
-
49
- Words appear in a design for one reason: to make it easier to understand, and therefore easier to use. They are design material, not decoration. Bring the same intentionality to copy that you bring to spacing and color. Before writing anything, ask what the design needs to say, and how it can best be said to help the person navigate the experience.
50
-
51
- Write from the end user's side of the screen. Name things by what people control and recognize, never by how the system is built. A person manages notifications, not webhook config. Describe what something does in plain terms rather than selling it. Being specific is always better than being clever.
52
-
53
- Use active voice as default. A control should say exactly what happens when it's used: "Save changes," not "Submit." An action keeps the same name through the whole flow, so the button that says "Publish" produces a toast that says "Published." The vocabulary of an interface is the signposting for someone navigating the product. Cohesion and consistency are how people learn their way around.
54
-
55
- Treat failure and emptiness as moments for direction, not mood. Explain what went wrong and how to fix it, in the interface's voice rather than a person's. Errors don't apologize, and they are never vague about what happened. An empty screen is an invitation to act.
56
-
57
- Keep the register conversational and tuned: plain verbs, sentence case, no filler, with tone matched to the brand and the audience. Let each element do exactly one job. A label labels, an example demonstrates, and nothing quietly does double duty.
58
-
59
- Brevity on a control is not the same as vagueness. Where the surrounding context already names the object, the visible label can be a single word and stay unambiguous the specific phrase then lives in the control's accessible name, so nothing is lost for someone who arrives without the context. The same discipline governs the visual vocabulary: a glyph is a word, and a word means one thing. Once a mark stands for "finished" it cannot also stand for "selected" three panels over, or the reader has to relearn the language on every screen.
92
+ Spend the boldness in one place. Let the signature element be the one memorable thing, keep
93
+ everything around it quiet and disciplined, and cut any decoration that does not serve the brief —
94
+ decorative emoji as UI, pill soup, glow effects, and gradient-on-everything are the usual instances
95
+ of decoration with no reason in the subject. Treat a surface with no deliberate risk as failing the
96
+ distinctiveness mandate. Meet the quality floor without announcing it: responsive down to mobile,
97
+ visible keyboard focus, reduced motion respected. Critique your own work as you build, and take
98
+ screenshots where the environment supports it. Read both themes and both the wide and the narrow
99
+ viewport from those captures, not the markup. Before shipping, remove one accessory: cut the
100
+ least-necessary decorative element, and restore it only where the surface demonstrably loses
101
+ information without it. Where notes persist across passes, record what you tried so the next pass
102
+ reads it.
103
+
104
+ ## Writing in design
105
+
106
+ Keep a word only where it makes the design easier to understand, and therefore easier to use. Bring
107
+ the same intentionality to copy as to spacing and color. Before writing anything, decide what the
108
+ design needs to say, and how to say it so the person can navigate the experience.
109
+
110
+ Write from the end user's side of the screen. Name things by what people control and recognize,
111
+ never by how the system is built: a person manages notifications, not webhook config. Describe what
112
+ something does in plain terms rather than selling it, and choose the specific word over the clever
113
+ one.
114
+
115
+ Use the active voice by default. Make a control say exactly what happens when it is used: "Save
116
+ changes," not "Submit." Keep an action's name through the whole flow, so the button that says
117
+ "Publish" produces a toast that says "Published." Hold one vocabulary across every screen.
118
+
119
+ Give failure and emptiness direction rather than mood. State what went wrong and how to fix it, in
120
+ the interface's voice rather than a person's, without apology and without vagueness about what
121
+ happened. Name the action that fills an empty screen.
122
+
123
+ Keep the register conversational and tuned: plain verbs, sentence case, no filler, tone matched to
124
+ the brand and the audience. Give each element exactly one job: a label labels, an example
125
+ demonstrates, and nothing does double duty.
126
+
127
+ Keep a short control label unambiguous. Where the surrounding context already names the object, make
128
+ the visible label a single word and carry the specific phrase in the control's accessible name, so
129
+ nothing is lost for someone who arrives without that context.
@@ -301,7 +301,7 @@ Helpers are single-purpose classes that sit alongside utilities.
301
301
 
302
302
  - **Spacing scale:** prefer `gap-*` on flex/grid parents over scattering `m-*` on every child — the parent owns rhythm, children stay reorderable. Use `p-3` / `p-4` for panel padding; reserve `p-5` for sparse marketing-like empty states.
303
303
  - **Body surfaces:** `bg-body`, `bg-body-secondary`, `bg-body-tertiary` track `data-bs-theme` — raw `bg-white` / `bg-light` freeze the surface in light mode.
304
- - **Text hierarchy:** `text-body` for content, `text-body-secondary` for meta, `text-*-emphasis` when a status must stay readable on subtle backgrounds. `text-body-tertiary` is the decoration tier — it misses the 4.5:1 bar for information-bearing small text, so anything a user must read is `text-body-secondary` or better.
304
+ - **Text hierarchy:** `text-body` for content, `text-body-secondary` for meta, `text-*-emphasis` for any status a reader acts on. The plain `text-success` / `text-danger` / `text-warning` colors and `text-body-tertiary` are the decoration tier ([SKILL.md](../SKILL.md) → Surfaces, color, contrast).
305
305
  - **Opacity traps:** `text-white-50` / `text-black-50` often fail contrast — prefer `text-opacity-75` on a known solid, or `text-body-secondary`. Every one of these pairings is measured against the shipped cascade in both themes; a skin retunes the same token names.
306
306
  - **Flex floors:** a flex column gives its items an automatic minimum size, and `text-truncate` removes it. Titles and marks that must keep their height carry `flex-shrink-0`; only the growing sibling absorbs the slack.
307
307
  - **Flex toolbars:** `d-flex align-items-center gap-2 flex-wrap` (or `flex-nowrap overflow-auto` for dense bars). Equal-height siblings: `align-items-stretch` + `h-100` on cards.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: orkestrel-human-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 — and generate the capture portfolio 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, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
4
+ ---
5
+
6
+ # Prove an application through human journeys
7
+
8
+ ## Load authority
9
+
10
+ Read the current files in this order:
11
+
12
+ 1. `AGENTS.md`.
13
+ 2. `.claude/rules/tests.md` for test law, real implementations, and shared test infrastructure;
14
+ `.claude/rules/browser.md` for browser and Vue usage; `.claude/rules/application.md` for app
15
+ composition and entries; `.claude/rules/documentation.md` for parity. Those rules are the
16
+ contract; this skill is the workflow.
17
+ 3. [layer.md](references/layer.md) before building, extending, or debugging the journey layer.
18
+ 4. [captures.md](references/captures.md) before registering a state or placing a capture.
19
+ 5. `guides/README.md`, the governing guide for the surface, and `ROADMAP.md` when present.
20
+ 6. The `*/types.ts` of every environment the journeys drive, plus the application's root component,
21
+ route entry, and store contract.
22
+
23
+ ## Apply the journey laws
24
+
25
+ 1. **Drive only what a person can see and reach.** Target every control by its ARIA role and its
26
+ accessible name as rendered. Never reach into a component instance, a store, a transport, a
27
+ copied credential, or a test-only hook to make a step succeed. Report a step that cannot be
28
+ performed through the interface as a finding about the interface.
29
+ 2. **Assert what is seen.** Quote the rendered text a person reads, which is `innerText` — a label
30
+ under `text-uppercase` asserts as `TRACE` where the source says `Trace`. Never let a state read
31
+ replace a perception assertion; it may only corroborate one, and `.claude/rules/tests.md` fixes
32
+ which state a test may read at all.
33
+ 3. **Assert what the interface withholds.** Assert every refusal through the resolver's exact
34
+ failure voice, and distinguish an absent control from a present but humanly unreachable one.
35
+ 4. **Keep transport and persistence proofs in their own declared block,** never inside a journey.
36
+ Assert every live or asynchronous fact by convergence — poll until it contains or equals — never
37
+ by an identity read of one frame.
38
+ 5. **Generate the portfolio from the acceptance journeys.** Place each registered state inside the
39
+ journey that reaches it, and never register a state no journey reaches.
40
+ 6. **Commit a value through an act a person performs:** Enter, Tab away, or a named button. Report a
41
+ surface that commits on a timer, on an unpredictable event, or only after work the person cannot
42
+ observe as a surface finding, and never work around it in the layer.
43
+ 7. **Type only what a person would.** Journeys carry trusted input; adversarial payloads belong to
44
+ the transport family and the parser suites.
45
+
46
+ ## Build or verify the journey layer
47
+
48
+ - Build the layer as shared browser test infrastructure under `.claude/rules/tests.md`: it lives in
49
+ the workspace's browser test setup module, exports every helper from there, and adds a journey
50
+ helper only where `@orkestrel/test` publishes none. Never declare a resolver inside a test file.
51
+ - Give the layer every capability [layer.md](references/layer.md) fixes: the role-scoped resolver
52
+ and its distinct failure voices, region-scoped resolution, the input and traversal verbs, the
53
+ perception readers, and the capture hook.
54
+ - Drive every step through the browser provider's user-event API, and never dispatch a constructed
55
+ event ([layer.md](references/layer.md) → What it drives).
56
+ - Re-verify the layer against what the application renders now whenever markup changes
57
+ ([layer.md](references/layer.md) → Role vocabulary).
58
+
59
+ ## Derive journeys from intents
60
+
61
+ Write one journey per user intent, named for what the person achieves rather than for the
62
+ components it passes through. Place them in the browser environment's `integration.test.ts`, whose
63
+ placement and scope `.claude/rules/tests.md` fixes.
64
+
65
+ - Enter through the real entry: mount the shipped root component with a real store and the route a
66
+ person lands on, and let the application load itself.
67
+ - Reach each surface's own controls through forward Tab traversal in at least one journey.
68
+ - Type keystroke by keystroke where the keystrokes are the subject; fill in one operation where the
69
+ text is only a payload the person pastes.
70
+ - Poll every fact the application produces asynchronously until it converges. Never assert one from
71
+ a single read after the action. A poll's predicate must be able to go false-to-true after the
72
+ action it observes; a predicate already true when the poll starts binds nothing.
73
+ - Assert the state the flow must reach, never the transient path taken to it. A criterion that bans
74
+ a harmless transient over-refuses and breaks on the next honest implementation.
75
+ - Assert the negative beside the positive whenever a value replaces another: the new sentence is
76
+ present **and** the old one is gone.
77
+ - After a confirmed destructive action, assert through trusted input that focus landed on a visible,
78
+ announced location.
79
+ - Assert the whole page's perception never matches the vocabulary the product does not speak —
80
+ engine, schema, and implementation words the interface is supposed to translate.
81
+ - Report a bare accessible name that answers for two reachable elements on one screen as a surface
82
+ finding, and target through role or region until the surface is fixed.
83
+
84
+ ## Prove the refusals
85
+
86
+ - Give every surface a refusal family: the controls a person must not reach in the state the
87
+ journey has put the surface in.
88
+ - Assert the exact failure voice the case means. Never write an assertion that accepts either of
89
+ two voices.
90
+ - Cover the restrictions the interface imposes on itself: a collapsed panel's field, a verb
91
+ belonging to another kind of object, a control disabled until its precondition lands.
92
+ - When a refusal changes voice after a markup change, read it as a role or reachability change
93
+ before treating the element as missing
94
+ ([layer.md](references/layer.md) → Role vocabulary).
95
+
96
+ ## Declare the transport family
97
+
98
+ - Name the block for what it proves — persistence, restart, storage failure.
99
+ - Drive it through the application's real session and store contracts. Build a store that stalls a
100
+ read, fails a fixed number of reads, or fails a write as an inert configurable implementation of
101
+ the published interface, under the real-implementation law in `AGENTS.md`.
102
+ - Prove the visible half in a journey: the failure sentence a person reads, and the retry control
103
+ that clears it.
104
+ - Assert restart by starting a second session over the same store and polling the restored value.
105
+
106
+ ## Generate the portfolio
107
+
108
+ Follow [captures.md](references/captures.md) for the state registry and its placement rules, the
109
+ theme-and-viewport variant matrix, the always-on filename proof, the capture-run membership proof,
110
+ and how a state that exists only during an activation is captured.
111
+
112
+ When a capture and a green suite disagree, the capture is the evidence and the fixture is the
113
+ defect.
114
+
115
+ Route review of the portfolio to the `orkestrel-polish-surface` campaign. Do not judge it here.
116
+
117
+ ## Accept
118
+
119
+ Completion requires all of:
120
+
121
+ - every in-scope user intent reaching its outcome through the interface, with no step that reaches
122
+ past it;
123
+ - keyboard-only reachability proven on every surface the journeys cover;
124
+ - a refusal family per surface, each asserting one exact failure voice;
125
+ - the transport family declared separately, driven through real implementations, and convergent;
126
+ - the registry-times-variants filename proof and the state-placement proof green in an ordinary run;
127
+ - one capture run per variant writing every registered file, each read back non-empty;
128
+ - perception assertions quoting rendered text, and the vocabulary sweep green on the whole page;
129
+ - the repository gates green, under the independent-verification law in `.agents/orchestration.md`.
130
+
131
+ Report each journey by the intent it proves, the refusals it establishes, the states it placed, and
132
+ every surface finding the layer's refusals exposed.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: 'Prove Human Journeys'
3
+ short_description: 'Prove an application through the interface a person uses'
4
+ default_prompt: 'Use $orkestrel-human-journey to prove this application through the interface a person uses, and generate the capture portfolio from those journeys.'
@@ -0,0 +1,74 @@
1
+ # The capture portfolio
2
+
3
+ Take every screenshot from an acceptance journey, at the moment that journey is in the state the
4
+ picture names. Never add a test whose only purpose is a screenshot, and never stage a state for the
5
+ camera that a journey did not reach through the interface.
6
+
7
+ ## The capture hook
8
+
9
+ ```ts
10
+ capture(state: string): Promise<string | undefined>
11
+ ```
12
+
13
+ - Return `undefined` and do nothing when the capture flag is unset, so an ordinary run neither
14
+ resizes the viewport nor writes a file.
15
+ - Read one variant value that names the theme and the viewport together, and refuse a value that
16
+ names no registered variant.
17
+ - Apply that variant's theme and viewport inside the hook, so the run's single variant value is the
18
+ only source of both.
19
+ - Write one file named `<state>--<variant>.png` under the workspace's git-ignored `tmp/` tree, and
20
+ return the path it wrote.
21
+
22
+ ## The registry
23
+
24
+ Declare two frozen lists in the journey file: the state names, and the variants.
25
+
26
+ - Name a state for its surface and its condition — `answer-partial`, `start-storage-failure`,
27
+ `case-delete-confirmation`.
28
+ - Register the states the design work actually needs, and place every registered one. Never leave a
29
+ registered state unplaced.
30
+ - Wrap the hook in a placement helper that refuses an unregistered state name, refuses a second
31
+ placement of the same state, records each written path, and refuses a filename written twice.
32
+ - Place a state from inside the journey that reaches it, immediately after the assertion that
33
+ proves the surface is in that state.
34
+
35
+ ## Variants
36
+
37
+ - Name a variant as one value carrying both the theme and the viewport, such as `dark-390`. Never
38
+ split them into two selectors: a split lets a run write a filename describing a combination it
39
+ did not render.
40
+ - Render one variant per run, and produce the portfolio — the registry times the variants — by
41
+ repeating the run once per variant.
42
+
43
+ ## The two proofs
44
+
45
+ | Proof | Runs | Asserts |
46
+ | -------------------- | --------------------------- | ---------------------------------------------------------------------------------------------- |
47
+ | Filename expansion | Always | The registry's length and uniqueness, the variant count, and that the expansion is unique |
48
+ | Portfolio membership | Always; disk under the flag | Every registered state was placed; under the flag, the files on disk are exactly the expansion |
49
+
50
+ - Keep the filename proof always-on, so a registry edit that introduces a duplicate or a collision
51
+ fails the ordinary run.
52
+ - Assert placement equality as set equality against the registry, in every run. Never assert a
53
+ count: a count passes while one state is placed twice and another never.
54
+ - Under the capture flag, assert the written filenames equal the registry expanded for the run's
55
+ variant, then read each file back and require non-empty contents. Never treat the path a
56
+ screenshot call returned as proof that a file exists.
57
+ - Put the membership proof last in the file, after every journey that feeds its tally.
58
+
59
+ ## Transient states
60
+
61
+ Capture a state that exists only while an activation is in flight from inside that activation,
62
+ never after the click returns.
63
+
64
+ - Attach a one-shot listener to the resolved control, place the capture from inside it, then click
65
+ through the normal verb and await the promise the listener recorded.
66
+ - Fail the step when the listener never ran.
67
+
68
+ ## Hygiene
69
+
70
+ - Keep the portfolio out of version control.
71
+ - Regenerate the whole matrix from the journeys after any surface change. Never judge a round
72
+ against a portfolio that is part old and part new.
73
+ - Route review of the portfolio to the `orkestrel-polish-surface` campaign, which owns preflight,
74
+ verdicts, and reconciliation. This reference owns only how the journeys generate it.
@@ -0,0 +1,131 @@
1
+ # The journey layer
2
+
3
+ Build every capability here before writing the first journey, and route every journey step through
4
+ it. Treat a journey that works around a missing capability as a layer defect.
5
+
6
+ Implement the signatures below as a contract in the workspace's browser test setup module; never
7
+ copy them as source. Name each helper for the human act it performs.
8
+
9
+ ## What it drives
10
+
11
+ - Drive the real browser through the installed Vitest browser provider. Import its `page` locators
12
+ and `userEvent` from `vitest/browser`; the `@vitest/browser/context` specifier is deprecated and
13
+ is not the import a new layer uses.
14
+ - Use the provider verbs for input: `click`, `keyboard`, `tab`, `type`, `clear`, `fill`, and
15
+ `selectOptions`. Use `page.viewport` and `page.screenshot` for captures, and the runner's file
16
+ command to read a written capture back.
17
+ - Never dispatch a constructed event from the layer or from a journey. The centralized event
18
+ factories `.claude/rules/tests.md` prescribes serve unit tests whose subject is the handler; a
19
+ journey drives input through the provider verbs only.
20
+ - Never let a helper take an element, a component instance, or a selector from the caller. Every
21
+ helper resolves its own target from role and accessible name.
22
+
23
+ ## The resolver
24
+
25
+ ```ts
26
+ resolveAccessible(name: string): HTMLElement
27
+ resolveAccessible(role: string, name: string): HTMLElement
28
+ ```
29
+
30
+ - Match the accessible name exactly, never a substring.
31
+ - Search a fixed set of interactive roles for the bare-name form, and exactly the named role for
32
+ the two-argument form.
33
+ - Count a match as reachable only when every condition holds: it is connected; it passes a
34
+ visibility check that honours opacity and CSS; its box has non-zero width and height; its
35
+ `tabIndex` is at least zero; it matches neither `:disabled` nor `[aria-disabled="true"]`; and it
36
+ has no `[inert]` ancestor.
37
+ - Scroll a wholly off-viewport target into view once, then measure reachability again. Count a
38
+ control a person can scroll to as reachable, and one that stays outside the viewport as
39
+ unreachable.
40
+ - Give the layer a rendered-only resolver beneath the public one, and use it from the acting verbs
41
+ so a click does not fail on a target the act itself scrolls into view.
42
+
43
+ ### The failure voices
44
+
45
+ Keep these distinct, and never merge two into one message.
46
+
47
+ | Condition | The voice it must throw |
48
+ | ------------------------------------------ | ---------------------------------------------------------------- |
49
+ | No element carries the name | `No interactive element has the accessible name "<name>"` |
50
+ | Every match fails a reachability condition | `Interactive target "<name>" is not visible and focus-reachable` |
51
+ | Several matches are reachable | `Interactive target "<name>" is ambiguous across <n> elements` |
52
+ | Still off-viewport after being scrolled to | `Interactive target "<name>" is unreachable after scrolling` |
53
+
54
+ - Report an absent control and a present-but-unreachable one as different findings: the first names
55
+ a missing control, the second names the interface gating one that exists.
56
+ - Report ambiguity as a finding about the surface. Name the match count in the message, and
57
+ re-target the journey by role or region.
58
+
59
+ ## Role vocabulary
60
+
61
+ Never infer a role from markup. Confirm the computed role in the browser whenever a target stops
62
+ resolving.
63
+
64
+ - A `list`-bearing input computes `combobox`, not `textbox`. Attaching native suggestion machinery
65
+ to a field is a role change: re-target every journey that names that field, and read a resolver
66
+ miss immediately after such a change as this before treating the element as missing.
67
+ - Always target a tab by its role. A tab and its panel collide on a bare name by construction,
68
+ because the panel is labelled by its tab.
69
+ - `<summary>` is exposed as a native disclosure rather than through a role the provider's role
70
+ locators accept. Give the layer a separate disclosure verb keyed to the summary's rendered text,
71
+ applying the same reachability conditions and its own failure voices.
72
+
73
+ ## Region-scoped resolution
74
+
75
+ ```ts
76
+ clickAccessibleWithin(region: string, role: string, name: string): Promise<void>
77
+ ```
78
+
79
+ - Provide this form for repeated short verbs such as `Add`, and for a control whose accessible name
80
+ is completed by a status the row renders.
81
+ - Apply the same reachability conditions inside the region, and throw voices that name the region
82
+ as well as the target.
83
+
84
+ ## Input and traversal
85
+
86
+ | Verb | Contract |
87
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
88
+ | `typeAccessible(name, text)` | Focus the field, select all, delete, then send real keystrokes. Escape the provider's key syntax in the text. |
89
+ | `fillAccessible(name, text)` | Replace the value in one operation for text too long to type. The real element still publishes real input. |
90
+ | `pressKeys(keys)` | Send a provider keyboard sequence for Enter, arrows, modifiers, and combinations. |
91
+ | `traverseAccessible(name)` | Move focus by forward Tab from wherever focus is, and return the target once focus lands on it. |
92
+
93
+ - Count a traversal step only when focus actually lands on an element, and never charge the step
94
+ bound for a Tab that moved nothing.
95
+ - End the traversal when focus revisits an element, which is one complete cycle of the tab order,
96
+ and throw the traversal's own voice, carrying the trail of what focus did reach. Keep a hard cap
97
+ above the cycle so a page with no tab order fails instead of hanging.
98
+ - Re-resolve the target by role and name on every step, and never hold a node reference across
99
+ steps. A framework may replace the node between resolution and focus arrival.
100
+ - Never call the browser's focus method to place focus.
101
+
102
+ ## Perception
103
+
104
+ ```ts
105
+ readPerception(name: string): string
106
+ ```
107
+
108
+ - Return the normalized `innerText` of exactly one visible named region, dialog, table, tab panel,
109
+ alert, or status. Collapse whitespace runs to single spaces and trim.
110
+ - Read `innerText`, never `textContent`: `innerText` applies CSS transforms and leaves out content
111
+ the layout hides. Quote that text in assertions.
112
+ - Include descendant visually-hidden text, which a screen reader perceives and which a clip-based
113
+ hiding technique leaves in `innerText`.
114
+ - Throw when the named region is absent, hidden, or ambiguous.
115
+ - Give the layer a whole-page perception reader for cross-region sentences and the vocabulary
116
+ sweep, a focus reader that returns the active element's rendered text, and a value reader that
117
+ returns a resolved control's value. A perception assertion may quote that value: it is a rendered
118
+ fact rather than internal state.
119
+
120
+ ## Mounting and cleanup
121
+
122
+ - Mount the shipped root component with its real provisions and return an idempotent cleanup that
123
+ unmounts the app and removes its container.
124
+ - Undo everything a journey changed after each test: unmount, destroy the session, reset the theme,
125
+ clear the keys the application persisted, and return the route to its entry. Never let a journey
126
+ inherit the previous journey's state.
127
+
128
+ ## The capture hook
129
+
130
+ Give the layer exactly one capture helper. [captures.md](captures.md) owns the registry, the
131
+ variant matrix, and the proofs that read what it wrote.