forgedock 1.0.16 → 1.0.18

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 (190) hide show
  1. package/README.md +54 -3
  2. package/bin/cross-publish.mjs +344 -0
  3. package/bin/forgedock.mjs +2205 -541
  4. package/bin/github-app-manifest.json.license +2 -0
  5. package/bin/hooks/session-start.mjs +4 -5
  6. package/bin/registry.mjs +20 -0
  7. package/bin/tui.mjs +172 -96
  8. package/commands/adopt.md +433 -0
  9. package/commands/analytics.md +2 -0
  10. package/commands/audit-agents.md +2 -0
  11. package/commands/audit.md +2 -0
  12. package/commands/autopilot.md +77 -0
  13. package/commands/changelog.md +354 -0
  14. package/commands/cleanup.md +2 -0
  15. package/commands/compat-audit.md +330 -0
  16. package/commands/deploy-info.md +6 -5
  17. package/commands/design-bench.md +176 -0
  18. package/commands/design-render-critique-loop.md +103 -0
  19. package/commands/design.md +259 -0
  20. package/commands/diagnose.md +483 -0
  21. package/commands/explain.md +349 -0
  22. package/commands/forge-stats.md +2 -0
  23. package/commands/forgedock-init.md +2 -0
  24. package/commands/geo-audit.md +2 -0
  25. package/commands/incident-response.md +2 -0
  26. package/commands/issue.md +9 -3
  27. package/commands/milestone.md +2 -0
  28. package/commands/optimize.md +411 -0
  29. package/commands/orchestrate.md +239 -0
  30. package/commands/pipeline-health.md +60 -1
  31. package/commands/qa-sweep.md +39 -2
  32. package/commands/quality-gate.md +67 -0
  33. package/commands/replay.md +263 -0
  34. package/commands/resume.md +264 -0
  35. package/commands/review-pr-agents.md +5 -1
  36. package/commands/review-pr-staging.md +160 -2
  37. package/commands/review-pr.md +85 -3
  38. package/commands/rollback.md +2 -0
  39. package/commands/scope.md +280 -0
  40. package/commands/security-audit.md +2 -0
  41. package/commands/status.md +262 -0
  42. package/commands/sync-ecosystem.md +2 -0
  43. package/commands/test-gate.md +929 -0
  44. package/commands/validate.md +2 -0
  45. package/commands/work-on/build/architect.md +128 -18
  46. package/commands/work-on/build/context.md +130 -17
  47. package/commands/work-on/build/implement.md +7 -3
  48. package/commands/work-on/build/validate.md +2 -0
  49. package/commands/work-on/build.md +32 -7
  50. package/commands/work-on/close.md +85 -0
  51. package/commands/work-on/decompose.md +2 -0
  52. package/commands/work-on/investigate.md +30 -3
  53. package/commands/work-on/review.md +13 -1
  54. package/commands/work-on-monolithic.md +2 -0
  55. package/commands/work-on.md +676 -38
  56. package/docs/BRAND.md +51 -0
  57. package/docs/CI.md +177 -0
  58. package/docs/CONFIG.md +94 -0
  59. package/docs/FORGE-PROTOCOL.md +324 -0
  60. package/docs/articles/b-vs-c-baseline.md +210 -0
  61. package/docs/articles/full-abc-benchmark.md +205 -0
  62. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
  63. package/docs/articles/selective-spec-loading-token-savings.md +140 -0
  64. package/docs/claude-breakpoints.json +47 -0
  65. package/docs/design/abc-benchmark.md +195 -0
  66. package/docs/design/design-architect-rationale.md +123 -0
  67. package/docs/design/design-memory.md +61 -0
  68. package/docs/design/design-spec-schema.md +248 -0
  69. package/docs/design/design-system-lint.md +150 -0
  70. package/docs/design/divergent-generation.md +106 -0
  71. package/docs/design/effects-appropriateness.md +133 -0
  72. package/docs/design/fixtures/briefs/README.md +47 -0
  73. package/docs/design/fixtures/briefs/cadence.md +39 -0
  74. package/docs/design/fixtures/briefs/plume.md +39 -0
  75. package/docs/design/fixtures/briefs/slipstream.md +39 -0
  76. package/docs/design/fixtures/briefs/tender.md +39 -0
  77. package/docs/design/fixtures/briefs/voltage.md +39 -0
  78. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
  79. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
  80. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
  81. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
  82. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
  83. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
  84. package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
  85. package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
  86. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
  87. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
  88. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
  89. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
  90. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
  91. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
  92. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
  93. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
  94. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
  95. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
  96. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
  97. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
  98. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
  99. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
  100. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
  101. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
  102. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
  103. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
  104. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
  105. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
  106. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
  107. package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
  108. package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
  109. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
  110. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
  111. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
  112. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
  113. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
  114. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
  115. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
  116. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
  117. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
  118. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
  119. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
  120. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
  121. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
  122. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
  123. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
  124. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
  125. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
  126. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
  127. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
  128. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
  129. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
  130. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
  131. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
  132. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
  133. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
  134. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
  135. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
  136. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
  137. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
  138. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
  139. package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
  140. package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
  141. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
  142. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
  143. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
  144. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
  145. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
  146. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
  147. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
  148. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
  149. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
  150. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
  151. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
  152. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
  153. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
  154. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
  155. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
  156. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
  157. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
  158. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
  159. package/docs/design/foundations/brutalist.css +294 -0
  160. package/docs/design/foundations/editorial.css +300 -0
  161. package/docs/design/foundations/minimal-luxury.css +295 -0
  162. package/docs/design/foundations/technical.css +293 -0
  163. package/docs/design/foundations/warm-photo.css +299 -0
  164. package/docs/design/reference-corpus.md +1726 -0
  165. package/docs/design/render-critique-loop.md +57 -0
  166. package/docs/site/getting-started.md +21 -0
  167. package/docs/spec-graph-schema.md +397 -0
  168. package/package.json +3 -3
  169. package/scripts/bench-scorecard.mjs +239 -0
  170. package/scripts/build-spec-graph.mjs +509 -0
  171. package/scripts/classify-lane.sh +138 -0
  172. package/scripts/design-system-lint.mjs +993 -0
  173. package/scripts/graph-query.sh +486 -0
  174. package/scripts/transition-label.sh +169 -0
  175. package/scripts/validate-pr-target.sh +30 -0
  176. package/scripts/validate-spec-graph.sh +423 -0
  177. package/templates/devdocs/README.md +72 -0
  178. package/templates/devdocs/agent/using-forgedock.md +3 -0
  179. package/templates/devdocs/agent/using-github.md +3 -0
  180. package/templates/devdocs/index.yaml +162 -0
  181. package/templates/devdocs/project/architecture.md +3 -0
  182. package/templates/devdocs/project/conventions.md +3 -0
  183. package/templates/devdocs/project/custom-instructions.md +3 -0
  184. package/templates/devdocs/project/glossary.md +3 -0
  185. package/templates/devdocs/project/stack.md +3 -0
  186. package/templates/devdocs/templates/commit-convention.md +3 -0
  187. package/templates/devdocs/templates/issue.md +3 -0
  188. package/templates/devdocs/templates/pull-request.md +3 -0
  189. package/templates/devdocs/templates/review-finding.md +3 -0
  190. package/templates/workflows/forgedock-review.yml +96 -0
@@ -0,0 +1,248 @@
1
+ # FORGE:DESIGN_SPEC — Schema
2
+
3
+ > **Status:** Committed foundation — canonical schema for the UI Taste Harness (milestone #13, issue #881).
4
+ > Output of the [design-architect phase](design-architect-rationale.md) (#886); input to generate + the
5
+ > render-critique loop (#882); enforced by the anti-slop linter (#884); persisted to
6
+ > [design-memory](design-memory.md) (#887) at close. Draws its vocabulary from the
7
+ > [reference corpus](reference-corpus.md) (#880).
8
+ > Extended: `craft` object + `surface_depth` rubric dimension (#1047); `motion.tier`, `motion.hero_technique`,
9
+ > `motion.video_placeholder` fields + `motion` rubric dimension (#1043); `product_mock` field + `product_mock`
10
+ > rubric dimension (#1045); `scroll_narrative` object + `scroll_narrative` rubric dimension (#1046).
11
+ >
12
+ > Registered as a FORGE annotation type in [`../FORGE-PROTOCOL.md`](../FORGE-PROTOCOL.md).
13
+
14
+ ## Purpose
15
+
16
+ A structured, **machine-checkable** representation of the design language for one page — carried across pipeline
17
+ stages as a `FORGE:DESIGN_SPEC` annotation, exactly like other FORGE annotations carry context. It exists so taste
18
+ decisions **persist** instead of being re-rolled on every generation, and so a deterministic linter and a vision
19
+ critic can both check the output against the *same* committed intent.
20
+
21
+ Critically: the spec is **produced by the [rationale](design-architect-rationale.md)** (the reasoning), not authored
22
+ from nowhere. Rationale → spec → page.
23
+
24
+ ## Schema
25
+
26
+ ```jsonc
27
+ {
28
+ "meta": {
29
+ "product": "Voltage", // anonymized brief name
30
+ "archetype": "technical-dense", // one of the corpus archetypes — committed, not blended
31
+ "corpus_version": "2026.6", // which grammar snapshot this was designed against
32
+ "rationale_ref": "<issue/comment URL of the FORGE:DESIGN_RATIONALE that produced this>"
33
+ },
34
+ "typography": {
35
+ "display_family": "…", // CONSTRAINT: non-default — never Inter-at-default
36
+ "body_family": "…",
37
+ "mono_family": "…", // optional
38
+ "scale_ratio": 1.25, // modular scale; large, confident jumps
39
+ "base_size_px": 17,
40
+ "weights": [400, 500, 700],
41
+ "headline_tracking_em": -0.02
42
+ },
43
+ "color": {
44
+ "mode": "dark", // light | dark | auto (dark-first is common)
45
+ "background": "#…",
46
+ "foreground": "#…",
47
+ "accent": "#…", // ONE disciplined accent
48
+ "supporting": ["#…"],
49
+ "rules": ["no-default-tailwind-palette", "contrast>=4.5"]
50
+ },
51
+ "spacing": { "base_unit_px": 8, "scale": [4,8,12,16,24,32,48,64,96] }, // all spacing on-scale
52
+ "radius": { "scale": [4, 8, 16] }, // deliberate system, NOT uniform rounded-everything
53
+ "shadow": { "tokens": ["0 1px 2px rgba(0,0,0,.06)", "0 8px 24px rgba(0,0,0,.12)"] }, // soft, low-spread
54
+ "craft": { // see reference-corpus.md#the-craft-vocabulary — #1047
55
+ "radius_system": {
56
+ "card_px": 16, // CONSTRAINT: distinct_count >= 3 across card/button/badge/input
57
+ "button_px": 8,
58
+ "badge_px": 9999, // 9999 = pill
59
+ "input_px": 6
60
+ },
61
+ "button": {
62
+ "depth": "shadow-lift", // shadow-lift | gradient | flat — flat triggers N14
63
+ "hover": "shadow+translate", // how hover is signaled — must not be color-only
64
+ "active": "press-down", // translateY(1px) + reduced shadow
65
+ "focus": "custom-ring" // custom-ring | none — none triggers N18
66
+ },
67
+ "links": {
68
+ "treatment": "animated-underline", // animated-underline | static-underline | color-only
69
+ "underline_offset_px": 3,
70
+ "underline_thickness_px": 1
71
+ },
72
+ "icons": {
73
+ "container": "tinted-square", // tinted-circle | tinted-square | none
74
+ "size_context": { // different sizes by context — uniform triggers N16
75
+ "feature_px": 24,
76
+ "nav_px": 18,
77
+ "inline_px": 16
78
+ }
79
+ },
80
+ "micro_details": {
81
+ "custom_selection": true, // ::selection — false triggers N18
82
+ "custom_scrollbar": true, // thin, theme-matched scrollbar
83
+ "custom_focus_rings": true // branded focus rings on all interactive elements
84
+ },
85
+ "dividers": "spacing-only" // spacing-only | gradient-fade | hairline | color-shift
86
+ },
87
+ "motion": {
88
+ "tier": 1, // 1 = CSS-only | 2 = SVG+JS | 3 = video placeholder — from corpus hero motion vocabulary (#1043)
89
+ "hero_technique": "text-reveal", // specific technique id from the corpus tier (e.g. "gradient-shift", "typewriter", "svg-path-draw", "video-scaffold")
90
+ "video_placeholder": false, // true = Tier 3 scaffold included — CSS gradient poster generated, drop-in comment in HTML
91
+ "vocabulary": ["scroll-reveal", "micro-hover"], // below-fold motion tokens (Tier 1F: scroll-triggered fade-in)
92
+ "default_ms": 200, "easing": "cubic-bezier(.2,.0,.0,1)",
93
+ "reduced_motion": "required" // MUST honor prefers-reduced-motion — @media (prefers-reduced-motion: reduce) on all animated elements
94
+ },
95
+ "product_mock": { // see reference-corpus.md#interactive-product-mock-vocabulary — #1045
96
+ "type": "deploy-infra", // issue-tracker | payment-fintech | deploy-infra | api-developer | email-messaging | none
97
+ "interactions": [ // select exactly 2 from the per-type list in the corpus
98
+ "status-building-pulse", // CSS animation — pulsing dot while deploy is running
99
+ "deploy-step-progress" // CSS animation — progress bar filling
100
+ ],
101
+ "css_only": true // true = CSS-only interactions (preferred) | false = lightweight vanilla JS required (e.g. typing animation, counter)
102
+ },
103
+ "scroll_narrative": { // see reference-corpus.md#below-the-fold-narrative-vocabulary — #1046
104
+ "section_sequence": [ // committed section order below the fold (derived from rationale step 3 — communication hierarchy)
105
+ "problem-context", // section type ids from the corpus scroll narrative vocabulary
106
+ "solution-showcase",
107
+ "social-proof",
108
+ "deep-features",
109
+ "final-cta"
110
+ ],
111
+ "section_count_min": 5, // total sections including hero — MUST be ≥5 (triggers N25 when < 5)
112
+ "sticky_nav": true, // true = nav transforms on scroll (transparent → solid); false = static nav
113
+ "social_proof_section": "metrics", // logos | metrics | testimonials | none — "none" skips N26 check
114
+ "cta_placements_min": 2 // minimum CTA count across the full page — must be ≥2 (triggers N27 when < 2)
115
+ },
116
+ "layout_grammar": {
117
+ // ordered sections with purpose + density — MUST NOT be the boilerplate skeleton
118
+ // (hero → 3 cards → testimonial → CTA). Sections derive from the rationale's communication hierarchy.
119
+ "sections": [
120
+ { "id": "hero", "purpose": "…", "density": "low" },
121
+ { "id": "…", "purpose": "…", "density": "high" }
122
+ ],
123
+ "rhythm": "alternating-density",
124
+ "intentional_breaks": ["asymmetric hero", "full-bleed product shot"]
125
+ },
126
+ "effects_plan": { // see effects-appropriateness.md (#885) — reasoned via the effects doctrine, not chosen blindly
127
+ "per_section": [
128
+ { "section": "hero", "effect": "subtle-technical-visualization", "intensity": "low",
129
+ "justification": "product is GPU-compute; one earned hero centerpiece" }
130
+ ],
131
+ "budget": { "lcp_ms": 2000, "max_js_kb": 150 },
132
+ "never": ["3d-on-non-hero", "parallax-on-pricing"]
133
+ },
134
+ "negatives": [ /* anti-slop checklist pulled from corpus — linter + critic both check */ ],
135
+ "acceptance": {
136
+ "perf_budget": { "lcp_ms": 2000, "cls": 0.1, "inp_ms": 200 },
137
+ "a11y": { "contrast_min": 4.5, "reduced_motion": true },
138
+ "divergence_ref": "<design-memory check result — see #887>"
139
+ }
140
+ }
141
+ ```
142
+
143
+ ## How each field defends against slop
144
+
145
+ | Field | Defends against |
146
+ |---|---|
147
+ | `archetype` (committed) | mode-collapse to the mean (forces a tail direction, not the average) |
148
+ | `typography.display_family` non-default | the Inter-default tell |
149
+ | `color.rules: no-default-tailwind-palette` | slate/indigo "AI gradient" tell |
150
+ | `spacing.scale` (on-scale only) | arbitrary padding / no rhythm |
151
+ | `layout_grammar` (non-boilerplate) | the hero→3-cards→testimonial→CTA skeleton |
152
+ | `effects_plan` (justified + budgeted) — doctrine: [effects-appropriateness](effects-appropriateness.md) (#885) | gratuitous 3D/parallax; perf blowups |
153
+ | `acceptance.perf_budget` | "earned its milliseconds" becomes objective (via Playwright, #875) |
154
+ | `craft.radius_system` (`distinct_count >= 3`) | one-radius-fits-all AI tell (N13) |
155
+ | `craft.button.depth` (not `flat`) | flat Tailwind button with no depth (N14) |
156
+ | `craft.micro_details.custom_selection` + `custom_focus_rings` | missing branded micro-details (N18) |
157
+ | `craft.icons.size_context` (distinct sizes by context) | default uniform icon treatment (N16) |
158
+ | `craft.links.treatment` (not `color-only`) | bare color-only or browser-default link treatment (N17) |
159
+ | `motion.tier` + `motion.hero_technique` (committed) | static hero with no motion — the "poster" tell (N21); jQuery-era effects (N23) |
160
+ | `motion.reduced_motion: "required"` | motion that ignores `prefers-reduced-motion` — accessibility violation |
161
+ | `motion.video_placeholder: true` (when applicable) | video-shaped hero gap: empty space where a demo would go, or placeholder image with no scaffolding |
162
+ | `product_mock.type` (committed, not `none`) | static product screenshot in a browser frame with no interactions — the "screenshot poster" tell (N24) |
163
+ | `product_mock.interactions` (exactly 2 selected) | mock with too many interactions (demo feel) or zero interactions (static poster) |
164
+ | `product_mock.css_only: false` acknowledged explicitly | lightweight JS used without acknowledgement; framework dependency creeping in |
165
+ | `scroll_narrative.section_count_min` (≥5) | hero-only page with no narrative arc — the "above-the-fold only" tell (N25) |
166
+ | `scroll_narrative.social_proof_section` (committed, not `none`) | missing social proof section — page makes claims without third-party validation (N26) |
167
+ | `scroll_narrative.cta_placements_min` (≥2) | single CTA at hero only — convinced visitors at the bottom of the page have nowhere to convert (N27) |
168
+ | `scroll_narrative.sticky_nav` (committed) | nav disappears on scroll — brand and primary CTA lost mid-narrative |
169
+
170
+ ## Benchmark rubric dimensions
171
+
172
+ The ABC benchmark (#878) evaluates generated pages on a 1–5 rubric. The `craft` field extension (#1047) added
173
+ two dimensions (`surface_depth`, `craft`); the `motion` field extension (#1043) adds a seventh dimension;
174
+ the `scroll_narrative` field extension (#1046) adds a ninth dimension.
175
+
176
+ | Dimension | What it measures | 1 (worst) | 5 (best) |
177
+ |---|---|---|---|
178
+ | `typography` | Type hierarchy, scale, non-default faces | System font, single weight, no scale | Non-default face, confident scale ratio, tight tracking |
179
+ | `color` | Palette restraint, accent discipline, contrast | Default Tailwind palette, multiple loud accents | Near-monochrome + one disciplined accent, ≥4.5 contrast |
180
+ | `surface_depth` | Shadows, borders, radius system, layering | Flat, no depth cues, uniform radius | Soft multi-token shadows, 3+ distinct radii, clear z-layering |
181
+ | `craft` | Micro-detail quality — buttons, links, icons, forms, micro-details | All framework defaults, no custom states | Custom depth on buttons, animated links, tinted icon containers, branded micro-details |
182
+ | `layout` | Grid, asymmetry, negative space, section rhythm | Centered columns, uniform padding, boilerplate skeleton | Deliberate grid breaks, asymmetry, spacing-as-divider |
183
+ | `effects` | Effect appropriateness and restraint | Gratuitous 3D/parallax on non-visual product | Effects justified by product nature, performance-budgeted |
184
+ | `motion` | Hero vitality — appropriate motion technique committed and executed per archetype | Static hero (poster) — N21 hit; or jQuery-era effects — N23 hit | Archetype-appropriate tier selected, `prefers-reduced-motion` honored, at most 2 simultaneous motion elements |
185
+ | `product_mock` | Product mock interactivity — browser-chrome wrapper with live micro-interactions vs. static screenshot | No mock present, or mock with no interaction — N24 hit | Product type correctly identified, 2 interactions committed from vocabulary, CSS-only preferred, `prefers-reduced-motion` honored |
186
+ | `scroll_narrative` | Full-page narrative completeness — below-fold section depth, social proof presence, CTA rhythm | Hero-only page, no below-fold narrative, single CTA — N25/N26/N27 hit | ≥5 sections committed and generated, social proof section present and animated, ≥2 CTAs placed at narrative milestones |
187
+
188
+ The `craft` and `surface_depth` dimensions are new in `corpus_version: 2026.3` (#1047). The `motion` dimension
189
+ is new in `corpus_version: 2026.4` (#1043). The `product_mock` dimension is new in `corpus_version: 2026.5`
190
+ (#1045). The `scroll_narrative` dimension is new in `corpus_version: 2026.6` (#1046). Past benchmark runs scored
191
+ under a 4-dimension rubric (no `surface_depth`, `craft`, `motion`, `product_mock`, or `scroll_narrative`).
192
+ Comparison across versions must note the rubric version.
193
+
194
+ ## Section-level surgical re-generation contract
195
+
196
+ <!-- Added: forge#1044 — user-feedback loop -->
197
+
198
+ The `layout_grammar.sections` array is the structural hook for **targeted, section-only edits**. Each section carries
199
+ an `id` (e.g. `"hero"`, `"features"`, `"pricing"`), a `purpose`, and a `density`. This structure enables Stage 4.5 of
200
+ the `/design` pipeline (user-feedback loop, #1044) to re-generate a single section without touching the rest of the
201
+ page.
202
+
203
+ ### Rules for surgical re-generation
204
+
205
+ 1. **Section IDs are the targeting key.** When `FORGE:USER_FEEDBACK` names a `section_target`, the re-generation agent
206
+ receives: the full current HTML + the FORGE:DESIGN_SPEC + the feedback for that section ID only. It produces a
207
+ replacement for that section's markup and nothing else.
208
+
209
+ 1.5. **`"all"` is the page-wide sentinel.** When `section_target == "all"`, the re-generation agent applies the modification as a global pass — updating CSS custom properties (color palette, typography scale) that span all sections — without replacing any individual section's markup. <!-- Added: forge#1057 -->
210
+
211
+ 2. **Committed archetype and signature move are locked.** User feedback can modify a section's visual execution
212
+ (motion, layout detail, asset placement, color treatment) but MUST NOT change the committed `meta.archetype` or the
213
+ signature move recorded in `FORGE:DESIGN_RATIONALE`. Those choices were the architect's and are fixed once the spec
214
+ is committed. If a user requests a direction change that would require a different archetype, the feedback loop
215
+ should note the constraint and suggest the closest achievable variation within the committed direction.
216
+
217
+ 3. **The spec is the source of truth for all non-targeted sections.** Sections not named in `section_target` are
218
+ reproduced from the committed spec unchanged — no style drift, no re-rolling of other sections' taste decisions.
219
+
220
+ 4. **`effects_plan.per_section` updates are allowed.** If feedback changes the hero's visual treatment (e.g., adds a
221
+ product video, changes the motion vocabulary), the `effects_plan.per_section` entry for that section may be updated
222
+ in a revised FORGE:DESIGN_SPEC annotation — this is the only part of the spec a feedback iteration may mutate.
223
+
224
+ 5. **Benchmark bypass.** Section-level re-generation is triggered by user presence (Stage 4.5). The ABC benchmark arm A
225
+ runs non-interactively and bypasses Stage 4.5 entirely — automated runs proceed directly from Stage 4 to Stage 5.
226
+
227
+ ### Asset integration via section targeting
228
+
229
+ When `FORGE:USER_FEEDBACK` carries an asset, the re-generation agent applies it to the `section_target`:
230
+
231
+ | Asset type | Section target | Integration |
232
+ |---|---|---|
233
+ | Video URL (mp4/webm) | `hero` | Replace or augment hero visual with `<video autoplay loop muted playsinline>` scaffold; adjust hero layout to accommodate |
234
+ | Video embed URL (YouTube/Vimeo) | `hero` | `<iframe>` with `loading="lazy"`, aspect-ratio container, no autoplay |
235
+ | Logo SVG | `nav` / `hero` | Replace text logo node; adjust `width`/`height` to maintain proportional sizing; verify contrast against background |
236
+ | Brand hex colors | any | Update `color.*` CSS custom properties matching the section; run contrast check (WCAG AA ≥ 4.5:1) before emitting |
237
+ | Product screenshot/image | `hero` / `features` | Replace placeholder with `<img>` or `background-image`; maintain existing sizing/aspect-ratio tokens |
238
+
239
+ ## Lifecycle
240
+
241
+ 1. **architect** (#886) emits this spec from the rationale.
242
+ 2. **generate** produces HTML constrained by it.
243
+ 3. **linter** ([`design-system-lint.mjs`](design-system-lint.md), #884) checks deterministic fields (palette, spacing, radius, layout skeleton, craft.radius_system, craft.button.depth, craft.micro_details).
244
+ 4. **critique loop** (#882) renders + checks `negatives`, `effects_plan` justification, and `acceptance` against the
245
+ rendered result; iterates until pass.
246
+ 4.5. **user-feedback loop** (#1044) prompts for hero-specific user input; applies surgical section-level re-generation
247
+ using section IDs as targeting keys; bypassed in automated (benchmark) runs.
248
+ 5. **close** writes the realized spec + outcome to [design-memory](design-memory.md) (#887).
@@ -0,0 +1,150 @@
1
+ # Design-System Linter — `scripts/design-system-lint.mjs`
2
+
3
+ > **Status:** Committed — the deterministic anti-slop gate for the UI Taste Harness (milestone #13, issue #884).
4
+ > Consumes the [`FORGE:DESIGN_SPEC` schema](design-spec-schema.md) (#881); enforces the machine-checkable
5
+ > negatives from the [reference corpus](reference-corpus.md) (#880); invoked by the
6
+ > render → critique loop (#882) and/or quality-gate.
7
+ > Extended: `checkFoundationUsage` check for per-archetype CSS foundation class usage (#1048).
8
+
9
+ ## Purpose
10
+
11
+ The positive, deterministic floor of the harness. It **hard-fails** generated landing-page output that
12
+ violates the committed design system, rather than hoping prose prevents regression to generic AI defaults.
13
+
14
+ > Don't let the LLM "reason its way" back to generic defaults — **constrain it out of them deterministically**.
15
+ > Prose is a suggestion; a failing lint is a constraint. This raises the floor and never lets it regress,
16
+ > regardless of model variance. (Same philosophy as the #639 fix.)
17
+
18
+ The linter and the vision critic (#882) check the rendered output against the **same** committed intent
19
+ (the `FORGE:DESIGN_SPEC`). The linter owns the **deterministic** negatives; the critic owns the perceptual ones.
20
+
21
+ ## Scope — what the linter owns
22
+
23
+ The [corpus](reference-corpus.md) tags each negative with its check surface. The linter implements exactly the
24
+ deterministic ones; the rest are left to the vision critic.
25
+
26
+ | Negative | Check | Default severity | Surface |
27
+ |---|---|---|---|
28
+ | **N1** | Purple/blue gradient hero on a white/near-white background | BLOCKING when spec opts in | `checkHeroGradient` |
29
+ | **N2** | Inter / system-default typeface at default weights as the display/body face | BLOCKING (typography is core taste) | `checkTypography` |
30
+ | **N4** | Exactly three identical feature cards (structural pattern only) | WARNING — icon judgement is the critic's | `checkRepeatedCards` |
31
+ | **N5** | A single radius applied to everything, or off-scale radii | BLOCKING when a multi-token `radius.scale` exists | `checkRadius` |
32
+ | **N6** | Default Tailwind palette (slate/gray/zinc/neutral/indigo/violet) used as the brand color | BLOCKING when `no-default-tailwind-palette` is set | `checkPalette` |
33
+ | **N7** | The boilerplate skeleton: hero → 3-cards → testimonial → CTA, in that order | BLOCKING when `layout_grammar.sections` exists | `checkSectionSkeleton` |
34
+ | **N24** | Static product mock: `product_mock.type` is committed (not `"none"`) but the rendered hero has a `.product-window` or `.mock-*` element with no `animation`, `transition`, or JS `addEventListener` present <!-- Added: forge#1045 --> | BLOCKING when `product_mock.type` is set and not `"none"` | `checkProductMock` |
35
+ | **N25** | Truncated narrative: `scroll_narrative` is committed in spec but the rendered page has ≤1 below-fold section (`<section>` or `<div role="region">` elements outside the first/hero section) <!-- Added: forge#1046 — spec only; .mjs implementation follows --> | BLOCKING when `scroll_narrative.section_count_min` is set | `checkScrollNarrative` |
36
+ | **N26** | Missing social proof: `scroll_narrative.social_proof_section` is committed (not `"none"`) but no element with class `section-social-proof`, `social-proof`, or `[data-section="social-proof"]` is present <!-- Added: forge#1046 — spec only; .mjs implementation follows --> | BLOCKING when `scroll_narrative.social_proof_section` is set and not `"none"` | `checkSocialProof` |
37
+ | **N27** | Single CTA: `scroll_narrative.cta_placements_min` is ≥ 2 but the rendered page has fewer than 2 elements matching `.btn-primary`, `.cta-button`, `[data-cta]`, or `<a>` with class containing `cta` <!-- Added: forge#1046 — spec only; .mjs implementation follows --> | BLOCKING when `scroll_narrative.cta_placements_min` ≥ 2 | `checkCTACount` |
38
+ | — | Foundation class usage: a foundation CSS file was injected (Stage 3, #1048) but none of `.btn`, `.card`, `.badge`, `.input` appear in the generated HTML — generator replaced foundation classes with Tailwind equivalents <!-- Added: forge#1048 --> | WARNING when `meta.archetype` maps to a known foundation file | `checkFoundationUsage` |
39
+ | — | Off-scale spacing (padding/margin/gap not on `spacing.scale`) | WARNING | `checkSpacing` |
40
+ | — | Contrast: `color.foreground` vs `color.background` below `acceptance.a11y.contrast_min` | BLOCKING when a contrast floor is declared | `checkContrast` |
41
+
42
+ **Opt-in trigger for N24**: `product_mock.type` present in spec and not `"none"`. When `product_mock.type: "none"`,
43
+ N24 is skipped entirely — the architect deliberately opted out of a mock, and no check is needed.
44
+
45
+ **Opt-in trigger for N25**: `scroll_narrative.section_count_min` present in spec. The check counts `<section>`
46
+ (and `<div role="region">`) elements in the rendered HTML; when the count is below `section_count_min`, the check
47
+ blocks. When `scroll_narrative` is absent from the spec, N25 is skipped.
48
+
49
+ **Opt-in trigger for N26**: `scroll_narrative.social_proof_section` present in spec and not `"none"`. Checks for
50
+ social proof element presence using class-name patterns (`section-social-proof`, `social-proof`) and
51
+ `[data-section="social-proof"]` attribute. When `social_proof_section: "none"`, the architect deliberately opted
52
+ out and N26 is skipped.
53
+
54
+ **Opt-in trigger for N27**: `scroll_narrative.cta_placements_min` ≥ 2. Counts CTA elements using class-name
55
+ patterns (`.btn-primary`, `.cta-button`, `[data-cta]`, and `<a>` elements with class containing `cta`). When
56
+ `cta_placements_min` is absent or < 2, N27 is skipped.
57
+
58
+ **Out of scope** (vision-critic-owned, #882): N3 (everything centered), N8 (stock illustration / generic 3D),
59
+ N9 (glassmorphism overuse), N10 (no visual hierarchy), N11 (abstract copy / no product shown),
60
+ N12 (decorative effects that serve nothing), N24 perceptual quality (whether the 2 interactions feel right — the critic
61
+ judges that; the linter only checks that interaction signals are present at all), N25/N26/N27 narrative quality
62
+ (whether the section content is compelling — the critic judges that; the linter only checks structural presence).
63
+ These require perceptual judgement and are not deterministically checkable.
64
+ N12 / effect justification specifically is governed by the [effects-appropriateness doctrine](effects-appropriateness.md) (#885)
65
+ and judged by the vision critic (#882) against the per-section `justification`; the perf gate (#875) owns the hard `budget`. The linter stays out.
66
+
67
+ ## Spec-driven gating
68
+
69
+ Which checks **block** (vs. merely warn) is driven by the spec, not hardcoded — so the same linter serves any
70
+ archetype. A check escalates to `BLOCKING` when the spec opts in via:
71
+
72
+ - `color.rules` — e.g. `"no-default-tailwind-palette"` (gates N6/N1), `"contrast>=4.5"` (gates contrast).
73
+ - `negatives[]` — e.g. `"N6"`, `"N7"` (raw IDs, `{ "id": "N6" }` objects, or descriptive strings are all accepted).
74
+ - Presence of the relevant token field — a multi-entry `radius.scale` opts N5 in; `layout_grammar.sections` opts N7 in;
75
+ a committed non-default `typography.display_family` opts N2 in; `acceptance.a11y.contrast_min` opts contrast in;
76
+ `product_mock.type` set to any value other than `"none"` opts N24 in;
77
+ `scroll_narrative.section_count_min` present opts N25 in;
78
+ `scroll_narrative.social_proof_section` set to any value other than `"none"` opts N26 in;
79
+ `scroll_narrative.cta_placements_min` ≥ 2 opts N27 in;
80
+ `meta.archetype` matching a known foundation archetype opts `checkFoundationUsage` in (WARNING only — see #1048). <!-- Added: forge#1048 -->
81
+
82
+ `--strict` forces **every** deterministic check to block regardless of spec opt-in.
83
+
84
+ ## Usage
85
+
86
+ ```bash
87
+ node scripts/design-system-lint.mjs --spec <spec.json> --html <page.html> [options]
88
+ ```
89
+
90
+ | Option | Meaning |
91
+ |---|---|
92
+ | `--spec <path>` | The `FORGE:DESIGN_SPEC` JSON (required) |
93
+ | `--html <path>` | The generated HTML to lint (required, unless `--selftest`) |
94
+ | `--css <path>` | Optional separate CSS file (inline `<style>` and `style="…"` are always read) |
95
+ | `--baseline` | Report findings but always exit `0` (survey existing slop, never fail the build) |
96
+ | `--strict` | Every deterministic check blocks, even without spec opt-in |
97
+ | `--json` | Emit findings as JSON on stdout (for the #882 critique loop to consume) |
98
+ | `--selftest` | Run internal self-checks (contrast math, palette table) and exit |
99
+ | `--help` | Show usage |
100
+
101
+ ### Output
102
+
103
+ Plain mode prints one finding per line with the established severity prefixes, then a summary:
104
+
105
+ ```
106
+ BLOCKING: [N6] Committed color accent=#4f46e5 is a default Tailwind palette value — not a brand color.
107
+ WARNING: [SPACING] Off-scale spacing value(s): 17px — not on spacing.scale [4, 8, 12, 16, 24, 32, 48, 64, 96].
108
+ OK: [N1] No purple/blue-gradient-on-white hero detected.
109
+
110
+ === Summary ===
111
+ Checks run: 13
112
+ Blocking: 11
113
+ Warnings: 1
114
+ ```
115
+
116
+ `--json` emits `{ blocking, warnings, baseline, findings: [{ severity, negative, message }] }`.
117
+
118
+ ### Exit codes
119
+
120
+ Matches the scripts-layer convention (`scripts/verify-env-vars.sh`):
121
+
122
+ | Code | Meaning |
123
+ |---|---|
124
+ | `0` | Pass — no blocking findings |
125
+ | `1` | Blocking findings present |
126
+ | `2` | Warnings only (or bad invocation) |
127
+
128
+ `--baseline` always exits `0`.
129
+
130
+ ## Integration
131
+
132
+ - **Render → critique loop (#882):** call the linter with `--json` on the generated HTML before the vision pass.
133
+ Blocking findings short-circuit the iteration (the page is regenerated against the spec) without spending a
134
+ vision-critique turn. The linter is deterministic and dependency-free, so it is cheap to run every iteration.
135
+ - **quality-gate:** invoke as a sibling scripts-layer check (like `scripts/verify-*.sh`) and key off the exit code.
136
+ Use `--baseline` for a non-failing survey, or default mode to hard-gate.
137
+
138
+ ## Design notes
139
+
140
+ - **Zero runtime dependencies** — Node built-ins only (`node:fs`), matching `scripts/gen-logo.mjs`. HTML/CSS is
141
+ scanned with tolerant regex (attribute order, quote style, and whitespace are accommodated); no jsdom/cheerio.
142
+ - **Injection-safe** — inputs are read as data and never interpolated into a shell; there is no `eval`/`exec`
143
+ (avoids the class of review-findings #73 / #335).
144
+ - **Deterministic** — no network, no randomness, stable finding order. The contrast check uses the standard WCAG
145
+ relative-luminance formula (self-tested: `#000`/`#fff` = 21:1).
146
+ - **Parser limitations** — being regex-based, the linter reads inline `<style>` blocks and `style="…"` attributes
147
+ plus Tailwind utility classes; it does not resolve an external Tailwind build's computed colors. Ambiguous cases
148
+ (e.g. a committed font that cannot be confirmed in the output) are reported as `WARNING`, not `BLOCKING`, to avoid
149
+ false hard-fails. For computed/rendered-pixel checks (e.g. N8–N12), defer to the vision critic with a real render
150
+ (Playwright, #875).
@@ -0,0 +1,106 @@
1
+ # Divergent generation + archetype sampling + taste-judge selection
2
+
3
+ > **Status**: committed foundation doc for the UI Taste Harness (#883).
4
+ > The variance lever: how the harness produces *different* good pages instead of mode-collapsing to one.
5
+ > Consumes: [reference-corpus](reference-corpus.md) archetypes (#880), the [design-architect rationale](design-architect-rationale.md) (#886).
6
+ > Produces: a `FORGE:DESIGN_CANDIDATES` set whose winner hands off into [`FORGE:DESIGN_SPEC`](design-spec-schema.md) (#881).
7
+ > Judge is independent from the render → vision-critique loop (#882) and the [ABC benchmark judge](abc-benchmark.md) (#878) — anti-Goodhart.
8
+ > Part of #877.
9
+
10
+ ## Purpose
11
+
12
+ Two failure modes bracket AI UI generation. **Mode collapse**: "make it beautiful" yields the same gradient-hero
13
+ template every time — zero variance. **Incoherent blending**: averaging archetypes ("a bit editorial, a bit
14
+ brutalist") yields mush — variance without taste. This doc defines the lever that sits between them: **commit to one
15
+ archetype, then diverge *within* it across N distinct directions, and let an independent taste-judge pick the winner.**
16
+
17
+ Variance between runs comes from *which archetype + which direction* is chosen — never from blending. Coherence within
18
+ a run comes from committing and not drifting.
19
+
20
+ ## Step 1 — Archetype sampling (commit, never blend)
21
+
22
+ From the design-blind brief + the architect rationale (#886), select **exactly one** archetype from the five committed
23
+ in [reference-corpus](reference-corpus.md) (#880). The ids are fixed tokens — they must match exactly, because the
24
+ selected id is written into `meta.archetype` in the [spec](design-spec-schema.md):
25
+
26
+ | `archetype` id | When it fits |
27
+ |---|---|
28
+ | `editorial-typographic` | considered, magazine, type-led products; content-forward |
29
+ | `technical-dense` | dashboards, compute, infra; capable, data-forward |
30
+ | `minimal-luxury` | premium, lots of air; restraint as the message |
31
+ | `bold-brutalist` | opinionated, high-contrast; a stance |
32
+ | `warm-photographic` | human, approachable; image/people-led |
33
+
34
+ **Selection rule** (from the corpus): pick the one appropriate to *product nature* + *audience* inferred from the
35
+ brief, then **commit**. No blending, no averaging, no "60% A / 40% B". The variance lever is the *choice between*
36
+ archetypes across runs and the divergent directions below — not a blend within a run.
37
+
38
+ ## Step 2 — Divergent generation (N distinct directions)
39
+
40
+ Within the committed archetype, generate **N distinct directions** (default N=3). Each direction is a genuinely
41
+ different interpretation of the *same* archetype + rationale — different layout grammar, different signature move,
42
+ different typographic instantiation — not N near-duplicates with a hue shift. Divergence is structural, not cosmetic.
43
+
44
+ Each direction carries:
45
+ - a one-line concept (the interpretation),
46
+ - its **signature move** (from the rationale's diary — the non-obvious idea that makes it *this* page),
47
+ - the salient token/grammar choices that distinguish it from its siblings.
48
+
49
+ Sampling within one archetype keeps every direction coherent; requiring *structural* difference keeps them genuinely
50
+ divergent. If two directions collapse to the same signature move, one is rejected and re-rolled — duplicate directions
51
+ defeat the lever.
52
+
53
+ ## Step 3 — Taste-judge score + select
54
+
55
+ An **independent taste-judge** scores the N directions and selects one winner. The judge:
56
+ - scores each direction on a small taste rubric (hierarchy/intent, signature-move strength, archetype fit, restraint),
57
+ - rejects any that trip the corpus negatives or duplicate a sibling,
58
+ - selects the highest-scoring surviving direction; ties break toward the stronger signature move.
59
+
60
+ ### Judge independence (anti-Goodhart) — non-negotiable
61
+
62
+ This taste-judge is **distinct** from both:
63
+ - the **render → vision-critique loop** (#882), which judges a *rendered* page against its committed spec, and
64
+ - the **ABC benchmark judge** (#878), which scores arms blind against arm C for evaluation.
65
+
66
+ Reusing either as the selection judge would let the harness optimize directly against its own evaluator — the textbook
67
+ Goodhart failure. The selection judge scores *candidate directions* (pre-render intent); the critic scores *rendered
68
+ output*; the benchmark scores *finished arms*. Three separate judges, three separate jobs.
69
+
70
+ ## Step 4 — Hand-off to DESIGN_SPEC
71
+
72
+ The winning direction is what gets fully specified: it hands off into [`FORGE:DESIGN_SPEC`](design-spec-schema.md)
73
+ (#881) — `meta.archetype` = the committed id, `layout_grammar`/`signature` = the winning direction. The losing
74
+ directions are retained in the `FORGE:DESIGN_CANDIDATES` annotation (below) so the choice is auditable and so
75
+ [design-memory](design-memory.md) (#887) can later diverge from past winners.
76
+
77
+ ## The `FORGE:DESIGN_CANDIDATES` annotation
78
+
79
+ Registered in [FORGE-PROTOCOL](../FORGE-PROTOCOL.md). Posted at the divergent-generation step, between the rationale
80
+ and the spec. It records the committed archetype, the N directions, and the judge's selection + scores — so the
81
+ variance decision is a first-class, machine-queryable artifact, not a hidden roll.
82
+
83
+ ```markdown
84
+ <!-- FORGE:DESIGN_CANDIDATES -->
85
+ ## Design Candidates — {product}
86
+
87
+ **Archetype (committed):** {one of the 5 corpus ids}
88
+ **Directions:**
89
+ 1. {concept} — signature: {move} — {distinguishing grammar/tokens}
90
+ 2. {concept} — signature: {move} — {…}
91
+ 3. {concept} — signature: {move} — {…}
92
+ **Judge scores:** 1) {score} 2) {score} 3) {score}
93
+ **Selected:** #{n} — because {reason}
94
+
95
+ → Winner produces DESIGN_SPEC: {link}
96
+ ```
97
+
98
+ ## Acceptance (worked expectation)
99
+
100
+ On the seed set, the same brief produces a *committed archetype* and visibly different *directions*:
101
+ - **Voltage** (`technical-dense`): directions differ on whether the signature move is a scaling-compute visualization,
102
+ a code-first hero, or a benchmark-grid — same archetype, three structures.
103
+ - **Plume** (`minimal-luxury` / `editorial-typographic`): directions differ on keyboard-motif vs cycle-timeline vs
104
+ pure-type hierarchy — all restrained, all coherent.
105
+
106
+ Variance shows up as *which archetype + which direction wins*, across runs — never as a blend within a run.
@@ -0,0 +1,133 @@
1
+ # Effects-appropriateness layer
2
+
3
+ > **Status**: committed foundation doc for the UI Taste Harness (#885).
4
+ > Consumes: [reference-corpus](reference-corpus.md) effect-usage patterns (#880),
5
+ > [design-spec-schema](design-spec-schema.md) `effects_plan` slot (#881).
6
+ > Enforced by: the perf gate (#875, hard/objective) and the vision critique loop (#882, perceptual — N12).
7
+ > Stays out of: the deterministic [linter](design-system-lint.md) (#884) — effect justification is not deterministically checkable.
8
+ > Authored to be consumed by: the [design-architect rationale](design-architect-rationale.md) (#886) and [design-memory](design-memory.md) (#887) phases.
9
+ > Part of #877.
10
+
11
+ ## Purpose
12
+
13
+ The schema (#881) gives every page an `effects_plan` — a slot for *which* heavy techniques run *where*, at *what*
14
+ intensity, with a *budget* and a *never* list. The slot is a placeholder. This doc is the **reasoning doctrine that
15
+ fills it**: how the harness decides, **from the design-blind brief alone**, when a heavy effect earns its cost and
16
+ when restraint is the more confident choice.
17
+
18
+ The thesis: AI UI mode-collapses toward gratuitous motion because "make it impressive" has no cost model. The fix is
19
+ to treat every effect as a spend that must be *justified against the content it serves and the budget it consumes* —
20
+ and to make the default **restraint**, with effects added only on evidence, never by reflex.
21
+
22
+ ## The core asymmetry
23
+
24
+ Not all effects reward the same posture. This is the single most important rule in this doc:
25
+
26
+ > **Compositional contrast rewards *more* intention. 3D/WebGL, parallax, and multi-layer depth reward *restraint*.**
27
+
28
+ Flattening this into "less is always more" loses the doctrine. Low contrast is itself a slop tell (flat, equal-weight,
29
+ nothing directs the eye — see N12's sibling failures). Deliberate contrast is almost always correct. The *decorative*
30
+ effects (depth, motion, 3D) are the ones that must earn their place against a hard cost.
31
+
32
+ ## Effect taxonomy + appropriateness/cost
33
+
34
+ Derived from the corpus effect-usage table (#880); this layer formalizes the `Earns its place when…` / `Slop when…`
35
+ columns into authoring rules with an explicit cost axis.
36
+
37
+ | Effect | Posture | Earns its place when… | Slop when… | Primary cost |
38
+ |---|---|---|---|---|
39
+ | **Compositional contrast** | *more* intention | almost always — direct the eye, establish hierarchy | flat, equal-weight, everything the same size/color | ~none (CSS) |
40
+ | **Multi-layer depth** (glass / grain / gradient) | restraint | one hero backdrop; foreground/background separation that aids reading | over-glass, muddy contrast, applied everywhere | contrast/a11y risk; minor paint |
41
+ | **Parallax / scroll-driven** | restraint | a single progressive-capability reveal where depth *is* the message | on every section; jank/CLS; fights readability | INP/CLS; JS |
42
+ | **3D / WebGL** | strong restraint | the product is spatial/visual; exactly one premium hero centerpiece with a fallback | bolted onto non-visual B2B; wrecks LCP; no reduced-motion/no-WebGL fallback | LCP + JS-kb (largest) |
43
+
44
+ ## The 6 decision inputs
45
+
46
+ The architect/rationale phase (#886) infers all six from the brief alone, then writes them into `effects_plan`:
47
+
48
+ 1. **Product nature** — is the product itself *spatial/visual* (renders, maps, 3D, creative tooling), *technical/data*
49
+ (compute, dashboards, infra), or *text/workflow* (trackers, docs, email)? Spatial → a heavy hero may be earned;
50
+ text/workflow → near-zero decorative.
51
+ 2. **Archetype** — the committed archetype carries an **effects posture** prior (#880): `bold-brutalist` →
52
+ "sharp, near-zero decorative"; `technical-dense` → "code/data motion welcome"; `minimal-luxury` →
53
+ "restrained micro-motion"; `editorial-typographic` → "minimal, type-led"; `warm-photographic` → "image-driven, soft motion".
54
+ 3. **Section job** — what is *this* section for? A hero may carry one centerpiece; a pricing table or feature grid
55
+ carries none. Effect intensity is assigned **per section**, not per page.
56
+ 4. **Audience expectation** — engineers who "read the code sample closely" reward technical credibility and are
57
+ *repelled* by gratuitous motion; a consumer/creative audience tolerates more spectacle.
58
+ 5. **Measurable perf budget** (#875) — LCP / CLS / INP / JS-kb ceilings. This is the **hard gate**: an effect that
59
+ blows the budget is rejected regardless of how good it looks.
60
+ 6. **Accessibility** — `prefers-reduced-motion` must be honored; depth must not destroy contrast; every WebGL/3D
61
+ centerpiece needs a static fallback. A11y failures are not negotiable against aesthetics.
62
+
63
+ ## Mapping decision inputs → `effects_plan`
64
+
65
+ The fields mirror [design-spec-schema](design-spec-schema.md) exactly:
66
+
67
+ `effects_plan { per_section[{ section, effect, intensity, justification }], budget{ lcp_ms, max_js_kb }, never[] }`
68
+
69
+ - **`per_section[].effect` / `.intensity`** — from inputs 1–4. Default `intensity: none`. Raise it only when product
70
+ nature + archetype + section job *agree* that the effect serves the content. Each step up in intensity needs a
71
+ stronger justification.
72
+ - **`per_section[].justification`** — one sentence stating *what content the effect serves*. "Looks premium" is not a
73
+ justification; "the spatial product *is* the demo, shown once in the hero" is. The critic (#882) reads this field
74
+ and judges whether the rendered effect actually delivered on it (N12).
75
+ - **`budget{ lcp_ms, max_js_kb }`** — from input 5. The hard ceiling. The perf gate (#875) measures the rendered arm
76
+ against it; over-budget = fail, no aesthetic override.
77
+ - **`never[]`** — from inputs 2 + 6. Per-archetype prohibitions (e.g. `bold-brutalist` → `never: [parallax, glass]`)
78
+ and universal a11y rules (`never: [motion-without-reduced-motion-fallback]`).
79
+
80
+ ## Enforcement split
81
+
82
+ Three mechanisms, deliberately non-overlapping — no single check owns the whole problem:
83
+
84
+ | Mechanism | Owns | Nature |
85
+ |---|---|---|
86
+ | **Perf gate** (#875) | `budget` — did the effect stay within LCP/CLS/INP/JS-kb? | **Hard, objective.** Earned its *ms*. Blocking. |
87
+ | **Vision critic** (#882) | N12 — did each effect *serve the content* it claimed to? | **Perceptual.** Earned its *place*. Finding. |
88
+ | **Deterministic linter** (#884) | nothing here | Effects are explicitly **out of scope** — not deterministically checkable. |
89
+
90
+ The perf gate answers "could the user afford it?"; the critic answers "was it worth it?". An effect must pass **both**:
91
+ within budget *and* serving the content. Within budget but gratuitous → critic N12 finding. Justified but over budget →
92
+ perf-gate fail. The linter stays out because perceptual judgement can't be reduced to a regex without false hard-fails
93
+ (consistent with `design-system-lint.md`'s N8–N12 out-of-scope list).
94
+
95
+ ## Worked inferences (seed set)
96
+
97
+ Demonstrating that the doctrine produces **visibly different effect intensity from the briefs alone** — the acceptance
98
+ criterion. Each is derived purely from the design-blind brief; no visual direction is given.
99
+
100
+ ### Voltage — serverless compute / GPUs for ML engineers
101
+ - **Product nature**: technical/data (compute), with a *credible* spatial story (containers scaling zero→thousands).
102
+ - **Audience**: engineers who read the code sample closely → reward technical credibility, repelled by spectacle.
103
+ - **Inference**: `technical-dense` archetype; **one restrained** technical hero visualization (subtle scaling/compute
104
+ motion that *is* the product story), `intensity: subtle`. Code sample is the real hero. No parallax, no glass.
105
+ - `effects_plan`: hero → `effect: technical-motion, intensity: subtle, justification: "the zero→N scaling IS the product, shown once"`;
106
+ all other sections → `none`. `never: [parallax, decorative-3d]`. Tight JS budget (audience will notice bloat).
107
+
108
+ ### Plume — fast, keyboard-driven issue tracker for software teams
109
+ - **Product nature**: text/workflow. Speed and focus are the message.
110
+ - **Audience**: craft-focused technical teams who find heavy tools heavy → motion that slows them is *anti-product*.
111
+ - **Inference**: `minimal-luxury` / `editorial-typographic`; **near-zero motion**. The product's own speed is the
112
+ argument; any decorative effect contradicts it. Contrast and typography do all the work.
113
+ - `effects_plan`: every section → `intensity: none`; `never: [parallax, 3d, glass]`. Restraint *is* the design.
114
+
115
+ ### Slipstream — git-connected deploy platform / global edge
116
+ - **Product nature**: technical/infra; "confidence and restraint" stated explicitly in the brief.
117
+ - **Audience**: frontend/full-stack devs; serious, opinionated platform.
118
+ - **Inference**: restrained, typographic-technical. At most **one** subtle scroll reveal for the push→production flow
119
+ (depth *is* part of that narrative), `intensity: subtle`; everything else `none`. Restraint as the move.
120
+ - `effects_plan`: hero/flow → `effect: scroll-reveal, intensity: subtle, justification: "push→prod is a sequence; one reveal shows it"`;
121
+ rest → `none`. `never: [decorative-3d, glass]`.
122
+
123
+ **Differential**: Voltage gets one subtle *technical* hero motion; Plume gets *zero*; Slipstream gets one subtle
124
+ *scroll reveal* — three visibly different intensities, inferred from product nature + audience alone. None reaches for
125
+ 3D/WebGL, because none is a spatial/visual product. That restraint is the doctrine working.
126
+
127
+ ## Authoring checklist (for the rationale phase, #886)
128
+
129
+ - [ ] Default every section to `intensity: none`; raise only on agreement of inputs 1–4.
130
+ - [ ] Every non-`none` entry carries a content-serving `justification` (not "looks premium").
131
+ - [ ] `budget` set from #875 ceilings; `never[]` set from archetype posture + a11y.
132
+ - [ ] Reduced-motion + WebGL/3D fallback covered in `never[]` or per-section notes.
133
+ - [ ] Contrast treated as *expected*, not optional; low contrast flagged as a tell.