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.
- package/README.md +54 -3
- package/bin/cross-publish.mjs +344 -0
- package/bin/forgedock.mjs +2205 -541
- package/bin/github-app-manifest.json.license +2 -0
- package/bin/hooks/session-start.mjs +4 -5
- package/bin/registry.mjs +20 -0
- package/bin/tui.mjs +172 -96
- package/commands/adopt.md +433 -0
- package/commands/analytics.md +2 -0
- package/commands/audit-agents.md +2 -0
- package/commands/audit.md +2 -0
- package/commands/autopilot.md +77 -0
- package/commands/changelog.md +354 -0
- package/commands/cleanup.md +2 -0
- package/commands/compat-audit.md +330 -0
- package/commands/deploy-info.md +6 -5
- package/commands/design-bench.md +176 -0
- package/commands/design-render-critique-loop.md +103 -0
- package/commands/design.md +259 -0
- package/commands/diagnose.md +483 -0
- package/commands/explain.md +349 -0
- package/commands/forge-stats.md +2 -0
- package/commands/forgedock-init.md +2 -0
- package/commands/geo-audit.md +2 -0
- package/commands/incident-response.md +2 -0
- package/commands/issue.md +9 -3
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +239 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +39 -2
- package/commands/quality-gate.md +67 -0
- package/commands/replay.md +263 -0
- package/commands/resume.md +264 -0
- package/commands/review-pr-agents.md +5 -1
- package/commands/review-pr-staging.md +160 -2
- package/commands/review-pr.md +85 -3
- package/commands/rollback.md +2 -0
- package/commands/scope.md +280 -0
- package/commands/security-audit.md +2 -0
- package/commands/status.md +262 -0
- package/commands/sync-ecosystem.md +2 -0
- package/commands/test-gate.md +929 -0
- package/commands/validate.md +2 -0
- package/commands/work-on/build/architect.md +128 -18
- package/commands/work-on/build/context.md +130 -17
- package/commands/work-on/build/implement.md +7 -3
- package/commands/work-on/build/validate.md +2 -0
- package/commands/work-on/build.md +32 -7
- package/commands/work-on/close.md +85 -0
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on/investigate.md +30 -3
- package/commands/work-on/review.md +13 -1
- package/commands/work-on-monolithic.md +2 -0
- package/commands/work-on.md +676 -38
- package/docs/BRAND.md +51 -0
- package/docs/CI.md +177 -0
- package/docs/CONFIG.md +94 -0
- package/docs/FORGE-PROTOCOL.md +324 -0
- package/docs/articles/b-vs-c-baseline.md +210 -0
- package/docs/articles/full-abc-benchmark.md +205 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/articles/selective-spec-loading-token-savings.md +140 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/design/abc-benchmark.md +195 -0
- package/docs/design/design-architect-rationale.md +123 -0
- package/docs/design/design-memory.md +61 -0
- package/docs/design/design-spec-schema.md +248 -0
- package/docs/design/design-system-lint.md +150 -0
- package/docs/design/divergent-generation.md +106 -0
- package/docs/design/effects-appropriateness.md +133 -0
- package/docs/design/fixtures/briefs/README.md +47 -0
- package/docs/design/fixtures/briefs/cadence.md +39 -0
- package/docs/design/fixtures/briefs/plume.md +39 -0
- package/docs/design/fixtures/briefs/slipstream.md +39 -0
- package/docs/design/fixtures/briefs/tender.md +39 -0
- package/docs/design/fixtures/briefs/voltage.md +39 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
- package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
- package/docs/design/foundations/brutalist.css +294 -0
- package/docs/design/foundations/editorial.css +300 -0
- package/docs/design/foundations/minimal-luxury.css +295 -0
- package/docs/design/foundations/technical.css +293 -0
- package/docs/design/foundations/warm-photo.css +299 -0
- package/docs/design/reference-corpus.md +1726 -0
- package/docs/design/render-critique-loop.md +57 -0
- package/docs/site/getting-started.md +21 -0
- package/docs/spec-graph-schema.md +397 -0
- package/package.json +3 -3
- package/scripts/bench-scorecard.mjs +239 -0
- package/scripts/build-spec-graph.mjs +509 -0
- package/scripts/classify-lane.sh +138 -0
- package/scripts/design-system-lint.mjs +993 -0
- package/scripts/graph-query.sh +486 -0
- package/scripts/transition-label.sh +169 -0
- package/scripts/validate-pr-target.sh +30 -0
- package/scripts/validate-spec-graph.sh +423 -0
- package/templates/devdocs/README.md +72 -0
- package/templates/devdocs/agent/using-forgedock.md +3 -0
- package/templates/devdocs/agent/using-github.md +3 -0
- package/templates/devdocs/index.yaml +162 -0
- package/templates/devdocs/project/architecture.md +3 -0
- package/templates/devdocs/project/conventions.md +3 -0
- package/templates/devdocs/project/custom-instructions.md +3 -0
- package/templates/devdocs/project/glossary.md +3 -0
- package/templates/devdocs/project/stack.md +3 -0
- package/templates/devdocs/templates/commit-convention.md +3 -0
- package/templates/devdocs/templates/issue.md +3 -0
- package/templates/devdocs/templates/pull-request.md +3 -0
- package/templates/devdocs/templates/review-finding.md +3 -0
- 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.
|