better-design 0.43.0 → 0.43.2
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/dist/index.js +130 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -330,9 +330,9 @@ var init_constants = __esm({
|
|
|
330
330
|
RULE_BODY = `## Better Design
|
|
331
331
|
|
|
332
332
|
For every UI build, run these steps in order, and never present a page that skipped step 2 or 4:
|
|
333
|
-
1. Design system: \`find-design-system\`, then \`get-design-system-kit\`. For a product with a live site, \`extract-from-url\` gives its tokens.
|
|
333
|
+
1. Design system: \`find-design-system\`, then \`get-design-system-kit\`. For a product with a live site, \`extract-from-url\` gives its tokens; an app screen takes only the accent and font from them.
|
|
334
334
|
2. Principles: \`get-ui-principle\` before styling, \`get-ux-principle\` before behavior.
|
|
335
|
-
3. Build.
|
|
335
|
+
3. Build. A product screen sits inside its app shell, with realistic sample data and an app-scale title.
|
|
336
336
|
4. Review: \`get-review-rules\`, \`check-comprehension\` and \`inspect-spacing\`. Fix every critical and serious finding.
|
|
337
337
|
|
|
338
338
|
Always use the Better Design MCP tools for any frontend, UI, UX, interaction, flow, form, navigation, dialog, error, onboarding, component, styling, article widget, calculator, estimator, converter, generator, quiz, checklist, React Native, Expo, Three.js, Shopify, or sales work, without me having to explicitly ask. For a calculator or article widget, load \`get-widget-guide\`; for React Native or Expo, load task guidance with \`get-react-native-guide\`; for Three.js or React Three Fiber, use \`get-three-js-guide\`; for Shopify Liquid or theme work, use \`get-shopify-guide\`; for sales conversations or assets, use \`get-sales-principle\`. Load visual guidance with \`get-ui-principle\`, load flow guidance with \`get-ux-principle\`, and self-review with \`get-review-rules\` and \`check-comprehension\`. Run \`inspect-spacing\` on rendered DOM evidence. For dashboards, deploy the exact source to a public HTTPS preview; \`finalize-dashboard-review\` must return VALID after Better Design captures it at 390, 1440, and 1728. Start UI builds with \`find-design-system\`. For a custom brand, call \`create-design-system\`, poll \`get-design-system-status\`, and run its React install command in coding hosts; in inline canvases, call \`get-design-system-kit\` with target \`html\` and the generated projectId. For a catalog system, call \`get-design-system-kit\` with target \`react\` in coding hosts or \`html\` in inline canvases. For a Shopify store, load \`get-shopify-guide\`, then call \`get-design-system-kit\` with target \`shopify\`.`;
|
|
@@ -1075,7 +1075,7 @@ var MCP_IMPLEMENTATION_VERSION;
|
|
|
1075
1075
|
var init_version = __esm({
|
|
1076
1076
|
"../shared/src/mcp/version.ts"() {
|
|
1077
1077
|
"use strict";
|
|
1078
|
-
MCP_IMPLEMENTATION_VERSION = "0.43.
|
|
1078
|
+
MCP_IMPLEMENTATION_VERSION = "0.43.2";
|
|
1079
1079
|
}
|
|
1080
1080
|
});
|
|
1081
1081
|
|
|
@@ -1936,8 +1936,8 @@ ${PRESENTATION_RUNTIME_RULE}
|
|
|
1936
1936
|
|
|
1937
1937
|
## Build or modify UI, in order
|
|
1938
1938
|
${DESIGN_TRADEOFF_REMINDER}
|
|
1939
|
-
Classify: repair, polish
|
|
1940
|
-
1. Design system: keep the selected DS and its icons. Else find-design-system and show ~4 choices (unattended: take the top one); custom brand: create-design-system, poll status. Then get-design-system-kit: "react" with files/terminal, "html" in inline canvases; real components, never runtime @iconify/react. Live
|
|
1939
|
+
Classify: repair, polish or redesign. Redesign needs layout/component changes and before/after at matching widths. Follow the kit's scope contract.
|
|
1940
|
+
1. Design system: keep the selected DS and its icons. Else find-design-system and show ~4 choices (unattended: take the top one); custom brand: create-design-system, poll status. Then get-design-system-kit: "react" with files/terminal, "html" in inline canvases; real components, never runtime @iconify/react. Live site: extract-from-url tokens (app screen: accent, font only).
|
|
1941
1941
|
2. get-ui-principle before styling; get-ux-principle before behavior.
|
|
1942
1942
|
3. Build: semantic HTML, keyboard access, WCAG AA.
|
|
1943
1943
|
4. Review: get-review-rules (copy/AI-tell rules), check-comprehension, inspect-spacing; fix critical/serious findings. Dashboards need a VALID finalize-dashboard-review receipt. No rendered evidence means unverified.
|
|
@@ -2529,7 +2529,7 @@ and never leave a \`var(--font-*)\` reference undefined. In the rendered app, aw
|
|
|
2529
2529
|
\`document.fonts.ready\`, inspect the computed font-family on the page and a heading,
|
|
2530
2530
|
and confirm the intended family loaded before presenting the screen.
|
|
2531
2531
|
|
|
2532
|
-
Hierarchy is pass/fail, not polish. Give the first view one P0 task and at most one P1 summary; move P2 history, standings, detailed metadata, and empty-state chrome below it or behind a tab/disclosure. Unless the
|
|
2532
|
+
Hierarchy is pass/fail, not polish. Give the first view one P0 task and at most one P1 summary; move P2 history, standings, detailed metadata, and empty-state chrome below it or behind a tab/disclosure. Unless the screen is a dense tool (traces, logs, tables and dashboards) or the user asked for density, fail and simplify any first view with more than three major regions, five immediately visible actions, or 25 visible text fragments. A dense tool keeps its summary stats, its records and the selected record's detail in one view: order them with hierarchy instead of cutting them. Do not make density look compact by shrinking everything: the primary display must be at least 1.5\xD7 base UI text, section anchors at least 1.2\xD7, and bold must be reserved for the primary display, section anchors, and active values. Labels are a last resort; remove captions that restate obvious values.
|
|
2533
2533
|
|
|
2534
2534
|
Dashboard quality is pass-fail. Start with the domain page title, one supporting line, and one primary action
|
|
2535
2535
|
in the same header group. Preserve this scan order: metrics \u2192 records \u2192 interpretation.
|
|
@@ -18049,7 +18049,7 @@ function resolveCopy(input) {
|
|
|
18049
18049
|
return {
|
|
18050
18050
|
headline: input.headline?.trim() || extracted?.headline || null,
|
|
18051
18051
|
body: input.body?.length ? input.body : extracted?.body ?? [],
|
|
18052
|
-
actions: input.actions?.length ? input.actions : extracted?.actions ?? [],
|
|
18052
|
+
actions: (input.actions?.length ? input.actions : extracted?.actions ?? []).filter((a) => !NOT_AN_ACTION.test(a.trim())),
|
|
18053
18053
|
unlabelledControls: extracted?.unlabelledControls ?? 0
|
|
18054
18054
|
};
|
|
18055
18055
|
}
|
|
@@ -18244,13 +18244,14 @@ function analyzeComprehension(input) {
|
|
|
18244
18244
|
}
|
|
18245
18245
|
};
|
|
18246
18246
|
}
|
|
18247
|
-
var WORD_BOUNDARY_SAFE;
|
|
18247
|
+
var WORD_BOUNDARY_SAFE, NOT_AN_ACTION;
|
|
18248
18248
|
var init_analyze = __esm({
|
|
18249
18249
|
"../shared/src/mcp/comprehension/analyze.ts"() {
|
|
18250
18250
|
"use strict";
|
|
18251
18251
|
init_extract();
|
|
18252
18252
|
init_lexicon();
|
|
18253
18253
|
WORD_BOUNDARY_SAFE = /[.*+?^${}()|[\]\\]/g;
|
|
18254
|
+
NOT_AN_ACTION = /^(?:search|filter|sort|all|any)\b/i;
|
|
18254
18255
|
}
|
|
18255
18256
|
});
|
|
18256
18257
|
|
|
@@ -18375,7 +18376,7 @@ Use knownTerms for words your audience genuinely knows, so the check stops flagg
|
|
|
18375
18376
|
screen: z13.string().trim().min(1).max(120).describe("What this screen is, e.g. 'lesson end card', 'billing page'."),
|
|
18376
18377
|
headline: z13.string().trim().max(300).optional().describe("The screen's title, if you are passing copy rather than markup."),
|
|
18377
18378
|
body: z13.array(z13.string().trim().min(1).max(2e3)).max(50).optional().describe("Supporting copy, one string per visible line or paragraph."),
|
|
18378
|
-
actions: z13.array(z13.string().trim().min(1).max(120)).max(20).optional().describe("Visible button and link labels, e.g. ['Claim badge', 'Not now']."),
|
|
18379
|
+
actions: z13.array(z13.string().trim().min(1).max(120)).max(20).optional().describe("Visible button and link labels, e.g. ['Claim badge', 'Not now']. Search fields, filters and selects are not actions."),
|
|
18379
18380
|
code: z13.string().max(6e4).optional().describe("JSX, TSX or HTML for the screen. Copy is extracted from it when headline/body/actions are omitted."),
|
|
18380
18381
|
knownTerms: z13.array(z13.string().trim().min(1).max(60)).max(50).optional().describe("Words this audience already knows, so they are not flagged as insider language."),
|
|
18381
18382
|
surface: z13.enum(["screen", "slide"]).optional().describe('"slide" adds the slide density test and withholds a pass until a screenshot review. Defaults to "slide" when the screen name says slide, deck or presentation.'),
|
|
@@ -19140,8 +19141,8 @@ align in a column.
|
|
|
19140
19141
|
|
|
19141
19142
|
**Hierarchy is pass/fail.** Give the first view one P0 task and at most one P1
|
|
19142
19143
|
summary; move P2 history, standings, detailed metadata, and empty-state chrome
|
|
19143
|
-
below it or behind a tab/disclosure. Unless the
|
|
19144
|
-
|
|
19144
|
+
below it or behind a tab/disclosure. Unless the screen is a dense tool (traces, logs,
|
|
19145
|
+
tables and dashboards) or the user asked for density, simplify any first view with more than three major regions, five
|
|
19145
19146
|
immediately visible actions, or 25 visible text fragments. Do not shrink everything
|
|
19146
19147
|
to make it fit: the primary display must be at least 1.5\xD7 base UI text, section
|
|
19147
19148
|
anchors at least 1.2\xD7, and labels are a last resort. Reserve bold for the primary
|
|
@@ -19263,6 +19264,43 @@ CI runs the same check with \`npx better-design review --base main\`.
|
|
|
19263
19264
|
}
|
|
19264
19265
|
});
|
|
19265
19266
|
|
|
19267
|
+
// ../shared/src/motion-tokens.ts
|
|
19268
|
+
var MOTION_PROFILES;
|
|
19269
|
+
var init_motion_tokens = __esm({
|
|
19270
|
+
"../shared/src/motion-tokens.ts"() {
|
|
19271
|
+
"use strict";
|
|
19272
|
+
MOTION_PROFILES = {
|
|
19273
|
+
/** Dense tools, terminals, industrial and brutalist systems. */
|
|
19274
|
+
crisp: {
|
|
19275
|
+
"--transition-duration-fast": "120ms",
|
|
19276
|
+
"--transition-duration-base": "160ms",
|
|
19277
|
+
"--transition-duration-slow": "220ms",
|
|
19278
|
+
"--ease-out": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
|
19279
|
+
"--ease-in-out": "cubic-bezier(0.455, 0.03, 0.515, 0.955)",
|
|
19280
|
+
"--ease-in": "cubic-bezier(0.55, 0.085, 0.68, 0.53)"
|
|
19281
|
+
},
|
|
19282
|
+
/** Product UI, and the default for a generated system. */
|
|
19283
|
+
standard: {
|
|
19284
|
+
"--transition-duration-fast": "150ms",
|
|
19285
|
+
"--transition-duration-base": "200ms",
|
|
19286
|
+
"--transition-duration-slow": "280ms",
|
|
19287
|
+
"--ease-out": "cubic-bezier(0.165, 0.84, 0.44, 1)",
|
|
19288
|
+
"--ease-in-out": "cubic-bezier(0.645, 0.045, 0.355, 1)",
|
|
19289
|
+
"--ease-in": "cubic-bezier(0.55, 0.055, 0.675, 0.19)"
|
|
19290
|
+
},
|
|
19291
|
+
/** Luxury, glass and editorial systems. */
|
|
19292
|
+
soft: {
|
|
19293
|
+
"--transition-duration-fast": "180ms",
|
|
19294
|
+
"--transition-duration-base": "240ms",
|
|
19295
|
+
"--transition-duration-slow": "300ms",
|
|
19296
|
+
"--ease-out": "cubic-bezier(0.19, 1, 0.22, 1)",
|
|
19297
|
+
"--ease-in-out": "cubic-bezier(0.77, 0, 0.175, 1)",
|
|
19298
|
+
"--ease-in": "cubic-bezier(0.895, 0.03, 0.685, 0.22)"
|
|
19299
|
+
}
|
|
19300
|
+
};
|
|
19301
|
+
}
|
|
19302
|
+
});
|
|
19303
|
+
|
|
19266
19304
|
// ../shared/src/mcp/html-kit.ts
|
|
19267
19305
|
function fence(lang, body) {
|
|
19268
19306
|
return "```" + lang + "\n" + body + "\n```";
|
|
@@ -19382,10 +19420,11 @@ function buildDsHtmlKit({ name, tokensCss, viewUrl, source, componentKit }) {
|
|
|
19382
19420
|
Full live showcase: ${viewUrl}` : ""
|
|
19383
19421
|
].join("\n");
|
|
19384
19422
|
}
|
|
19385
|
-
var MINIMUM_TEXT_CONTRAST, HOVER_POLICY, INLINE_TEXT_COLOR_POLICY, HOST_ISOLATION_CSS_TEMPLATE, BESPOKE_ASSET_POLICY, INLINE_COMPOSITION_POLICY, INLINE_HIERARCHY_POLICY, DS_KIT_CSS, DS_KIT_EXAMPLE_HTML, HTML_KIT_CONDUCT_MD;
|
|
19423
|
+
var MINIMUM_TEXT_CONTRAST, HOVER_POLICY, INLINE_TEXT_COLOR_POLICY, HOST_ISOLATION_CSS_TEMPLATE, BESPOKE_ASSET_POLICY, INLINE_COMPOSITION_POLICY, INLINE_HIERARCHY_POLICY, KIT_FAST, KIT_EASE, KIT_TIMING, DS_KIT_CSS, DS_KIT_EXAMPLE_HTML, HTML_KIT_CONDUCT_MD;
|
|
19386
19424
|
var init_html_kit = __esm({
|
|
19387
19425
|
"../shared/src/mcp/html-kit.ts"() {
|
|
19388
19426
|
"use strict";
|
|
19427
|
+
init_motion_tokens();
|
|
19389
19428
|
MINIMUM_TEXT_CONTRAST = "4.5:1";
|
|
19390
19429
|
HOVER_POLICY = "Put every :hover rule inside @media (hover: hover) and (pointer: fine); never move controls or override selected/pressed styling. For selectable controls, unselected hover stays neutral: only strengthen the neutral surface or edge, never changes text to the primary color, and never borrows the primary/accent fill. Do not add app-specific :hover rules to ds-* controls; use the kit states verbatim.";
|
|
19391
19430
|
INLINE_TEXT_COLOR_POLICY = [
|
|
@@ -19421,11 +19460,13 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19421
19460
|
].join(" ");
|
|
19422
19461
|
INLINE_HIERARCHY_POLICY = [
|
|
19423
19462
|
"Give the first view one P0 task and at most one P1 summary; move P2 history, standings, detailed metadata, and empty-state chrome below the first view or behind a tab or disclosure.",
|
|
19424
|
-
"At the actual inline-canvas size, the first view fails when it exposes more than three major regions, more than five immediately visible actions, or more than 25 visible text fragments unless the user
|
|
19463
|
+
"At the actual inline-canvas size, the first view fails when it exposes more than three major regions, more than five immediately visible actions, or more than 25 visible text fragments unless the screen is a dense tool (traces, logs, tables and dashboards) or the user asked for density.",
|
|
19425
19464
|
"Do not make a busy screen feel compact by shrinking everything. Use `ds-display` only inside the one P0 composition: once for a single primary task or value, or on co-equal peers within the same event, such as opposing teams. It must compute to at least 1.5\xD7 the base UI text. Use `ds-section-title` for scan anchors at at least 1.2\xD7 the base UI text, and `ds-supporting` for genuinely secondary copy.",
|
|
19426
19465
|
"Labels are a last resort: remove captions that restate an obvious value, do not label the same fact twice, and keep no more than two supporting metadata items around the primary entity.",
|
|
19427
19466
|
"Bold is selective hierarchy, not decoration: emphasize the primary display, section anchors, and the active value; keep labels, metadata, table headers, and helper copy quieter."
|
|
19428
19467
|
].join(" ");
|
|
19468
|
+
({ "--transition-duration-fast": KIT_FAST, "--ease-out": KIT_EASE } = MOTION_PROFILES.standard);
|
|
19469
|
+
KIT_TIMING = `var(--transition-duration-fast,${KIT_FAST}) var(--ease-out,${KIT_EASE})`;
|
|
19429
19470
|
DS_KIT_CSS = `*,*::before,*::after{box-sizing:border-box}
|
|
19430
19471
|
:where(.ds){font-family:var(--font-sans,ui-sans-serif,system-ui,sans-serif);color:var(--foreground,oklch(0.2 0 0));background:var(--background,oklch(1 0 0));line-height:1.5}
|
|
19431
19472
|
.ds :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small){color:inherit}
|
|
@@ -19443,7 +19484,7 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19443
19484
|
.ds-muted{color:var(--muted-foreground,oklch(0.55 0 0))}
|
|
19444
19485
|
|
|
19445
19486
|
/* Button */
|
|
19446
|
-
.ds-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;font:inherit;font-size:.875rem;font-weight:500;line-height:1;padding:.6rem 1rem;border-radius:var(--radius-md,calc(var(--radius,0.5rem) - 2px));border:1px solid transparent;cursor:pointer;touch-action:manipulation;transition:opacity
|
|
19487
|
+
.ds-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;font:inherit;font-size:.875rem;font-weight:500;line-height:1;padding:.6rem 1rem;border-radius:var(--radius-md,calc(var(--radius,0.5rem) - 2px));border:1px solid transparent;cursor:pointer;touch-action:manipulation;transition:opacity ${KIT_TIMING},background ${KIT_TIMING};text-decoration:none}
|
|
19447
19488
|
.ds-btn:focus-visible{outline:2px solid var(--ring,var(--primary,oklch(0.45 0.15 264)));outline-offset:2px}
|
|
19448
19489
|
.ds-btn:disabled{background:var(--muted,oklch(0.94 0 0));color:var(--foreground,oklch(0.2 0 0));border-color:var(--border,oklch(0.85 0 0));opacity:1;cursor:not-allowed}
|
|
19449
19490
|
.ds-btn-primary{background:var(--primary,oklch(0.45 0.15 264));color:var(--primary-foreground,oklch(1 0 0));box-shadow:var(--shadow-sm,var(--shadow-xs,none))}
|
|
@@ -19456,7 +19497,7 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19456
19497
|
|
|
19457
19498
|
/* Form fields */
|
|
19458
19499
|
.ds-label{display:block;font-size:.8125rem;font-weight:500;margin-bottom:.4rem;color:var(--foreground,oklch(0.2 0 0))}
|
|
19459
|
-
.ds-input,.ds-textarea,.ds-select{width:100%;font:inherit;font-size:.875rem;padding:.55rem .75rem;border-radius:var(--radius-lg,var(--radius,0.5rem));border:1px solid var(--border,oklch(0.85 0 0));background:var(--input,var(--background,oklch(1 0 0)));color:var(--foreground,oklch(0.2 0 0));box-shadow:var(--shadow-inset,var(--shadow-2xs,none));transition:border-color
|
|
19500
|
+
.ds-input,.ds-textarea,.ds-select{width:100%;font:inherit;font-size:.875rem;padding:.55rem .75rem;border-radius:var(--radius-lg,var(--radius,0.5rem));border:1px solid var(--border,oklch(0.85 0 0));background:var(--input,var(--background,oklch(1 0 0)));color:var(--foreground,oklch(0.2 0 0));box-shadow:var(--shadow-inset,var(--shadow-2xs,none));transition:border-color ${KIT_TIMING},box-shadow ${KIT_TIMING}}
|
|
19460
19501
|
.ds-input::placeholder,.ds-textarea::placeholder{color:var(--muted-foreground,oklch(0.6 0 0))}
|
|
19461
19502
|
.ds-input:focus,.ds-textarea:focus,.ds-select:focus{outline:none;border-color:var(--ring,var(--primary,oklch(0.45 0.15 264)));box-shadow:var(--shadow-inset,0 0 0 0 transparent),0 0 0 2px color-mix(in oklab,var(--ring,var(--primary,oklch(0.45 0.15 264))) 35%,transparent)}
|
|
19462
19503
|
.ds-textarea{min-height:5rem;resize:vertical}
|
|
@@ -19495,9 +19536,9 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19495
19536
|
|
|
19496
19537
|
/* Switch */
|
|
19497
19538
|
.ds-switch{display:inline-flex;align-items:center;gap:.6rem;font-size:.875rem;cursor:pointer}
|
|
19498
|
-
.ds-switch-track{width:2.25rem;height:1.25rem;border-radius:999px;background:var(--muted,oklch(0.9 0 0));position:relative;transition:background
|
|
19539
|
+
.ds-switch-track{width:2.25rem;height:1.25rem;border-radius:999px;background:var(--muted,oklch(0.9 0 0));position:relative;transition:background ${KIT_TIMING};flex:none}
|
|
19499
19540
|
.ds-switch-track.on{background:var(--primary,oklch(0.45 0.15 264))}
|
|
19500
|
-
.ds-switch-thumb{position:absolute;top:2px;left:2px;width:calc(1.25rem - 4px);height:calc(1.25rem - 4px);border-radius:999px;background:var(--background,oklch(1 0 0));box-shadow:var(--shadow-sm,0 1px 2px oklch(0 0 0 / 0.2));transition:transform
|
|
19541
|
+
.ds-switch-thumb{position:absolute;top:2px;left:2px;width:calc(1.25rem - 4px);height:calc(1.25rem - 4px);border-radius:999px;background:var(--background,oklch(1 0 0));box-shadow:var(--shadow-sm,0 1px 2px oklch(0 0 0 / 0.2));transition:transform ${KIT_TIMING}}
|
|
19501
19542
|
.ds-switch-track.on .ds-switch-thumb{transform:translateX(1rem)}
|
|
19502
19543
|
|
|
19503
19544
|
/* Tabs */
|
|
@@ -19515,7 +19556,7 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19515
19556
|
.ds-table th{font-weight:600;color:var(--muted-foreground,oklch(0.5 0 0));font-size:.6875rem;text-transform:uppercase;letter-spacing:.04em}
|
|
19516
19557
|
|
|
19517
19558
|
/* Chip / keyboard key */
|
|
19518
|
-
.ds-chip{display:inline-flex;align-items:center;gap:.35rem;font:inherit;font-size:.8125rem;padding:.3rem .7rem;border-radius:999px;border:1px solid var(--border,oklch(0.88 0 0));background:var(--background,oklch(1 0 0));color:var(--foreground,oklch(0.2 0 0));cursor:pointer;touch-action:manipulation;transition:background-color
|
|
19559
|
+
.ds-chip{display:inline-flex;align-items:center;gap:.35rem;font:inherit;font-size:.8125rem;padding:.3rem .7rem;border-radius:999px;border:1px solid var(--border,oklch(0.88 0 0));background:var(--background,oklch(1 0 0));color:var(--foreground,oklch(0.2 0 0));cursor:pointer;touch-action:manipulation;transition:background-color ${KIT_TIMING},border-color ${KIT_TIMING},color ${KIT_TIMING}}
|
|
19519
19560
|
.ds-chip:focus-visible{outline:2px solid var(--ring,var(--primary,oklch(0.45 0.15 264)));outline-offset:2px}
|
|
19520
19561
|
.ds .ds-chip[aria-pressed="true"],.ds .ds-chip[aria-pressed="true"]:hover{background:var(--primary,oklch(0.45 0.15 264));color:var(--primary-foreground,oklch(1 0 0));border-color:var(--primary,oklch(0.45 0.15 264))}
|
|
19521
19562
|
.ds-kbd{font-family:var(--font-mono,ui-monospace,monospace);font-size:.75rem;padding:.1rem .4rem;border-radius:var(--radius-sm,4px);border:1px solid var(--border,oklch(0.85 0 0));background:var(--muted,oklch(0.97 0 0));color:var(--foreground,oklch(0.2 0 0))}
|
|
@@ -19567,7 +19608,7 @@ APP_ROOT :where(h1,h2,h3,h4,h5,h6,p,strong,label,legend,li,dt,dd,small):not(.ds-
|
|
|
19567
19608
|
"2. Wrap the entire artifact in `.ds` and use the kit's `ds-*` classes for component styling. Custom CSS is layout-only; do not replace the selected system's components, tokens, states, radii or shadows.",
|
|
19568
19609
|
'3. This class contract is pass/fail: do not rename `ds-*` to an app-specific prefix. Before presenting, inspect the artifact source. If it does not contain a root `class="ds"` and `ds-*` component classes, revise the artifact instead of presenting it.',
|
|
19569
19610
|
"4. Paste `hostIsolationCssTemplate` after host/base CSS, replacing `APP_ROOT` with the real root. Follow `textColorPolicy`: never use surface tokens as text colors, never dim text with opacity, and use `--muted-foreground` for secondary copy.",
|
|
19570
|
-
"5. First-view hierarchy is pass/fail: one P0 task, at most one P1 summary; unless a dense
|
|
19611
|
+
"5. First-view hierarchy is pass/fail: one P0 task, at most one P1 summary; unless the screen is a dense tool (traces, logs, tables and dashboards) or density was requested, no more than three major regions, five immediately visible actions, or 25 visible text fragments.",
|
|
19571
19612
|
"6. Composition is pass/fail: one identity, content-sized task, aligned field/action, accurate paired assets, and event-only decoration.",
|
|
19572
19613
|
"",
|
|
19573
19614
|
"## Before you show it (mandatory)",
|
|
@@ -19968,6 +20009,7 @@ function dtcgType(name, value) {
|
|
|
19968
20009
|
if (n.includes("radius") || n.includes("spacing") || n.includes("size")) return "dimension";
|
|
19969
20010
|
if (n.includes("font") || n.includes("family")) return "fontFamily";
|
|
19970
20011
|
if (n.includes("duration") || n.includes("delay")) return "duration";
|
|
20012
|
+
if (/^cubic-bezier\(/i.test(value.trim())) return "cubicBezier";
|
|
19971
20013
|
if (toOklch(value) !== null) return "color";
|
|
19972
20014
|
if (/^-?[\d.]+(px|rem|em|%|vh|vw|ch)$/.test(value.trim())) return "dimension";
|
|
19973
20015
|
if (/^-?[\d.]+m?s$/.test(value.trim())) return "duration";
|
|
@@ -19988,6 +20030,15 @@ function toDuration(value) {
|
|
|
19988
20030
|
if (!Number.isFinite(n)) return null;
|
|
19989
20031
|
return { value: n, unit: m[2]?.toLowerCase() ?? "ms" };
|
|
19990
20032
|
}
|
|
20033
|
+
function toCubicBezier(value) {
|
|
20034
|
+
const m = /^cubic-bezier\(([^)]*)\)$/i.exec(value.trim());
|
|
20035
|
+
if (!m) return null;
|
|
20036
|
+
const points = m[1].split(",").map((part) => part.trim() === "" ? Number.NaN : Number(part));
|
|
20037
|
+
if (points.length !== 4 || !points.every(Number.isFinite)) return null;
|
|
20038
|
+
const [x1, y1, x2, y2] = points;
|
|
20039
|
+
if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) return null;
|
|
20040
|
+
return [x1, y1, x2, y2];
|
|
20041
|
+
}
|
|
19991
20042
|
function toColor(value) {
|
|
19992
20043
|
const oklch = toOklch(value);
|
|
19993
20044
|
if (oklch === null) return null;
|
|
@@ -20061,6 +20112,8 @@ function typedValue(type, value) {
|
|
|
20061
20112
|
return toDimension(v);
|
|
20062
20113
|
case "duration":
|
|
20063
20114
|
return toDuration(v);
|
|
20115
|
+
case "cubicBezier":
|
|
20116
|
+
return toCubicBezier(v);
|
|
20064
20117
|
case "shadow":
|
|
20065
20118
|
return toShadow(v);
|
|
20066
20119
|
case "fontFamily":
|
|
@@ -26345,9 +26398,9 @@ var init_build_loop = __esm({
|
|
|
26345
26398
|
"use strict";
|
|
26346
26399
|
BUILD_LOOP = [
|
|
26347
26400
|
"Required, in this order, before you present any UI:",
|
|
26348
|
-
"1. Design system: install the system you take with `get-design-system-kit
|
|
26401
|
+
"1. Design system: install the system you take with `get-design-system-kit`, choosing one whose colour scheme matches the brand's own site: a light site gets a light system. When `extract-from-url` returns both themes, the site follows the visitor's system setting, so ship both and switch them with `prefers-color-scheme`. For a marketing page of a product with a live site, its `extract-from-url` tokens are this step. For a product screen, take only its accent colour and font from `extract-from-url`, never its backgrounds, borders or input styles: those come from the kit.",
|
|
26349
26402
|
"2. Principles: call `get-ui-principle` before styling and `get-ux-principle` before behavior.",
|
|
26350
|
-
"3. Build with those tokens and principles.",
|
|
26403
|
+
"3. Build with those tokens and principles. A product screen sits inside the product's app shell: navigation with the product's real sections, the current one active, and the workspace or project it belongs to. Show the summary, the records and the selected record's detail together, not behind tabs. Fill it with realistic sample data, including the metrics, IDs and fields a working user would see: sample data in a product screen is not an invented claim. Only claims about the company itself, such as customers, results and endorsements, must stay real. Keep the page title at app scale, the size of a section heading, not a marketing display. Keep each table row on one line: drop or truncate columns instead of wrapping cells.",
|
|
26351
26404
|
"4. Review: call `get-review-rules`, then `check-comprehension` with the page's real copy, and `inspect-spacing` on the rendered page. Fix every critical and serious finding.",
|
|
26352
26405
|
"Do not present a page that skipped step 2 or step 4. If a step fails, say which one."
|
|
26353
26406
|
].join("\n");
|
|
@@ -26628,7 +26681,7 @@ ${formatCatalogMatches(results)}`;
|
|
|
26628
26681
|
return `${heading} The catalog search failed, so call \`find-design-system\` with the app description, show the user the top matches, and ask which to start from or whether to go fully custom.`;
|
|
26629
26682
|
}
|
|
26630
26683
|
};
|
|
26631
|
-
const MATCH_INSTRUCTIONS = `Keep any already selected design system and its assigned icons; do not reopen that choice unless requested. If none is selected, present these as options, keeping each name as its clickable markdown link (no scores) so they can open the preview. Explain each option's expected buyer fit and trade-offs using known project context; do not claim conversion effects without evidence. Recommend by buyer, never by the product's material or subject (a steel tool does not need a steel-dark UI). For ecommerce and DTC product pages, default to light, warm, photography-led systems unless the brand already owns a dark identity. Ask which to start their brand from, or to go fully custom, then pass the chosen \`id\` as \`baseDesignSystemId\` to create-design-system, or pass \`userConfirmedCustom: true\` if they choose fully custom. If no person is there to answer, as in an unattended run, take the top match yourself, say which one you took and why, and call \`get-design-system-kit\` with its \`id\`.`;
|
|
26684
|
+
const MATCH_INSTRUCTIONS = `Keep any already selected design system and its assigned icons; do not reopen that choice unless requested. If none is selected, present these as options, keeping each name as its clickable markdown link (no scores) so they can open the preview. Explain each option's expected buyer fit and trade-offs using known project context; do not claim conversion effects without evidence. Recommend by buyer, never by the product's material or subject (a steel tool does not need a steel-dark UI). For ecommerce and DTC product pages, default to light, warm, photography-led systems unless the brand already owns a dark identity. Ask which to start their brand from, or to go fully custom, then pass the chosen \`id\` as \`baseDesignSystemId\` to create-design-system, or pass \`userConfirmedCustom: true\` if they choose fully custom. If no person is there to answer, as in an unattended run, take the top match whose colour scheme fits the brand yourself (a dark or two-theme site needs a system with a dark mode), say which one you took and why, and call \`get-design-system-kit\` with its \`id\`.`;
|
|
26632
26685
|
const makeSearchHandler = (toolName, includeBuildRules = false) => async ({ query }) => {
|
|
26633
26686
|
const start = Date.now();
|
|
26634
26687
|
const finish = (response, topResults, isError = false, errorMessage) => {
|
|
@@ -28683,7 +28736,7 @@ function extractFromSources(sources, name) {
|
|
|
28683
28736
|
Object.assign(light, parsed.light);
|
|
28684
28737
|
Object.assign(dark, parsed.dark);
|
|
28685
28738
|
} else if (kind === "computed") {
|
|
28686
|
-
Object.assign(light, computedToVars(source.content));
|
|
28739
|
+
Object.assign(source.scheme === "dark" ? dark : light, computedToVars(source.content));
|
|
28687
28740
|
} else {
|
|
28688
28741
|
Object.assign(light, tokensToVars(source.content));
|
|
28689
28742
|
}
|
|
@@ -28756,15 +28809,24 @@ function authHeaders2(authContext) {
|
|
|
28756
28809
|
const bearer = authContext.kind === "api-key" ? authContext.apiKey : authContext.accessToken;
|
|
28757
28810
|
return { Authorization: `Bearer ${bearer}` };
|
|
28758
28811
|
}
|
|
28812
|
+
function nextStepsFor(themeMode) {
|
|
28813
|
+
return themeMode === "dual" ? `${DUAL_THEME_NOTE}
|
|
28814
|
+
|
|
28815
|
+
${NEXT_STEPS}` : NEXT_STEPS;
|
|
28816
|
+
}
|
|
28759
28817
|
function withLoopFirst(summary2) {
|
|
28760
|
-
|
|
28818
|
+
const steps = nextStepsFor(summary2.themeMode);
|
|
28819
|
+
return Object.assign({ buildInstructions: steps }, summary2, { buildInstructions: steps });
|
|
28761
28820
|
}
|
|
28762
28821
|
function fitSheet(text4, maxTokens) {
|
|
28763
28822
|
if (!maxTokens) return text4;
|
|
28764
28823
|
return truncateToTokens(text4, Math.max(0, maxTokens - NOTE_TOKENS));
|
|
28765
28824
|
}
|
|
28766
|
-
function withNextSteps(text4) {
|
|
28767
|
-
|
|
28825
|
+
function withNextSteps(text4, themeMode) {
|
|
28826
|
+
const lead = themeMode === "dual" ? `${DUAL_THEME_NOTE}
|
|
28827
|
+
|
|
28828
|
+
` : "";
|
|
28829
|
+
return `${lead}${BUILD_LOOP}
|
|
28768
28830
|
|
|
28769
28831
|
${text4}
|
|
28770
28832
|
|
|
@@ -28820,7 +28882,7 @@ Common vendor names (Shopify's --color-button family) are mapped into empty sema
|
|
|
28820
28882
|
const result = extractFromSources(all, name);
|
|
28821
28883
|
const text4 = fitSheet(formatExtraction(result), maxTokens);
|
|
28822
28884
|
return {
|
|
28823
|
-
content: [{ type: "text", text: withNextSteps(text4) }],
|
|
28885
|
+
content: [{ type: "text", text: withNextSteps(text4, result.themeMode) }],
|
|
28824
28886
|
structuredContent: withLoopFirst(summarizeExtraction(result))
|
|
28825
28887
|
};
|
|
28826
28888
|
})
|
|
@@ -28869,13 +28931,13 @@ ${BUILD_LOOP}`
|
|
|
28869
28931
|
${body.markdown}` : body.markdown;
|
|
28870
28932
|
const text4 = fitSheet(raw, maxTokens);
|
|
28871
28933
|
return {
|
|
28872
|
-
content: [{ type: "text", text: withNextSteps(text4) }],
|
|
28934
|
+
content: [{ type: "text", text: withNextSteps(text4, body.summary?.themeMode) }],
|
|
28873
28935
|
...body.summary && { structuredContent: withLoopFirst(body.summary) }
|
|
28874
28936
|
};
|
|
28875
28937
|
})
|
|
28876
28938
|
);
|
|
28877
28939
|
}
|
|
28878
|
-
var REFERENCE_NOTE, NEXT_STEPS, NOTE_TOKENS;
|
|
28940
|
+
var REFERENCE_NOTE, NEXT_STEPS, DUAL_THEME_NOTE, NOTE_TOKENS;
|
|
28879
28941
|
var init_extract_tools = __esm({
|
|
28880
28942
|
"../shared/src/mcp/tools/extract-tools.ts"() {
|
|
28881
28943
|
"use strict";
|
|
@@ -28893,6 +28955,7 @@ var init_extract_tools = __esm({
|
|
|
28893
28955
|
NEXT_STEPS = `${BUILD_LOOP}
|
|
28894
28956
|
|
|
28895
28957
|
${REFERENCE_NOTE}`;
|
|
28958
|
+
DUAL_THEME_NOTE = "This site has a light and a dark theme and follows the visitor's system setting. Build both, switched with `prefers-color-scheme`, and choose a design system that has a dark mode.";
|
|
28896
28959
|
NOTE_TOKENS = Math.ceil(NEXT_STEPS.length / 4) + 1;
|
|
28897
28960
|
}
|
|
28898
28961
|
});
|
|
@@ -39482,15 +39545,38 @@ function attribute2(attributes2, name) {
|
|
|
39482
39545
|
const match = new RegExp(`(?:^|\\s)${name}\\s{0,2}=\\s{0,2}(?:"([^"]*)"|'([^']*)'|([^\\s"']+))`, "i").exec(attributes2);
|
|
39483
39546
|
return match ? match[1] ?? match[2] ?? match[3] ?? "" : "";
|
|
39484
39547
|
}
|
|
39548
|
+
function wiringReport(targets, refs) {
|
|
39549
|
+
const broken = /* @__PURE__ */ new Map();
|
|
39550
|
+
for (const { control, id } of refs) {
|
|
39551
|
+
const target = targets.get(id);
|
|
39552
|
+
const ok = target !== void 0 && (control.needs === "element" || (control.needs === "popover" ? target.popover : target.tag === "dialog"));
|
|
39553
|
+
if (ok) continue;
|
|
39554
|
+
const ids = broken.get(control.attribute) ?? [];
|
|
39555
|
+
ids.push(id);
|
|
39556
|
+
broken.set(control.attribute, ids);
|
|
39557
|
+
}
|
|
39558
|
+
return [...broken].map(([name, ids]) => ({
|
|
39559
|
+
name,
|
|
39560
|
+
reason: `${ids.length} control(s) point at a missing or wrong target (${ids.slice(0, 3).map((id) => JSON.stringify(id.slice(0, 40))).join(", ")}), so clicking them does nothing`
|
|
39561
|
+
}));
|
|
39562
|
+
}
|
|
39485
39563
|
function partsReport(body, css = "") {
|
|
39486
39564
|
const rules = classRules(css);
|
|
39487
39565
|
const padded = (names) => names.some((name) => rules.padded.has(name));
|
|
39488
39566
|
const bare = /* @__PURE__ */ new Map();
|
|
39489
39567
|
let loose = 0;
|
|
39490
39568
|
let card = null;
|
|
39569
|
+
const targets = /* @__PURE__ */ new Map();
|
|
39570
|
+
const refs = [];
|
|
39491
39571
|
for (const match of body.matchAll(OPENING_TAG)) {
|
|
39492
39572
|
const tag = match[1].toLowerCase();
|
|
39493
39573
|
const classes = attribute2(match[2], "class").split(/\s+/);
|
|
39574
|
+
const id = attribute2(match[2], "id");
|
|
39575
|
+
if (id) targets.set(id, { tag, popover: /(?:^|\s)popover(?=[\s=/]|$)/i.test(match[2]) });
|
|
39576
|
+
for (const control of CONTROL_TARGETS) {
|
|
39577
|
+
const target = attribute2(match[2], control.attribute);
|
|
39578
|
+
if (target) refs.push({ control, id: target });
|
|
39579
|
+
}
|
|
39494
39580
|
if (card) {
|
|
39495
39581
|
const between2 = body.slice(card.end, match.index);
|
|
39496
39582
|
const empty = between2.trim().startsWith("</");
|
|
@@ -39507,6 +39593,7 @@ function partsReport(body, css = "") {
|
|
|
39507
39593
|
name,
|
|
39508
39594
|
reason: `${count} on the page carry no ds-* class and no class the page styles, so they render as browser defaults`
|
|
39509
39595
|
}));
|
|
39596
|
+
report.push(...wiringReport(targets, refs));
|
|
39510
39597
|
if (loose) {
|
|
39511
39598
|
report.push({
|
|
39512
39599
|
name: "ds-card",
|
|
@@ -39515,11 +39602,17 @@ function partsReport(body, css = "") {
|
|
|
39515
39602
|
}
|
|
39516
39603
|
return report;
|
|
39517
39604
|
}
|
|
39605
|
+
function kitVariantsAfter(componentCss) {
|
|
39606
|
+
return DS_KIT_CSS.split("\n").filter((line2) => {
|
|
39607
|
+
const variant = /^\.(ds-[a-z]+-[a-z0-9-]+)\{/.exec(line2)?.[1];
|
|
39608
|
+
return variant !== void 0 && !componentCss.includes(`.${variant}`);
|
|
39609
|
+
}).join("\n");
|
|
39610
|
+
}
|
|
39518
39611
|
function modelPrototypeDocument(input) {
|
|
39519
39612
|
const { system } = input;
|
|
39520
39613
|
const css = input.css.replace(/</g, "");
|
|
39521
39614
|
const body = withoutUnsafeMarkup(input.body);
|
|
39522
|
-
const kitCss = [compactTokensCss(system.tokensCss), DS_KIT_CSS, system.componentCss].filter(Boolean).join("\n");
|
|
39615
|
+
const kitCss = [compactTokensCss(system.tokensCss), DS_KIT_CSS, system.componentCss, system.componentCss ? kitVariantsAfter(system.componentCss) : ""].filter(Boolean).join("\n");
|
|
39523
39616
|
return [
|
|
39524
39617
|
"<!doctype html>",
|
|
39525
39618
|
'<html lang="en">',
|
|
@@ -39580,7 +39673,7 @@ async function renderPrototypeWithModel(input) {
|
|
|
39580
39673
|
const page = pageFromReply({ frame, designSystem: input.designSystem, text: reply2.text, author: provider.model, omitted: prompt.omitted, warnings });
|
|
39581
39674
|
return { ...page, durationMs, inputTokens: reply2.inputTokens, outputTokens: reply2.outputTokens };
|
|
39582
39675
|
}
|
|
39583
|
-
var unplacedSchema, UNSAFE_BLOCKS, UNSAFE_TAGS, ATTRIBUTE_IN_TAG, COLOUR_PROPERTY, ENTITIES, NATIVE_INPUTS, CONTROLS, CARD_PARTS, OPENING_TAG, MODEL_PAGE_CSP, CONTEXT_CUT_RATIO;
|
|
39676
|
+
var unplacedSchema, UNSAFE_BLOCKS, UNSAFE_TAGS, ATTRIBUTE_IN_TAG, COLOUR_PROPERTY, ENTITIES, NATIVE_INPUTS, CONTROLS, CARD_PARTS, OPENING_TAG, CONTROL_TARGETS, MODEL_PAGE_CSP, CONTEXT_CUT_RATIO;
|
|
39584
39677
|
var init_figma_model = __esm({
|
|
39585
39678
|
"../shared/src/mcp/figma-model.ts"() {
|
|
39586
39679
|
"use strict";
|
|
@@ -39603,6 +39696,12 @@ var init_figma_model = __esm({
|
|
|
39603
39696
|
CONTROLS = /* @__PURE__ */ new Set(["button", "select", "textarea", "input"]);
|
|
39604
39697
|
CARD_PARTS = ["ds-card-header", "ds-card-body", "ds-card-footer"];
|
|
39605
39698
|
OPENING_TAG = /<([a-zA-Z][\w-]*)(?![\w-])([^<>]*)>/g;
|
|
39699
|
+
CONTROL_TARGETS = [
|
|
39700
|
+
{ attribute: "popovertarget", needs: "popover" },
|
|
39701
|
+
{ attribute: "data-dialog-open", needs: "dialog" },
|
|
39702
|
+
{ attribute: "data-toggle", needs: "element" },
|
|
39703
|
+
{ attribute: "data-toggle-open", needs: "element" }
|
|
39704
|
+
];
|
|
39606
39705
|
MODEL_PAGE_CSP = `default-src 'none'; script-src '${PROTOTYPE_RUNTIME_HASH}'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src data:; form-action 'none'; base-uri 'none'`;
|
|
39607
39706
|
CONTEXT_CUT_RATIO = 0.5;
|
|
39608
39707
|
}
|