sdtk-design-kit 0.2.0 → 0.2.1

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.
@@ -2,7 +2,9 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- const { ROLE_FLOORS, assessPrototypeDensity, normalizeScreenRole } = require("./prototype-density");
5
+ const { briefForScreen, loadPrototypeBriefIndex } = require("./prototype-briefs");
6
+ const { componentClassFor, renderBriefComponent, renderDataSlotPlaceholder } = require("./prototype-component-map");
7
+ const { assessPrototypeDensity, normalizeScreenRole } = require("./prototype-density");
6
8
 
7
9
  function escapeHtml(value) {
8
10
  return String(value == null ? "" : value)
@@ -73,8 +75,6 @@ textarea { padding-top: 10px; min-height: 80px; }
73
75
  .timeline { margin: 0; padding-left: 18px; }
74
76
  .state-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin: 14px 0; }
75
77
  .state-card, .product-card, .feature-card, .category-card, .history-row, .result-row { border: 1px solid var(--border); border-radius: 8px; padding: 12px; background: color-mix(in srgb, var(--surface) 96%, var(--bg)); }
76
- .density-evidence { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; margin-top: 14px; }
77
- .density-evidence article { border: 1px dashed var(--border); border-radius: 8px; padding: 10px; color: var(--muted); background: color-mix(in srgb, var(--surface) 94%, var(--bg)); }
78
78
  @media (max-width: 920px) {
79
79
  .screen-layout, .split-layout, .feature-grid, .product-grid, .metric-grid { grid-template-columns: 1fr; }
80
80
  h1 { font-size: 36px; }
@@ -96,24 +96,79 @@ document.addEventListener("click", (event) => {
96
96
  function stateStrip(states) {
97
97
  const values = Array.isArray(states) && states.length > 0 ? states : ["loading", "empty", "success", "error"];
98
98
  return `<section class="state-strip" aria-label="State coverage"><div class="chip-row">${values
99
- .map((state) => `<span class="chip" data-chip-type="state">${escapeHtml(state)}</span>`)
99
+ .map((state) => `<span class="chip" data-chip-type="state" data-state-id="${escapeHtml(state)}">${escapeHtml(state)}</span>`)
100
100
  .join("")}</div>${values
101
- .map((state) => `<article class="state-card ${escapeHtml(state)}-state"><strong>${escapeHtml(state)}</strong><p>Static prototype guidance for the ${escapeHtml(state)} state.</p></article>`)
101
+ .map((state) => `<article class="state-card ${escapeHtml(state)}-state" data-state-id="${escapeHtml(state)}"><strong>${escapeHtml(state)}</strong><p>Static prototype guidance for the ${escapeHtml(state)} state.</p></article>`)
102
102
  .join("")}</section>`;
103
103
  }
104
104
 
105
- function evidenceGrid(screen, role, count) {
106
- return `<section class="density-evidence" aria-label="Renderer evidence">${Array.from({ length: count }, (_, index) => {
107
- const slot = index + 1;
108
- return `<article><strong>${escapeHtml(screen.title)} evidence ${slot}</strong><p>${escapeHtml(role)} layout contract covers visible data slots, control states, source-backed acceptance criteria, responsive grouping, and implementation-safe static interaction guidance.</p></article>`;
109
- }).join("")}</section>`;
105
+ function slotLookupForBrief(brief) {
106
+ const lookup = new Map();
107
+ for (const slot of Array.isArray(brief && brief.data_slots) ? brief.data_slots : []) {
108
+ lookup.set(slot.id, slot);
109
+ }
110
+ return lookup;
111
+ }
112
+
113
+ function renderNeedsBrief(screen, role) {
114
+ const slots = [
115
+ { id: "brief-status", label: "Brief status" },
116
+ { id: "screen-contract", label: "Screen contract" },
117
+ { id: "source-evidence", label: "Source evidence" },
118
+ { id: "primary-record", label: "Primary record" },
119
+ { id: "next-action", label: "Next action" },
120
+ ];
121
+ const section = {
122
+ id: "needs-brief-actions",
123
+ title: "Brief recovery actions",
124
+ purpose: "Prototype generation continues with visible review evidence instead of silently falling back to role-default filler.",
125
+ data_slots: slots,
126
+ interactions: ["Review brief", "Regenerate brief", "Continue prototype review"],
127
+ };
128
+ const brief = { screen_title: screen.title, primary_action: "Review brief" };
129
+ return `<section class="panel needs-brief" data-section-id="needs-brief-summary" data-section-role="${escapeHtml(role)}"><p class="eyebrow">NEEDS_BRIEF</p><h2>NEEDS_BRIEF</h2><p class="support">${escapeHtml(screen.title)} requires .sdtk/design/screen-briefs/${escapeHtml(screen.screenId)}.json before high-fidelity composition.</p><div class="chip-row">${slots.map((slot) => renderDataSlotPlaceholder({ slot, section, escapeHtml })).join("")}</div></section><section class="panel" data-section-id="needs-brief-actions" data-section-role="${escapeHtml(role)}"><h2>Brief recovery actions</h2>${renderBriefComponent({ componentId: { id: "primary-panel", label: "Primary panel" }, section, brief, role, slotLookup: new Map(slots.map((slot) => [slot.id, slot])), escapeHtml })}${stateStrip(screen.requiredStates)}</section>`;
130
+ }
131
+
132
+ function renderBriefSlotSummary(brief) {
133
+ const slots = Array.isArray(brief.data_slots) ? brief.data_slots : [];
134
+ if (slots.length === 0) return "";
135
+ return `<div class="panel" data-component-id="screen-data-slots"><h3>Data slots</h3><div class="chip-row">${slots
136
+ .map((slot) => renderDataSlotPlaceholder({ slot, section: { id: "screen-data-slots" }, escapeHtml }))
137
+ .join("")}</div></div>`;
138
+ }
139
+
140
+ function sectionHasHeroComponent(section) {
141
+ const components = Array.isArray(section && section.components) ? section.components : [];
142
+ return components.some((component) => componentClassFor(component && component.id ? component.id : String(component || "component")) === "hero-section");
143
+ }
144
+
145
+ function briefHasHeroComponent(brief) {
146
+ return Array.isArray(brief && brief.sections) && brief.sections.some((section) => sectionHasHeroComponent(section));
147
+ }
148
+
149
+ function renderBriefSection({ section, brief, role, slotLookup, headingTag = "h2" }) {
150
+ const components = Array.isArray(section.components) && section.components.length > 0 ? section.components : [{ id: "component-placeholder", label: section.title }];
151
+ return `<section class="panel brief-section" data-section-id="${escapeHtml(section.id)}" data-section-role="${escapeHtml(role)}"><p class="eyebrow">${escapeHtml(role.replace(/-/g, " "))}</p><${headingTag}>${escapeHtml(section.title)}</${headingTag}><p class="support">${escapeHtml(section.purpose)}</p><div class="brief-component-stack">${components
152
+ .map((component) => renderBriefComponent({ componentId: component, section, brief, role, slotLookup, escapeHtml }))
153
+ .join("")}</div></section>`;
154
+ }
155
+
156
+ function renderBriefContent({ screen, role, brief }) {
157
+ if (!brief || !Array.isArray(brief.sections) || brief.sections.length === 0) {
158
+ return renderNeedsBrief(screen, role);
159
+ }
160
+ const slotLookup = slotLookupForBrief(brief);
161
+ const hasHeroComponent = briefHasHeroComponent(brief);
162
+ const sections = brief.sections
163
+ .map((section, index) => renderBriefSection({ section, brief, role, slotLookup, headingTag: !hasHeroComponent && index === 0 ? "h1" : "h2" }))
164
+ .join("");
165
+ return `${sections}${renderBriefSlotSummary(brief)}${stateStrip(brief.states.length > 0 ? brief.states : screen.requiredStates)}`;
110
166
  }
111
167
 
112
168
  function renderHome(screen) {
113
169
  return `<section class="hero"><p class="eyebrow">Hero</p><h1>${escapeHtml(screen.title)} hero</h1><p class="support">Category-first commerce landing with featured products, clear entry points, and guided configurator access.</p><div class="button-row"><a class="btn btn-primary" href="#" data-static-action>Start purchase</a><a class="btn btn-secondary" href="#" data-static-action>Open configurator</a></div></section>
114
170
  <section class="panel" aria-label="Featured products"><h2>Featured products</h2><div class="feature-grid">${["Category range", "Featured products", "Configurator quick start", "Order support"].map((label) => `<article class="feature-card category-card"><h3>${label}</h3><p>Structured entry point with summary, state, and action guidance.</p></article>`).join("")}</div></section>
115
- ${stateStrip(screen.requiredStates)}
116
- ${evidenceGrid(screen, "home", 18)}`;
171
+ ${stateStrip(screen.requiredStates)}`;
117
172
  }
118
173
 
119
174
  function productCards(count) {
@@ -121,46 +176,51 @@ function productCards(count) {
121
176
  }
122
177
 
123
178
  function renderCategory(screen) {
124
- return `<section class="screen-layout"><aside class="panel"><h3>Filter sidebar</h3>${["Category", "Availability", "Material", "Size", "Price", "Delivery"].map((label) => `<label>${label}<select><option>${label} option</option></select></label>`).join("")}</aside><div class="panel"><h3>Product grid</h3><div class="product-grid">${productCards(8)}</div></div></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "category", 10)}`;
179
+ return `<section class="screen-layout"><aside class="panel"><h3>Filter sidebar</h3>${["Category", "Availability", "Material", "Size", "Price", "Delivery"].map((label) => `<label>${label}<select><option>${label} option</option></select></label>`).join("")}</aside><div class="panel"><h3>Product grid</h3><div class="product-grid">${productCards(8)}</div></div></section>${stateStrip(screen.requiredStates)}`;
125
180
  }
126
181
 
127
182
  function renderProductDetail(screen) {
128
- return `<section class="split-layout"><div class="panel"><h3>Gallery and spec area</h3><table class="table spec-table"><tbody>${["Voltage", "Length", "Material", "Finish", "Certification"].map((label, index) => `<tr><th>${label}</th><td>Source-backed specification value ${index + 1}</td></tr>`).join("")}</tbody></table></div><aside class="panel"><h3>Price and quantity</h3><div class="qty-stepper"><button>-</button><span>1</span><button>+</button></div><div class="button-row"><a class="btn btn-primary" href="#" data-static-action>Add to cart</a></div></aside></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "product-detail", 10)}`;
183
+ return `<section class="split-layout"><div class="panel"><h3>Gallery and spec area</h3><table class="table spec-table"><tbody>${["Voltage", "Length", "Material", "Finish", "Certification"].map((label, index) => `<tr><th>${label}</th><td>Source-backed specification value ${index + 1}</td></tr>`).join("")}</tbody></table></div><aside class="panel"><h3>Price and quantity</h3><div class="qty-stepper"><button>-</button><span>1</span><button>+</button></div><div class="button-row"><a class="btn btn-primary" href="#" data-static-action>Add to cart</a></div></aside></section>${stateStrip(screen.requiredStates)}`;
129
184
  }
130
185
 
131
186
  function renderSearch(screen) {
132
- return `<section class="panel"><h3>Search toolbar</h3><div class="search-toolbar"><input aria-label="Search query" placeholder="Search product"><select><option>Sort by relevance</option></select><button class="btn btn-primary">Search</button></div><div class="result-list">${Array.from({ length: 5 }, (_, index) => `<article class="result-row"><h4>Result item ${index + 1}</h4><p>Matched term, category, availability, and action.</p></article>`).join("")}<article class="result-row no-result"><h4>No-result state</h4><p>Suggest alternate query, category reset, and support route.</p></article></div></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "search", 7)}`;
187
+ return `<section class="panel"><h3>Search toolbar</h3><div class="search-toolbar"><input aria-label="Search query" placeholder="Search product"><select><option>Sort by relevance</option></select><button class="btn btn-primary">Search</button></div><div class="result-list">${Array.from({ length: 5 }, (_, index) => `<article class="result-row"><h4>Result item ${index + 1}</h4><p>Matched term, category, availability, and action.</p></article>`).join("")}<article class="result-row no-result"><h4>No-result state</h4><p>Suggest alternate query, category reset, and support route.</p></article></div></section>${stateStrip(screen.requiredStates)}`;
133
188
  }
134
189
 
135
190
  function renderCart(screen) {
136
- return `<section class="split-layout"><div class="panel"><h3>Cart table</h3><table class="table cart-table"><thead><tr><th>Item</th><th>Qty</th><th>Unit</th><th>Total</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 4 }, (_, index) => `<tr><td>Line item ${index + 1}</td><td><div class="qty-stepper"><button>-</button><span>${index + 1}</span><button>+</button></div></td><td>1000</td><td>${(index + 1) * 1000}</td><td><button>Remove</button></td></tr>`).join("")}</tbody></table></div><aside class="panel summary-panel"><h3>Summary panel</h3><p>Subtotal, tax, freight, and checkout CTA.</p><a class="btn btn-primary" href="#" data-static-action>Checkout</a></aside></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "cart", 8)}`;
191
+ return `<section class="split-layout"><div class="panel"><h3>Cart table</h3><table class="table cart-table"><thead><tr><th>Item</th><th>Qty</th><th>Unit</th><th>Total</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 4 }, (_, index) => `<tr><td>Line item ${index + 1}</td><td><div class="qty-stepper"><button>-</button><span>${index + 1}</span><button>+</button></div></td><td>1000</td><td>${(index + 1) * 1000}</td><td><button>Remove</button></td></tr>`).join("")}</tbody></table></div><aside class="panel summary-panel"><h3>Summary panel</h3><p>Subtotal, tax, freight, and checkout CTA.</p><a class="btn btn-primary" href="#" data-static-action>Checkout</a></aside></section>${stateStrip(screen.requiredStates)}`;
137
192
  }
138
193
 
139
194
  function renderCheckout(screen) {
140
- return `<section class="split-layout"><div class="panel"><h3>Checkout stepper</h3><ol class="checkout-stepper">${["Information", "Delivery", "Payment", "Review"].map((step) => `<li class="stepper-step">${step}</li>`).join("")}</ol><form class="form-grid">${["Recipient", "Address", "Phone", "Purchase order"].map((label) => `<label>${label}<input type="text" value="${label} value"></label>`).join("")}</form></div><aside class="panel summary-panel"><h3>Summary card</h3><p>Order totals, delivery window, and final confirmation.</p><button class="btn btn-primary">Place order</button></aside></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "checkout", 8)}`;
195
+ return `<section class="split-layout"><div class="panel"><h3>Checkout stepper</h3><ol class="checkout-stepper">${["Information", "Delivery", "Payment", "Review"].map((step) => `<li class="stepper-step">${step}</li>`).join("")}</ol><form class="form-grid">${["Recipient", "Address", "Phone", "Purchase order"].map((label) => `<label>${label}<input type="text" value="${label} value"></label>`).join("")}</form></div><aside class="panel summary-panel"><h3>Summary card</h3><p>Order totals, delivery window, and final confirmation.</p><button class="btn btn-primary">Place order</button></aside></section>${stateStrip(screen.requiredStates)}`;
141
196
  }
142
197
 
143
198
  function renderOrderHistory(screen) {
144
- return `<section class="panel"><h3>Order history list</h3><table class="table"><thead><tr><th>Order</th><th>Status</th><th>Total</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 5 }, (_, index) => `<tr class="history-row"><td>#10${index}</td><td>Processing</td><td>${(index + 2) * 1200}</td><td><a href="#" data-static-action>Open detail</a></td></tr>`).join("")}</tbody></table></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "order-history", 7)}`;
199
+ return `<section class="panel"><h3>Order history list</h3><table class="table"><thead><tr><th>Order</th><th>Status</th><th>Total</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 5 }, (_, index) => `<tr class="history-row"><td>#10${index}</td><td>Processing</td><td>${(index + 2) * 1200}</td><td><a href="#" data-static-action>Open detail</a></td></tr>`).join("")}</tbody></table></section>${stateStrip(screen.requiredStates)}`;
145
200
  }
146
201
 
147
202
  function renderOrderDetail(screen) {
148
- return `<section class="split-layout"><div class="panel"><h3>Timeline</h3><ol class="timeline">${["Placed", "Approved", "Packed", "Shipping", "Delivered"].map((item) => `<li>${item}</li>`).join("")}</ol></div><aside class="panel detail-summary"><h3>Detail summary</h3><p>Delivery, payment, line item, and support snapshot.</p></aside></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "order-detail", 8)}`;
203
+ return `<section class="split-layout"><div class="panel"><h3>Timeline</h3><ol class="timeline">${["Placed", "Approved", "Packed", "Shipping", "Delivered"].map((item) => `<li>${item}</li>`).join("")}</ol></div><aside class="panel detail-summary"><h3>Detail summary</h3><p>Delivery, payment, line item, and support snapshot.</p></aside></section>${stateStrip(screen.requiredStates)}`;
149
204
  }
150
205
 
151
206
  function renderAccountInfo(screen) {
152
- return `<section class="screen-layout"><aside class="panel"><h3>Account sidebar</h3><nav class="screen-nav"><a href="#">Company</a><a href="#">Users</a><a href="#">Billing</a><a href="#">Security</a></nav></aside><div class="panel"><h3>View or edit form</h3><form class="form-grid">${["Company", "Contact", "Email", "Phone", "Address"].map((label) => `<label>${label}<input type="text" value="${label}"></label>`).join("")}</form></div></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "account-info", 7)}`;
207
+ return `<section class="screen-layout"><aside class="panel"><h3>Account sidebar</h3><nav class="screen-nav"><a href="#">Company</a><a href="#">Users</a><a href="#">Billing</a><a href="#">Security</a></nav></aside><div class="panel"><h3>View or edit form</h3><form class="form-grid">${["Company", "Contact", "Email", "Phone", "Address"].map((label) => `<label>${label}<input type="text" value="${label}"></label>`).join("")}</form></div></section>${stateStrip(screen.requiredStates)}`;
153
208
  }
154
209
 
155
210
  function renderConfigurator(screen) {
156
- return `<section class="panel"><h3>Configurator wizard</h3><ol class="checkout-stepper">${["Project", "Assembly", "Construction", "Review", "Complete"].map((step) => `<li class="stepper-step">${step}</li>`).join("")}</ol><div class="split-layout"><div class="panel preview-panel"><h4>Preview panel</h4><p>Derived material preview with recalculation state and validation summary.</p>${stateStrip(screen.requiredStates)}</div><div class="panel"><h4>BOM table</h4><table class="table cart-table"><thead><tr><th>Material</th><th>Spec</th><th>Qty</th><th>Unit</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 8 }, (_, index) => `<tr><td>Material ${index + 1}</td><td>Generated spec ${index + 1}</td><td>${index + 2}</td><td>pcs</td><td><button>Exclude</button></td></tr>`).join("")}</tbody></table><div class="button-row"><a class="btn btn-secondary" href="#" data-static-action>Recalculate</a><a class="btn btn-primary" href="#" data-static-action>Add BOM to cart</a></div></div></div></section>${evidenceGrid(screen, "configurator-bom", 24)}`;
211
+ return `<section class="panel"><h3>Configurator wizard</h3><ol class="checkout-stepper">${["Project", "Assembly", "Construction", "Review", "Complete"].map((step) => `<li class="stepper-step">${step}</li>`).join("")}</ol><div class="split-layout"><div class="panel preview-panel"><h4>Preview panel</h4><p>Derived material preview with recalculation state and validation summary.</p>${stateStrip(screen.requiredStates)}</div><div class="panel"><h4>BOM table</h4><table class="table cart-table"><thead><tr><th>Material</th><th>Spec</th><th>Qty</th><th>Unit</th><th>Action</th></tr></thead><tbody>${Array.from({ length: 8 }, (_, index) => `<tr><td>Material ${index + 1}</td><td>Generated spec ${index + 1}</td><td>${index + 2}</td><td>pcs</td><td><button>Exclude</button></td></tr>`).join("")}</tbody></table><div class="button-row"><a class="btn btn-secondary" href="#" data-static-action>Recalculate</a><a class="btn btn-primary" href="#" data-static-action>Add BOM to cart</a></div></div></div></section>`;
157
212
  }
158
213
 
159
214
  function renderGeneric(screen) {
160
- return `<section class="panel"><h3>${escapeHtml(screen.title)} workspace</h3><p>Renderer-ready generic screen with explicit sections, components, states, interactions, and source evidence from the screen brief.</p></section>${stateStrip(screen.requiredStates)}${evidenceGrid(screen, "generic", 8)}`;
215
+ return `<section class="panel"><h3>${escapeHtml(screen.title)} workspace</h3><p>Renderer-ready generic screen with explicit sections, components, states, interactions, and source evidence from the screen brief.</p></section>${stateStrip(screen.requiredStates)}`;
216
+ }
217
+
218
+ function renderRoleContent({ screen, role, brief }) {
219
+ if (brief) return renderBriefContent({ screen, role, brief });
220
+ return renderNeedsBrief(screen, role);
161
221
  }
162
222
 
163
- function renderRoleContent(screen, role) {
223
+ function renderLegacyRoleContent(screen, role) {
164
224
  if (role === "home") return renderHome(screen);
165
225
  if (role === "category") return renderCategory(screen);
166
226
  if (role === "product-detail") return renderProductDetail(screen);
@@ -217,7 +277,7 @@ function indexHtml({ screens, profile, densityReport, styleName }) {
217
277
  <header class="topbar">
218
278
  <div class="topline">
219
279
  <strong>SDTK-DESIGN multi-page prototype</strong>
220
- <span class="meta">screens=${screens.length} | profile=${escapeHtml(profile || "none")} | density=${densityReport.totalBytes}/${densityReport.minTotalBytes} bytes</span>
280
+ <span class="meta">screens=${screens.length} | profile=${escapeHtml(profile || "none")} | density=${densityReport.mode === "semantic" ? `${densityReport.aggregate.sectionCount} sections / ${densityReport.aggregate.componentCount} components` : `${densityReport.totalBytes}/${densityReport.minTotalBytes} bytes`}</span>
221
281
  </div>
222
282
  <nav class="screen-nav" aria-label="Screen navigation">
223
283
  ${screens.map((screen) => `<a href="screens/${screenFileName(screen)}">${escapeHtml(screen.title)}</a>`).join("")}
@@ -232,9 +292,11 @@ function indexHtml({ screens, profile, densityReport, styleName }) {
232
292
  `;
233
293
  }
234
294
 
235
- function renderScreenHtml({ screen, role, previousScreen, nextScreen }) {
236
- const content = renderRoleContent(screen, role);
237
- const initial = pageChrome({
295
+ function renderScreenHtml({ screen, role, brief, previousScreen, nextScreen }) {
296
+ const content = renderRoleContent({ screen, role, brief });
297
+ const hasBriefSections = Boolean(brief && Array.isArray(brief.sections) && brief.sections.length > 0);
298
+ const heroHeadingTag = hasBriefSections ? "h2" : "h1";
299
+ return pageChrome({
238
300
  title: screen.title,
239
301
  cssHref: "../assets/prototype.css",
240
302
  backHref: "../index.html",
@@ -242,27 +304,14 @@ function renderScreenHtml({ screen, role, previousScreen, nextScreen }) {
242
304
  next: { title: nextScreen.title, href: screenFileName(nextScreen) },
243
305
  screenId: screen.screenId,
244
306
  role,
245
- bodyContent: `<section class="hero"><p class="eyebrow">${escapeHtml(role.replace(/-/g, " "))}</p><h1>${escapeHtml(screen.title)}</h1><p class="support">${escapeHtml(screen.purpose || screen.userIntent || "Renderer-ready screen generated from explicit design brief.")}</p></section>${content}`,
307
+ bodyContent: `<section class="hero" data-component-id="hero"><p class="eyebrow">${escapeHtml(role.replace(/-/g, " "))}</p><${heroHeadingTag}>${escapeHtml(screen.title)}</${heroHeadingTag}><p class="support">${escapeHtml(screen.purpose || screen.userIntent || "Renderer-ready screen generated from explicit design brief.")}</p></section>${content}`,
246
308
  });
247
- return ensureDensityTarget(initial, screen, role);
248
- }
249
-
250
- function ensureDensityTarget(html, screen, role) {
251
- const floor = ROLE_FLOORS[role] || { minBytes: 0 };
252
- const targetBytes = floor.minBytes > 0 ? floor.minBytes + 2300 : Buffer.byteLength(html, "utf8");
253
- let result = html;
254
- let index = 1;
255
- while (Buffer.byteLength(result, "utf8") < targetBytes && index <= 80) {
256
- const block = `<section class="density-evidence" aria-label="Additional screen detail ${index}"><article><strong>${escapeHtml(screen.title)} implementation detail ${index}</strong><p>${escapeHtml(role)} page includes source-backed layout notes, component state behavior, responsive grouping, data slot mapping, accessibility guidance, and static interaction acceptance evidence for renderer and QA review.</p></article><article><strong>${escapeHtml(screen.title)} QA checkpoint ${index}</strong><p>Generated content remains local static prototype output under docs/design/prototype and does not depend on sample files, network calls, app runtime code, or external UI references.</p></article></section>`;
257
- result = result.replace("</main>", `${block}</main>`);
258
- index += 1;
259
- }
260
- return result;
261
309
  }
262
310
 
263
- function renderMultiPagePrototype({ paths, statePayload, contractBundle, styleName }) {
311
+ function renderMultiPagePrototype({ paths, statePayload, contractBundle, styleName, briefIndex }) {
264
312
  const screens = statePayload.screenModel.screens;
265
313
  const tokens = contractBundle.tokens || {};
314
+ const resolvedBriefIndex = briefIndex || loadPrototypeBriefIndex({ paths });
266
315
  fs.mkdirSync(paths.prototypePath, { recursive: true });
267
316
  fs.mkdirSync(paths.prototypeScreensPath, { recursive: true });
268
317
  fs.mkdirSync(paths.prototypeAssetsPath, { recursive: true });
@@ -282,11 +331,17 @@ function renderMultiPagePrototype({ paths, statePayload, contractBundle, styleNa
282
331
  ? contractBundle.screenContracts.get(screen.screenId).sidecar.template_role
283
332
  : null,
284
333
  });
285
- const html = renderScreenHtml({ screen, role, previousScreen, nextScreen });
334
+ const screenContract =
335
+ contractBundle.screenContracts && contractBundle.screenContracts.get(screen.screenId)
336
+ ? contractBundle.screenContracts.get(screen.screenId)
337
+ : null;
338
+ const brief = briefForScreen({ screen, briefIndex: resolvedBriefIndex }) || (screenContract && screenContract.sidecar ? screenContract.sidecar : null);
339
+ const html = renderScreenHtml({ screen, role, brief, previousScreen, nextScreen });
286
340
  return {
287
341
  screenId: screen.screenId,
288
342
  title: screen.title,
289
343
  role,
344
+ brief,
290
345
  relativePath: screenRelativePath(screen),
291
346
  filePath: path.join(paths.prototypeScreensPath, screenFileName(screen)),
292
347
  html,
@@ -315,11 +370,13 @@ function renderMultiPagePrototype({ paths, statePayload, contractBundle, styleNa
315
370
  byteLength: Buffer.byteLength(page.html, "utf8"),
316
371
  })),
317
372
  densityReport,
373
+ briefFindings: resolvedBriefIndex.findings || [],
318
374
  };
319
375
  }
320
376
 
321
377
  module.exports = {
322
378
  renderMultiPagePrototype,
379
+ renderLegacyRoleContent,
323
380
  screenFileName,
324
381
  screenRelativePath,
325
382
  };