forgedock 1.0.17 → 1.0.19
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 +34 -1
- package/bin/cross-publish.mjs +344 -0
- package/bin/forgedock.mjs +229 -17
- package/commands/autopilot.md +77 -0
- package/commands/design-bench.md +176 -0
- package/commands/design-render-critique-loop.md +103 -0
- package/commands/design.md +259 -0
- package/commands/issue.md +7 -3
- package/commands/orchestrate.md +121 -0
- package/commands/{resume.md → pipeline-resume.md} +4 -4
- package/commands/{status.md → pipeline-status.md} +1 -1
- package/commands/qa-sweep.md +37 -2
- package/commands/quality-gate.md +57 -0
- package/commands/review-pr-staging.md +158 -2
- package/commands/test-gate.md +929 -0
- package/commands/work-on/build/implement.md +5 -3
- package/commands/work-on.md +53 -3
- package/docs/CI.md +177 -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/selective-spec-loading-token-savings.md +140 -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/spec-graph-schema.md +397 -0
- package/package.json +1 -1
- package/scripts/bench-scorecard.mjs +239 -0
- package/scripts/build-spec-graph.mjs +509 -0
- package/scripts/check-native-conflicts.sh +200 -0
- package/scripts/classify-lane.sh +0 -7
- package/scripts/design-system-lint.mjs +993 -0
- package/scripts/graph-query.sh +486 -0
- package/scripts/transition-label.sh +10 -2
- package/scripts/validate-spec-graph.sh +423 -0
- package/templates/workflows/forgedock-review.yml +96 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# The ABC Benchmark — Methodology
|
|
2
|
+
|
|
3
|
+
> **Status:** Foundation spec for issue #878 (milestone #13, UI Taste Harness).
|
|
4
|
+
> The benchmark is the milestone's **fitness function** — built *before* the harness. It is what tells us
|
|
5
|
+
> whether each lever the harness adds actually moves design quality. Harness development reduces to one
|
|
6
|
+
> question this rig answers: *"did arm A's score against C go up?"*
|
|
7
|
+
>
|
|
8
|
+
> Companion to [design-spec-schema](design-spec-schema.md) (#881), [reference-corpus](reference-corpus.md) (#880),
|
|
9
|
+
> and the [`/design-bench`](../../commands/design-bench.md) command spec.
|
|
10
|
+
> Results are carried as a `FORGE:BENCH_SCORECARD` annotation — registered in
|
|
11
|
+
> [`../FORGE-PROTOCOL.md`](../FORGE-PROTOCOL.md).
|
|
12
|
+
|
|
13
|
+
## Why this exists first
|
|
14
|
+
|
|
15
|
+
A taste benchmark is not a test we run at the end. It is the reward signal. Without it, every "improvement" to
|
|
16
|
+
the harness is a guess. With it, each lever (archetype sampling, anti-slop negatives, the critique loop) is a
|
|
17
|
+
measurable hypothesis: add the lever, re-run the benchmark, confirm arm A's win-rate against C went up. Build the
|
|
18
|
+
ruler before building the thing it measures.
|
|
19
|
+
|
|
20
|
+
## The three arms — same input, one variable
|
|
21
|
+
|
|
22
|
+
The **input is a single design-blind product brief** derived from a real reference page. It describes only what the
|
|
23
|
+
product *does* — no "make it like X", no colors, no type, no layout hints. The real product name is **anonymized**
|
|
24
|
+
so the generation model cannot recall the real brand's design from training. (Brand recall would measure memory,
|
|
25
|
+
not taste, and would unfairly inflate arm B — see [Anonymization](#anonymization-is-load-bearing).)
|
|
26
|
+
|
|
27
|
+
| Arm | What it is | Role |
|
|
28
|
+
|-----|------------|------|
|
|
29
|
+
| **A** | **ForgeDock harness** — the brief run through the `/design` pipeline (#888). | The thing under test. |
|
|
30
|
+
| **B** | **Raw frontier model**, one shot, no tools, no iteration, given the same brief. | Control / lower bound. |
|
|
31
|
+
| **C** | **The real reference page** the brief was stripped from. | Gold standard + judge calibration. |
|
|
32
|
+
|
|
33
|
+
**The one variable rule.** Arms A and B MUST use the **same generation model**. The harness (its pipeline,
|
|
34
|
+
structured design language, render→critique loop) is then the *only* difference between A and B — which is exactly
|
|
35
|
+
what makes the A-vs-B delta attributable to the harness. If A and B use different models, you are benchmarking
|
|
36
|
+
models, not the harness. This is non-negotiable.
|
|
37
|
+
|
|
38
|
+
## C does double duty
|
|
39
|
+
|
|
40
|
+
C is both the gold standard and a **free validity check on the judge**:
|
|
41
|
+
|
|
42
|
+
- As gold standard, win-rate against C is the primary metric for A and B.
|
|
43
|
+
- As calibration, if the judge ever ranks C **below** A or B in the blind pairwise comparison, the judge is
|
|
44
|
+
miscalibrated — its result for that run is suspect and flagged in the scorecard. A real, professionally designed,
|
|
45
|
+
funded-SaaS page losing to a one-shot generation is a judge defect, not a harness triumph.
|
|
46
|
+
|
|
47
|
+
## Rendering — kill the confounds
|
|
48
|
+
|
|
49
|
+
All three arms are rendered through the **identical** pipeline so the only thing the judge compares is design, not
|
|
50
|
+
rendering artifacts.
|
|
51
|
+
|
|
52
|
+
- **Self-contained HTML** for arms A and B (Tailwind CDN is fine; no build step).
|
|
53
|
+
- **Playwright** (milestone render dependency) renders all three at:
|
|
54
|
+
- **1440px** desktop and **390px** mobile,
|
|
55
|
+
- `networkidle` + fonts loaded before capture,
|
|
56
|
+
- **full-page** screenshot.
|
|
57
|
+
- **C is captured through the same pipeline.** Trim cookie banners and hero video so C is compared on its design,
|
|
58
|
+
not on a consent modal. Document any trim applied to C in the run record for reproducibility.
|
|
59
|
+
|
|
60
|
+
## Judging — three layers
|
|
61
|
+
|
|
62
|
+
The judge is a **named, independent stage**. See [Anti-Goodhart](#anti-goodhart-judge-independence) — it MUST NOT
|
|
63
|
+
be the harness's own critique loop (#882).
|
|
64
|
+
|
|
65
|
+
### Layer 1 — Blind randomized pairwise (primary metric)
|
|
66
|
+
|
|
67
|
+
The judge sees **two anonymized screenshots in shuffled order** and answers one question:
|
|
68
|
+
|
|
69
|
+
> *"Which reads more like a professionally designed, well-funded SaaS landing page?"*
|
|
70
|
+
|
|
71
|
+
Run all three pairings: **A-vs-C**, **B-vs-C**, **A-vs-B**. Screenshots carry no arm labels; left/right order is
|
|
72
|
+
randomized per comparison. **Metric = win-rate against C.** The harness improves when A's win-rate against C rises
|
|
73
|
+
(and ideally approaches or exceeds B's gap to C closing).
|
|
74
|
+
|
|
75
|
+
### Layer 2 — Rubric (1–5 per dimension)
|
|
76
|
+
|
|
77
|
+
A per-arm absolute score across six dimensions:
|
|
78
|
+
|
|
79
|
+
| Dimension | Looks for |
|
|
80
|
+
|-----------|-----------|
|
|
81
|
+
| Hierarchy | clear primary action, deliberate reading order |
|
|
82
|
+
| Typography | confident scale, non-default families, tight headline tracking |
|
|
83
|
+
| Color discipline | restrained palette, one accent, no "AI gradient" tell |
|
|
84
|
+
| Whitespace rhythm | composed negative space, not uniform padding |
|
|
85
|
+
| Originality vs slop | a committed direction, not the hero→3-cards→testimonial→CTA mean |
|
|
86
|
+
| Mobile | the 390px capture holds up, not a squished desktop |
|
|
87
|
+
|
|
88
|
+
### Layer 3 — Slop detector (binary checklist)
|
|
89
|
+
|
|
90
|
+
A nearly objective pass/fail checklist over the **negatives** corpus (#880): default Inter at default weights,
|
|
91
|
+
slate/indigo gradient, rounded-everything, boilerplate section skeleton, stock 3D blobs, etc. Each negative is a
|
|
92
|
+
yes/no. The slop score is the count of negatives present (lower is better).
|
|
93
|
+
|
|
94
|
+
## Two non-negotiable methodology rules
|
|
95
|
+
|
|
96
|
+
### n=1 is a lie
|
|
97
|
+
|
|
98
|
+
Taste output is high-variance. A single generation per arm tells you almost nothing. **Run 3–5 generations per arm
|
|
99
|
+
across 3–5 products** and report **distributions** — win-rate over runs, and the mean and spread of rubric scores.
|
|
100
|
+
The scorecard aggregator (`scripts/bench-scorecard.mjs`) **rejects runs with n<3** to enforce this.
|
|
101
|
+
|
|
102
|
+
### Anti-Goodhart — judge independence
|
|
103
|
+
|
|
104
|
+
The benchmark judge **MUST be independent from the harness's own critique loop (#882)**. If the harness optimizes
|
|
105
|
+
against the same judge that scores the benchmark, you are training to the test — the number goes up while real
|
|
106
|
+
quality does not. Concretely: the benchmark judge uses its own prompt and is invoked as a distinct stage; it does
|
|
107
|
+
**not** import, share state with, or reuse the #882 critique judge. This independence is asserted in both this doc
|
|
108
|
+
and the [`/design-bench`](../../commands/design-bench.md) command spec.
|
|
109
|
+
|
|
110
|
+
## Anonymization is load-bearing
|
|
111
|
+
|
|
112
|
+
Each brief is derived from a real reference (arm C) but uses an **anonymized product name** and strips every visual
|
|
113
|
+
cue. The mapping from anonymized brief → real reference lives in
|
|
114
|
+
[`fixtures/briefs/README.md`](fixtures/briefs/README.md) (kept separate from the briefs so the generator never sees
|
|
115
|
+
it). A real brand name in a brief lets the model reproduce that brand's actual page from training — measuring recall,
|
|
116
|
+
not taste, and inflating arm B. Treat any real-brand leak in a brief as a fixture bug.
|
|
117
|
+
|
|
118
|
+
## Seed inputs (fixtures)
|
|
119
|
+
|
|
120
|
+
Briefs are **fixed and hand-written**, kept as reproducible fixtures under
|
|
121
|
+
[`fixtures/briefs/`](fixtures/briefs/): **Cadence**, **Tender**, **Slipstream**, **Voltage**, **Plume**. Fixed
|
|
122
|
+
inputs are what make the benchmark reproducible across runs and over time — the same five briefs are re-run as the
|
|
123
|
+
harness evolves, so score movement is attributable to the harness, not to changing inputs.
|
|
124
|
+
|
|
125
|
+
## Scorecard schema
|
|
126
|
+
|
|
127
|
+
The aggregator consumes a **runs file** (the raw per-run judge output) and emits a **scorecard**.
|
|
128
|
+
|
|
129
|
+
### Input — runs file (`runs.json`)
|
|
130
|
+
|
|
131
|
+
```jsonc
|
|
132
|
+
{
|
|
133
|
+
"corpus_version": "2026.2", // grammar snapshot the briefs were designed against
|
|
134
|
+
"generation_model": "…", // the SAME model used for arms A and B
|
|
135
|
+
"judge_model": "…", // the INDEPENDENT benchmark judge (not #882)
|
|
136
|
+
"products": [
|
|
137
|
+
{
|
|
138
|
+
"product": "Voltage", // anonymized brief name
|
|
139
|
+
"reference": "modal.com", // real arm-C reference (internal record only)
|
|
140
|
+
"runs": [
|
|
141
|
+
{
|
|
142
|
+
"run": 1,
|
|
143
|
+
"pairwise": { // winner of each blind pairwise comparison
|
|
144
|
+
"A_vs_C": "C", // "A" | "B" | "C" | "tie"
|
|
145
|
+
"B_vs_C": "C",
|
|
146
|
+
"A_vs_B": "A"
|
|
147
|
+
},
|
|
148
|
+
"rubric": { // 1–5 per arm per dimension
|
|
149
|
+
"A": { "hierarchy": 4, "typography": 3, "color": 4, "whitespace": 4, "originality": 3, "mobile": 4 },
|
|
150
|
+
"B": { "hierarchy": 3, "typography": 2, "color": 2, "whitespace": 3, "originality": 2, "mobile": 3 },
|
|
151
|
+
"C": { "hierarchy": 5, "typography": 5, "color": 5, "whitespace": 5, "originality": 5, "mobile": 5 }
|
|
152
|
+
},
|
|
153
|
+
"slop": { // count of negatives present (lower is better)
|
|
154
|
+
"A": 1, "B": 4, "C": 0
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// … runs 2..n (n >= 3)
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
// … 3–5 products
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Output — scorecard
|
|
166
|
+
|
|
167
|
+
```jsonc
|
|
168
|
+
{
|
|
169
|
+
"n_products": 5,
|
|
170
|
+
"n_runs_per_product": 4,
|
|
171
|
+
"win_rate_vs_C": { // primary metric — fraction of runs the arm beat C
|
|
172
|
+
"A": { "mean": 0.15, "by_product": { "Voltage": 0.25, "…": 0.10 } },
|
|
173
|
+
"B": { "mean": 0.05, "by_product": { "…": 0.00 } }
|
|
174
|
+
},
|
|
175
|
+
"a_vs_b_win_rate": 0.70, // fraction of runs A beat B (the harness delta)
|
|
176
|
+
"rubric": { // mean + spread per arm per dimension across all runs
|
|
177
|
+
"A": { "hierarchy": { "mean": 3.8, "stdev": 0.6 }, "…": {} },
|
|
178
|
+
"B": { "…": {} },
|
|
179
|
+
"C": { "…": {} }
|
|
180
|
+
},
|
|
181
|
+
"slop": { // mean negatives present per arm (lower better)
|
|
182
|
+
"A": 1.2, "B": 3.9, "C": 0.1
|
|
183
|
+
},
|
|
184
|
+
"judge_calibration": { // C must win A-vs-C and B-vs-C; flag if not
|
|
185
|
+
"miscalibrated_runs": [], // runs where C lost to A or B
|
|
186
|
+
"ok": true
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Running the benchmark
|
|
192
|
+
|
|
193
|
+
The full reproducible workflow — reference URL → brief → render arms → judge → scorecard — is driven by the
|
|
194
|
+
[`/design-bench`](../../commands/design-bench.md) command. The deterministic aggregation step is
|
|
195
|
+
`node scripts/bench-scorecard.mjs runs.json`.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Design-Architect Phase & FORGE:DESIGN_RATIONALE — the designer's diary
|
|
2
|
+
|
|
3
|
+
> **Status:** Committed foundation — design-architect phase spec for issue #886 (milestone #13).
|
|
4
|
+
> The reasoning-before-generation phase. Produces the [DESIGN_SPEC](design-spec-schema.md) (#881);
|
|
5
|
+
> queries [design-memory](design-memory.md) (#887); read by generate + the critique loop (#882).
|
|
6
|
+
> Extended: motion tier selection added as diary step 5 (#1043); product mock decision added as diary step 6 (#1045); scroll narrative planning added as diary step 7.5 (#1046).
|
|
7
|
+
|
|
8
|
+
## Why this exists
|
|
9
|
+
|
|
10
|
+
Everything else in the harness (spec, effects, critique loop) can still **raw-dog** the output: jump straight to a
|
|
11
|
+
page and defend it afterward. A page produced without deliberation is a guess. What separates craft from a guess is
|
|
12
|
+
**thinking before generating** — and capturing that thinking so it's reviewable.
|
|
13
|
+
|
|
14
|
+
ForgeDock already does this for code: `investigate → architect → implement → validate`, where the *architect* phase
|
|
15
|
+
reasons about *how* before any code is written, and writes a FORGE annotation the next stage reads. **A design harness
|
|
16
|
+
without a design-architect phase is just vibes.** So this is not a new paradigm — it is the ForgeDock pattern applied
|
|
17
|
+
to design:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
design-investigate (what's the message?) → design-architect (diary + rationale → spec) → generate → critique-loop
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The architect phase's output is a `FORGE:DESIGN_RATIONALE` annotation — the diary — which *produces* the
|
|
24
|
+
[DESIGN_SPEC](design-spec-schema.md) instead of the spec appearing from nowhere.
|
|
25
|
+
|
|
26
|
+
## The diary — structured chain of thought
|
|
27
|
+
|
|
28
|
+
The phase must reason through all ten, in order. This is what a designer's diary actually contains.
|
|
29
|
+
|
|
30
|
+
1. **Intent & emotional register** — One core message. One feeling to evoke (trust / speed / power / craft / play).
|
|
31
|
+
What should the visitor feel, and what should they do?
|
|
32
|
+
2. **Audience & objection** — Who lands here? What do they already believe? What is the single objection this page
|
|
33
|
+
must overcome?
|
|
34
|
+
3. **Communication hierarchy** — What gets said 1st, 2nd, 3rd. This *is* the visual hierarchy — it is derived from
|
|
35
|
+
meaning, not chosen as decoration. (Feeds `layout_grammar.sections` in the spec.)
|
|
36
|
+
4. **Direction & why** — The chosen archetype/concept *and the reasoning for it* — why it serves this message for this
|
|
37
|
+
audience. **Plus the alternatives considered and explicitly rejected, and why.** (Rejection is where taste shows.)
|
|
38
|
+
5. **Motion tier & hero technique** — Which motion tier (1–3) from the [corpus hero motion vocabulary](reference-corpus.md#hero-motion-vocabulary--making-the-hero-feel-alive)
|
|
39
|
+
is appropriate for this archetype and product nature? Which specific technique? Commit to both. The default per-archetype
|
|
40
|
+
motion profile in the corpus is the starting point; deviation requires explicit reasoning. Every page requires a
|
|
41
|
+
committed motion posture — "no motion" for `bold-brutalist` or `minimal-luxury` is a valid deliberate choice, but
|
|
42
|
+
it must be stated, not omitted.
|
|
43
|
+
- Feeds `motion.tier` and `motion.hero_technique` in the [DESIGN_SPEC](design-spec-schema.md).
|
|
44
|
+
- If a video placeholder is appropriate (Tier 3): set `motion.video_placeholder: true` — the generator scaffolds
|
|
45
|
+
the HTML and CSS gradient poster.
|
|
46
|
+
- State the `prefers-reduced-motion` fallback explicitly (e.g., "gradient-shift pauses; text remains visible").
|
|
47
|
+
6. **Product mock decision** — Does this brief imply a product interface that can be shown live in the hero? <!-- Added: forge#1045 -->
|
|
48
|
+
Consult the [interactive product mock vocabulary](reference-corpus.md#interactive-product-mock-vocabulary) to
|
|
49
|
+
identify the matching product type.
|
|
50
|
+
- **Decision signals**: does the brief describe a dashboard, issue board, payment flow, deploy pipeline, code
|
|
51
|
+
API, or inbox? → commit a product mock. Does the product have no single canonical interface, is it image-driven
|
|
52
|
+
(e.g. physical device, marketplace), or is the archetype `minimal-luxury` and restraint is the move? → commit
|
|
53
|
+
`product_mock.type: "none"` with explicit reasoning.
|
|
54
|
+
- If committing a mock: select exactly 2 interactions from the per-type defaults table in the corpus. State
|
|
55
|
+
whether they are CSS-only or require lightweight JS. State the `prefers-reduced-motion` fallback.
|
|
56
|
+
- Feeds `product_mock.type`, `product_mock.interactions`, and `product_mock.css_only` in the
|
|
57
|
+
[DESIGN_SPEC](design-spec-schema.md).
|
|
58
|
+
- Restraint rule: 2 interactions only. The mock should feel alive, not be a demo. Never animate primary copy.
|
|
59
|
+
7. **The signature move** — The one memorable, non-obvious idea that makes it *this* page and not a template. The "hook."
|
|
60
|
+
7.5. **Scroll narrative planning** — Commit the full-page below-fold narrative before generation. <!-- Added: forge#1046 -->
|
|
61
|
+
Consult the [below-the-fold narrative vocabulary](reference-corpus.md#below-the-fold-narrative-vocabulary--scroll-driven-full-page-structure)
|
|
62
|
+
and the per-archetype scroll narrative defaults table.
|
|
63
|
+
- **Section sequence**: list the committed section order (e.g. `problem-context → solution-showcase → social-proof → deep-features → final-cta`). Deviate from the archetype default with explicit reasoning. Minimum 4 below-fold sections (5 total including hero); `minimal-luxury` archetype may use 3 below-fold sections with explicit restraint rationale.
|
|
64
|
+
- **Sticky nav decision**: commit `sticky_nav: true` or `false`. True is the default for all archetypes when total sections ≥ 5. False requires explicit rationale. For `minimal-luxury`: omit backdrop-filter blur (glassmorphism contradiction) and use fully-opaque background instead.
|
|
65
|
+
- **Social proof type**: commit one of `logos | metrics | testimonials | none`. Consult the archetype social proof defaults. `none` requires explicit rationale (e.g., pre-launch product with no social proof yet — acceptable).
|
|
66
|
+
- **CTA rhythm**: commit ≥ 2 CTA placements — one in hero, one in final-cta. Note any additional mid-page CTA placement and its rationale.
|
|
67
|
+
- Feeds `scroll_narrative.section_sequence`, `scroll_narrative.sticky_nav`, `scroll_narrative.social_proof_section`, and `scroll_narrative.cta_placements_min` in the [DESIGN_SPEC](design-spec-schema.md).
|
|
68
|
+
8. **What I'm trying this time** — A technique or learning being deliberately applied. Queries
|
|
69
|
+
[design-memory](design-memory.md) so it builds on, and diverges from, prior work.
|
|
70
|
+
9. **Non-goals** — What this page deliberately will *not* do. Restraint stated as a decision, not an omission.
|
|
71
|
+
|
|
72
|
+
## FORGE:DESIGN_RATIONALE — annotation format
|
|
73
|
+
|
|
74
|
+
Posted to the design issue at the architect stage (see the `/design` pipeline, #888):
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
<!-- FORGE:DESIGN_RATIONALE -->
|
|
78
|
+
## Design Rationale — {product}
|
|
79
|
+
|
|
80
|
+
**Intent / feeling:** {one message} · {one feeling}
|
|
81
|
+
**Audience / objection:** {who} — must overcome: {objection}
|
|
82
|
+
**Communication hierarchy:** 1) {…} 2) {…} 3) {…}
|
|
83
|
+
**Direction:** {archetype} — because {reasoning}
|
|
84
|
+
- Considered & rejected: {alt A} (because {…}); {alt B} (because {…})
|
|
85
|
+
**Motion:** Tier {1|2|3} — technique: {hero_technique} — because {reasoning}
|
|
86
|
+
- `prefers-reduced-motion` fallback: {what degrades gracefully}
|
|
87
|
+
- Video placeholder: {yes — Tier 3 scaffold generated | no}
|
|
88
|
+
**Product mock:** {type id | none} — because {reasoning}
|
|
89
|
+
- Interactions: {interaction-id-1}, {interaction-id-2} (or "n/a — type: none")
|
|
90
|
+
- CSS-only: {yes | no — lightweight JS for {reason}}
|
|
91
|
+
- `prefers-reduced-motion` fallback: {what degrades gracefully, or "n/a"}
|
|
92
|
+
**Scroll narrative:** {section sequence} — sticky nav: {yes|no} — social proof: {logos|metrics|testimonials|none}
|
|
93
|
+
- CTA placements: {count} ({hero + final-cta} or note any additional mid-page CTA)
|
|
94
|
+
- Deviation from archetype default: {reasoning, or "none — follows archetype default"}
|
|
95
|
+
**Signature move:** {the one non-obvious idea}
|
|
96
|
+
**Trying this time:** {technique/learning} (from memory: avoiding {prior move})
|
|
97
|
+
**Non-goals:** {what this won't do}
|
|
98
|
+
|
|
99
|
+
→ Produces DESIGN_SPEC: {link}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Why it matters for a non-designer
|
|
103
|
+
|
|
104
|
+
The rationale is **reviewable without judging pixels.** A non-designer can read the *thinking* and catch when the
|
|
105
|
+
reasoning is thin or generic — "this hook is the same as every other page," "the direction has no justification" —
|
|
106
|
+
**before a single pixel renders.** The harness externalizes the designer's thought process the user may not have.
|
|
107
|
+
This is the review checkpoint that makes the whole pipeline trustworthy.
|
|
108
|
+
|
|
109
|
+
## Acceptance
|
|
110
|
+
|
|
111
|
+
- Every generated page is preceded by a DESIGN_RATIONALE covering all 10 elements, with ≥1 explicitly rejected
|
|
112
|
+
alternative, a committed motion tier + technique, a product mock decision, a scroll narrative plan, and a named signature move.
|
|
113
|
+
- The `**Motion:**` line must be present in every rationale — "no motion" is acceptable only for `bold-brutalist`
|
|
114
|
+
and `minimal-luxury` archetypes, and must be stated with reasoning, not left absent.
|
|
115
|
+
- The `**Product mock:**` line must be present in every rationale — `type: none` is acceptable when the product
|
|
116
|
+
has no single canonical interface or when archetype restraint is the deliberate choice, but must be stated with
|
|
117
|
+
reasoning, not left absent.
|
|
118
|
+
- The `**Scroll narrative:**` line must be present in every rationale — a committed section sequence, sticky nav
|
|
119
|
+
decision, social proof type, and CTA count are all required. `social_proof_section: none` is acceptable for
|
|
120
|
+
pre-launch products with no social proof, but must be stated with reasoning, not left absent.
|
|
121
|
+
- On the seed set, rationales for Voltage / Plume / Slipstream show genuinely different intent, hierarchy,
|
|
122
|
+
motion techniques, product mock decisions, scroll narrative sequences, and signature moves — derived from the
|
|
123
|
+
briefs alone (no design hints in the brief).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Design Memory & Deliberate Divergence
|
|
2
|
+
|
|
3
|
+
> **Status:** DRAFT for review — foundation spec for issue #887 (milestone #13).
|
|
4
|
+
> Queried by the [design-architect phase](design-architect-rationale.md) (#886) for the "different from my previous
|
|
5
|
+
> work / what new thing am I applying" questions. Enforced by the critique loop (#882).
|
|
6
|
+
|
|
7
|
+
## The problem it solves
|
|
8
|
+
|
|
9
|
+
Mode-collapse is not only a *per-page* phenomenon — it is why *all* AI output looks the same *across* pages. Per-page
|
|
10
|
+
taste (the spec, the critique loop) fixes one page. **Design memory fixes the portfolio.** To answer "how is this
|
|
11
|
+
different from before," the harness must *remember before*.
|
|
12
|
+
|
|
13
|
+
## The store
|
|
14
|
+
|
|
15
|
+
Local, file-based, on the AGPL CLI side — under `.forgedock/design-memory/` (like per-repo adaptive scripts:
|
|
16
|
+
`.gitignore`d by default, opt-in to commit). One record per shipped design:
|
|
17
|
+
|
|
18
|
+
```jsonc
|
|
19
|
+
{
|
|
20
|
+
"product": "…",
|
|
21
|
+
"shipped_at": "<timestamp>",
|
|
22
|
+
"corpus_version": "2026.2", // taste shifts; record what was "current" then
|
|
23
|
+
"archetype": "technical-dense",
|
|
24
|
+
"signature_move": "…", // the hook — the thing to NOT repeat next time
|
|
25
|
+
"palette_move": "…", // the distinctive color decision
|
|
26
|
+
"type_pairing": "…",
|
|
27
|
+
"effects_used": ["…"],
|
|
28
|
+
"rationale_summary": "…",
|
|
29
|
+
"learnings": ["…"] // techniques discovered/applied — grows the vocabulary
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Two mechanisms
|
|
34
|
+
|
|
35
|
+
### 1. Diverge (anti-sameness)
|
|
36
|
+
The architect phase receives a summary of recent records and is required to **not repeat** the last N designs':
|
|
37
|
+
- signature moves (the hook),
|
|
38
|
+
- archetype (don't ship `technical-dense` three times running for similar briefs),
|
|
39
|
+
- palette/type/effect moves.
|
|
40
|
+
|
|
41
|
+
The [render → vision-critique loop](render-critique-loop.md) (#882) can enforce a **divergence check**: "is this meaningfully distinct from prior outputs,
|
|
42
|
+
or a reskin?" A reskin fails.
|
|
43
|
+
|
|
44
|
+
### 2. Evolve (incorporate learnings)
|
|
45
|
+
`learnings` accumulate into a rotating technique/pattern library. The architect's "what I'm trying this time"
|
|
46
|
+
(rationale item 6) draws from it, so the harness deliberately applies and rotates new techniques rather than
|
|
47
|
+
converging on a comfortable default. This is the autopilot / self-improvement angle: **the system gets better the
|
|
48
|
+
more it ships.**
|
|
49
|
+
|
|
50
|
+
## Open-core boundary
|
|
51
|
+
|
|
52
|
+
- Memory is **local, AGPL CLI side**. No cross-repo import.
|
|
53
|
+
- It emits nothing the Platform needs to function; if a user opts in, a summary could feed the Platform's
|
|
54
|
+
observability (per `devdocs/project/architecture.md` boundary rules) — but the CLI never depends on the Platform.
|
|
55
|
+
|
|
56
|
+
## Acceptance
|
|
57
|
+
|
|
58
|
+
- Architect phase receives prior signature moves and avoids repeating them.
|
|
59
|
+
- Across N consecutive generations for similar briefs, outputs show measurable variance in concept / hook / palette —
|
|
60
|
+
not the same page reskinned.
|
|
61
|
+
- `learnings` demonstrably influence later designs ("trying this time" references a prior learning).
|
|
@@ -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).
|