sdtk-design-kit 0.2.1 → 0.3.0

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.
@@ -30,53 +30,300 @@ const BASELINE_PATTERNS = [
30
30
  "Primary and secondary action row",
31
31
  ];
32
32
 
33
- function contractTokens(profile, statePayload) {
33
+ const TYPOGRAPHY_RAMP_V2 = {
34
+ display: { fontSize: 48, fontWeight: 800, lineHeight: 1.05, letterSpacing: 0 },
35
+ h1: { fontSize: 40, fontWeight: 800, lineHeight: 1.1, letterSpacing: 0 },
36
+ h2: { fontSize: 32, fontWeight: 750, lineHeight: 1.15, letterSpacing: 0 },
37
+ h3: { fontSize: 24, fontWeight: 700, lineHeight: 1.2, letterSpacing: 0 },
38
+ h4: { fontSize: 20, fontWeight: 700, lineHeight: 1.25, letterSpacing: 0 },
39
+ h5: { fontSize: 18, fontWeight: 700, lineHeight: 1.3, letterSpacing: 0 },
40
+ h6: { fontSize: 16, fontWeight: 700, lineHeight: 1.35, letterSpacing: 0 },
41
+ bodyLg: { fontSize: 18, fontWeight: 400, lineHeight: 1.55, letterSpacing: 0 },
42
+ body: { fontSize: 16, fontWeight: 400, lineHeight: 1.55, letterSpacing: 0 },
43
+ bodySm: { fontSize: 14, fontWeight: 400, lineHeight: 1.5, letterSpacing: 0 },
44
+ caption: { fontSize: 12, fontWeight: 600, lineHeight: 1.4, letterSpacing: 0 },
45
+ };
46
+
47
+ const FONT_FAMILY_V2 = {
48
+ display: "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
49
+ body: "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
50
+ mono: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
51
+ };
52
+
53
+ const SECTION_TOKENS_V2 = {
54
+ containerMaxWidth: 1180,
55
+ contentMaxWidth: 760,
56
+ sectionGap: 40,
57
+ heroMinHeight: 520,
58
+ gridColumns: 12,
59
+ };
60
+
61
+ const COMPONENT_TOKENS_V2 = {
62
+ radius: { card: 8, control: 8, pill: 999 },
63
+ shadow: {
64
+ none: "none",
65
+ sm: "0 1px 2px rgba(15, 23, 42, 0.08)",
66
+ md: "0 8px 24px rgba(15, 23, 42, 0.10)",
67
+ lg: "0 18px 45px rgba(15, 23, 42, 0.12)",
68
+ },
69
+ borderWidth: { hairline: 1, default: 1, strong: 2 },
70
+ };
71
+
72
+ const STATE_TOKENS_V2 = {
73
+ success: { base: "#15803D", soft: "#DCFCE7", text: "#166534" },
74
+ warning: { base: "#B45309", soft: "#FEF3C7", text: "#92400E" },
75
+ error: { base: "#B91C1C", soft: "#FEE2E2", text: "#991B1B" },
76
+ info: { base: "#2563EB", soft: "#DBEAFE", text: "#1E40AF" },
77
+ };
78
+
79
+ const PROFILE_PALETTE_DEFAULTS = {
80
+ "b2b-commerce": {
81
+ primary: "#0F766E", primaryHover: "#115E59", primarySoft: "#CCFBF1",
82
+ accent: "#2563EB", accentHover: "#1D4ED8", accentSoft: "#DBEAFE",
83
+ surface: "#FFFFFF", surfaceAlt: "#F7F8FA", surfaceInverse: "#111827",
84
+ textPrimary: "#111827", textSecondary: "#334155", textMuted: "#64748B",
85
+ border: "#D8E0EA", borderStrong: "#94A3B8",
86
+ state: STATE_TOKENS_V2,
87
+ },
88
+ "b2b-industrial-commerce": {
89
+ primary: "#0F766E", primaryHover: "#115E59", primarySoft: "#CCFBF1",
90
+ accent: "#2563EB", accentHover: "#1D4ED8", accentSoft: "#DBEAFE",
91
+ surface: "#FFFFFF", surfaceAlt: "#F7F8FA", surfaceInverse: "#111827",
92
+ textPrimary: "#111827", textSecondary: "#334155", textMuted: "#64748B",
93
+ border: "#D8E0EA", borderStrong: "#94A3B8",
94
+ state: STATE_TOKENS_V2,
95
+ },
96
+ generic: {
97
+ primary: "#2563EB", primaryHover: "#1D4ED8", primarySoft: "#DBEAFE",
98
+ accent: "#0F766E", accentHover: "#115E59", accentSoft: "#CCFBF1",
99
+ surface: "#FFFFFF", surfaceAlt: "#F7F8FA", surfaceInverse: "#111827",
100
+ textPrimary: "#111827", textSecondary: "#334155", textMuted: "#64748B",
101
+ border: "#D8E0EA", borderStrong: "#94A3B8",
102
+ state: STATE_TOKENS_V2,
103
+ },
104
+ };
105
+
106
+ const PROFILE_BRAND_DEFAULTS = {
107
+ "b2b-commerce": { mood: "professional-b2b", density: "balanced" },
108
+ "b2b-industrial-commerce": { mood: "industrial-b2b", density: "balanced" },
109
+ generic: { mood: "general-purpose", density: "balanced" },
110
+ };
111
+
112
+ const HEX_COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
113
+ const BRAND_OVERRIDE_HEX_KEYS = [
114
+ "primary", "primaryHover", "primarySoft",
115
+ "accent", "accentHover", "accentSoft",
116
+ "surface", "surfaceAlt", "surfaceInverse",
117
+ "textPrimary", "textSecondary", "textMuted",
118
+ "border", "borderStrong",
119
+ ];
120
+ const VALID_DENSITIES = ["compact", "balanced", "spacious"];
121
+
122
+ function isValidHex(value) {
123
+ return typeof value === "string" && HEX_COLOR_RE.test(value);
124
+ }
125
+
126
+ function validateBrandOverride(override, filePath) {
127
+ const ctx = filePath ? ` in ${filePath}` : "";
128
+ if (!override || typeof override !== "object" || Array.isArray(override)) {
129
+ throw new ValidationError(`--brand-tokens must be a JSON object${ctx}. No project files were changed.`);
130
+ }
131
+ if (override.palette !== undefined) {
132
+ if (typeof override.palette !== "object" || Array.isArray(override.palette)) {
133
+ throw new ValidationError(`--brand-tokens palette must be an object${ctx}. No project files were changed.`);
134
+ }
135
+ for (const key of BRAND_OVERRIDE_HEX_KEYS) {
136
+ if (key in override.palette && !isValidHex(override.palette[key])) {
137
+ throw new ValidationError(
138
+ `--brand-tokens palette.${key} must be a valid 6-digit hex color (e.g. "#0F766E")${ctx}. No project files were changed.`
139
+ );
140
+ }
141
+ }
142
+ }
143
+ if (override.fontFamily !== undefined && (typeof override.fontFamily !== "object" || Array.isArray(override.fontFamily))) {
144
+ throw new ValidationError(`--brand-tokens fontFamily must be an object${ctx}. No project files were changed.`);
145
+ }
146
+ if (override.mood !== undefined && (typeof override.mood !== "string" || !override.mood.trim())) {
147
+ throw new ValidationError(`--brand-tokens mood must be a non-empty string${ctx}. No project files were changed.`);
148
+ }
149
+ if (override.density !== undefined && !VALID_DENSITIES.includes(override.density)) {
150
+ throw new ValidationError(
151
+ `--brand-tokens density must be one of: ${VALID_DENSITIES.join(", ")}${ctx}. No project files were changed.`
152
+ );
153
+ }
154
+ }
155
+
156
+ function loadBrandTokensV2(filePath) {
157
+ if (!fs.existsSync(filePath)) {
158
+ throw new ValidationError(`--brand-tokens file not found: ${filePath}. No project files were changed.`);
159
+ }
160
+ let raw;
161
+ try {
162
+ raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
163
+ } catch (_err) {
164
+ throw new ValidationError(`--brand-tokens file is not valid JSON: ${filePath}. No project files were changed.`);
165
+ }
166
+ validateBrandOverride(raw, filePath);
167
+ return raw;
168
+ }
169
+
170
+ // BK-229: project-facts validation + loader (factual content provenance, governance-safe).
171
+ const PROJECT_FACTS_STRING_KEYS = ["name", "company"];
172
+ const PROJECT_FACTS_URL_KEYS = ["contactUrl", "privacyUrl", "termsUrl"];
173
+ const PROJECT_FACTS_ALLOWED_TOP_KEYS = new Set([
174
+ ...PROJECT_FACTS_STRING_KEYS,
175
+ ...PROJECT_FACTS_URL_KEYS,
176
+ "categories",
177
+ ]);
178
+ const PROJECT_FACTS_URL_RE = /^https?:\/\//i;
179
+ const PROJECT_FACTS_CATEGORY_KEY_RE = /^[a-z0-9-]+$/;
180
+
181
+ function validateProjectFacts(facts, filePath) {
182
+ const ctx = filePath ? ` in ${filePath}` : "";
183
+ if (!facts || typeof facts !== "object" || Array.isArray(facts)) {
184
+ throw new ValidationError(`--project-facts must be a JSON object${ctx}. No project files were changed.`);
185
+ }
186
+ for (const k of Object.keys(facts)) {
187
+ if (!PROJECT_FACTS_ALLOWED_TOP_KEYS.has(k)) {
188
+ throw new ValidationError(
189
+ `--project-facts contains unknown top-level field "${k}"${ctx}. Allowed: ${Array.from(PROJECT_FACTS_ALLOWED_TOP_KEYS).join(", ")}. No project files were changed.`
190
+ );
191
+ }
192
+ }
193
+ for (const k of PROJECT_FACTS_STRING_KEYS) {
194
+ if (facts[k] !== undefined) {
195
+ if (typeof facts[k] !== "string" || !facts[k].trim() || facts[k].length > 200) {
196
+ throw new ValidationError(
197
+ `--project-facts ${k} must be a non-empty string <=200 chars${ctx}. No project files were changed.`
198
+ );
199
+ }
200
+ }
201
+ }
202
+ for (const k of PROJECT_FACTS_URL_KEYS) {
203
+ if (facts[k] !== undefined) {
204
+ if (typeof facts[k] !== "string" || !PROJECT_FACTS_URL_RE.test(facts[k]) || facts[k].length > 500) {
205
+ throw new ValidationError(
206
+ `--project-facts ${k} must be a string starting with http(s):// and <=500 chars${ctx}. No project files were changed.`
207
+ );
208
+ }
209
+ }
210
+ }
211
+ if (facts.categories !== undefined) {
212
+ if (!Array.isArray(facts.categories) || facts.categories.length > 50) {
213
+ throw new ValidationError(
214
+ `--project-facts categories must be an array of <=50 items${ctx}. No project files were changed.`
215
+ );
216
+ }
217
+ for (let i = 0; i < facts.categories.length; i++) {
218
+ const c = facts.categories[i];
219
+ if (!c || typeof c !== "object" || Array.isArray(c)) {
220
+ throw new ValidationError(`--project-facts categories[${i}] must be an object${ctx}. No project files were changed.`);
221
+ }
222
+ if (typeof c.key !== "string" || !PROJECT_FACTS_CATEGORY_KEY_RE.test(c.key)) {
223
+ throw new ValidationError(
224
+ `--project-facts categories[${i}].key must match ^[a-z0-9-]+$${ctx}. No project files were changed.`
225
+ );
226
+ }
227
+ if (typeof c.label !== "string" || !c.label.trim() || c.label.length > 200) {
228
+ throw new ValidationError(
229
+ `--project-facts categories[${i}].label must be a non-empty string <=200 chars${ctx}. No project files were changed.`
230
+ );
231
+ }
232
+ for (const ck of Object.keys(c)) {
233
+ if (ck !== "key" && ck !== "label") {
234
+ throw new ValidationError(
235
+ `--project-facts categories[${i}] contains unknown field "${ck}"${ctx}. Allowed: key, label. No project files were changed.`
236
+ );
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ function loadProjectFactsV1(filePath) {
244
+ if (!fs.existsSync(filePath)) {
245
+ throw new ValidationError(`--project-facts file not found: ${filePath}. No project files were changed.`);
246
+ }
247
+ let raw;
248
+ try {
249
+ raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
250
+ } catch (_err) {
251
+ throw new ValidationError(`--project-facts file is not valid JSON: ${filePath}. No project files were changed.`);
252
+ }
253
+ validateProjectFacts(raw, filePath);
254
+ return raw;
255
+ }
256
+
257
+ function profileDefaultTokensV2(profile, statePayload, brandOverride, brandTokensPath, projectFacts, projectFactsPath) {
258
+ const resolvedProfile = profile && PROFILE_PALETTE_DEFAULTS[profile] ? profile : "generic";
259
+ const paletteBase = PROFILE_PALETTE_DEFAULTS[resolvedProfile];
260
+ const brandBase = PROFILE_BRAND_DEFAULTS[resolvedProfile];
34
261
  const mappedRefs =
35
262
  statePayload &&
36
263
  statePayload.referenceMap &&
37
264
  typeof statePayload.referenceMap.mappedCount === "number"
38
265
  ? statePayload.referenceMap.mappedCount
39
266
  : 0;
267
+
268
+ const overridePalette = brandOverride && brandOverride.palette ? brandOverride.palette : {};
269
+ const overrideFontFamily = brandOverride && brandOverride.fontFamily ? brandOverride.fontFamily : {};
270
+ const overrideMood = brandOverride && typeof brandOverride.mood === "string" ? brandOverride.mood : null;
271
+ const overrideDensity = brandOverride && VALID_DENSITIES.includes(brandOverride.density) ? brandOverride.density : null;
272
+
273
+ // Merge palette flat keys; state tokens always come from profile default.
274
+ const palette = { ...paletteBase, ...overridePalette, state: paletteBase.state };
275
+ const fontFamily = { ...FONT_FAMILY_V2, ...overrideFontFamily };
276
+
277
+ // BK-229: brand.source value matrix.
278
+ const hasFacts = !!(projectFacts && Object.keys(projectFacts).length > 0);
279
+ const hasBrandOverride = !!brandOverride;
280
+ let brandSource = "profile-default";
281
+ if (hasFacts && hasBrandOverride) brandSource = "project-facts+brand-tokens";
282
+ else if (hasFacts) brandSource = "project-facts";
283
+ else if (hasBrandOverride) brandSource = "brand-tokens";
284
+ const facts = projectFacts || {};
285
+
40
286
  return {
41
- schema: "sdtk.design.tokens.v1",
42
- profile: profile || "generic",
43
- color: {
44
- surface: "#FFFFFF",
45
- surfaceAlt: "#F7F8FA",
46
- textPrimary: "#111827",
47
- textMuted: "#64748B",
48
- border: "#D8E0EA",
49
- accentPrimary: profile === "b2b-commerce" ? "#0F766E" : "#2563EB",
50
- accentSecondary: profile === "b2b-commerce" ? "#2563EB" : "#0F8A5F",
287
+ schema: "sdtk.design.tokens.v2",
288
+ version: 2,
289
+ profile: resolvedProfile,
290
+ brand: {
291
+ source: brandSource,
292
+ name: typeof facts.name === "string" ? facts.name : null,
293
+ company: typeof facts.company === "string" ? facts.company : null,
294
+ contactUrl: typeof facts.contactUrl === "string" ? facts.contactUrl : null,
295
+ privacyUrl: typeof facts.privacyUrl === "string" ? facts.privacyUrl : null,
296
+ termsUrl: typeof facts.termsUrl === "string" ? facts.termsUrl : null,
297
+ categories: Array.isArray(facts.categories) ? facts.categories : [],
298
+ mood: overrideMood || brandBase.mood,
299
+ density: overrideDensity || brandBase.density,
51
300
  },
301
+ palette,
52
302
  typography: {
53
- fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
54
- headingWeight: 800,
55
- bodyWeight: 400,
56
- uiWeight: 700,
57
- letterSpacing: 0,
58
- },
59
- spacing: {
60
- xs: 4,
61
- sm: 8,
62
- md: 12,
63
- lg: 16,
64
- xl: 24,
65
- sectionGap: 32,
66
- },
67
- radius: {
68
- card: 8,
69
- control: 8,
70
- chip: 999,
303
+ fontFamily,
304
+ ramp: TYPOGRAPHY_RAMP_V2,
71
305
  },
306
+ section: SECTION_TOKENS_V2,
307
+ component: COMPONENT_TOKENS_V2,
72
308
  metadata: {
73
309
  generatedFrom: "START_INPUT_STATE",
74
310
  explicitScreenCount: statePayload.screenModel.totalScreens,
75
311
  mappedReferenceCount: mappedRefs,
312
+ brandTokensPath: brandTokensPath || null,
313
+ projectFactsPath: projectFactsPath || null,
314
+ sourcePolicy: "profile-default-or-explicit-brand-tokens-and-project-facts-only",
76
315
  },
77
316
  };
78
317
  }
79
318
 
319
+ function contractTokens(profile, statePayload, options) {
320
+ const brandOverride = options && options.brandOverride ? options.brandOverride : null;
321
+ const brandTokensPath = options && options.brandTokensPath ? options.brandTokensPath : null;
322
+ const projectFacts = options && options.projectFacts ? options.projectFacts : null;
323
+ const projectFactsPath = options && options.projectFactsPath ? options.projectFactsPath : null;
324
+ return profileDefaultTokensV2(profile, statePayload, brandOverride, brandTokensPath, projectFacts, projectFactsPath);
325
+ }
326
+
80
327
  function markdownLibrary(profile, statePayload) {
81
328
  const mappedRefs =
82
329
  statePayload &&
@@ -84,7 +331,8 @@ function markdownLibrary(profile, statePayload) {
84
331
  typeof statePayload.referenceMap.mappedCount === "number"
85
332
  ? statePayload.referenceMap.mappedCount
86
333
  : 0;
87
- const patterns = profile === "b2b-commerce" ? ECOMMERCE_PATTERNS : BASELINE_PATTERNS;
334
+ const isCommerce = profile === "b2b-commerce" || profile === "b2b-industrial-commerce";
335
+ const patterns = isCommerce ? ECOMMERCE_PATTERNS : BASELINE_PATTERNS;
88
336
  return `# Component Pattern Library
89
337
 
90
338
  ## Contract Meta
@@ -108,7 +356,7 @@ ${patterns.map((item) => `- ${item}`).join("\n")}
108
356
  `;
109
357
  }
110
358
 
111
- function writeComponentContractArtifacts(projectPath, inputContractState) {
359
+ function writeComponentContractArtifacts(projectPath, inputContractState, options) {
112
360
  const paths = describeDesignPaths(projectPath);
113
361
  if (
114
362
  !inputContractState ||
@@ -122,21 +370,39 @@ function writeComponentContractArtifacts(projectPath, inputContractState) {
122
370
  );
123
371
  }
124
372
  const profile = inputContractState.profileSelection || null;
125
- const tokensPayload = contractTokens(profile, inputContractState);
373
+ const brandTokensPath = options && options.brandTokensPath ? options.brandTokensPath : null;
374
+ const projectFactsPath = options && options.projectFactsPath ? options.projectFactsPath : null;
375
+
376
+ // BK-223 + BK-229: validate and load both inputs before any write (fail-closed).
377
+ let brandOverride = null;
378
+ if (brandTokensPath) {
379
+ brandOverride = loadBrandTokensV2(brandTokensPath);
380
+ }
381
+ let projectFacts = null;
382
+ if (projectFactsPath) {
383
+ projectFacts = loadProjectFactsV1(projectFactsPath);
384
+ }
385
+
386
+ const tokensPayload = contractTokens(profile, inputContractState, {
387
+ brandOverride, brandTokensPath, projectFacts, projectFactsPath,
388
+ });
126
389
  const libraryMarkdown = markdownLibrary(profile, inputContractState);
127
390
 
128
391
  fs.writeFileSync(paths.componentPatternLibraryPath, `${libraryMarkdown}\n`, "utf-8");
129
392
  fs.writeFileSync(paths.designTokensPath, `${JSON.stringify(tokensPayload, null, 2)}\n`, "utf-8");
130
393
 
394
+ const isCommerce = profile === "b2b-commerce" || profile === "b2b-industrial-commerce";
131
395
  return {
132
396
  componentLibraryRelativePath: "docs/design/COMPONENT_PATTERN_LIBRARY.md",
133
397
  designTokensRelativePath: "docs/design/DESIGN_TOKENS.json",
134
398
  profile: profile || "generic",
135
- patternCount: profile === "b2b-commerce" ? ECOMMERCE_PATTERNS.length : BASELINE_PATTERNS.length,
399
+ patternCount: isCommerce ? ECOMMERCE_PATTERNS.length : BASELINE_PATTERNS.length,
136
400
  };
137
401
  }
138
402
 
139
403
  module.exports = {
404
+ loadBrandTokensV2,
405
+ loadProjectFactsV1,
406
+ validateProjectFacts,
140
407
  writeComponentContractArtifacts,
141
408
  };
142
-
@@ -337,11 +337,12 @@ function firstSectionList(raw) {
337
337
 
338
338
  function normalizeVisualTokenUsage(value) {
339
339
  const payload = value && typeof value === "object" && !Array.isArray(value) ? value : {};
340
+ // BK-225: v3 groups — section_keys and component_keys replace spacing_keys and radius_keys.
340
341
  return {
341
342
  palette_keys: firstStringList(payload, ["palette_keys", "paletteKeys"]),
342
343
  typography_keys: firstStringList(payload, ["typography_keys", "typographyKeys"]),
343
- spacing_keys: firstStringList(payload, ["spacing_keys", "spacingKeys"]),
344
- radius_keys: firstStringList(payload, ["radius_keys", "radiusKeys"]),
344
+ section_keys: firstStringList(payload, ["section_keys", "sectionKeys"]),
345
+ component_keys: firstStringList(payload, ["component_keys", "componentKeys"]),
345
346
  };
346
347
  }
347
348
 
@@ -9,6 +9,7 @@ const DESIGN_PROTOTYPE_RELATIVE = path.join("docs", "design", "prototype");
9
9
  const DESIGN_PROTOTYPE_SCREENS_RELATIVE = path.join("docs", "design", "prototype", "screens");
10
10
  const DESIGN_PROTOTYPE_ASSETS_RELATIVE = path.join("docs", "design", "prototype", "assets");
11
11
  const DESIGN_SCREENS_RELATIVE = path.join("docs", "design", "screens");
12
+ const DESIGN_PROTOTYPE_MANIFEST_RELATIVE = path.join("docs", "design", "prototype", ".manifest.json");
12
13
  const DESIGN_PROTOTYPE_INDEX_RELATIVE = path.join("docs", "design", "prototype", "index.html");
13
14
  const DESIGN_PROTOTYPE_CSS_RELATIVE = path.join("docs", "design", "prototype", "assets", "prototype.css");
14
15
  const DESIGN_PROTOTYPE_JS_RELATIVE = path.join("docs", "design", "prototype", "assets", "prototype.js");
@@ -59,6 +60,7 @@ function describeDesignPaths(projectPath) {
59
60
  prototypeScreensPath: path.join(root, DESIGN_PROTOTYPE_SCREENS_RELATIVE),
60
61
  prototypeAssetsPath: path.join(root, DESIGN_PROTOTYPE_ASSETS_RELATIVE),
61
62
  screensPath: path.join(root, DESIGN_SCREENS_RELATIVE),
63
+ prototypeManifestPath: path.join(root, DESIGN_PROTOTYPE_MANIFEST_RELATIVE),
62
64
  prototypeIndexPath: path.join(root, DESIGN_PROTOTYPE_INDEX_RELATIVE),
63
65
  prototypeCssPath: path.join(root, DESIGN_PROTOTYPE_CSS_RELATIVE),
64
66
  prototypeJsPath: path.join(root, DESIGN_PROTOTYPE_JS_RELATIVE),
@@ -83,6 +85,7 @@ module.exports = {
83
85
  DESIGN_HANDOFF_RELATIVE,
84
86
  DESIGN_MANIFEST_RELATIVE,
85
87
  DESIGN_PROTOTYPE_INDEX_RELATIVE,
88
+ DESIGN_PROTOTYPE_MANIFEST_RELATIVE,
86
89
  DESIGN_PROTOTYPE_ASSETS_RELATIVE,
87
90
  DESIGN_PROTOTYPE_CSS_RELATIVE,
88
91
  DESIGN_PROTOTYPE_JS_RELATIVE,
@@ -32,6 +32,37 @@ const DESIGN_PROFILES = {
32
32
  "mode-b-configurator": ["configurator wizard", "bom/material table", "sticky purchase/cta"],
33
33
  },
34
34
  },
35
+ "b2b-industrial-commerce": {
36
+ name: "b2b-industrial-commerce",
37
+ summary: "B2B industrial commerce primitives for product catalog, configuration, and procurement workflows.",
38
+ primitives: [
39
+ "catalog grid",
40
+ "filter sidebar",
41
+ "product card",
42
+ "pdp gallery/spec/cta",
43
+ "technical spec table",
44
+ "search results/no-result",
45
+ "cart line items/summary",
46
+ "checkout steps",
47
+ "order history/detail",
48
+ "account shell",
49
+ "configurator wizard",
50
+ "bom/material table",
51
+ "procurement summary panel",
52
+ ],
53
+ screenRoleHints: {
54
+ home: ["catalog grid", "product card", "procurement summary panel"],
55
+ category: ["filter sidebar", "catalog grid", "product card"],
56
+ "product-detail": ["pdp gallery/spec/cta", "technical spec table", "sticky purchase/cta"],
57
+ search: ["search results/no-result", "product card", "filter sidebar"],
58
+ cart: ["cart line items/summary", "sticky purchase/cta"],
59
+ checkout: ["checkout steps", "cart line items/summary", "procurement summary panel"],
60
+ "order-history": ["order history/detail"],
61
+ "order-detail": ["order history/detail"],
62
+ "account-info": ["account shell"],
63
+ "mode-b-configurator": ["configurator wizard", "bom/material table", "technical spec table"],
64
+ },
65
+ },
35
66
  };
36
67
 
37
68
  function availableProfileNames() {