sdtk-design-kit 0.1.0 → 0.1.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.
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { parseFlags } = require("../lib/args");
6
+ const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths");
7
+ const { inferDomainProfile } = require("../lib/domain-profile");
8
+ const { ValidationError } = require("../lib/errors");
9
+ const { DEFAULT_STYLE, availableStyleNames, resolveStyleName } = require("../lib/style-presets");
10
+
11
+ const PROTOTYPE_FLAG_DEFS = {
12
+ help: { type: "boolean" },
13
+ "project-path": { type: "string" },
14
+ force: { type: "boolean" },
15
+ style: { type: "string" },
16
+ };
17
+
18
+ const REQUIRED_ARTIFACTS = [
19
+ ["docs/design/DESIGN_BRIEF.md", "designBriefPath"],
20
+ ["docs/design/SCREEN_MAP.md", "screenMapPath"],
21
+ ["docs/design/DESIGN_SYSTEM.md", "designSystemPath"],
22
+ ["docs/design/wireframes/LANDING.md", "landingWireframePath"],
23
+ ["docs/design/wireframes/ONBOARDING.md", "onboardingWireframePath"],
24
+ ["docs/design/wireframes/DASHBOARD.md", "dashboardWireframePath"],
25
+ ];
26
+
27
+ const THEME_TOKENS = {
28
+ "minimal-saas": {
29
+ bg: "#F7F8FA",
30
+ surface: "#FFFFFF",
31
+ text: "#1F2933",
32
+ muted: "#667085",
33
+ primary: "#2563EB",
34
+ accent: "#0F8A5F",
35
+ border: "#D9DEE7",
36
+ shadow: "0 14px 40px rgba(31,41,51,0.10)",
37
+ },
38
+ "premium-dashboard": {
39
+ bg: "#F5F7FB",
40
+ surface: "#FFFFFF",
41
+ text: "#111827",
42
+ muted: "#64748B",
43
+ primary: "#0F766E",
44
+ accent: "#2563EB",
45
+ border: "#D8E0EA",
46
+ shadow: "0 18px 48px rgba(15,23,42,0.12)",
47
+ },
48
+ "bold-founder": {
49
+ bg: "#111111",
50
+ surface: "#18181B",
51
+ text: "#FAFAFA",
52
+ muted: "#A1A1AA",
53
+ primary: "#F97316",
54
+ accent: "#22C55E",
55
+ border: "#27272A",
56
+ shadow: "0 20px 0 rgba(249,115,22,0.24)",
57
+ },
58
+ "warm-editorial": {
59
+ bg: "#FAF7F2",
60
+ surface: "#FFFFFF",
61
+ text: "#1C1A17",
62
+ muted: "#8A817A",
63
+ primary: "#C0512F",
64
+ accent: "#2F5B4F",
65
+ border: "rgba(47,91,79,0.16)",
66
+ shadow: "0 16px 36px rgba(28,26,23,0.08)",
67
+ },
68
+ };
69
+
70
+ function cmdPrototypeHelp() {
71
+ console.log(`SDTK-DESIGN Prototype
72
+
73
+ Usage:
74
+ sdtk-design prototype [--style <preset>] [--project-path <path>] [--force]
75
+
76
+ Example:
77
+ sdtk-design prototype --style premium-dashboard
78
+
79
+ Style presets:
80
+ ${availableStyleNames().join(", ")}
81
+ Default: inferred from docs/design/DESIGN_SYSTEM.md, then ${DEFAULT_STYLE}
82
+
83
+ Reads:
84
+ docs/design/DESIGN_BRIEF.md
85
+ docs/design/SCREEN_MAP.md
86
+ docs/design/DESIGN_SYSTEM.md
87
+ docs/design/wireframes/LANDING.md
88
+ docs/design/wireframes/ONBOARDING.md
89
+ docs/design/wireframes/DASHBOARD.md
90
+
91
+ Creates:
92
+ docs/design/prototype/index.html
93
+
94
+ Safety:
95
+ Local files only.
96
+ Existing prototype index is not overwritten unless --force is explicit.
97
+ No production app code outside docs/design/prototype.
98
+ No JavaScript runtime, server, network call, .sdtk/atlas, or SDTK-WIKI output mutation.`);
99
+ return 0;
100
+ }
101
+
102
+ function wireframePath(paths, fileName) {
103
+ return path.join(paths.wireframesPath, fileName);
104
+ }
105
+
106
+ function requiredArtifactTargets(paths) {
107
+ return REQUIRED_ARTIFACTS.map(([relativePath, key]) => {
108
+ const filePath =
109
+ key === "landingWireframePath"
110
+ ? wireframePath(paths, "LANDING.md")
111
+ : key === "onboardingWireframePath"
112
+ ? wireframePath(paths, "ONBOARDING.md")
113
+ : key === "dashboardWireframePath"
114
+ ? wireframePath(paths, "DASHBOARD.md")
115
+ : paths[key];
116
+ return { relativePath, filePath };
117
+ });
118
+ }
119
+
120
+ function firstLine(content, fallback) {
121
+ const line = String(content || "")
122
+ .split(/\r?\n/)
123
+ .map((value) => value.trim())
124
+ .find(Boolean);
125
+ return line ? line.replace(/^#\s*/, "") : fallback;
126
+ }
127
+
128
+ function escapeHtml(value) {
129
+ return String(value)
130
+ .replace(/&/g, "&amp;")
131
+ .replace(/</g, "&lt;")
132
+ .replace(/>/g, "&gt;")
133
+ .replace(/"/g, "&quot;");
134
+ }
135
+
136
+ function inferStyleFromDesignSystem(designSystemContent) {
137
+ const content = String(designSystemContent || "");
138
+ return availableStyleNames().find((styleName) => new RegExp(`Preset:\\s*${styleName}\\b`, "i").test(content)) || DEFAULT_STYLE;
139
+ }
140
+
141
+ function prototypeContent({ style, briefContent, screenMapContent, designSystemContent, wireframeContents = [] }) {
142
+ const styleName = resolveStyleName(style);
143
+ const tokens = THEME_TOKENS[styleName];
144
+ const profile = inferDomainProfile({ briefContent, screenMapContent, designSystemContent, wireframeContents });
145
+
146
+ return `<!doctype html>
147
+ <html lang="en">
148
+ <head>
149
+ <meta charset="utf-8">
150
+ <meta name="viewport" content="width=device-width, initial-scale=1">
151
+ <title>${escapeHtml(profile.productName)} Prototype</title>
152
+ <style>
153
+ :root {
154
+ --bg: ${tokens.bg};
155
+ --surface: ${tokens.surface};
156
+ --text: ${tokens.text};
157
+ --muted: ${tokens.muted};
158
+ --primary: ${tokens.primary};
159
+ --accent: ${tokens.accent};
160
+ --border: ${tokens.border};
161
+ --shadow: ${tokens.shadow};
162
+ }
163
+ * { box-sizing: border-box; }
164
+ body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.5 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
165
+ .sdtk-prototype { width: min(1180px, calc(100vw - 32px)); margin: 0 auto; padding: 32px 0 56px; }
166
+ .prototype-header { display: flex; justify-content: space-between; gap: 20px; align-items: center; margin-bottom: 22px; }
167
+ .brand { font-weight: 800; letter-spacing: 0; }
168
+ .style-pill, .status-pill { display: inline-flex; align-items: center; min-height: 28px; border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; color: var(--muted); background: var(--surface); font-size: 12px; font-weight: 750; }
169
+ .prototype-section { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; box-shadow: var(--shadow); margin: 18px 0; overflow: hidden; }
170
+ .section-inner { padding: clamp(22px, 4vw, 44px); }
171
+ .eyebrow { margin: 0 0 8px; color: var(--primary); font-size: 12px; font-weight: 850; text-transform: uppercase; letter-spacing: 0; }
172
+ h1, h2, h3, p { margin-top: 0; }
173
+ h1 { max-width: 860px; font-size: clamp(34px, 7vw, 72px); line-height: 1.02; margin-bottom: 16px; }
174
+ h2 { font-size: clamp(26px, 4vw, 40px); line-height: 1.12; margin-bottom: 12px; }
175
+ h3 { font-size: 16px; margin-bottom: 8px; }
176
+ .support { max-width: 700px; color: var(--muted); font-size: 18px; }
177
+ .button-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }
178
+ .btn { display: inline-flex; align-items: center; justify-content: center; min-height: 42px; border-radius: 8px; padding: 0 18px; font-weight: 800; text-decoration: none; }
179
+ .btn-primary { background: var(--primary); color: #fff; }
180
+ .btn-secondary { color: var(--text); border: 1px solid var(--border); background: transparent; }
181
+ .workflow-grid, .metric-grid, .dashboard-grid { display: grid; gap: 14px; }
182
+ .workflow-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 28px; }
183
+ .workflow-card, .metric-card, .item-card, .form-card, .empty-state { border: 1px solid var(--border); border-radius: 8px; padding: 16px; background: color-mix(in srgb, var(--surface) 92%, var(--bg)); }
184
+ .workflow-card strong, .metric-card strong { display: block; font-size: 24px; }
185
+ .onboarding-layout { display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(320px, 0.55fr); gap: 20px; align-items: start; }
186
+ .field { display: grid; gap: 6px; margin-bottom: 12px; }
187
+ .input-preview { min-height: 42px; border: 1px solid var(--border); border-radius: 8px; display: flex; align-items: center; padding: 0 12px; color: var(--muted); }
188
+ .metric-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 16px 0; }
189
+ .dashboard-grid { grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr); }
190
+ .item-card { display: grid; gap: 8px; }
191
+ .item-meta { display: flex; flex-wrap: wrap; gap: 8px; }
192
+ .status-pill { color: var(--primary); }
193
+ .empty-state { color: var(--muted); border-style: dashed; }
194
+ .style-premium-dashboard .metric-card strong { font-size: 30px; }
195
+ .style-bold-founder h1 { text-transform: uppercase; }
196
+ .style-warm-editorial h1, .style-warm-editorial h2 { font-family: Georgia, "Times New Roman", serif; }
197
+ @media (max-width: 780px) {
198
+ .prototype-header, .onboarding-layout { display: block; }
199
+ .workflow-grid, .metric-grid, .dashboard-grid { grid-template-columns: 1fr; }
200
+ h1 { font-size: 36px; }
201
+ }
202
+ </style>
203
+ </head>
204
+ <body>
205
+ <main class="sdtk-prototype style-${styleName}" data-style-preset="${styleName}">
206
+ <header class="prototype-header">
207
+ <div class="brand">${escapeHtml(profile.productName)}</div>
208
+ <span class="style-pill">Style preset: ${styleName}</span>
209
+ </header>
210
+
211
+ <section class="prototype-section section-landing" aria-labelledby="landing-title">
212
+ <div class="section-inner">
213
+ <p class="eyebrow">Landing</p>
214
+ <h1 id="landing-title">${escapeHtml(profile.promise)}</h1>
215
+ <p class="support">A demo-ready first screen with one clear CTA, a focused product promise, and a workflow preview that shows what happens after signup.</p>
216
+ <div class="button-row">
217
+ <a class="btn btn-primary" href="#onboarding">${escapeHtml(profile.primaryAction)}</a>
218
+ <a class="btn btn-secondary" href="#dashboard">${escapeHtml(profile.secondaryAction)}</a>
219
+ </div>
220
+ <div class="workflow-grid">
221
+ <div class="workflow-card"><strong>01</strong><span>Capture a ${escapeHtml(profile.itemSingular)} with status and context.</span></div>
222
+ <div class="workflow-card"><strong>02</strong><span>Record the next action before it slips.</span></div>
223
+ <div class="workflow-card"><strong>03</strong><span>Scan the dashboard and choose what to do next.</span></div>
224
+ </div>
225
+ </div>
226
+ </section>
227
+
228
+ <section class="prototype-section section-onboarding" id="onboarding" aria-labelledby="onboarding-title">
229
+ <div class="section-inner onboarding-layout">
230
+ <div>
231
+ <p class="eyebrow">Onboarding</p>
232
+ <h2 id="onboarding-title">Create a focused ${escapeHtml(profile.setupLabel.toLowerCase())} in one step.</h2>
233
+ <p class="support">The setup flow asks only for the minimum needed before the user can create the first ${escapeHtml(profile.itemSingular)}.</p>
234
+ </div>
235
+ <div class="form-card">
236
+ <div class="field"><strong>${escapeHtml(profile.setupNameLabel)}</strong><div class="input-preview">${escapeHtml(profile.setupNameValue)}</div></div>
237
+ <div class="field"><strong>Primary workflow</strong><div class="input-preview">${escapeHtml(profile.itemAction)}</div></div>
238
+ <a class="btn btn-primary" href="#dashboard">Continue</a>
239
+ </div>
240
+ </div>
241
+ </section>
242
+
243
+ <section class="prototype-section section-dashboard" id="dashboard" aria-labelledby="dashboard-title">
244
+ <div class="section-inner">
245
+ <p class="eyebrow">Dashboard</p>
246
+ <h2 id="dashboard-title">${escapeHtml(profile.dashboardTitle)}</h2>
247
+ <div class="metric-grid">
248
+ <div class="metric-card"><span>${escapeHtml(profile.metricLabels[0])}</span><strong>${escapeHtml(profile.metricValues[0])}</strong></div>
249
+ <div class="metric-card"><span>${escapeHtml(profile.metricLabels[1])}</span><strong>${escapeHtml(profile.metricValues[1])}</strong></div>
250
+ <div class="metric-card"><span>${escapeHtml(profile.metricLabels[2])}</span><strong>${escapeHtml(profile.metricValues[2])}</strong></div>
251
+ <div class="metric-card"><span>${escapeHtml(profile.metricLabels[3])}</span><strong>${escapeHtml(profile.metricValues[3])}</strong></div>
252
+ </div>
253
+ <div class="dashboard-grid">
254
+ <div class="form-card">
255
+ <h3>${escapeHtml(profile.itemAction)}</h3>
256
+ <div class="field"><strong>${escapeHtml(profile.itemNameLabel)}</strong><div class="input-preview">${escapeHtml(profile.itemNameValue)}</div></div>
257
+ <div class="field"><strong>${escapeHtml(profile.itemContextLabel)}</strong><div class="input-preview">${escapeHtml(profile.itemContextValue)}</div></div>
258
+ <a class="btn btn-primary" href="#dashboard">${escapeHtml(profile.saveAction)}</a>
259
+ </div>
260
+ <div class="item-card">
261
+ <h3>${escapeHtml(profile.itemNameValue)}</h3>
262
+ <div class="item-meta">
263
+ <span class="status-pill">${escapeHtml(profile.statusExample)}</span>
264
+ <span class="status-pill">${escapeHtml(profile.itemContextValue)}</span>
265
+ </div>
266
+ <p class="support">Keep context, status, and next action visible in the ${escapeHtml(profile.collectionSurface)}.</p>
267
+ <div class="empty-state">${escapeHtml(profile.emptyState)}</div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </section>
272
+ </main>
273
+ </body>
274
+ </html>
275
+ `;
276
+ }
277
+
278
+ function runDesignPrototype({ projectPath, force = false, style }) {
279
+ const explicitStyleName = typeof style === "string" && style.trim() ? resolveStyleName(style) : null;
280
+ const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
281
+ if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
282
+ throw new ValidationError(`--project-path is not a valid directory: ${resolvedProjectPath}. No project files were changed.`);
283
+ }
284
+
285
+ const paths = describeDesignPaths(resolvedProjectPath);
286
+ const missing = requiredArtifactTargets(paths).filter((target) => !fs.existsSync(target.filePath));
287
+ if (missing.length > 0) {
288
+ const list = missing.map((target) => target.relativePath).join(", ");
289
+ throw new ValidationError(`Missing required design artifacts: ${list}. Run sdtk-design start --idea "<idea>" first. No project files were changed.`);
290
+ }
291
+ if (fs.existsSync(paths.prototypeIndexPath) && !force) {
292
+ throw new ValidationError("docs/design/prototype/index.html already exists. Re-run with --force to replace this managed prototype.");
293
+ }
294
+
295
+ const designSystemContent = fs.readFileSync(paths.designSystemPath, "utf-8");
296
+ const styleName = explicitStyleName || inferStyleFromDesignSystem(designSystemContent);
297
+ const wireframeContents = REQUIRED_ARTIFACTS
298
+ .filter(([relativePath]) => relativePath.startsWith("docs/design/wireframes/"))
299
+ .map(([, key]) => {
300
+ const filePath =
301
+ key === "landingWireframePath"
302
+ ? wireframePath(paths, "LANDING.md")
303
+ : key === "onboardingWireframePath"
304
+ ? wireframePath(paths, "ONBOARDING.md")
305
+ : wireframePath(paths, "DASHBOARD.md");
306
+ return fs.readFileSync(filePath, "utf-8");
307
+ });
308
+ const content = prototypeContent({
309
+ style: styleName,
310
+ briefContent: fs.readFileSync(paths.designBriefPath, "utf-8"),
311
+ screenMapContent: fs.readFileSync(paths.screenMapPath, "utf-8"),
312
+ designSystemContent,
313
+ wireframeContents,
314
+ });
315
+
316
+ fs.mkdirSync(path.dirname(paths.prototypeIndexPath), { recursive: true });
317
+ fs.writeFileSync(paths.prototypeIndexPath, content, "utf-8");
318
+
319
+ return {
320
+ projectPath: resolvedProjectPath,
321
+ relativePrototypePath: "docs/design/prototype/index.html",
322
+ forced: Boolean(force),
323
+ style: styleName,
324
+ };
325
+ }
326
+
327
+ function cmdPrototype(args) {
328
+ const { flags } = parseFlags(args || [], PROTOTYPE_FLAG_DEFS);
329
+ if (flags.help) return cmdPrototypeHelp();
330
+
331
+ const result = runDesignPrototype({
332
+ projectPath: flags["project-path"],
333
+ force: Boolean(flags.force),
334
+ style: flags.style,
335
+ });
336
+
337
+ console.log(`[design] Wrote ${result.relativePrototypePath}: ${result.projectPath}`);
338
+ console.log(`[design] Style: ${result.style}`);
339
+ console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
340
+ console.log("[design] Prototype mode: static HTML/CSS preview only.");
341
+ console.log("[design] No app code outside docs/design/prototype, server, network, .sdtk/atlas, or SDTK-WIKI output was modified.");
342
+ console.log("[design] Next: sdtk-design review --artifact docs/design/prototype/index.html");
343
+ return 0;
344
+ }
345
+
346
+ module.exports = {
347
+ cmdPrototype,
348
+ cmdPrototypeHelp,
349
+ inferStyleFromDesignSystem,
350
+ prototypeContent,
351
+ runDesignPrototype,
352
+ };
@@ -18,20 +18,22 @@ function cmdReviewHelp() {
18
18
 
19
19
  Usage:
20
20
  sdtk-design review --artifact docs/design/wireframes/LANDING.md [--project-path <path>] [--force]
21
+ sdtk-design review --artifact docs/design/prototype/index.html [--project-path <path>] [--force]
21
22
 
22
23
  Example:
23
24
  sdtk-design review --artifact docs/design/wireframes/LANDING.md
25
+ sdtk-design review --artifact docs/design/prototype/index.html
24
26
 
25
27
  Reads:
26
- A project-local markdown design artifact.
28
+ A project-local markdown or static HTML design artifact.
27
29
 
28
30
  Creates:
29
31
  docs/design/reviews/DESIGN_REVIEW_YYYYMMDD.md
30
32
 
31
33
  Safety:
32
- Local markdown artifact review only.
34
+ Local artifact review only.
33
35
  Existing same-day review report is not overwritten unless --force is explicit.
34
- No URL, browser, screenshot, vision, or DOM review in BK-161.
36
+ No URL, browser, screenshot, vision, or DOM review.
35
37
  No .sdtk/atlas creation or mutation.
36
38
  No SDTK-WIKI output mutation.
37
39
  No network call, Pro entitlement, or production app code generation.`);
@@ -62,7 +64,55 @@ function hasHeading(content, heading) {
62
64
  return content.toLowerCase().includes(heading.toLowerCase());
63
65
  }
64
66
 
67
+ function isPrototypeHtmlArtifact(artifactRelativePath, artifactContent) {
68
+ const lowerPath = toPosix(artifactRelativePath).toLowerCase();
69
+ const lowerContent = String(artifactContent || "").toLowerCase();
70
+ return lowerPath.endsWith(".html") || lowerContent.includes("<!doctype html") || lowerContent.includes("<html");
71
+ }
72
+
73
+ function includesAny(content, terms) {
74
+ const lower = String(content || "").toLowerCase();
75
+ return terms.some((term) => lower.includes(term.toLowerCase()));
76
+ }
77
+
78
+ function visualPolishChecks({ artifactRelativePath, artifactContent, prototypeHtml }) {
79
+ const lower = String(artifactContent || "").toLowerCase();
80
+ const hasLanding = lower.includes("section-landing") || lower.includes("landing");
81
+ const hasOnboarding = lower.includes("section-onboarding") || lower.includes("onboarding");
82
+ const hasDashboard = lower.includes("section-dashboard") || lower.includes("dashboard");
83
+ const hasPreset = lower.includes("data-style-preset") || lower.includes("style-premium-dashboard") || lower.includes("style-minimal-saas") || lower.includes("style-bold-founder") || lower.includes("style-warm-editorial");
84
+ const hasResponsive = lower.includes("@media") || lower.includes("viewport") || lower.includes("mobile");
85
+ const hasCta = includesAny(artifactContent, ["btn-primary", "Primary CTA", "Create workspace", "Add lead", "Continue", "Start"]);
86
+ const hasDashboardDensity = hasDashboard && includesAny(artifactContent, ["metric", "kpi", "pipeline", "status", "lead", "table", "card"]);
87
+ const hasAccessibility = includesAny(artifactContent, ["accessibility", "aria-", "button", "label", "44px", "focus"]);
88
+ const hasSpacing = includesAny(artifactContent, ["gap:", "padding:", "spacing", "density", "grid", "margin"]);
89
+ const modeEvidence = prototypeHtml
90
+ ? "Prototype HTML is checked as visual direction only, not production application code."
91
+ : "Markdown artifact is checked for implementation-ready visual direction.";
92
+
93
+ return [
94
+ "## Visual Polish Checks",
95
+ "",
96
+ "| Check | Result | Evidence |",
97
+ "|---|---|---|",
98
+ `| Hierarchy | ${hasLanding && hasOnboarding && hasDashboard ? "Pass" : "Needs attention"} | ${hasLanding && hasOnboarding && hasDashboard ? "Landing, onboarding, and dashboard sections create a clear read order." : "Ensure the artifact clearly separates landing, onboarding, and dashboard intent."} |`,
99
+ `| Spacing / Density | ${hasSpacing ? "Pass" : "Needs attention"} | ${hasSpacing ? "Artifact includes grid, gap, padding, margin, or spacing guidance." : "Add spacing and density rules so the implementation does not regress into a flat wireframe."} |`,
100
+ `| CTA Clarity | ${hasCta ? "Pass" : "Needs attention"} | ${hasCta ? "A concrete primary action is visible in the artifact." : "Add one dominant CTA with concrete action copy."} |`,
101
+ `| Dashboard Information Density | ${hasDashboardDensity ? "Pass" : "Needs attention"} | ${hasDashboardDensity ? "Dashboard direction includes metrics, pipeline/status, cards, lists, or table surfaces." : "Dashboard needs enough KPI, status, list, or table detail to guide a real MVP screen."} |`,
102
+ `| Responsive / Mobile Risk | ${hasResponsive ? "Pass" : "Needs attention"} | ${hasResponsive ? "Responsive or mobile behavior is represented." : "Add mobile breakpoint or narrow-screen behavior before implementation."} |`,
103
+ `| Accessibility Baseline | ${hasAccessibility ? "Pass" : "Needs attention"} | ${hasAccessibility ? "Artifact includes accessible-control or baseline accessibility signals." : "Add labels, focus, touch-target, and color-not-alone guidance."} |`,
104
+ `| Anti-Wireframe / Mockup Warning | ${prototypeHtml && hasPreset ? "Pass" : "Needs attention"} | ${prototypeHtml && hasPreset ? "Prototype includes style preset tokens/classes and should guide visual polish beyond basic wireframes." : "Do not treat the artifact as complete UI; add concrete style preset tokens/classes or visual contract details."} |`,
105
+ "",
106
+ `- Reviewed visual artifact: \`${artifactRelativePath}\`.`,
107
+ `- ${modeEvidence}`,
108
+ "- Keep the output local-first: no browser, screenshot, network, Pro entitlement, or app generation is required for this review.",
109
+ "",
110
+ ];
111
+ }
112
+
65
113
  function reviewContent({ artifactRelativePath, artifactContent }) {
114
+ const prototypeHtml = isPrototypeHtmlArtifact(artifactRelativePath, artifactContent);
115
+ const reviewMode = prototypeHtml ? "local prototype HTML review" : "local markdown artifact review";
66
116
  const hasPrimaryCta = hasHeading(artifactContent, "Primary CTA") || artifactContent.toLowerCase().includes("cta");
67
117
  const hasMobile = hasHeading(artifactContent, "Mobile Notes") || artifactContent.toLowerCase().includes("mobile");
68
118
  const hasStates = hasHeading(artifactContent, "State Handling") || ["empty", "success", "error"].every((term) => artifactContent.toLowerCase().includes(term));
@@ -74,7 +124,7 @@ function reviewContent({ artifactRelativePath, artifactContent }) {
74
124
  "## Reviewed Artifact",
75
125
  "",
76
126
  `- Artifact: \`${artifactRelativePath}\``,
77
- "- Review mode: local markdown artifact review",
127
+ `- Review mode: ${reviewMode}`,
78
128
  "- No URL, browser, screenshot, vision, or DOM review was used.",
79
129
  "",
80
130
  "## Findings By Severity",
@@ -86,6 +136,7 @@ function reviewContent({ artifactRelativePath, artifactContent }) {
86
136
  `| Medium | ${hasStates ? "State handling is represented." : "Empty, success, or error states are incomplete."} | ${hasStates ? "Carry the states into implementation acceptance criteria." : "Document empty, success, and error handling."} |`,
87
137
  `| Low | ${hasAcceptance ? "Acceptance criteria are present." : "Acceptance criteria are missing."} | ${hasAcceptance ? "Use criteria as SDTK-CODE test obligations." : "Add concrete acceptance criteria before coding."} |`,
88
138
  "",
139
+ ...visualPolishChecks({ artifactRelativePath, artifactContent, prototypeHtml }),
89
140
  "## UX Issues",
90
141
  "",
91
142
  "- Keep the screen focused on one job and one dominant next action.",
@@ -171,6 +222,7 @@ module.exports = {
171
222
  cmdReview,
172
223
  cmdReviewHelp,
173
224
  formatDateYYYYMMDD,
225
+ isPrototypeHtmlArtifact,
174
226
  reviewContent,
175
227
  runDesignReview,
176
228
  };
@@ -9,12 +9,14 @@ const { runDesignWireframe } = require("./wireframe");
9
9
  const { parseFlags } = require("../lib/args");
10
10
  const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths");
11
11
  const { ValidationError } = require("../lib/errors");
12
+ const { DEFAULT_STYLE, availableStyleNames, resolveStyleName } = require("../lib/style-presets");
12
13
 
13
14
  const START_FLAG_DEFS = {
14
15
  help: { type: "boolean" },
15
16
  idea: { type: "string" },
16
17
  "project-path": { type: "string" },
17
18
  force: { type: "boolean" },
19
+ style: { type: "string" },
18
20
  };
19
21
 
20
22
  const REQUIRED_WIREFRAME_FILES = ["LANDING.md", "ONBOARDING.md", "DASHBOARD.md"];
@@ -23,10 +25,15 @@ function cmdStartHelp() {
23
25
  console.log(`SDTK-DESIGN Start
24
26
 
25
27
  Usage:
26
- sdtk-design start --idea "<rough MVP idea>" [--project-path <path>] [--force]
28
+ sdtk-design start --idea "<rough MVP idea>" [--style <preset>] [--project-path <path>] [--force]
27
29
 
28
30
  Example:
29
31
  sdtk-design start --idea "I want to build a lightweight CRM for solo consultants to track leads."
32
+ sdtk-design start --idea "ClientPulse for consultants" --style premium-dashboard
33
+
34
+ Style presets:
35
+ ${availableStyleNames().join(", ")}
36
+ Default: ${DEFAULT_STYLE}
30
37
 
31
38
  Runs:
32
39
  brief -> screens -> wireframe --screen all -> system
@@ -65,11 +72,12 @@ function coreOutputTargets(paths) {
65
72
  ];
66
73
  }
67
74
 
68
- function runDesignStart({ idea, projectPath, force = false }) {
75
+ function runDesignStart({ idea, projectPath, force = false, style = DEFAULT_STYLE }) {
69
76
  const normalizedIdea = normalizeIdea(idea);
70
77
  if (!normalizedIdea) {
71
78
  throw new ValidationError('Missing required --idea "<rough MVP idea>". No project files were changed.');
72
79
  }
80
+ const styleName = resolveStyleName(style);
73
81
 
74
82
  const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
75
83
  if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
@@ -86,12 +94,13 @@ function runDesignStart({ idea, projectPath, force = false }) {
86
94
  runDesignBrief({ idea: normalizedIdea, projectPath: resolvedProjectPath, force });
87
95
  runDesignScreens({ projectPath: resolvedProjectPath, force });
88
96
  runDesignWireframe({ screen: "all", projectPath: resolvedProjectPath, force });
89
- runDesignSystem({ projectPath: resolvedProjectPath, force });
97
+ runDesignSystem({ projectPath: resolvedProjectPath, force, style: styleName });
90
98
 
91
99
  return {
92
100
  projectPath: resolvedProjectPath,
93
101
  written: coreOutputTargets(paths).map((target) => target.relativePath),
94
102
  forced: Boolean(force),
103
+ style: styleName,
95
104
  };
96
105
  }
97
106
 
@@ -103,13 +112,15 @@ function cmdStart(args) {
103
112
  idea: flags.idea,
104
113
  projectPath: flags["project-path"],
105
114
  force: Boolean(flags.force),
115
+ style: flags.style,
106
116
  });
107
117
 
108
118
  console.log(`[design] Started SDTK-DESIGN package: ${result.projectPath}`);
109
119
  console.log(`[design] Wrote core artifacts: ${result.written.join(", ")}`);
120
+ console.log(`[design] Style: ${result.style}`);
110
121
  console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
111
122
  console.log("[design] No review, handoff, URL, browser, screenshot, vision, network, .sdtk/atlas, or SDTK-WIKI output was used.");
112
- console.log("[design] Next: sdtk-design review --artifact docs/design/wireframes/LANDING.md");
123
+ console.log("[design] Next: sdtk-design prototype");
113
124
  return 0;
114
125
  }
115
126
 
@@ -41,6 +41,7 @@ function artifactPlan(paths) {
41
41
  { label: "Onboarding wireframe", relativePath: "docs/design/wireframes/ONBOARDING.md", filePath: path.join(paths.wireframesPath, "ONBOARDING.md"), phase: "core" },
42
42
  { label: "Dashboard wireframe", relativePath: "docs/design/wireframes/DASHBOARD.md", filePath: path.join(paths.wireframesPath, "DASHBOARD.md"), phase: "core" },
43
43
  { label: "Design system", relativePath: "docs/design/DESIGN_SYSTEM.md", filePath: paths.designSystemPath, phase: "core" },
44
+ { label: "Prototype preview", relativePath: "docs/design/prototype/index.html", filePath: paths.prototypeIndexPath, phase: "prototype" },
44
45
  { label: "Design handoff", relativePath: "docs/design/DESIGN_HANDOFF.md", filePath: paths.designHandoffPath, phase: "handoff" },
45
46
  ];
46
47
  }
@@ -65,13 +66,16 @@ function inspectDesignStatus(projectPath) {
65
66
  }));
66
67
  const reviewExists = hasReview(paths);
67
68
  const coreMissing = artifacts.filter((artifact) => artifact.phase === "core" && !artifact.exists);
69
+ const prototypeMissing = artifacts.find((artifact) => artifact.phase === "prototype" && !artifact.exists);
68
70
  const handoffMissing = artifacts.find((artifact) => artifact.phase === "handoff" && !artifact.exists);
69
71
 
70
72
  let nextCommand = "SDTK-CODE can consume docs/design/DESIGN_HANDOFF.md";
71
73
  if (coreMissing.length > 0) {
72
74
  nextCommand = 'sdtk-design start --idea "<idea>"';
75
+ } else if (prototypeMissing) {
76
+ nextCommand = "sdtk-design prototype";
73
77
  } else if (!reviewExists) {
74
- nextCommand = "sdtk-design review --artifact docs/design/wireframes/LANDING.md";
78
+ nextCommand = "sdtk-design review --artifact docs/design/prototype/index.html";
75
79
  } else if (handoffMissing) {
76
80
  nextCommand = "sdtk-design handoff";
77
81
  }