@orkestrel/scaffold 0.0.59 → 0.0.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +13 -10
  2. package/dist/bin/main.js +632 -320
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/CLAUDE.md +5 -1
  5. package/dist/host/agents/orchestration.md +44 -19
  6. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +91 -82
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +5 -5
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +15 -15
  9. package/dist/host/agents/skills/enterprise-bootstrap/references/inputs.md +501 -0
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +167 -0
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +2 -2
  12. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +10 -9
  14. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +21 -11
  15. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +20 -2
  16. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
  17. package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +1 -1
  18. package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +1 -1
  19. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +4 -1
  20. package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +71 -50
  21. package/dist/host/agents/skills/orkestrel-prove-journey/SKILL.md +93 -29
  22. package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +1 -1
  23. package/dist/host/agents/skills/orkestrel-prove-journey/references/captures.md +62 -38
  24. package/dist/host/agents/skills/orkestrel-prove-journey/references/decide.md +68 -0
  25. package/dist/host/agents/skills/orkestrel-prove-journey/references/layer.md +107 -79
  26. package/dist/host/agents/skills/orkestrel-prove-journey/references/statechart.md +84 -0
  27. package/dist/host/agents/skills/orkestrel-prove-journey/references/styles.md +87 -0
  28. package/dist/host/agents/templates/brief.md +16 -7
  29. package/dist/host/agents/transports/claude.md +4 -2
  30. package/dist/host/agents/transports/codex.md +4 -1
  31. package/dist/host/claude/agents/analyst.md +3 -1
  32. package/dist/host/claude/agents/application.md +1 -1
  33. package/dist/host/claude/agents/builder.md +3 -3
  34. package/dist/host/claude/agents/checker.md +5 -0
  35. package/dist/host/claude/agents/grok.md +15 -5
  36. package/dist/host/claude/agents/implementer.md +1 -1
  37. package/dist/host/claude/agents/orkestrel.md +12 -11
  38. package/dist/host/claude/agents/planner.md +10 -0
  39. package/dist/host/claude/agents/reviewer.md +14 -8
  40. package/dist/host/claude/agents/sol.md +3 -1
  41. package/dist/host/claude/agents/verifier.md +2 -4
  42. package/dist/host/claude/rules/architecture.md +7 -5
  43. package/dist/host/claude/rules/documentation.md +1 -0
  44. package/dist/host/claude/rules/names.md +23 -5
  45. package/dist/host/claude/rules/patterns.md +1 -0
  46. package/dist/host/claude/rules/quality.md +1 -1
  47. package/dist/host/claude/rules/tests.md +3 -3
  48. package/dist/host/claude/rules/typescript.md +4 -1
  49. package/dist/host/claude/rules/writing.md +2 -2
  50. package/dist/host/claude/skills/orkestrel-prove-journey/SKILL.md +1 -1
  51. package/dist/host/codex/agents/builder.toml +6 -6
  52. package/dist/host/codex/agents/checker.toml +2 -1
  53. package/dist/host/codex/agents/grok.toml +12 -5
  54. package/dist/host/codex/agents/implementer.toml +2 -2
  55. package/dist/host/codex/agents/opus.toml +6 -1
  56. package/dist/host/codex/agents/planner.toml +11 -6
  57. package/dist/host/codex/agents/reviewer.toml +8 -6
  58. package/dist/host/guides/scaffold.md +39 -14
  59. package/dist/host/manifest.json +81 -51
  60. package/dist/host/scripts/codex.sh +0 -0
  61. package/dist/host/scripts/cursor.sh +0 -0
  62. package/dist/host/scripts/deps.sh +0 -0
  63. package/dist/host/scripts/ollama.sh +0 -0
  64. package/dist/src/core/index.cjs +424 -282
  65. package/dist/src/core/index.cjs.map +1 -1
  66. package/dist/src/core/index.d.cts +361 -220
  67. package/dist/src/core/index.d.ts +361 -220
  68. package/dist/src/core/index.js +421 -283
  69. package/dist/src/core/index.js.map +1 -1
  70. package/dist/src/server/index.cjs +208 -170
  71. package/dist/src/server/index.cjs.map +1 -1
  72. package/dist/src/server/index.d.cts +276 -152
  73. package/dist/src/server/index.d.ts +276 -152
  74. package/dist/src/server/index.js +200 -172
  75. package/dist/src/server/index.js.map +1 -1
  76. package/package.json +8 -7
@@ -9,7 +9,8 @@ follows it. This file adds only what Claude Code does differently, and cannot we
9
9
  ## Dispatch mechanism
10
10
 
11
11
  - Use the Agent tool for a single dispatch, including when later control flow depends on its result.
12
- - Use a Workflow for a deterministic fan-out, staged pipeline, or loop. Serialize writing nodes.
12
+ - Use a Workflow for a deterministic fan-out, staged pipeline, or loop. Serialize writing nodes, and
13
+ name each node's model alias per § Models.
13
14
  - Recover an interrupted Workflow with `resumeFromRunId`.
14
15
  - Foreground Bash is hard-capped at 10 minutes regardless of its timeout parameter. Launch anything
15
16
  that can exceed it as a harness-tracked background command.
@@ -20,6 +21,9 @@ follows it. This file adds only what Claude Code does differently, and cannot we
20
21
 
21
22
  - Use the aliases `opus` and `sonnet`. Never use a fixed Claude model ID and never use `inherit`.
22
23
  - Never set `CLAUDE_CODE_SUBAGENT_MODEL`. It flattens the engine split.
24
+ - Every Workflow `agent()` node names its model alias explicitly. The Workflow custom-agent path
25
+ does not apply a role file's `model:` pin, so a node that omits the alias runs on the session
26
+ model and the lane reads normal on the wrong engine.
23
27
  - Run the main session on `opus` at high effort, set by `/model opus` or `"model": "opus"`. Opus 5
24
28
  is the Orchestrator in this harness. Its Orchestrator duties are unchanged if it is configured
25
29
  otherwise.
@@ -35,8 +35,7 @@ One workflow runs across all providers. Each engine has one job and never takes
35
35
  - Route each nontrivial implementation unit to Opus or Sol. Objective, constraint-heavy,
36
36
  mechanical-precision work goes to Sol. API-shape, naming, and documentation-voice work goes to
37
37
  Opus. Cursor Composer is not an implementation route, and no `composer` role exists.
38
- - Design runs the adversarial pass. An audit runs the lanes its round names, with at least one whose
39
- engine did not write the work.
38
+ - Design runs the adversarial pass. § Execution loop's audit step fixes which lanes an audit runs.
40
39
 
41
40
  ## Orchestration by harness
42
41
 
@@ -64,12 +63,15 @@ the execution loop's audit step names, on the same clean-context terms.
64
63
 
65
64
  | Lane | Argues |
66
65
  | -------------- | --------------------------------------------------------------------------- |
67
- | **Subjective** | Shape, taste, naming, ergonomics, design fit, what the API should feel like |
66
+ | **Subjective** | Shape, taste, naming, ergonomics, design fit, the feel the API must present |
68
67
  | **Objective** | Correctness, constraints, and what the code and contracts actually permit |
69
68
 
70
69
  **A required lane always runs.** Never collapse required lanes into one. Never let an engine's
71
70
  absence stand in for a required lane.
72
71
 
72
+ Call a lane the round did not dispatch **not run**. `dark` names a bench that cannot round-trip and
73
+ names nothing else, so never write it of a lane. A verdict file's recorded reason uses those words.
74
+
73
75
  ### Clean contexts
74
76
 
75
77
  - Dispatch each lane as a fresh subagent. Never run a lane inside the Orchestrator's own context.
@@ -134,6 +136,9 @@ Fall back in this order and record the substitution:
134
136
  nothing, and returns the required distillate.
135
137
  - Work directly on a typo, a one-line fix, or a single lookup. Orchestrate when isolation,
136
138
  parallelism, independent review, or substantial context justifies it.
139
+ - Dispatch staging, packing, gate-chain invocation, and instrument authorship as units — `builder`
140
+ for a fully specified script, `verifier` for its evidence — each with a brief and an audit like
141
+ any other unit. Only the commit and the push stay with the Orchestrator.
137
142
 
138
143
  ## Roles
139
144
 
@@ -196,8 +201,8 @@ Every role honours this floor. No dispatch may widen it.
196
201
  those roles cannot inspect the tree by writing to it, the Orchestrator supplies the actual diff
197
202
  and status evidence in every review dispatch.
198
203
  - `verifier` has no edit or write tools and never fixes a failure.
199
- - Run writing roles in the main checkout, strictly serialized: one writer at a time, dispatched
200
- from a clean committed baseline, each owning disjoint files.
204
+ - Run one writing role per checkout, on disjoint checkouts, each dispatched from a clean committed
205
+ baseline and each owning disjoint files. In a single checkout that is one writer at a time.
201
206
  - Treat every shared file as report-only.
202
207
  - No role commits, pushes, tags, publishes, installs dependencies, or runs a destructive command.
203
208
  - No role runs `git checkout`, `git restore`, `git stash`, `git reset`, or `git clean`. Each discards
@@ -249,8 +254,8 @@ Every role honours this floor. No dispatch may widen it.
249
254
  Concurrent executors share a filesystem unless isolated. Follow these rules to prevent clobbered
250
255
  edits, formatter and build races, cache phantoms, and validation cross-talk.
251
256
 
252
- 1. Serialize writing executors in the main checkout. Commit a checkpoint before each writing
253
- dispatch so git is the rollback mechanism.
257
+ 1. Serialize writers as § Permission floor states: one per checkout, checkouts disjoint. Commit a
258
+ checkpoint before each writing dispatch so git is the rollback mechanism.
254
259
  2. Assign disjoint owned files plus explicit shared and off-limits files.
255
260
  3. Keep shared files report-only. Executors return exact patches for serial integration.
256
261
  4. Restrict concurrent executors to read-only, scoped validation. A tree-wide result may contain a
@@ -272,8 +277,8 @@ edits, formatter and build races, cache phantoms, and validation cross-talk.
272
277
  and a flake makes that look like it worked. Refuse the failed row, name it, and re-run it alone
273
278
  before deciding what it was.
274
279
  9. Run a fleet pass in slices that report as they finish, never as one block. A block hides its first
275
- failure behind every target that follows, so the failure surfaces after the work it should have
276
- stopped. A slice hands control back while most of the fleet is still unstarted.
280
+ failure behind every target that follows, so the failure surfaces after the work it exists to
281
+ stop. A slice hands control back while most of the fleet is still unstarted.
277
282
  10. Re-run a timing or resource failure alone before believing it. Concurrent slices, builds, and
278
283
  suites make a container miss deadlines it meets when idle, so a red result under load is a
279
284
  question rather than an answer. A unit re-running the file alone is not alone: its own exec,
@@ -326,21 +331,21 @@ longer holds.
326
331
  ends the campaign, each to end implemented, repaired, retained, or intentionally excluded on
327
332
  evidence. A plan that names work but not its end can only be abandoned, never finished.
328
333
  3. **Implement.** Route each nontrivial objective unit to the Sol `implementer` and each nontrivial
329
- subjective unit to the Opus `implementer`, in the main checkout, one writer at a time. Route a
330
- fully specified taste-free unit to `builder`. Never route implementation to an engine the unit's
331
- judgment load exceeds.
334
+ subjective unit to the Opus `implementer`, in the checkout the unit writes, one writer per
335
+ checkout. Route a fully specified taste-free unit to `builder`. Never route implementation to an
336
+ engine the unit's judgment load exceeds.
332
337
  4. **Integrate.** Evaluate each distillate against its acceptance criteria, apply shared-file
333
338
  patches serially, and route cross-cutting findings. Integration applies exact returned patches
334
339
  and mechanical conflict resolution only. A new type, mechanism, behavior, or acceptance
335
340
  criterion discovered at integration is a successor brief routed to a writer, never an
336
341
  integration edit.
337
- 5. **Audit adversarially.** Audit every nontrivial implementation with at least one lane whose
338
- engine did not write it: `reviewer` for the subjective lane and `analyst` for the objective lane,
339
- the way step 2 names its lanes. Run the second lane when the first returns FAIL, when the subject
340
- is a rendered or externally driven surface, or when the unit's claims span both correctness and
341
- shape. Dispatch `checker` in addition when the acceptance criteria are mechanical — counts,
342
- paths, parity rows, scope honesty — never in place of a lane. Record in the round's verdict file
343
- when a lane or the checker did not run.
342
+ 5. **Audit adversarially.** Audit every nontrivial implementation with the objective lane and the
343
+ subjective lane — `analyst` and `reviewer`, the way the design step names its lanes — at least
344
+ one of them on an engine that did not write the work. Dispatch `checker` in addition when the
345
+ acceptance criteria are mechanical — counts, paths, parity rows, scope honesty — never in place
346
+ of a lane. A round that runs fewer lanes than its brief names, or omits the checker its criteria
347
+ call for, records the deviation in its verdict file with that round's own reason, never a
348
+ template sentence.
344
349
  - State the audit's subject as numbered falsifiable claims and require per-claim verdicts with
345
350
  evidence, per the Falsification law in `.claude/rules/quality.md` and the `orkestrel-falsify`
346
351
  value set, unless the dispatch names a different skill that fixes another.
@@ -447,6 +452,8 @@ The harness bridge names the concrete mechanism for each of these.
447
452
  the record transcribes them, because the committed instrument re-produces the film. The **Bench
448
453
  laws** rule "Ephemeral streams, durable records" owns journals and points here for everything
449
454
  durable.
455
+ - Name a retained log with the `<unit>.log.txt` pattern, never with a bare `.log` suffix, which the
456
+ root `.gitignore` file ignores.
450
457
  - Promote anything that must outlive the campaign into a durable artifact before the sweep — a
451
458
  commit message, a guide, a rule, a retrospective. What is only in a swept file did not survive,
452
459
  and a debrief that must quote the record verbatim has nothing to quote.
@@ -461,6 +468,11 @@ The harness bridge names the concrete mechanism for each of these.
461
468
  wave's plan, routing ledger, and verdicts sit together rather than split across the packages they
462
469
  rule on.
463
470
  - Never put them in the package they are about. A published package's tree is its product.
471
+ - Where the orchestrator's repository is itself a subject package, keep `.orkestrel/` as the
472
+ artifact home and stage every landing chain by path, never with `git add -A`. Run that
473
+ checkout's own CLI through the built `node dist/bin/main.js` entry, never through the `npx`
474
+ launcher, and record its audit reading beside the landing instead of gating the landing on it.
475
+ Commit the records before the landing's commit step, never during it.
464
476
  - Claim nothing outside `.orkestrel/` unless Orkestrel scaffold mandates it. Everything Orkestrel
465
477
  owns in a consumer's tree lives beneath that folder, so a convention can be settled there without
466
478
  colliding with a convention that is not Orkestrel's.
@@ -537,6 +549,9 @@ each check. Then run this checklist against what you filled.
537
549
  derived from it, and a template change with the materialized copy the package generates from it.
538
550
  - Read each criterion against the off-limits list, line by line. Grant the file a criterion needs or
539
551
  strike that criterion. A file the change will break that appears in neither list is unscoped.
552
+ - Where a scope line names the `tests/**` or `src/**` glob, name the paths the `scaffold repair`
553
+ command restores as off-limits in the same sentence. Bound a criterion wider than its Sites by
554
+ the brief's scope.
540
555
  - Scope a unit that changes a mechanism to own the prose describing it. Where a brief scopes that
541
556
  prose out, name the carrier and dispatch it before the change ships.
542
557
  - Give a small unrelated obligation its own unit.
@@ -747,6 +762,8 @@ transport.
747
762
 
748
763
  ### Recovering a dark bench
749
764
 
765
+ - A probe that finds no bench binary records the bench dark and, in the same turn, names to the user
766
+ the install command and the bench it unblocks. Re-probe when the user answers.
750
767
  - A probe that finds a bench binary present but authentication unavailable starts recovery in the
751
768
  same turn. Do not record the bench dark and wait.
752
769
  - Background the login command with its output captured under `tmp/<bench>/`, surface the
@@ -775,6 +792,9 @@ five-minute upload window in `references/window.md`. Load the skill when the use
775
792
  release, and follow it there rather than reconstructing the procedure here. What remains in this
776
793
  section binds an executor who is not publishing.
777
794
 
795
+ A wave over unpublished tips derives its order per run from the graph and records only the round
796
+ each package landed in, never the order itself.
797
+
778
798
  ### Fixing a dependency before it publishes
779
799
 
780
800
  A defect a consumer meets sometimes lives in a package the consumer only has from the registry.
@@ -793,6 +813,11 @@ Build the dependency from source, pack it, and **install the tarball** into the
793
813
  - **Rebuild and repack whenever the source moves.** A stale tarball is the same defect as a stale
794
814
  `dist/`, and it is worse for being invisible: the consumer's gates go green against a fix that no
795
815
  longer exists in the dependency's tree.
816
+ - **Run one unit per checkout, at that checkout's catalog layer.** Give a checkout with no rows an
817
+ adopt unit only when its typecheck against the staged closure reddens.
818
+ - **Fetch and merge the dependency's default branch before packing it**, wherever another session
819
+ can move that branch. A pack from a stale tip ships the consumer a dependency the dependency's own
820
+ repository no longer has.
796
821
  - **Restore the registry copy before any gate that must prove the published artifact, and before
797
822
  publishing anything.** A distribution proof run against a local tarball proves the local tarball.
798
823
  The release still follows layer order: the dependency publishes first, then the consumer re-pins to
@@ -24,18 +24,22 @@ every claim about the result from what renders.
24
24
  Open the reference that owns a subject before writing markup. Never guess a class name: an invented
25
25
  utility (`.vw-50`, `.pointer-events-none`) has no rule in the shipped CSS and fails silently. Pick
26
26
  components from [components.md](references/components.md) → Choosing components, take their markup
27
- from the same file, and take fine layout from [utilities.md](references/utilities.md). Where
28
- Bootstrap ships no component for the need — combobox, date picker, tags input, data grid, tree —
29
- work the native-first ladder in [bootstrap-reference.md](references/bootstrap-reference.md) → When
30
- not to hand-roll before building one.
27
+ from the same file, and take fine layout from [utilities.md](references/utilities.md). Pick an
28
+ input's affordance from [inputs.md](references/inputs.md) by what the person is asked for, not by
29
+ what a schema calls the field. Where Bootstrap ships no component for the need — combobox, date
30
+ picker, tags input, data grid, tree — work the native-first ladder in
31
+ [bootstrap-reference.md](references/bootstrap-reference.md) → When not to hand-roll before building
32
+ one.
31
33
 
32
34
  | Layer | File | Holds |
33
35
  | -------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
34
- | Operate | `SKILL.md` (this file) | Process, decision rules, checklist |
36
+ | Operate | `SKILL.md` | Process, decision rules, checklist |
35
37
  | Design craft | [frontend-design.md](references/frontend-design.md) | Aesthetic, typography, signature, interface copy, anti-defaults |
36
38
  | Components | [components.md](references/components.md) | Bootstrap component markup + enterprise selection notes |
39
+ | Inputs | [inputs.md](references/inputs.md) | Affordance per input category, its alternates, its rung, its states |
37
40
  | Utilities | [utilities.md](references/utilities.md) | Class index, helpers, composition notes |
38
41
  | Bootstrap deep | [bootstrap-reference.md](references/bootstrap-reference.md) | Color modes, theming/tokens, forms, JS lifecycle, a11y depth, enterprise patterns |
42
+ | Instruments | [inspection.md](references/inspection.md) | Property, population, reading, negative control, and coverage per instrument |
39
43
 
40
44
  ---
41
45
 
@@ -43,20 +47,20 @@ not to hand-roll before building one.
43
47
 
44
48
  1. **Assume no stack.** Infer it from the workspace. Do not assume Vue, React, a skin library, a folder layout, or a named product.
45
49
  2. **Target Bootstrap 5.3.x** class names and behaviors. Hold a compatible skin that keeps `.btn`, `.card`, `.form-control`, and `data-bs-*` to the same contracts.
46
- 3. **Follow the project's code law.** Its `AGENTS.md`, lint rules, and design system decide language, layout, and forbidden patterns. This package owns UI craft and Bootstrap usage, not language law.
50
+ 3. **Follow the project's code law.** Take language, layout, and forbidden patterns from its `AGENTS.md` file, its lint rules, and its design system. Take UI craft and Bootstrap usage from here, and never language law.
47
51
  4. **Write framework-neutral markup** — semantic HTML plus Bootstrap classes. Wire behavior with what the project already uses; in an SPA prefer the framework-native Bootstrap wrappers over raw `bootstrap.*` JS ([bootstrap-reference.md](references/bootstrap-reference.md) → JavaScript lifecycle).
48
52
  5. **Keep this folder intact** so the relative links between its files resolve. Install or vendor it wherever the tooling looks for skills; the paths are tooling-specific, the content is not.
49
53
  6. **Use the project's installed Bootstrap** when it has one; otherwise take the CDN snippet from [bootstrap-reference.md](references/bootstrap-reference.md) → Quick start (5.3.8).
50
- 7. **Apply this package** to UI, Bootstrap, and visual-design work matching the description above. When the user points at it, treat it as authoritative for the visual pass.
54
+ 7. **Apply this package** to UI, Bootstrap, and visual-design work matching the frontmatter description. When the user points at it, treat it as authoritative for the visual pass.
51
55
 
52
56
  ---
53
57
 
54
58
  ## The mandate
55
59
 
56
60
  1. **Design direction** — take a point of view rooted in the _subject_ (audience, job-to-be-done, vernacular). Take one justified aesthetic risk, in one place.
57
- 2. **Bootstrap execution** — components and utilities first; custom CSS only when the system cannot express the need; paint through `--bs-*` so light and dark both survive.
61
+ 2. **Bootstrap execution** — take components and utilities first, custom CSS only when the system cannot express the need, and paint through `--bs-*` so light and dark both survive.
58
62
 
59
- Match the density to the context: a marketing page may open with a thesis-hero, an authenticated
63
+ Match the density to the context: a marketing page can open with a thesis-hero, an authenticated
60
64
  tool opens with clarity and scan paths. In product UI put the signature in the chrome, never in the
61
65
  data ([frontend-design.md](references/frontend-design.md) → Where the signature lives).
62
66
 
@@ -64,9 +68,9 @@ data ([frontend-design.md](references/frontend-design.md) → Where the signatur
64
68
 
65
69
  ## Process
66
70
 
67
- Design craft — subject grounding, hero and thesis, typography, structure, motion, restraint, and
68
- interface copy — lives in [frontend-design.md](references/frontend-design.md). Read it before
69
- setting a direction. The loop:
71
+ Read [frontend-design.md](references/frontend-design.md) before setting a direction; it owns subject
72
+ grounding, hero and thesis, typography, structure, motion, restraint, and interface copy. Then work
73
+ this loop:
70
74
 
71
75
  1. **Ground** — name the subject, the audience, and the screen's single job, and state them. Use known user preferences and prior designs as hints, not templates.
72
76
  2. **Plan** — build a token system: **color** (4–6 named values), **type** (display / body / utility), **layout** (prose plus ASCII if useful), **signature** (one memorable element).
@@ -74,53 +78,51 @@ setting a direction. The loop:
74
78
  4. **Build** — compose Bootstrap components and utilities; map the plan's tokens onto theme variables or a thin skin, with no scattered one-off hex ([bootstrap-reference.md](references/bootstrap-reference.md) → Theming & design tokens). Watch selector specificity: a utility and a custom rule that cancel each other show up as padding and margin bugs.
75
79
  5. **Critique the render** — remove one accessory. Check contrast, focus, `prefers-reduced-motion`, mobile, and every data state. Critique what rendered, not the markup.
76
80
 
77
- Brainstorm privately; show a direction only once it satisfies the brief and the quality floor
78
- ([frontend-design.md](references/frontend-design.md) → Process).
81
+ Show a direction only after it satisfies the brief and the quality floor, and keep every earlier
82
+ draft private ([frontend-design.md](references/frontend-design.md) → Process).
79
83
 
80
84
  **Rendered proof.** Settle every claim about a screen from a capture, never from source alone;
81
- `.agents/orchestration.md` owns this law where it is present. The review input here is captures at
82
- both viewports and both themes plus an accessibility snapshot; source only corroborates the
83
- mechanism. For a full review-round campaign built on that evidence, use the
85
+ `.agents/orchestration.md` owns this law where it is present. Take captures at every viewport and
86
+ every theme the surface declares, plus an accessibility snapshot, as the review input, and use source only to corroborate
87
+ the mechanism. For a full review-round campaign built on that evidence, use the
84
88
  `orkestrel-polish-surface` skill instead of improvising one here.
85
89
 
86
- **Mechanical proof.** These instruments settle what a capture cannot. Pair each one with a negative
87
- control drawn from outside the population it covers, and treat an instrument whose control passes as
88
- broken; `.claude/rules/quality.md` owns this law where it is present:
89
-
90
- - **Contrast, composited.** Read every pairing through a reader that composites the painted layers, in both themes ([bootstrap-reference.md](references/bootstrap-reference.md) → Measuring the bars).
91
- - **Authored classes against the shipped cascade.** Extract every class authored in the templates and components, and fail the run on one that has no rule in the compiled CSS the page loads. Assert a population floor so an extractor that quietly matched nothing cannot pass, and control it with a class you know is absent.
92
- - **One glyph, one meaning.** Register each status glyph against the meaning it carries. No meaning takes more than one glyph, no glyph serves more than one meaning, and every registered glyph resolves in the icon set actually shipped.
90
+ **Mechanical proof.** Run every instrument in [inspection.md](references/inspection.md) with the
91
+ negative control it names, and treat an instrument whose negative control passes as broken;
92
+ `.claude/rules/quality.md` owns that law where it is present. Those instruments settle what a capture
93
+ cannot: composited contrast, authored classes against the shipped cascade, declared class
94
+ combinations, style escapes, token discipline, a custom rule doing a utility's job, and one glyph per
95
+ meaning. Hold every check the deliverable lists to that shape, whether or not inspection.md names
96
+ it: each states its population, its negative control, and its coverage, and a check that cannot
97
+ name a negative control is recorded as open rather than listed as a check.
93
98
 
94
99
  ---
95
100
 
96
101
  ## Bootstrap operating principles
97
102
 
98
- 1. **Mobile first** — smallest screen first, then `sm` / `md` / `lg` / `xl` / `xxl`.
99
- 2. **Semantic HTML** — `nav`, `main`, `section`, heading order.
100
- 3. **Work down the styling ladder below** — component classes, then utilities, then Bootstrap's own extension points.
103
+ 1. **Mobile first** — build the smallest screen first, then `sm` / `md` / `lg` / `xl` / `xxl`.
104
+ 2. **Semantic HTML** — use `nav`, `main`, and `section`, and hold the heading order.
105
+ 3. **Work down the styling ladder that follows** — component classes, then utilities, then Bootstrap's own extension points.
101
106
  4. **Test every breakpoint you claim.**
102
107
  5. **Reach for Bootstrap's own transitions before writing custom animation**, spend one orchestrated moment at most, and wrap any custom animation in `prefers-reduced-motion: no-preference` ([bootstrap-reference.md](references/bootstrap-reference.md) → Reduced motion).
103
- 6. **Resolve every treatment in the shipped cascade** — Bootstrap plus every skin and dependency stylesheet the page pulls in — never from docs memory. A class with no rule of its own may still inherit one, and a token pair that passes in stock Bootstrap can fail under a compatible skin. Measure the `*-subtle` / `*-emphasis` recipes too, once per theme, with a reader that composites the translucent layers ([bootstrap-reference.md](references/bootstrap-reference.md) → Measuring the bars).
108
+ 6. **Resolve every treatment in the shipped cascade** — Bootstrap plus every skin and dependency stylesheet the page pulls in — never from docs memory. A class with no rule of its own can still inherit one, and a token pair that passes in stock Bootstrap can fail under a compatible skin. Measure the `*-subtle` / `*-emphasis` recipes too, once per theme, with a reader that composites the translucent layers ([bootstrap-reference.md](references/bootstrap-reference.md) → Measuring the bars).
104
109
 
105
110
  ### The styling ladder
106
111
 
107
- Work down these rungs in order. Reach a lower rung only when the one above genuinely cannot express
108
- the need.
112
+ Work down these rungs in order. Reach a rung only when the preceding one cannot express the need.
109
113
 
110
- 1. **The component's own classes, in its documented structure.** Use the right elements, nesting, class names, and required ARIA: a card is `.card` wrapping `.card-body` wrapping `.card-title`, not a `div` with borrowed padding. Variants, states, color modes, and responsive behavior all hang off that structure.
114
+ 1. **The component's own classes, in its documented structure.** Use the right elements, nesting, class names, and required ARIA: a card is `.card` wrapping `.card-body` wrapping `.card-title`, not a `div` with borrowed padding. Modifier classes, affordance states, color modes, and responsive behavior all hang off that structure.
111
115
  2. **Bootstrap utilities, for refinement.** Spacing, flex, display, sizing, text, borders, color. Compose utilities rather than reaching past them, and use only classes that exist in [utilities.md](references/utilities.md).
112
116
  3. **Bootstrap's own extension points.** Component `--bs-{component}-*` variables and the utilities API, when a real gap remains after the component-class and utility tiers.
113
- 4. **Anything beyond Bootstrap's conventions is the developer's call, not yours.** Stop at rung 3 and say plainly what rung 4 would require.
117
+ 4. **Leave anything beyond Bootstrap's conventions to the developer.** Stop at rung 3 and say plainly what rung 4 would require. Take rung 4 unasked only where [inspection.md](references/inspection.md) → When an authored rule is already earned opens it.
114
118
 
115
- Never open at rung 4. Specifically, do not reach first for:
119
+ Never reach first for any of these, because each ends the cascade for that element and then survives
120
+ no `--bs-*` retheming, no breakpoint change, and no color-mode change:
116
121
 
117
122
  - a `style="..."` attribute;
118
123
  - a `<style>` block in a page or component;
119
124
  - a new stylesheet rule for something a utility already does.
120
125
 
121
- Each ends the cascade for that element: it outranks the utilities, it ignores `--bs-*` retheming, and
122
- it does not change across breakpoints or color modes.
123
-
124
126
  ### Hierarchy & actions
125
127
 
126
128
  | Intent | Typical choice |
@@ -131,61 +133,67 @@ it does not change across breakpoints or color modes.
131
133
  | Tertiary | `btn-link` or text links |
132
134
  | Status | `badge` / `alert` / `*-emphasis` — **icon + color + word** |
133
135
 
134
- **Outline buttons are the decorative tier.** They paint no background of their own, so they borrow
135
- whatever surface they sit on and their contrast is surface- and theme-dependent by construction:
136
- against stock Bootstrap the whole `btn-outline-*` family misses 4.5:1 across the dark theme and on
137
- light tinted surfaces — cards, subtle alerts. Give any action that carries information or
138
- consequence the solid variant. Solid variants paint their own background and measure identically on
139
- every surface, and the stock fills sit at the 4.5:1 bar with nothing to spare. Re-measure a solid
140
- variant whenever anything layers over it — an `opacity-*` utility, a translucent overlay, a skin's
141
- own tint.
136
+ **Give any action that carries information or consequence a solid `btn-*` class, and keep outline
137
+ buttons decorative.** Against stock Bootstrap the whole `btn-outline-*` family misses 4.5:1 across
138
+ the dark theme and on light tinted surfaces — cards, subtle alerts — because an outline button
139
+ paints no background of its own and borrows the surface it sits on.
142
140
 
143
- A status mark with **no text** is an icon glyph, never a `badge`
141
+ **Re-measure a solid fill whenever anything layers over it** — an `opacity-*` utility, a translucent
142
+ overlay, a skin's own tint — because the stock fills sit at the 4.5:1 bar with nothing to spare.
143
+
144
+ Draw a status mark with **no text** as an icon glyph, never as a `badge`
144
145
  ([components.md](references/components.md) → Badge).
145
146
 
146
147
  ### Surfaces, color, contrast
147
148
 
148
- - **Contrast bars, measured in both themes:** **≥ 4.5:1** for anything information-bearing — `small`, captions, and meta text included — and **≥ 3:1** for textless marks, state indicators, and the hover/focus chrome that carries state. Verify Bootstrap's own palette too; the docs admit some defaults fall short. Read both themes — a pairing that passes light routinely fails dark.
149
- - **Information-bearing status text takes the `-emphasis` pair.** Plain `text-success` and `text-danger` miss the bar across the dark theme and on light tinted surfaces, and `text-warning` is theme-asymmetric — unreadable on light, comfortable on dark. Never make a plain semantic color the encoding; use it only as decoration beside an encoding that already passes.
150
- - `text-body-tertiary` carries no information anywhere: it measures under 4.5:1 on every surface in both themes. Tier text a user must read `text-body-secondary` or better, and keep tertiary for genuinely decorative marks.
151
- - **A subtle fill degrades everything inside it one notch.** Inside `alert-*` and the `*-subtle` backgrounds, outline buttons and plain semantic text fail even in light — so information-bearing text there is `-emphasis` and every button is solid.
152
- - **A primary fill destroys every semantic color.** On `.active`, `.bg-primary`, and `text-bg-*` surfaces every tone class measured lands under the bar in both themes, the `-emphasis` family included, because the fill supplies its own contrast color and the tone class overrides it with one tuned for a different background. Carry no tone class inside such a fill; let the surface's contrast color take the text, keep the status encoded by icon and word, and verify by capturing the selected state ([components.md](references/components.md) → Selection fills).
153
- - Disabled controls are exempt from the bars, but a disabled **destructive** control must not keep full danger saturation — at full strength it still reads as armed. Neutralize the variant while it is disabled and carry the reason on the control with `aria-describedby` (plus `title` for pointer users), never `title` alone.
149
+ - **Measure these contrast bars in both themes:** **≥ 4.5:1** for anything information-bearing — `small`, captions, and meta text included — and **≥ 3:1** for textless marks, state indicators, and the hover/focus chrome that carries state. Verify Bootstrap's own palette too; the docs admit some defaults fall short. Read both themes — a pairing that passes light routinely fails dark.
150
+ - **Take the `-emphasis` pair for information-bearing status text.** Plain `text-success` and `text-danger` miss the bar across the dark theme and on light tinted surfaces, and `text-warning` is theme-asymmetric — unreadable on light, comfortable on dark. Never make a plain semantic color the encoding; use it only as decoration beside an encoding that already passes.
151
+ - **Tier text a person must read `text-body-secondary` or better**, and keep `text-body-tertiary` for decorative marks: tertiary measures under 4.5:1 on every surface in both themes, so it carries no information anywhere.
152
+ - **Inside `alert-*` and the `*-subtle` backgrounds, take `-emphasis` for information-bearing text and a solid `btn-*` class for every button.** A subtle fill degrades everything inside it one notch, so outline buttons and plain semantic text fail there even in light.
153
+ - **Carry no tone class inside a primary fill.** On `.active`, `.bg-primary`, and `text-bg-*` surfaces every tone class measured lands under the bar in both themes, the `-emphasis` family included, because the fill supplies its own contrast color and the tone class overrides it with one tuned for a different background. Let the surface's contrast color take the text, keep the status encoded by icon and word, and verify by capturing the selected state ([components.md](references/components.md) → Selection fills).
154
+ - Exempt a disabled control from the bars, but never leave a disabled **destructive** control at full danger saturation — at full strength it still reads as armed. Neutralize the danger tone while the control is disabled and carry the reason on the control with `aria-describedby` (plus `title` for pointer users), never `title` alone.
154
155
  - Prefer `bg-body`, `bg-body-secondary`, `bg-body-tertiary` over raw `bg-white` / `bg-light`, and drive custom paint from `var(--bs-…)` — they track `data-bs-theme`, a hard-coded hex does not.
155
- - Pairings: `text-bg-*`, `*-subtle`, `*-emphasis`, `text-body` / `text-body-secondary`. `text-muted` is deprecated — use `text-body-secondary`.
156
- - On **dark surfaces**, scope `data-bs-theme="dark"` rather than the deprecated component variants `navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, and `carousel-dark`; gray-on-dark outlines often fail contrast.
157
- - Support `data-bs-theme="light"` and `dark` when the product offers both. Mechanics: [bootstrap-reference.md](references/bootstrap-reference.md) → Color modes.
156
+ - Take pairings from `text-bg-*`, `*-subtle`, `*-emphasis`, and `text-body` / `text-body-secondary`. `text-muted` is deprecated — use `text-body-secondary`.
157
+ - On **dark surfaces**, scope `data-bs-theme="dark"` rather than the deprecated `*-dark` component classes `navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, and `carousel-dark`; gray-on-dark outlines often fail contrast.
158
+ - Support `data-bs-theme="light"` and `dark` when the product offers both, and take the mechanics from [bootstrap-reference.md](references/bootstrap-reference.md) → Color modes.
158
159
 
159
160
  ### Density, layout, responsive
160
161
 
161
- - Enterprise density: `table-sm`, `btn-sm` / `btn-group-sm`, compact toolbars — but keep every interactive target **≥ 24×24px**, measured on the rendered box rather than assumed from the class (WCAG 2.2); pad hit areas rather than shrinking them.
162
+ - Take enterprise density from `table-sm`, `btn-sm` / `btn-group-sm`, and compact toolbars, but keep every interactive target **≥ 24×24px**, measured on the rendered box rather than assumed from the class (WCAG 2.2); pad hit areas rather than shrinking them.
162
163
  - Where information density is the screen's job, take the `-sm` family across a control row together — `btn-sm` with `form-control-sm`, `form-select-sm`, `input-group-sm` — so the row shares one height. Never mix control sizes within one row.
163
- - Cards earn their keep: `.card` when grouping helps; otherwise spacing and type.
164
+ - Take `.card` where grouping earns it; otherwise carry the grouping with spacing and type.
164
165
  - Swap conditional chrome in place. A bulk-action bar or an alert that shoves the toolbar down shifts the layout mid-task.
165
- - App shell, dense tables, filter bars, and the ranked responsive strategies for wide data: [bootstrap-reference.md](references/bootstrap-reference.md) → Enterprise patterns. Spacing, toolbar, truncation, and print composition: [utilities.md](references/utilities.md) → Composition habits.
166
+ - Take the app shell, dense tables, filter bars, and the ranked responsive strategies for wide data from [bootstrap-reference.md](references/bootstrap-reference.md) → Enterprise patterns, and spacing, toolbar, truncation, and print composition from [utilities.md](references/utilities.md) → Composition habits.
166
167
 
167
168
  ### States & feedback
168
169
 
169
- - **Every data surface ships these states:** ideal, empty, loading, partial, error. It is not done until every one exists. Loading thresholds, empty and error specifics, and the channel matrix for toast / inline alert / banner / modal: [bootstrap-reference.md](references/bootstrap-reference.md) → The data states, Feedback discipline.
170
+ - **Ship every one of these states on every data surface:** ideal, empty, loading, partial, error. Treat the surface as unfinished until every one exists. Take loading thresholds, empty and error specifics, and the channel matrix for toast / inline alert / banner / modal from [bootstrap-reference.md](references/bootstrap-reference.md) → The data states, Feedback discipline.
170
171
  - **Build a blocking decision on the native `<dialog>`.** `showModal()` brings focus containment, Esc, an inert background, and top-layer stacking from the platform, with no instance to construct and none to leak on unmount. Dress it with Bootstrap chrome inside ([components.md](references/components.md) → Modal). Reach for `.modal` and its JS only when the project already drives its dialogs that way.
171
- - **Destructive actions:** prefer undoable over interrupting. Ladder and confirmation contracts: [bootstrap-reference.md](references/bootstrap-reference.md) → Destructive actions.
172
+ - **Make a destructive action undoable rather than interrupting**, and take the ladder and the confirmation contracts from [bootstrap-reference.md](references/bootstrap-reference.md) → Destructive actions.
172
173
 
173
174
  ### Forms
174
175
 
176
+ - Choose each field's affordance in [inputs.md](references/inputs.md) → The catalog by what the person is asked for, draw every state in that file's fixed set, and obey its cross-category rules — read-only chrome, the locked select, the chosen filter's accent tone, the non-drag path for a file drop.
175
177
  - Give every field a visible label (top-aligned by default) or `.form-floating` — never placeholder-only.
176
178
  - Validate on **blur**, re-validate error fields on input, re-check everything on submit, and keep submit **enabled**. Never disable submit as a validation strategy.
177
179
  - Pair a focusable error summary with inline `.invalid-feedback` per field (`aria-describedby`, `aria-invalid`).
178
- - Layout, validation mechanics and their assistive-tech limitation, input groups, autosave, and multi-step rules: [bootstrap-reference.md](references/bootstrap-reference.md) → Forms in production, Wizards & multi-step forms.
180
+ - Take layout, validation mechanics and their assistive-technology limitation, input groups, autosave, and multi-step rules from [bootstrap-reference.md](references/bootstrap-reference.md) → Forms in production, Wizards & multi-step forms.
179
181
 
180
182
  ### When custom CSS is justified
181
183
 
182
- This is rung 4 of the styling ladder, so it is the developer's decision. Propose it, name what it
183
- buys, and do not take it unprompted. Exhaust rungs 1–3 first: correct component structure, then
184
- utilities, then the extension points — component `--bs-{component}-*` variables for restyling, the
185
- utilities API for missing utility steps ([bootstrap-reference.md](references/bootstrap-reference.md)
186
- → Theming).
184
+ Treat custom CSS as rung 4 and the developer's decision: propose it, name what it buys, and take it
185
+ unprompted only under the exception that follows. Exhaust rungs 1–3 first — correct component
186
+ structure, then utilities, then the extension points: component `--bs-{component}-*` variables for
187
+ restyling, the utilities API for missing utility steps
188
+ ([bootstrap-reference.md](references/bootstrap-reference.md) → Theming).
189
+
190
+ Take an authored rule without asking only where an instrument in
191
+ [inspection.md](references/inspection.md) reports the vendor cascade failing a stated bar, the rule
192
+ cites that reading, the rule restores the bar and does nothing else, and the rule is written over
193
+ tokens. [inspection.md](references/inspection.md) → When an authored rule is already earned states
194
+ the whole condition. Treat anything wider as a proposal.
187
195
 
188
- When the developer authorizes it:
196
+ When the developer authorizes it, or that exception opens:
189
197
 
190
198
  - Name it in Bootstrap vocabulary.
191
199
  - Take colors from `var(--bs-…)` and theme tokens so light and dark both work.
@@ -197,20 +205,20 @@ When the developer authorizes it:
197
205
 
198
206
  ## Accessibility baseline
199
207
 
200
- - Skip link to main; landmarks; `h1` → `h2` order.
201
- - `aria-label` on icon-only controls; targets ≥ 24×24px.
202
- - `aria-current` / `aria-selected` on active nav and tabs — exactly one `aria-current` per selection.
203
- - `aria-expanded` / `aria-controls` for disclosure.
204
- - `aria-describedby` for help and errors; `aria-invalid` on failed fields.
205
- - Live regions match the message: an async status mark is `role="status"`; an alert-styled notice is `role="alert"`.
206
- - A form whose host already names the request associates with that name (`aria-labelledby`) instead of repeating the prompt as its own label.
207
- - Visible focus — keep the Bootstrap rings, use the `.focus-ring` helper for custom elements, and never write `outline: none`.
208
- - Focus not obscured by sticky chrome (`scroll-margin-top`); focus moved deliberately on SPA route change, failed submit, and row delete.
209
- - Meaning never by color alone; contrast verified.
210
- - Every drag interaction has a non-drag alternative.
211
- - Dialogs carry `aria-labelledby`; let the platform or Bootstrap trap and restore focus rather than scripting it; dispose Bootstrap instances in SPAs on unmount.
212
-
213
- WCAG 2.2 deltas, APG pattern contracts, reduced motion, and SPA focus recipes:
208
+ - Give the page a skip link to main, landmarks, and `h1` → `h2` heading order.
209
+ - Name every icon-only control with `aria-label`, and keep every target ≥ 24×24px.
210
+ - Mark active nav and tabs with `aria-current` / `aria-selected` — exactly one `aria-current` per selection.
211
+ - Wire every disclosure with `aria-expanded` and `aria-controls`.
212
+ - Wire help and errors with `aria-describedby`, and mark a failed field `aria-invalid`.
213
+ - Match the live region to the message: `role="status"` for an async status mark, `role="alert"` for an alert-styled notice.
214
+ - Associate a form with the name its host already gives the request (`aria-labelledby`) rather than repeating the prompt as its own label.
215
+ - Keep focus visible: keep the Bootstrap rings, use the `.focus-ring` helper for custom elements, and never write `outline: none`.
216
+ - Keep focus clear of sticky chrome (`scroll-margin-top`), and move focus deliberately on SPA route change, failed submit, and row delete.
217
+ - Never carry meaning by color alone, and verify the contrast.
218
+ - Give every drag interaction a non-drag alternative.
219
+ - Give every dialog `aria-labelledby`, let the platform or Bootstrap trap and restore focus rather than scripting it, and dispose Bootstrap instances in an SPA on unmount.
220
+
221
+ Take WCAG 2.2 deltas, APG pattern contracts, reduced motion, and SPA focus recipes from
214
222
  [bootstrap-reference.md](references/bootstrap-reference.md) → Accessibility.
215
223
 
216
224
  ---
@@ -219,10 +227,12 @@ WCAG 2.2 deltas, APG pattern contracts, reduced motion, and SPA focus recipes:
219
227
 
220
228
  ```
221
229
  Progress:
230
+ - [ ] Every check that follows, inspection.md instrument or not, reports its population, names the negative control that failed, and states its coverage; a check that can name no negative control is listed as open instead
222
231
  - [ ] Project code law followed; no wrong-stack assumptions
223
232
  - [ ] Subject, audience, single job stated
224
233
  - [ ] Design plan critiqued against the AI defaults: palette, type, layout, one signature
225
234
  - [ ] Shell from components.md, utilities from utilities.md; no invented class
235
+ - [ ] Input affordances from inputs.md; every state in its fixed set drawn, per field
226
236
  - [ ] Styling ladder held: no `style` attribute, no `<style>` block, no custom rule doing a utility's job
227
237
  - [ ] Plan tokens mapped to theme / --bs-* (no hex scatter); light and dark both shipped where both are offered
228
238
  - [ ] Copy in user language, verbs consistent, empty/error/loading text useful
@@ -230,12 +240,11 @@ Progress:
230
240
  - [ ] Contrast composited and measured in both themes: ≥ 4.5:1 information-bearing (small included), ≥ 3:1 marks and state chrome; meaning not color-alone
231
241
  - [ ] Tiers held: `-emphasis` for information-bearing status, solid buttons for real actions, no tone class inside a filled surface
232
242
  - [ ] Every treatment resolved in the shipped cascade, not from docs memory
233
- - [ ] Authored classes checked against that cascade; one glyph per meaning; every instrument's control failed
234
243
  - [ ] Keyboard: focus visible, not obscured, targets ≥ 24px, icon controls named
235
244
  - [ ] Reduced motion respected; every drag has a non-drag path
236
245
  - [ ] Forms: labels visible, blur validation, error summary + inline, submit enabled
237
246
  - [ ] Claimed breakpoints spot-checked; RTL-safe (start/end only)
238
247
  - [ ] States present: hover / focus / disabled / invalid / active
239
248
  - [ ] SPA hygiene: JS instances disposed on unmount, or framework wrappers used
240
- - [ ] Rendered proof: captures at both viewports and both themes + an accessibility snapshot
249
+ - [ ] Rendered proof: captures at every viewport and every theme the surface declares + an accessibility snapshot
241
250
  ```
@@ -81,7 +81,7 @@ CDN (5.3.8 is the current — and final — 5.3.x patch before 5.4):
81
81
 
82
82
  ## Color Modes (light / dark / custom)
83
83
 
84
- The 5.3 color-mode system replaces the old per-component dark variants.
84
+ The 5.3 color-mode system replaces the old per-component `*-dark` classes.
85
85
 
86
86
  ### Mechanics
87
87
 
@@ -290,7 +290,7 @@ Client-side, the documented pattern:
290
290
  </div>
291
291
  ```
292
292
 
293
- Details: input groups with feedback need `.has-validation` on the group (border-radius fix). `.valid/invalid-tooltip` variants need a `position-relative` parent. Validation colors are mode-adaptive via `--bs-form-valid-color`, `--bs-form-valid-border-color`, `--bs-form-invalid-color`, `--bs-form-invalid-border-color`.
293
+ Details: input groups with feedback need `.has-validation` on the group (border-radius fix). `.valid-tooltip` and `.invalid-tooltip` need a `position-relative` parent. Validation colors are mode-adaptive via `--bs-form-valid-color`, `--bs-form-valid-border-color`, `--bs-form-invalid-color`, `--bs-form-invalid-border-color`.
294
294
 
295
295
  ### Autosave vs explicit save
296
296
 
@@ -354,7 +354,7 @@ Hold the baseline in [SKILL.md](../SKILL.md) → Accessibility baseline. Its Boo
354
354
  - collects every painted layer from the element upward to the first opaque one, then composites them top over bottom (Porter-Duff `over`) onto that opaque base;
355
355
  - composites a translucent foreground over that result before taking the ratio, rather than reading the declared color;
356
356
  - measures both themes in one run, since the theme swap re-points the tokens under every layer;
357
- - carries a negative control drawn from outside the population it covers — a pairing known to fail — and voids the run if that control passes.
357
+ - carries a negative control drawn from outside the population it covers — a pairing known to fail — and voids the run if that negative control passes.
358
358
 
359
359
  Wire the reader into the suite once it has settled a question.
360
360
 
@@ -485,7 +485,7 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
485
485
  </div>
486
486
  ```
487
487
 
488
- Give header cells an **opaque background** (`bg-body-secondary` or a table variant) — table backgrounds are transparent by default, so rows show through a sticky header otherwise. Sticky chrome is the prime Focus-Not-Obscured offender: add `scroll-margin-top` on row focusables equal to the header height. Sticky first column only when row identity is lost on horizontal scroll — it costs paint and complexity.
488
+ Give header cells an **opaque background** (`bg-body-secondary` or a `.table-*` tone class) — table backgrounds are transparent by default, so rows show through a sticky header otherwise. Sticky chrome is the prime Focus-Not-Obscured offender: add `scroll-margin-top` on row focusables equal to the header height. Sticky first column only when row identity is lost on horizontal scroll — it costs paint and complexity.
489
489
 
490
490
  - **Sorting:** the whole header is a button (not a bare caret), with a visible direction indicator, and `aria-sort="ascending|descending"` on the active `<th>` only:
491
491
 
@@ -551,7 +551,7 @@ Match friction to reversibility × blast radius:
551
551
 
552
552
  Do not type-gate a single-row delete; do not one-tap a tenant wipe. Confirm only where this ladder calls for it — a confirmation on every action gets clicked through.
553
553
 
554
- **Neutralize a disabled destructive control.** `btn-danger` at full saturation reads as armed whatever the `disabled` attribute says, and the contrast exemption for disabled controls does not excuse it. While the action is unavailable, drop to the neutral or outline variant (or let the disabled state mute the fill) so the color stops promising an action, and say _why_ it is unavailable in text the assistive layer reaches: `aria-describedby` pointing at the reason, with `title` only as the pointer-user convenience on top. Never use `title` alone — it never reaches a keyboard or screen-reader user, and it disappears on touch.
554
+ **Neutralize a disabled destructive control.** `btn-danger` at full saturation reads as armed whatever the `disabled` attribute says, and the contrast exemption for disabled controls does not excuse it. While the action is unavailable, drop to the neutral or outline `btn-*` class (or let the disabled state mute the fill) so the color stops promising an action, and say _why_ it is unavailable in text the assistive layer reaches: `aria-describedby` pointing at the reason, with `title` only as the pointer-user convenience on top. Never use `title` alone — it never reaches a keyboard or screen-reader user, and it disappears on touch.
555
555
 
556
556
  ## RTL
557
557