diff --git a/AGENTS.md b/AGENTS.md index 0456414..8ecd576 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,12 +14,15 @@ Read in this order before making decisions: 6. `guide/intent-specification-layer.md` 7. `guide/spec-review-loop.md` 8. `guide/spec-to-test-bridge.md` -9. The relevant template or example for the task +9. `guide/intent-to-ui-translation.md` (when the task touches user-facing surfaces) +10. The relevant template or example for the task For a compact workflow after this file, use `guide/agent-operating-protocol.md`. For method updates or upstream rule propagation, also read `guide/method-update-propagation.md`. +For user-facing surfaces and design translations, also read +`spec/features/design-intent-governance/spec.md`. ## Non-Negotiable Rules diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdd67d..ddbfac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ This project follows the spirit of Keep a Changelog. +## [Unreleased] + +### Added + +- New `spec/features/design-intent-governance/spec.md` defining + `REQ-DESIGN-GOV-001..007` — story-class classification (`user` / `system` / + `mixed` / `method`), intent-to-UI design translation, `governingSpec` + frontmatter, trail vs authority for design references, and design-spec + coupling enforcement. +- New `guide/intent-to-ui-translation.md` explaining the UI object tree (region + → element → variant → property), fit judgments (`fit` / `weak` / `drift` / + `violation` / `not_applicable`), and drift sources (`wrong_intent` / + `missing_intent` / `weak_translation` / `wrong_implementation` / + `missing_evidence` / `decision_gap`). +- New `templates/user-design-translation.md` for hand-authored design + translations. +- New `examples/design-intent-translation/` worked example (checkout review + surface) showing file-level `storyClass`, per-REQ override, paired translation + with `governingSpec`, and property fit matrix. +- New `scripts/check-design-spec-coupling.mjs` enforcing that a design + translation change is paired with a change in at least one declared + `governingSpec` file or carries `noIntentChange: true` in a commit message. +- AGENTS.md load order extended with `intent-to-ui-translation.md` for + user-facing surface tasks. + +### Changed + +- README.md and `spec/README.md` indexed the new design intent governance + surfaces. +- ROADMAP.md 0.3 milestone records the Design Intent Governance addition, + field-validated by a real-world adoption in 2026-05. + ## [0.2.4] - 2026-05-15 ### Changed diff --git a/README.md b/README.md index 3264db7..89c4c50 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ agent is unsure whether to update the spec, code, tests, or review ledger. - [Adoption playbook](guide/adoption.md) - [Spec review loop](guide/spec-review-loop.md) - [Spec-to-test bridge](guide/spec-to-test-bridge.md) +- [Intent to UI translation](guide/intent-to-ui-translation.md) - [Naming and structure decision](guide/naming-and-structure.md) - [Research notes](research/method-comparison.md) - [References](references.md) diff --git a/ROADMAP.md b/ROADMAP.md index c449780..6c064ef 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,6 +15,10 @@ ## 0.3 +- Land Design Intent Governance (`spec/features/design-intent-governance/`, + `guide/intent-to-ui-translation.md`, `templates/user-design-translation.md`, + `examples/design-intent-translation/`, `scripts/check-design-spec-coupling.mjs`) + validated by real-world adoption in 2026-05. - Add real-world adoption reports. - Add negative examples showing over-specification. - Add translations after the English canonical text stabilizes. diff --git a/examples/design-intent-translation/README.md b/examples/design-intent-translation/README.md new file mode 100644 index 0000000..8f46532 --- /dev/null +++ b/examples/design-intent-translation/README.md @@ -0,0 +1,51 @@ +# Design Intent Translation Example + +This small example shows how to bridge an L2 spec to a concrete UI property +translation using the method defined in +[`spec/features/design-intent-governance/`](../../spec/features/design-intent-governance/). + +It deliberately uses a generic, non-Buzyrun domain (a coupon order checkout +review surface) so the pattern transfers to other projects. + +## Files + +| File | Role | +|---|---| +| [`feature-spec.md`](feature-spec.md) | Minimal L2 feature spec with `storyClass: user` and one `mixed` REQ override | +| [`translations/checkout.review.design.md`](translations/checkout.review.design.md) | Hand-authored property-level design translation paired with the spec | + +## What this example demonstrates + +1. **File-level `storyClass`** in the feature spec frontmatter sets the + default for all REQs in the file. +2. **Per-REQ `storyClass:` override** for one system-side REQ (`REQ-CHK-002`, + inventory recheck) inside an otherwise `user` file. +3. **`governingSpec:` in the translation frontmatter** points back to the + spec files that authorize the translation. +4. **UI object tree → property fit matrix** records concrete `fit`/`weak`/ + `drift` judgments per UI leaf — not the surface as a whole. +5. **Residual gaps** are tagged with a drift source so the fix target is + unambiguous (`spec`, `design`, `code`, or `evidence`). + +## What this example does NOT do + +- Define a design system (tokens, components). Projects supply their own. +- Mandate a per-route ledger format. The Buzyrun project layers a generated + `surface-ledgers/` set on top of the same governance; that is a project + choice, not a method requirement. +- Provide screenshots or evidence files. Evidence is project-local. + +## How a real project layers on top + +The Buzyrun Flutter project (the real-world adoption that validated this +governance in 2026-05) extends this example with: + +- A per-route authority registry that generates one Design Visual ledger per + route as a *coverage* artifact (`docs/design/v3/governance/surface-ledgers/`). +- Hand-authored translations for high-priority surfaces (~10 surfaces) that + carry property-level `fit` judgments. +- A coupling check (`scripts/check-design-spec-coupling.mjs`) registered in + the project's `check:spec` aggregate. + +Other projects can adopt any subset. The minimum is the `governingSpec:` +frontmatter and one translation per accepted user-facing L2 scope. diff --git a/examples/design-intent-translation/feature-spec.md b/examples/design-intent-translation/feature-spec.md new file mode 100644 index 0000000..b8e8830 --- /dev/null +++ b/examples/design-intent-translation/feature-spec.md @@ -0,0 +1,71 @@ +--- +id: SPEC-CHECKOUT-REVIEW +title: Checkout Review Surface (example) +status: active +owners: [example] +updated: 2026-05-27 +layers: [L2] +storyClass: user +--- + +# Checkout Review Surface Spec (example) + +A small example feature spec used by +[`examples/design-intent-translation/`](README.md). It governs the order +review surface a shopper sees after applying a coupon, before final payment. + +## Scope + +User-facing review surface only. Coupon application logic, payment +authorization, and inventory persistence live in other specs. + +## Layer 2: Behavior Spec + +- [REQ-CHK-001][State-driven] While the order is in `draft` state with at + least one line item, the system shall display the order total, the applied + coupon (if any), each line item's name and price, and a primary action that + proceeds to payment. +- [REQ-CHK-002][Ubiquitous] The system shall re-fetch inventory state for + each line item when the review surface opens, before computing the displayed + total. + + ```yaml + id: REQ-CHK-002 + type: ubiquitous + storyClass: system + ``` + +- [REQ-CHK-003][Event-driven] When the shopper changes the coupon while in + `draft`, the system shall recompute and re-render the total without + resetting other review state. +- [REQ-CHK-004][Unwanted] If the order total computation fails (network or + service error), then the system shall preserve the entered review state and + surface an actionable retry — it shall not silently replace the displayed + total with stale or zero values. +- [REQ-CHK-005][Unwanted] If a line item's inventory has dropped to zero + between cart and review, then the system shall mark that line item with an + out-of-stock visual treatment, exclude it from the total, and require the + shopper to remove or replace it before the primary action becomes + available. + +## Verification Map + +| Requirement / statement | Verification type | Evidence target | Execution / record | Status | +|---|---|---|---|---| +| REQ-CHK-001:S1 | unit + widget | review surface render in `draft` | project tests | unverified (example) | +| REQ-CHK-002:S1 | integration | inventory recheck on open | project tests | unverified (example) | +| REQ-CHK-003:S1 | widget | coupon change recompute | project tests | unverified (example) | +| REQ-CHK-004:S1 | widget | actionable retry on total failure | project tests | unverified (example) | +| REQ-CHK-005:S1 | widget + integration | out-of-stock visual + gated CTA | project tests | unverified (example) | + +## Notes for example readers + +- The file declares `storyClass: user` in frontmatter — most REQs in this + file render on the user-facing review surface. +- `REQ-CHK-002` (inventory recheck) is server-side work with no user-visible + surface of its own. It overrides the file default with + `storyClass: system`, so the design translation does not need to map a UI + property for it. Its *effect* (out-of-stock state) appears in `REQ-CHK-005` + and is translated there. +- The translation file (`translations/checkout.review.design.md`) lists this + spec in `governingSpec` so the coupling check links them. diff --git a/examples/design-intent-translation/translations/checkout.review.design.md b/examples/design-intent-translation/translations/checkout.review.design.md new file mode 100644 index 0000000..382e869 --- /dev/null +++ b/examples/design-intent-translation/translations/checkout.review.design.md @@ -0,0 +1,126 @@ +--- +id: EXAMPLE-DESIGN-TRANSLATION-CHECKOUT-REVIEW +title: Design Translation — checkout.review (example) +status: example +owners: [example] +updated: 2026-05-27 +governingSpec: [examples/design-intent-translation/feature-spec.md] +--- + +# Design Translation: checkout.review (example) + +A minimal hand-authored design translation paired with +[`feature-spec.md`](../feature-spec.md). See the upstream +[`templates/user-design-translation.md`](../../../templates/user-design-translation.md). + +## 1. Authority + +| Field | Value | +|---|---| +| Story class | `user` | +| Governing L0 | The project's value statement against silent failure during checkout (project-local). | +| Governing L1 | `order.state = draft`, `line_item`, `coupon`, `total_amount` | +| Governing L2 | `REQ-CHK-001`, `REQ-CHK-003`, `REQ-CHK-004`, `REQ-CHK-005` | +| Governing L3 | none for this example | +| Surface / story | `checkout.review` | +| Route / screen | `/checkout/review`, `screens/checkout/ReviewScreen` (project-local) | + +`REQ-CHK-002` is declared `storyClass: system` in the spec and intentionally +absent from this translation — it has no UI property of its own. + +## 2. User outcome + +```text +The shopper understands what they are about to pay for and why, can adjust +the coupon without losing context, and is never silently routed past an +out-of-stock line item or a failed total computation. +``` + +## 3. Design obligations + +| Obligation ID | Source intent | UI obligation | +|---|---|---| +| `OBL-CHK-CLARITY` | `REQ-CHK-001` | The total, the applied coupon (if any), each line item's name and price, and the primary action must be visible without scrolling on a standard-height viewport. | +| `OBL-CHK-COUPON-CHANGE-PRESERVE` | `REQ-CHK-003` | Changing the coupon must recompute the total in place and leave other review state intact (selected delivery option, gift note, etc.). | +| `OBL-CHK-FAIL-RECOVER` | `REQ-CHK-004` | A failed total computation must show an actionable retry on the surface — never a silent zero or stale total. | +| `OBL-CHK-OOS-VISIBLE` | `REQ-CHK-005` | Out-of-stock line items must carry a distinct visual treatment, must be excluded from the total, and must gate the primary action until removed or replaced. | + +## 4. UI object tree + +```text +checkout.review + header + title.copy + line_items + line_item + name.copy + price.copy + qty.copy + oos_badge (variant) + copy + colorToken + position + remove.action + replace.action + coupon_block + applied_state (variant) + label.copy + change.action + none_state (variant) + apply.action + total + label.copy + amount.copy + fail_state (variant) + detail.copy + retry.action + primary_action + label.copy + enabled_state (variant) + disabled_state (variant) + reason.copy +``` + +## 5. Domain state to UI property mapping + +| Domain state | Expected copy | Expected color token | Expected position | Expected action | Current fit | +|---|---|---|---|---|---| +| `draft + all_in_stock + total_ok` | total amount + "Pay now" label | primary | bottom | proceed to payment | `fit` | +| `draft + any_oos` | "Remove or replace to continue" reason copy | warning | inline on the line item | remove / replace | `weak` | +| `draft + total_fail` | "We couldn't compute your total" + actionable detail | error | inline above primary action | retry | `weak` | +| `coupon_applied` | coupon label + change action | accent | next to total | change coupon | `fit` | +| `coupon_none` | "Have a coupon?" prompt | neutral | next to total | apply coupon | `fit` | + +## 6. Interaction and recovery mapping + +| EARS statement | Trigger / state | UI feedback | Recovery | Evidence | +|---|---|---|---|---| +| `REQ-CHK-001:S1` | Surface open | render full review | back navigates to cart | project widget test | +| `REQ-CHK-003:S1` | Coupon change | recompute + re-render total only | other state preserved | project widget test | +| `REQ-CHK-004:S1` | Total fetch fails | `fail_state` variant visible | retry action triggers refetch; total area is never silently zero | project widget test | +| `REQ-CHK-005:S1` | Line item OOS | `oos_badge` visible + primary action disabled with reason | shopper removes or replaces | project integration test | + +## 7. UI property fit matrix + +| UI node | Property | Expected from intent | Current actual (example) | Fit | Fix target | +|---|---|---|---|---|---| +| `line_items.line_item.oos_badge` | colorToken | distinct from neutral and accent (warning family) | uses generic neutral badge | `weak` | design | +| `total.fail_state.retry` | label | result-naming verb ("Try again", not "OK") | "Retry" generic | `weak` | design | +| `primary_action.disabled_state.reason` | copy | explains which line item is blocking | absent (generic disabled) | `drift` | design + code | +| `coupon_block.applied_state.change` | action target | opens coupon picker without resetting other review state | unknown without project audit | `missing_evidence` | evidence | + +## 8. Evidence + +| Evidence type | Path or record | Status | +|---|---|---| +| widget test | project-local | `absent` (example only) | +| visual audit | project-local | `absent` (example only) | +| accessibility | project-local | `absent` (example only) | + +## 9. Residual gaps + +| Gap ID | Label | Cause (drift source) | Next action | +|---|---|---|---| +| `GAP-CHK-001` | OOS badge tonal weight unclear | `weak_translation` | Define a warning-family token in the project design system | +| `GAP-CHK-002` | Primary action's disabled reason missing | `drift` due to `wrong_implementation` | Wire the reason copy from the spec's OOS state into the disabled tooltip | +| `GAP-CHK-003` | No coupon-picker re-entry audit | `missing_evidence` | Add a widget test that asserts non-coupon review state is preserved across coupon change | diff --git a/generated/requirements.json b/generated/requirements.json index fabf76d..f72fe5a 100644 --- a/generated/requirements.json +++ b/generated/requirements.json @@ -1,15 +1,15 @@ { "generatedBy": "scripts/generate-req-tests.mjs", "totals": { - "requirements": 35, - "statements": 42, - "generatedStubSlots": 42, - "verificationMappedStatements": 42, + "requirements": 42, + "statements": 49, + "generatedStubSlots": 49, + "verificationMappedStatements": 49, "nonGeneratedSpecReferences": 76, "nonGeneratedTracedStatements": 30, "realSpecReferences": 76, "realTracedStatements": 30, - "pendingGeneratedOnlyStatements": 12, + "pendingGeneratedOnlyStatements": 19, "codeOnlyReferences": 0 }, "requirements": [ @@ -685,6 +685,174 @@ } ] }, + { + "id": "REQ-DESIGN-GOV-001", + "title": "Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 55, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-001", + "title": "Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped.", + "pattern": "Ubiquitous", + "text": "Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 55, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-001:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "authoring-convention | feature spec frontmatter `storyClass:` | manual / authoring-quality check | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-002", + "title": "Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 58, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-002", + "title": "Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default.", + "pattern": "Ubiquitous", + "text": "Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 58, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-002:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "authoring-convention | feature spec + REQ-level `storyClass:` override | manual / authoring-quality check | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-003", + "title": "When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 62, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-003", + "title": "When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations.", + "pattern": "Event-driven", + "text": "When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 62, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-003:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "template + review | `templates/user-design-translation.md` + review ledger | manual / review | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-004", + "title": "Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 66, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-004", + "title": "Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority.", + "pattern": "Ubiquitous", + "text": "Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 66, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-004:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "guardrail | translation frontmatter `governingSpec:` | `scripts/check-design-spec-coupling.mjs` | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-005", + "title": "If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 70, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-005", + "title": "If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing.", + "pattern": "Unwanted", + "text": "If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 70, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-005:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "guardrail | diff base ↔ HEAD coupling check | `scripts/check-design-spec-coupling.mjs` | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-006", + "title": "Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`).", + "source": "spec/features/design-intent-governance/spec.md", + "line": 76, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-006", + "title": "Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`).", + "pattern": "Ubiquitous", + "text": "Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`).", + "source": "spec/features/design-intent-governance/spec.md", + "line": 76, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-006:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "template + review | translation property fit matrix + drift source rows | manual / review | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, + { + "id": "REQ-DESIGN-GOV-007", + "title": "If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 81, + "statements": [ + { + "requirementId": "REQ-DESIGN-GOV-007", + "title": "If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior.", + "pattern": "Unwanted", + "text": "If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 81, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-007:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "review | trail vs spec authority comparison | manual / review ledger | unverified", + "generatedStub": true, + "verificationStatus": "pending" + } + ] + }, { "id": "REQ-DISC-001", "title": "GitHub Pages primary navigation shall expose only curated onboarding and evidence surfaces, not every template, internal experiment, or generated page.", @@ -1447,6 +1615,118 @@ "generatedStub": true, "verificationStatus": "pending" }, + { + "requirementId": "REQ-DESIGN-GOV-001", + "title": "Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped.", + "pattern": "Ubiquitous", + "text": "Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 55, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-001:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "authoring-convention | feature spec frontmatter `storyClass:` | manual / authoring-quality check | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-002", + "title": "Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default.", + "pattern": "Ubiquitous", + "text": "Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 58, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-002:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "authoring-convention | feature spec + REQ-level `storyClass:` override | manual / authoring-quality check | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-003", + "title": "When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations.", + "pattern": "Event-driven", + "text": "When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 62, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-003:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "template + review | `templates/user-design-translation.md` + review ledger | manual / review | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-004", + "title": "Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority.", + "pattern": "Ubiquitous", + "text": "Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 66, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-004:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "guardrail | translation frontmatter `governingSpec:` | `scripts/check-design-spec-coupling.mjs` | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-005", + "title": "If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing.", + "pattern": "Unwanted", + "text": "If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 70, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-005:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "guardrail | diff base ↔ HEAD coupling check | `scripts/check-design-spec-coupling.mjs` | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-006", + "title": "Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`).", + "pattern": "Ubiquitous", + "text": "Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`).", + "source": "spec/features/design-intent-governance/spec.md", + "line": 76, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-006:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "template + review | translation property fit matrix + drift source rows | manual / review | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, + { + "requirementId": "REQ-DESIGN-GOV-007", + "title": "If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior.", + "pattern": "Unwanted", + "text": "If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior.", + "source": "spec/features/design-intent-governance/spec.md", + "line": 81, + "style": "inline", + "statementIndex": 1, + "statementId": "REQ-DESIGN-GOV-007:S1", + "statementCount": 1, + "verificationMapped": true, + "plannedVerification": "review | trail vs spec authority comparison | manual / review ledger | unverified", + "generatedStub": true, + "verificationStatus": "pending" + }, { "requirementId": "REQ-DISC-001", "title": "GitHub Pages primary navigation shall expose only curated onboarding and evidence surfaces, not every template, internal experiment, or generated page.", diff --git a/generated/requirements.test.mjs b/generated/requirements.test.mjs index a373d13..9c65c38 100644 --- a/generated/requirements.test.mjs +++ b/generated/requirements.test.mjs @@ -273,6 +273,69 @@ test("REQ-COUPON-009:S1 [Optional] Where a premium entitlement is present, the s // Statement: Where a premium entitlement is present, the system shall apply the premium discount after coupon validation. }); +test("REQ-DESIGN-GOV-001:S1 [Ubiquitous] Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:55. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-001:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:55 + // Requirement: REQ-DESIGN-GOV-001 + // Statement: Each L2 behavior scope shall declare a `storyClass` of `user`, `system`, `mixed`, or `method` before a design translation is required or skipped. +}); + +test("REQ-DESIGN-GOV-002:S1 [Ubiquitous] Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:58. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-002:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:58 + // Requirement: REQ-DESIGN-GOV-002 + // Statement: Each feature spec frontmatter shall declare a file-level default `storyClass`. Each individual REQ whose story class differs from the file default shall declare an explicit `storyClass:` line in its yaml block. REQs without an explicit override inherit the file default. +}); + +test("REQ-DESIGN-GOV-003:S1 [Event-driven] When accepted user-facing L2 behavior changes, the matching design translation shall map governing...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:62. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-003:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:62 + // Requirement: REQ-DESIGN-GOV-003 + // Statement: When accepted user-facing L2 behavior changes, the matching design translation shall map governing intent to a concrete UI object tree (region → element → variant → property), to property obligations, and to evidence obligations. +}); + +test("REQ-DESIGN-GOV-004:S1 [Ubiquitous] Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:66. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-004:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:66 + // Requirement: REQ-DESIGN-GOV-004 + // Statement: Each design translation shall declare its `governingSpec` in frontmatter — the list of `spec/` files that authorize the translation. A translation without `governingSpec` is treated as missing authority. +}); + +test("REQ-DESIGN-GOV-005:S1 [Unwanted] If a design change modifies user-facing UI properties without a paired change to at least one...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:70. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-005:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:70 + // Requirement: REQ-DESIGN-GOV-005 + // Statement: If a design change modifies user-facing UI properties without a paired change to at least one declared `governingSpec` file (or without an explicit `noIntentChange: true` marker in a commit message in the same change), then the review shall record a `design_translation_gap` instead of treating the visual change as self-authorizing. +}); + +test("REQ-DESIGN-GOV-006:S1 [Ubiquitous] Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`,...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:76. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-006:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:76 + // Requirement: REQ-DESIGN-GOV-006 + // Statement: Each user-facing translation shall record per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or `not_applicable`, and shall classify each residual gap by drift source (`wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, or `decision_gap`). +}); + +test("REQ-DESIGN-GOV-007:S1 [Unwanted] If an external design document (design system reference, code-path catalog, mockup index) declares a...", { + skip: "Generated stub from spec/features/design-intent-governance/spec.md:81. Verification is still pending.", +}, () => { + // @Spec(REQ-DESIGN-GOV-007:S1) generated_stub=true verification_status=pending + // Source: spec/features/design-intent-governance/spec.md:81 + // Requirement: REQ-DESIGN-GOV-007 + // Statement: If an external design document (design system reference, code-path catalog, mockup index) declares a domain token, state, or behavior that has no entry in `spec/` (or declares an adoption status that disagrees with `spec/`), then the review shall record a `design_trail_authority_drift` and update the external document — design trails cannot unilaterally define product behavior. +}); + test("REQ-DISC-001:S1 [Ubiquitous] GitHub Pages primary navigation shall expose only curated onboarding and evidence surfaces, not every...", { skip: "Generated stub from spec/features/public-discovery/spec.md:35. Verification is still pending.", }, () => { diff --git a/generated/verification-report.md b/generated/verification-report.md index 6d71ad3..9138fa9 100644 --- a/generated/verification-report.md +++ b/generated/verification-report.md @@ -8,13 +8,13 @@ Generated stubs are not proof of behavior. They are pending verification slots. | Metric | Count | |---|---:| -| Requirements | 35 | -| EARS statements | 42 | -| Verification-map covered statements | 42 | -| Generated stub slots | 42 | +| Requirements | 42 | +| EARS statements | 49 | +| Verification-map covered statements | 49 | +| Generated stub slots | 49 | | Non-generated @Spec references outside generated artifacts | 76 | | Statements with non-generated @Spec trace | 30 | -| Pending generated-only statements | 12 | +| Pending generated-only statements | 19 | | Code-only @Spec references | 0 | A non-generated trace is not automatically execution proof. Mark a statement verified only after the referenced test, guardrail, smoke check, or manual review has run or been recorded. @@ -32,6 +32,13 @@ A non-generated trace is not automatically execution proof. Mark a statement ver | REQ-COUPON-007:S1 | Unwanted | examples/coupon-order-system/spec.md:62 | | REQ-COUPON-008:S1 | Unwanted | examples/coupon-order-system/spec.md:64 | | REQ-COUPON-009:S1 | Optional | examples/coupon-order-system/spec.md:66 | +| REQ-DESIGN-GOV-001:S1 | Ubiquitous | spec/features/design-intent-governance/spec.md:55 | +| REQ-DESIGN-GOV-002:S1 | Ubiquitous | spec/features/design-intent-governance/spec.md:58 | +| REQ-DESIGN-GOV-003:S1 | Event-driven | spec/features/design-intent-governance/spec.md:62 | +| REQ-DESIGN-GOV-004:S1 | Ubiquitous | spec/features/design-intent-governance/spec.md:66 | +| REQ-DESIGN-GOV-005:S1 | Unwanted | spec/features/design-intent-governance/spec.md:70 | +| REQ-DESIGN-GOV-006:S1 | Ubiquitous | spec/features/design-intent-governance/spec.md:76 | +| REQ-DESIGN-GOV-007:S1 | Unwanted | spec/features/design-intent-governance/spec.md:81 | | REQ-REL-003:S1 | Event-driven | spec/features/release-and-versioning/spec.md:42 | | REQ-SESSION-001:S1 | Event-driven | examples/account-session-heading-style/spec.md:44 | | REQ-SESSION-001:S2 | Unwanted | examples/account-session-heading-style/spec.md:48 | @@ -70,6 +77,13 @@ A non-generated trace is not automatically execution proof. Mark a statement ver | REQ-COUPON-007:S1 | Simulation T06 | | REQ-COUPON-008:S1 | Simulation T07 | | REQ-COUPON-009:S1 | Generated requirement stub; premium entitlement adapter test | +| REQ-DESIGN-GOV-001:S1 | authoring-convention \| feature spec frontmatter `storyClass:` \| manual / authoring-quality check \| unverified | +| REQ-DESIGN-GOV-002:S1 | authoring-convention \| feature spec + REQ-level `storyClass:` override \| manual / authoring-quality check \| unverified | +| REQ-DESIGN-GOV-003:S1 | template + review \| `templates/user-design-translation.md` + review ledger \| manual / review \| unverified | +| REQ-DESIGN-GOV-004:S1 | guardrail \| translation frontmatter `governingSpec:` \| `scripts/check-design-spec-coupling.mjs` \| unverified | +| REQ-DESIGN-GOV-005:S1 | guardrail \| diff base ↔ HEAD coupling check \| `scripts/check-design-spec-coupling.mjs` \| unverified | +| REQ-DESIGN-GOV-006:S1 | template + review \| translation property fit matrix + drift source rows \| manual / review \| unverified | +| REQ-DESIGN-GOV-007:S1 | review \| trail vs spec authority comparison \| manual / review ledger \| unverified | | REQ-DISC-001:S1 | test \| `tests/public-discovery.test.mjs` \| `npm run test:project` \| verified | | REQ-DISC-002:S1 | test \| `tests/public-discovery.test.mjs` \| `npm run test:project` \| verified | | REQ-DISC-003:S1 | test \| `tests/public-discovery.test.mjs` \| `npm run test:project` \| verified | diff --git a/guide/intent-to-ui-translation.md b/guide/intent-to-ui-translation.md new file mode 100644 index 0000000..72ec1a7 --- /dev/null +++ b/guide/intent-to-ui-translation.md @@ -0,0 +1,212 @@ +# Intent To UI Translation + +This guide explains how the Intent Specification Layer connects to concrete UI +properties for user-facing surfaces, and how a project verifies that a visual +change is *paired* with — and authorized by — accepted intent. + +This guide is the conceptual companion to +[`spec/features/design-intent-governance/spec.md`](../spec/features/design-intent-governance/spec.md). + +## 1. The problem + +Most design systems and design documents describe *what looks good*. A spec +governs *what must be true* about behavior. The middle layer — *which UI +property serves which governing intent, and how do we know* — is usually +implicit. + +When it is implicit: + +- A visual change can silently add product behavior (a new state, a new + recovery path, a new copy obligation) that no L2 spec has accepted. +- An L2 spec change can be approved without an obvious place for the design + decisions it implies (copy tone, color weight, motion preference). +- A drift report ("this screen looks wrong") cannot identify what to fix: + the intent? the translation? the implementation? the evidence? + +The Intent-to-UI translation method makes that middle layer explicit. + +## 2. Where it sits in the layer model + +| Layer | Role | Owns | +|---|---|---| +| L0 Constitution | Unchanging values, banned moves | What kind of product this is | +| L1 Domain Truth | Shared entities, states, tokens | Vocabulary | +| L2 Behavior Spec | EARS requirements | What the product does | +| L3 Interface Contract | Multi-step, partial-failure | How modules coordinate | +| **Design translation** | **Intent → UI property mapping** | **How a user actually meets the intent** | +| Code + evidence | Implementation + verification | Whether the rendered UI matches the translation | + +The design translation does not replace `spec/`. It is read *after* L2/L3 is +accepted. A translation file is a verification surface, not a behavior source. + +## 3. Story class + +Every L2 behavior scope has a `storyClass`: + +| Class | Meaning | Translation obligation | +|---|---|---| +| `user` | A user directly sees and operates the behavior | required | +| `system` | No user-facing surface | not required | +| `mixed` | A system trigger surfaces in a user-facing artifact (push, scheduled state visible in UI) | required, but only for the user-facing part | +| `method` | Governance / spec authoring rules | not applicable | + +Story class is declared in two places (`REQ-DESIGN-GOV-002`): + +1. **File default** — the feature spec frontmatter declares + `storyClass: ` as the default for every REQ in the file. +2. **Per-REQ override** — a REQ whose class differs from the file default + adds an explicit `storyClass:` to its yaml block. + +This lets a mostly-user feature file (e.g., a mission lifecycle) include a few +system-only REQs (e.g., auto-expiry) without misclassifying the whole feature. + +A note on layout: this method does not require splitting the repository into +`spec/user/` and `spec/system/` directories. Story class is a metadata +overlay; projects that already use global `spec/02_behavior/.md` +layout keep that layout and simply add `storyClass` metadata. + +## 4. The UI object tree + +A design translation describes its surface as a tree: + +```text + + + + + +``` + +| Node | Definition | +|---|---| +| Surface / story | An L2 scope or named user task (`checkout.review`) | +| Region | A structural area inside the surface (`header`, `line_items`, `total`) | +| Element | An information or action unit (`line_item`, `total.amount`, `primary_action`) | +| Variant | A state, role, theme, locale, or text-scale variant (`oos_badge`, `disabled_state`) | +| Property | The measurable leaf (`copy`, `colorToken`, `icon`, `position`, `motion`, `semanticLabel`) | + +The leaf is where intent-to-UI fit is judged. A "Checkout looks good" claim +without leaf-level rows is anecdotal; a translation with leaf-level rows is +auditable. + +## 5. Property fit judgments + +Each leaf row in the property fit matrix carries a `fit` judgment: + +| Judgment | Meaning | Next action | +|---|---|---| +| `fit` | The UI property directly serves the governing intent | Keep; preserve evidence | +| `weak` | The intent direction is right but the expression is under-weight (small copy, wrong position, low contrast) | Strengthen the UI property | +| `drift` | The UI property points away from the governing intent | Identify the drift source and fix the right layer | +| `violation` | The UI property directly contradicts an L0/L1/L2/L3 obligation | Block release; fix spec or UI immediately | +| `not_applicable` | The intent does not bind this property | Record the exclusion reason | + +## 6. Drift sources + +When `drift` or `weak` appears, classify the source so the fix target is +unambiguous: + +| Drift source | Meaning | Fix location | +|---|---|---| +| `wrong_intent` | The accepted spec is wrong or stale | `spec/` | +| `missing_intent` | The design needs an L2 behavior that the spec does not define | `spec/02_behavior/` or `spec/03_contracts/` | +| `weak_translation` | The spec is right; the design's UI property choice is too weak to express it | `docs/design/translations/` | +| `wrong_implementation` | The translation is right; the code does not render it correctly | code + tests | +| `missing_evidence` | The code is plausibly correct but no test, audit, or screenshot proves it | tests, visual audit, accessibility ledger | +| `decision_gap` | No one has authority to decide which of the above applies | `spec/reviews/` | + +This taxonomy aligns with the implementation gap taxonomy from +[`guide/spec-as-product-standard.md`](spec-as-product-standard.md). Design +drift is the design analogue of code-gap classification. + +## 7. Authority vs trail + +A *design authority* is the document that decides what must be true. A +*design trail* is a document that follows authority and helps developers +locate it (code paths, mockup indexes, system token catalogs). + +- Spec L1 (`spec/01_domain.md`, `spec/01_domain/tokens.md` if split) is + authoritative for domain tokens, states, and adoption decisions. +- The code enum source file is authoritative for the exact identifier shape + of an adopted enum. +- A design system reference, mockup index, or code-path catalog is a trail. + When it disagrees with the spec, the trail is updated, not the spec + (`REQ-DESIGN-GOV-007`). + +Projects that previously named a mockup index or code-path catalog "the +source of truth" should flip that wording when adopting this method. The +spec is the source of truth; the catalog is a trail. + +## 8. Pairing design changes with spec authority + +A design translation change does one of three things: + +| Change kind | Required pairing | +|---|---| +| Pure expression — copy / color / icon / spacing changed but no governing intent changed | `noIntentChange: true` line in a commit message in the same change | +| Translation change — the same intent expressed via different UI properties | translation file updated + evidence updated | +| Product behavior change — what the user can do, the states, the failure / recovery paths changed | L2 or L3 spec updated *first* (in the same change or earlier) | + +The coupling check (`scripts/check-design-spec-coupling.mjs`) inspects the +diff and enforces this pairing using each translation's `governingSpec:` +frontmatter list. + +## 9. What the translation file looks like + +See [`templates/user-design-translation.md`](../templates/user-design-translation.md) +for the full structure. The required sections are: + +1. **Authority** — story class + governing L0/L1/L2/L3 + surface + route. +2. **User outcome** — one sentence on what the user gets. +3. **Design obligations** — what the design must do to honor each intent. +4. **UI object tree** — region → element → variant → property breakdown. +5. **Domain state to UI property mapping** — per-state expected copy / icon / + color / position / action with current fit. +6. **Interaction and recovery mapping** — EARS statements → UI feedback → + recovery → evidence. +7. **UI property fit matrix** — per-leaf `fit` with drift source classification. +8. **Evidence** — golden, visual audit, widget/integration, accessibility, + usability records (present / absent / manual_only). +9. **Residual gaps** — each tagged with a drift source and a next action. + +## 10. What stays project-local + +The method governs *that* a translation exists and *that* it pairs with spec +authority. The method does not govern: + +- Which design system the project uses. +- Whether the project generates one ledger per route (Buzyrun does this) or + only writes hand-authored translations (a smaller project might). +- Which test framework or screenshot pipeline records the evidence. +- The exact naming inside a project's `docs/design/` tree. + +Pick conventions that match the project. Keep `governingSpec:` and the +property fit matrix shape consistent across translations so the coupling +check works. + +## 11. Adoption order + +1. Add `storyClass:` to every existing L2 feature spec frontmatter. +2. Add per-REQ `storyClass:` overrides for clearly divergent REQs (auto-jobs, + internal calculations) inside otherwise user-facing feature files. +3. Pick the highest-risk user-facing surface (the one most likely to drift) + and write the first translation using the template. +4. Add the coupling check (`scripts/check-design-spec-coupling.mjs`) to CI. +5. Translate the next-highest-risk surface. Repeat. + +Resist writing translations for every surface at once. The method works +better when the first few are deep and load-bearing; coverage follows after +the pattern is internalized. + +## 12. See also + +- [`spec/features/design-intent-governance/spec.md`](../spec/features/design-intent-governance/spec.md) + — the governing requirements (`REQ-DESIGN-GOV-001..007`). +- [`templates/user-design-translation.md`](../templates/user-design-translation.md) + — the translation template. +- [`examples/design-intent-translation/`](../examples/design-intent-translation/) + — a minimal worked example. +- [`guide/spec-as-product-standard.md`](spec-as-product-standard.md) — the + implementation gap taxonomy that drift source classification mirrors. +- [`guide/intent-specification-layer.md`](intent-specification-layer.md) — + the parent ILS method. diff --git a/package.json b/package.json index c4b5d9a..1ddea5a 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "check:authoring": "node scripts/check-spec-authoring.mjs", "check:links": "node scripts/check-links.mjs", "check:reqs": "node scripts/generate-req-tests.mjs --check", - "check": "npm run check:syntax && npm run check:agent && npm run check:authoring && npm run check:links && npm run check:reqs && npm run test:reqs && npm run test:project && npm run benchmark && npm run simulate" + "check:design-spec-coupling": "node scripts/check-design-spec-coupling.mjs", + "check": "npm run check:syntax && npm run check:agent && npm run check:authoring && npm run check:links && npm run check:reqs && npm run check:design-spec-coupling && npm run test:reqs && npm run test:project && npm run benchmark && npm run simulate" } } diff --git a/scripts/check-design-spec-coupling.mjs b/scripts/check-design-spec-coupling.mjs new file mode 100644 index 0000000..cf55de2 --- /dev/null +++ b/scripts/check-design-spec-coupling.mjs @@ -0,0 +1,215 @@ +#!/usr/bin/env node +// REQ-DESIGN-GOV-005 enforcement. +// +// When a hand-authored design translation under the configured translations +// directory is changed, the change must be paired with either: +// (a) a change in at least one of its declared `governingSpec:` files, or +// (b) an explicit `noIntentChange: true` marker in a commit message in the +// same PR. +// +// Without one of these, the PR is recorded as a `design_translation_gap` and +// this check fails. +// +// See: spec/features/design-intent-governance/spec.md +// guide/intent-to-ui-translation.md +// +// Usage: +// node scripts/check-design-spec-coupling.mjs # diff vs base +// node scripts/check-design-spec-coupling.mjs --base=main +// node scripts/check-design-spec-coupling.mjs --staged # staged only +// node scripts/check-design-spec-coupling.mjs --dir=docs/design/translations +// +// Configuration (in order of precedence): +// 1. --dir= CLI flag +// 2. DESIGN_TRANSLATIONS_DIR environment variable +// 3. Auto-detect from common conventional paths +// +// Filename convention: `*.design.md` under the configured directory tree. + +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { execSync } from "node:child_process"; + +const root = process.cwd(); +const NO_INTENT_MARKER = /^\s*noIntentChange:\s*true\s*$/im; + +// Conventional locations a project may use. First one that exists wins +// during auto-detection. +const CONVENTIONAL_DIRS = [ + "docs/design/translations", + "docs/design/v3/governance/translations", + "docs/translations", + "design/translations", +]; + +function parseArgs(argv) { + const envBase = process.env.BASE_REF + || (process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : null) + || "origin/main"; + const args = { + base: envBase, + staged: false, + dir: process.env.DESIGN_TRANSLATIONS_DIR || null, + }; + for (const a of argv) { + if (a.startsWith("--base=")) args.base = a.slice("--base=".length); + else if (a.startsWith("--dir=")) args.dir = a.slice("--dir=".length); + else if (a === "--staged") args.staged = true; + } + return args; +} + +function resolveTranslationsDir(explicit) { + if (explicit) { + if (!fs.existsSync(path.join(root, explicit))) { + console.error(`[check-design-spec-coupling] configured dir does not exist: ${explicit}`); + process.exit(2); + } + return explicit; + } + for (const candidate of CONVENTIONAL_DIRS) { + if (fs.existsSync(path.join(root, candidate))) return candidate; + } + // No translations directory found. The check is a no-op rather than a failure + // since not every project ships user-facing design translations. + return null; +} + +function sh(cmd) { + return execSync(cmd, { cwd: root, encoding: "utf8" }); +} + +function shSafe(cmd) { + try { + return execSync(cmd, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }); + } catch { + return ""; + } +} + +function changedFiles({ base, staged }) { + if (staged) { + return sh("git diff --cached --name-only --diff-filter=AMR").trim().split("\n").filter(Boolean); + } + let baseRef = base; + if (!shSafe(`git rev-parse --verify --quiet ${baseRef}`).trim()) { + if (!shSafe("git rev-parse --verify --quiet HEAD~1").trim()) return []; + baseRef = "HEAD~1"; + } + const mergeBase = shSafe(`git merge-base ${baseRef} HEAD`).trim() || baseRef; + return sh(`git diff --name-only --diff-filter=AMR ${mergeBase}...HEAD`).trim().split("\n").filter(Boolean); +} + +function commitMessagesBetween({ base, staged }) { + if (staged) return ""; + let baseRef = base; + if (!shSafe(`git rev-parse --verify --quiet ${baseRef}`).trim()) { + if (!shSafe("git rev-parse --verify --quiet HEAD~1").trim()) return ""; + baseRef = "HEAD~1"; + } + const mergeBase = shSafe(`git merge-base ${baseRef} HEAD`).trim() || baseRef; + return shSafe(`git log --format=%B ${mergeBase}..HEAD`); +} + +function parseFrontmatter(filePath) { + const text = fs.readFileSync(filePath, "utf8"); + if (!text.startsWith("---")) return null; + const end = text.indexOf("\n---", 3); + if (end === -1) return null; + return text.slice(3, end).trim(); +} + +function extractGoverningSpec(frontmatter) { + if (!frontmatter) return []; + const line = frontmatter.split("\n").find((l) => /^governingSpec\s*:/.test(l)); + if (!line) return []; + const rhs = line.split(":").slice(1).join(":").trim(); + if (rhs.startsWith("[") && rhs.endsWith("]")) { + return rhs.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean); + } + return [rhs]; +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const translationsDir = resolveTranslationsDir(args.dir); + if (!translationsDir) { + console.log("[check-design-spec-coupling] no translations directory found — skipping (REQ-DESIGN-GOV-005 not applicable to this project layout)"); + process.exit(0); + } + + const changed = changedFiles(args); + const changedSet = new Set(changed.map((f) => f.replaceAll(path.sep, "/"))); + + const changedTranslations = changed.filter( + (f) => f.startsWith(translationsDir + "/") && f.endsWith(".design.md") && !f.endsWith("README.md"), + ); + + if (changedTranslations.length === 0) { + console.log("[check-design-spec-coupling] no translation changes — ok"); + process.exit(0); + } + + const messages = commitMessagesBetween(args); + const hasNoIntentMarker = NO_INTENT_MARKER.test(messages); + + const failures = []; + for (const tFile of changedTranslations) { + const abs = path.join(root, tFile); + if (!fs.existsSync(abs)) continue; + const fm = parseFrontmatter(abs); + const govSpec = extractGoverningSpec(fm); + + if (govSpec.length === 0) { + failures.push({ + file: tFile, + reason: "missing `governingSpec:` frontmatter — translation has no declared spec authority (REQ-DESIGN-GOV-004)", + }); + continue; + } + + const missingFiles = govSpec.filter((g) => !fs.existsSync(path.join(root, g))); + if (missingFiles.length === govSpec.length) { + failures.push({ + file: tFile, + reason: `every declared governingSpec is missing on disk: ${govSpec.join(", ")}`, + }); + continue; + } + + const hasPairedSpecChange = govSpec.some((g) => changedSet.has(g)); + if (hasPairedSpecChange) continue; + if (hasNoIntentMarker) continue; + + failures.push({ + file: tFile, + reason: + "design translation changed but no governingSpec file in this PR changed " + + `(governingSpec: ${govSpec.join(", ")}). ` + + "Either include a paired spec change, or add `noIntentChange: true` " + + "to a commit message in this PR (REQ-DESIGN-GOV-005).", + }); + } + + if (failures.length === 0) { + console.log( + `[check-design-spec-coupling] ${changedTranslations.length} translation change(s) — all paired with spec or marker — ok`, + ); + process.exit(0); + } + + console.error("[check-design-spec-coupling] FAIL — design_translation_gap detected\n"); + for (const f of failures) { + console.error(` ✗ ${f.file}`); + console.error(` ${f.reason}\n`); + } + console.error( + "Authority: spec/features/design-intent-governance/spec.md REQ-DESIGN-GOV-004 / REQ-DESIGN-GOV-005. " + + "If the change is pure expression (copy/color/icon/spacing) with no governing intent change, " + + "add `noIntentChange: true` to one of this PR's commit messages.", + ); + process.exit(1); +} + +main(); diff --git a/spec/README.md b/spec/README.md index 003a481..cbdf5f4 100644 --- a/spec/README.md +++ b/spec/README.md @@ -20,6 +20,9 @@ changed, released, verified, and presented. guardrails. - `spec/features/spec-authoring-quality/spec.md`: archetype packs, valid-input failure, latency contracts, and under-decomposition checks. +- `spec/features/design-intent-governance/spec.md`: story-class classification, + intent-to-UI design translation, trail vs authority, design-spec coupling + check. - `spec/features/public-discovery/spec.md`: README, GitHub Pages, repository metadata, and community discovery surfaces. - `spec/features/release-and-versioning/spec.md`: package, changelog, tags, and diff --git a/spec/features/design-intent-governance/spec.md b/spec/features/design-intent-governance/spec.md new file mode 100644 index 0000000..744add3 --- /dev/null +++ b/spec/features/design-intent-governance/spec.md @@ -0,0 +1,167 @@ +--- +id: SPEC-DESIGN-INTENT-GOVERNANCE +title: Design Intent Governance +status: draft +owners: [maintainers] +updated: 2026-05-27 +layers: [L0, L1, L2] +target_release: v0.3.0 +--- + +# Design Intent Governance Spec + +## Scope + +How the Intent Specification Layer connects to user-facing UI design so that +copy, color, icon, position, shape, motion, and semantic-label decisions trace +back to governing intent (`L0`, `L1`, `L2`, `L3`), and so that visual changes +cannot quietly add or weaken product behavior. + +This spec is **not** a design system. It defines the *governance bridge* +between accepted intent and concrete UI properties. Whether a project uses +Material, HIG, a custom system, or a static-site template is out of scope. + +## Authority Sources + +- Layer 0: `spec/00_constitution.md`. +- Concepts: `guide/intent-to-ui-translation.md`. +- Templates: `templates/user-design-translation.md`. +- Related governance: `spec/features/spec-governance/spec.md`, + `spec/features/spec-authoring-quality/spec.md`. + +## Layer 1: Domain Truth + +| Term | Meaning | Source of truth | +|---|---|---| +| `storyClass` | Classification of an L2 behavior scope as `user`, `system`, `mixed`, or `method` | Feature spec frontmatter / per-REQ override | +| `design translation` | A user-facing behavior's mapping from `L0`/`L1`/`L2`/`L3` intent to UI object tree, concrete UI properties, and evidence obligations | `docs/design/translations/.design.md` (project-local naming) | +| `governingSpec` | The list of `spec/` files that authorize a design translation; declared in the translation's frontmatter | Translation file frontmatter | +| `noIntentChange` | A commit-level marker asserting that a design change is pure expression and modifies no governing intent | Commit message | +| `design trail` | An external design document (system reference, code-path catalog, mockup index) that follows spec authority and is updated when spec changes | Project's design references | +| `fit` / `weak` / `drift` / `violation` / `not_applicable` | Per-property judgments of how a UI property serves governing intent | Translation file property fit matrix | +| `drift source` | Why a drift exists: `wrong_intent`, `missing_intent`, `weak_translation`, `wrong_implementation`, `missing_evidence`, `decision_gap` | Translation file residual gaps | + +### Story class definitions + +| Class | Meaning | Design translation obligation | +|---|---|---| +| `user` | A user directly sees and operates the behavior | required | +| `system` | No user-facing surface (background jobs, infra, scheduled work) | not required; route ledger may still record why no translation is needed | +| `mixed` | A system trigger surfaces in a user-facing artifact (e.g., push notification, scheduled state change visible in UI) | required, but only for the user-facing part | +| `method` | Governance / spec authoring rules; not user behavior | not applicable | + +## Layer 2: Behavior Spec + +- [REQ-DESIGN-GOV-001][Ubiquitous] Each L2 behavior scope shall declare a + `storyClass` of `user`, `system`, `mixed`, or `method` before a design + translation is required or skipped. +- [REQ-DESIGN-GOV-002][Ubiquitous] Each feature spec frontmatter shall declare + a file-level default `storyClass`. Each individual REQ whose story class + differs from the file default shall declare an explicit `storyClass:` line in + its yaml block. REQs without an explicit override inherit the file default. +- [REQ-DESIGN-GOV-003][Event-driven] When accepted user-facing L2 behavior + changes, the matching design translation shall map governing intent to a + concrete UI object tree (region → element → variant → property), to property + obligations, and to evidence obligations. +- [REQ-DESIGN-GOV-004][Ubiquitous] Each design translation shall declare its + `governingSpec` in frontmatter — the list of `spec/` files that authorize the + translation. A translation without `governingSpec` is treated as missing + authority. +- [REQ-DESIGN-GOV-005][Unwanted] If a design change modifies user-facing UI + properties without a paired change to at least one declared `governingSpec` + file (or without an explicit `noIntentChange: true` marker in a commit + message in the same change), then the review shall record a + `design_translation_gap` instead of treating the visual change as + self-authorizing. +- [REQ-DESIGN-GOV-006][Ubiquitous] Each user-facing translation shall record + per-property `fit` judgments using `fit`, `weak`, `drift`, `violation`, or + `not_applicable`, and shall classify each residual gap by drift source + (`wrong_intent`, `missing_intent`, `weak_translation`, + `wrong_implementation`, `missing_evidence`, or `decision_gap`). +- [REQ-DESIGN-GOV-007][Unwanted] If an external design document (design system + reference, code-path catalog, mockup index) declares a domain token, state, + or behavior that has no entry in `spec/` (or declares an adoption status that + disagrees with `spec/`), then the review shall record a + `design_trail_authority_drift` and update the external document — design + trails cannot unilaterally define product behavior. + +## Experience Review + +### Happy Path + +1. A maintainer accepts new user-facing L2 behavior. +2. The maintainer (or design owner) writes a design translation under + `docs/design/translations/.design.md` using + `templates/user-design-translation.md`. +3. The translation declares `governingSpec`, `storyClass`, the UI object tree, + property fit matrix, and per-property fit judgments. +4. A future design-only change touches the same translation. The author either + pairs it with a `spec/` change (because intent changed) or includes + `noIntentChange: true` in a commit message (because only expression changed). +5. The coupling check passes; the design ledger check (if the project + generates per-route ledgers) passes; the project review records the new + `fit`/`weak`/`drift` rows. + +### Unwanted / Recovery Paths + +| Situation | Result | Next action | Requirement | +|---|---|---|---| +| Design change touches a translation but no governing spec changed, and no `noIntentChange` marker exists | Review records `design_translation_gap` and blocks the change | Pair the design change with the matching `spec/` change, OR add `noIntentChange: true` if the change is pure expression | REQ-DESIGN-GOV-005 | +| Design trail (system docs, F-registry, mockup) declares a token not present in `spec/` | Review records `design_trail_authority_drift` | Update the trail to match spec; if spec is wrong, update spec first | REQ-DESIGN-GOV-007 | +| Feature spec lacks `storyClass` in frontmatter | Spec authoring check flags missing classification | Add `storyClass:` to the feature spec frontmatter | REQ-DESIGN-GOV-001, REQ-DESIGN-GOV-002 | +| Translation lacks `governingSpec` in frontmatter | Coupling check fails | Add `governingSpec: []` to the translation frontmatter | REQ-DESIGN-GOV-004 | +| Property fit recorded as `drift` but no drift source classified | Review records `decision_gap` | Classify the drift source so the fix target is unambiguous | REQ-DESIGN-GOV-006 | + +## Implementation Gap Boundary + +Implementation readiness for a translation (does code render the obligated +property correctly? does the screenshot match?) belongs in the project's +verification map or review ledger, not in this governance spec. This spec +governs *that translation must exist and be paired with spec authority*; it +does not govern *whether implementation matches the translation*. + +When code drifts from a translation, classify per +`spec/features/spec-authoring-quality/` taxonomy: +`missing_implementation`, `partial_implementation`, `missing_test`, +`wrong_code`, or `wrong_spec`. + +## Verification Map + +| Requirement / statement | Verification type | Evidence target | Execution / record | Status | +|---|---|---|---|---| +| REQ-DESIGN-GOV-001:S1 | authoring-convention | feature spec frontmatter `storyClass:` | manual / authoring-quality check | unverified | +| REQ-DESIGN-GOV-002:S1 | authoring-convention | feature spec + REQ-level `storyClass:` override | manual / authoring-quality check | unverified | +| REQ-DESIGN-GOV-003:S1 | template + review | `templates/user-design-translation.md` + review ledger | manual / review | unverified | +| REQ-DESIGN-GOV-004:S1 | guardrail | translation frontmatter `governingSpec:` | `scripts/check-design-spec-coupling.mjs` | unverified | +| REQ-DESIGN-GOV-005:S1 | guardrail | diff base ↔ HEAD coupling check | `scripts/check-design-spec-coupling.mjs` | unverified | +| REQ-DESIGN-GOV-006:S1 | template + review | translation property fit matrix + drift source rows | manual / review | unverified | +| REQ-DESIGN-GOV-007:S1 | review | trail vs spec authority comparison | manual / review ledger | unverified | + +Status `unverified` reflects that this feature spec is `draft` in v0.3.0. Once +the guardrail script and template land, status moves to `verified`. + +## Out of Scope + +- Specific design systems (Material, HIG, custom). +- Specific token taxonomies beyond the spec/L1 authority pattern. +- Per-route or per-screen ledger formats — projects may add these locally + (see `examples/design-intent-translation/`), but the upstream method does + not mandate a single ledger format. +- Visual audit, accessibility audit, or golden-image tooling — these are + project-specific evidence mechanisms downstream of this governance. + +## Real-World Adoption + +The Buzyrun Flutter project (a multi-role consumer app) field-validated this +governance in 2026-05. Buzyrun's local artifacts demonstrate the method +end-to-end: + +- 22 feature specs classified by `storyClass`. +- 14 hand-authored design translations across priority 1, 2, and 3 surfaces. +- One `governingSpec` coupling check (`scripts/check-design-spec-coupling.mjs`) + enforced in CI. +- A `domain_token_authority` separation between spec L1 and an external + code-path trail. + +See `examples/design-intent-translation/` for a small abstracted example +derived from that adoption. diff --git a/templates/user-design-translation.md b/templates/user-design-translation.md new file mode 100644 index 0000000..3d86566 --- /dev/null +++ b/templates/user-design-translation.md @@ -0,0 +1,98 @@ +--- +id: TEMPLATE-USER-DESIGN-TRANSLATION +title: User Design Translation Template +status: template +owners: [maintainers] +updated: 2026-05-27 +governingSpec: [spec/02_behavior/.md] +--- + +# Design Translation: + +A design translation maps governing intent (`L0`, `L1`, `L2`, `L3`) to concrete +UI properties for a single user-facing surface or story. Use this template for +every `storyClass: user` scope and for the user-facing parts of +`storyClass: mixed` scopes. See `spec/features/design-intent-governance/spec.md`. + +## 1. Authority + +| Field | Value | +|---|---| +| Story class | `user` or `mixed-user-facing-part` | +| Governing L0 | `` | +| Governing L1 | `` | +| Governing L2 | `` | +| Governing L3 | `` | +| Surface / story | `` | +| Route / screen | ``, `` | + +Frontmatter `governingSpec:` must list every `spec/` file authorizing this +translation. The coupling check uses this list to enforce +`REQ-DESIGN-GOV-005`. + +## 2. User outcome + +```text + +``` + +## 3. Design obligations + +Obligations name the *user-facing UI obligation* that the governing intent +imposes — what the UI must do for the intent to be respected. Each obligation +links a UI behavior to a specific intent source. + +| Obligation ID | Source intent | UI obligation | +|---|---|---| +| `` | `` | `` | + +## 4. UI object tree + +```text + + + + + +``` + +Property leaves are the measurement points: `copy`, `icon`, `colorToken`, +`size`, `position`, `shape`, `motion`, `semanticLabel`, etc. + +## 5. Domain state to UI property mapping + +| Domain state | Expected copy | Expected icon | Expected color token | Expected position | Expected action | Current fit | +|---|---|---|---|---|---|---| +| `` | `` | `` | `` | `` | `` | `fit` / `weak` / `drift` / `violation` / `not_applicable` | + +## 6. Interaction and recovery mapping + +| EARS statement | Trigger / state | UI feedback | Recovery | Evidence | +|---|---|---|---|---| +| `` | `` | `` | `` | `` | + +## 7. UI property fit matrix + +| UI node | Property | Expected from intent | Current actual | Fit | Fix target | +|---|---|---|---|---|---| +| `` | `` | `` | `` | `fit` / `weak` / `drift` / `violation` / `not_applicable` | `spec` / `design` / `code` / `evidence` | + +## 8. Evidence + +| Evidence type | Path or record | Status | +|---|---|---| +| golden | `` | `present` / `absent` | +| visual audit | `` | `present` / `absent` | +| widget / integration test | `` | `present` / `absent` | +| accessibility | `` | `present` / `absent` / `manual_only` | +| usability / manual review | `` | `present` / `absent` / `manual_only` | + +## 9. Residual gaps + +Each gap is classified by drift source — see +`spec/features/design-intent-governance/spec.md` and +`guide/intent-to-ui-translation.md`. + +| Gap ID | Label | Cause (drift source) | Next action | +|---|---|---|---| +| `` | `weak_translation` / `wrong_intent` / `missing_intent` / `wrong_implementation` / `missing_evidence` / `decision_gap` | `` | `` |