@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.
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +146 -194
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +29 -22
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +31 -4
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +103 -33
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +1 -1
- package/dist/host/agents/skills/orkestrel-human-journey/SKILL.md +132 -0
- package/dist/host/agents/skills/orkestrel-human-journey/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-human-journey/references/captures.md +74 -0
- package/dist/host/agents/skills/orkestrel-human-journey/references/layer.md +131 -0
- package/dist/host/claude/rules/documentation.md +4 -1
- package/dist/host/claude/skills/orkestrel-human-journey/SKILL.md +10 -0
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +1 -1
- package/dist/host/manifest.json +30 -1
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/policy.test.ts +33 -0
- package/dist/host/tests/setupPolicy.ts +319 -2
- package/dist/src/core/index.cjs +1 -1
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +1 -1
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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 |
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
Open a web design's hero with the subject's thesis — the one claim the page makes — carried 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
43
|
+
Match complexity to the vision. Maximalist directions need elaborate execution; minimal directions
|
|
44
|
+
need precision in spacing, type, and detail.
|
|
24
45
|
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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`
|
|
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.
|