better-design 0.31.1 → 0.34.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.
- package/README.md +8 -0
- package/dist/index.js +1050 -414
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1232,6 +1232,7 @@ var init_tool_names = __esm({
|
|
|
1232
1232
|
"get-design-config",
|
|
1233
1233
|
"get-design-system-kit",
|
|
1234
1234
|
"get-design-system-status",
|
|
1235
|
+
"get-inspiration",
|
|
1235
1236
|
"get-jig-guide",
|
|
1236
1237
|
"get-motion-guide",
|
|
1237
1238
|
"get-react-native-guide",
|
|
@@ -1251,6 +1252,7 @@ var init_tool_names = __esm({
|
|
|
1251
1252
|
"review-dashboard-structure",
|
|
1252
1253
|
"review-illustration",
|
|
1253
1254
|
"review-ui-code",
|
|
1255
|
+
"send-feedback",
|
|
1254
1256
|
"set-design-config",
|
|
1255
1257
|
"update-feature-map"
|
|
1256
1258
|
];
|
|
@@ -1269,6 +1271,7 @@ var init_tool_names = __esm({
|
|
|
1269
1271
|
"get-design-config",
|
|
1270
1272
|
"get-design-system-kit",
|
|
1271
1273
|
"get-design-system-status",
|
|
1274
|
+
"get-inspiration",
|
|
1272
1275
|
"get-jig-guide",
|
|
1273
1276
|
"get-motion-guide",
|
|
1274
1277
|
"get-react-native-guide",
|
|
@@ -1288,6 +1291,7 @@ var init_tool_names = __esm({
|
|
|
1288
1291
|
"review-dashboard-structure",
|
|
1289
1292
|
"review-illustration",
|
|
1290
1293
|
"review-ui-code",
|
|
1294
|
+
"send-feedback",
|
|
1291
1295
|
"set-design-config",
|
|
1292
1296
|
"update-feature-map"
|
|
1293
1297
|
]);
|
|
@@ -1374,6 +1378,7 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1374
1378
|
|
|
1375
1379
|
- find-design-system searches the catalog. preview-design-system shows one system. get-design-system-kit returns its install command or CSS kit.
|
|
1376
1380
|
- create-design-system generates a custom system. get-design-system-status reports its progress.
|
|
1381
|
+
- get-inspiration returns real App Store screenshot sets from shipped apps as images.
|
|
1377
1382
|
- get-ui-principle, get-ux-principle and the platform guides return design principles and best practices on a topic.
|
|
1378
1383
|
- get-review-rules, check-comprehension, inspect-spacing and review-dashboard-structure check finished interfaces.
|
|
1379
1384
|
- find-icon-library, find-icons and install-icons return icon component files.`;
|
|
@@ -1386,9 +1391,9 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1386
1391
|
"get-shopify-guide": "Returns Better Design's best practices for Shopify Liquid and Online Store 2.0 themes: theme architecture, Liquid objects and filters, section schema, localization, accessibility, performance and Theme Check validation, with links to Shopify's documentation. Pass `topic`, `query`, or nothing for the index of topics.",
|
|
1387
1392
|
"get-motion-guide": "Returns best practices and components for building a product demo or promo video in code with Remotion: the brief, brand and motion tokens, format profiles, captions, pacing, rendering and review. `scaffold: true` returns a complete Remotion starter project. `component` returns one video component's source and install command, and no arguments lists the components. The components come from snapcn (MIT) through the Better Design registry.",
|
|
1388
1393
|
"get-swiftui-guide": "Returns best practices and Swift examples for SwiftUI interfaces: layout and view composition, animation and gestures, Metal shader effects and the platform APIs they use. Pass `topic`, `query`, or nothing for the index of topics.",
|
|
1389
|
-
"get-review-rules": "Returns Better Design's review rules for UI code, each with a severity: accessibility (WCAG 2.1), visual design, animation, content, comprehension and AI features. Pass `category` for one group. Use it when the user wants UI code reviewed. `maxTokens` limits the length of the response.",
|
|
1394
|
+
"get-review-rules": "Returns Better Design's review rules for UI code, each with a severity: accessibility (WCAG 2.1), visual design, animation, content, comprehension and AI features. Pass `category` for one group. Use it when the user wants UI code reviewed. `maxTokens` limits the length of the response. The rules are read against source, so a clean self-review is not visual approval.",
|
|
1390
1395
|
"get-widget-guide": "Returns a build brief for an interactive widget on an article or landing page, such as a calculator, estimator, converter, quiz or checklist. Pass what is known about the widget. The response lists any details still missing, and once the details are complete it returns the brief with rules for calculation accuracy, form behavior, search visibility, performance and privacy.",
|
|
1391
|
-
"check-comprehension":
|
|
1396
|
+
"check-comprehension": 'Checks interface copy for clarity with three tests: whether a person outside the team understands it, whether the screen can be explained in 15 seconds, and whether a screenshot alone says what the screen does. Pass the headline, body and action labels, or the markup. Returns each finding with a severity and a suggested fix. `knownTerms` lists words the audience already knows. `surface: "slide"` adds a density test for slides. It reads text only, not screenshots or visual hierarchy, so a pass is not visual approval. Better Design runs the checks itself and calls no other API.',
|
|
1392
1397
|
"review-dashboard-structure": "Reviews the source of a dashboard or overview page against a fixed rubric: complete source, unchanged data, supported claims, hierarchy, scan order and use of the installed Sidebar, StatCard and Table components. Pass the page source, the data fixture, its interpretation and the component sources. Returns PASS, or FAIL with every finding as a tool error.",
|
|
1393
1398
|
"finalize-dashboard-review": "Captures a deployed dashboard at a public HTTPS URL in Better Design's own browser at 390, 1440 and 1728 pixels wide, compares it with the submitted source, data and components, and returns a receipt tied to that deployment. The receipt reports hierarchy, component, copy and spacing faults and any hidden data fields.",
|
|
1394
1399
|
"find-icon-library": `Searches icon libraries by style, mood or use case, such as "minimal clean professional", and returns the closest matches with a note on each library's style.`,
|
|
@@ -1396,7 +1401,8 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1396
1401
|
"install-icons": "Fetches the named icons from the public Iconify API and returns one React component file per icon, plus an index.ts barrel, as text for the caller to save. It writes no files. Takes a `libraryId` and icon names from find-icons.",
|
|
1397
1402
|
"detect-icon-library": "Reads the contents of the `.library.json` file that an earlier icon install left in a project and returns the icon library the project uses and the icons it has installed, or reports that none is recorded.",
|
|
1398
1403
|
"find-design-system": "Searches Better Design's catalog of design systems by industry, personality or a description of the interface, and returns ranked matches with a preview link for each. Use it when the user wants a design system or a visual direction for an app, site, page, dashboard or presentation.",
|
|
1399
|
-
"get-design-system-kit": "Returns the build kit for a catalog design system or a generated project. `target` picks the format: `react` returns one shadcn install command for every component and the tokens, `html` returns framework-free token and component CSS for inline canvases, `react-native` and `swiftui` return mobile kits, `shopify` returns Liquid theme files,
|
|
1404
|
+
"get-design-system-kit": "Returns the build kit for a catalog design system or a generated project. `target` picks the format: `react` returns one shadcn install command for every component and the tokens, `html` returns framework-free token and component CSS for inline canvases, `react-native` and `swiftui` return mobile kits, `shopify` returns Liquid theme files, `dtcg` returns a W3C Design Tokens document, and `store-screenshots` returns slide files and a render script for App Store and Google Play screenshots. `component` narrows the React install to one component.",
|
|
1405
|
+
"get-inspiration": 'Returns real reference designs from Better Design\'s library as images. `type` picks the collection: today `app-store-screenshots`, the App Store screenshot sets of shipped apps, each with its title and category. `category`, such as "Productivity", narrows the sets, and `limit` sets how many come back, 1 to 6.',
|
|
1400
1406
|
"create-design-system": "Starts generating a custom design system from a description of the product and its look, and returns the project id and project URL at once. Generation runs in the background and typically takes 1 to 3 minutes. get-design-system-status reports progress and returns the install command when it finishes. `baseDesignSystemId` starts from a catalog system, and `brandCss` builds from an existing brand token sheet, such as the output of extract-design-system. Each call uses one credit from the user's Better Design account. Anyone with the registry or preview URL can view the generated source and previews.",
|
|
1401
1407
|
"get-design-system-status": "Reports the progress of a design system started with create-design-system. While generation runs it returns the current stage. When generation finishes it returns the live preview URL and the `npx shadcn add` install command.",
|
|
1402
1408
|
"preview-design-system": "Returns a visual preview of a catalog design system: its palette, typography, buttons, card, input and badge, with a link to the full live showcase. Takes an id from find-design-system.",
|
|
@@ -1406,12 +1412,13 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1406
1412
|
"extract-design-system": "Reads design tokens from CSS the caller passes in and returns the semantic palette, radius, fonts and shadow scale, with a token sheet that converts every color to `oklch()`. It accepts hex, rgb, hsl, named and oklch colors across `:root`, `.dark` and `@theme` blocks. `sources` merges several inputs, including a sample of computed styles from a live page. The server cannot read files, so the caller passes the text.",
|
|
1407
1413
|
"extract-from-url": "Fetches a public web page, reads its stylesheets and renders it in a headless browser to sample the colors, fonts and radii of its body, headings, links, buttons, inputs and cards, then returns the brand as an oklch token sheet. It reads the page the way a browser does and calls no other API. A call takes about 10 to 45 seconds.",
|
|
1408
1414
|
"review-illustration": "Renders an SVG illustration in a browser and returns its visible faults against Better Design's illustration principles, such as open shapes, floating parts, inconsistent light and contents outside their container. Pass the SVG and what it is meant to show. Pass the returned `reviewToken` back to review the same drawing again. SVG that carries scripts, event handlers or remote references is refused.",
|
|
1409
|
-
"inspect-spacing": "Measures spacing in a rendered interface from DOM geometry and returns the spacing faults with a correction for each. The first call returns a read-only capture script to run in the page. The second call takes the captured states, such as viewport widths or hover and focus states, and returns the measurements and findings.",
|
|
1415
|
+
"inspect-spacing": "Measures spacing in a rendered interface from DOM geometry and returns the spacing faults with a correction for each. The first call returns a read-only capture script to run in the page. The second call takes the captured states, such as viewport widths or hover and focus states, and returns the measurements and findings. It measures geometry only, so a clean result is not visual approval.",
|
|
1410
1416
|
"build-feature-map": "Drafts a map of a product's features and flows from its repository file list and returns it as JSON for .better-design/feature-map.json. Routes become screens, nested routes become flows, and components, API routes and hooks join the step they share a name with. Pass every path from `git ls-files` as `files`.",
|
|
1411
1417
|
"read-feature-map": "Returns the flows in a feature map that match a flow name, a feature or a file path, with the screen, route, components and data files of every step. Pass the contents of .better-design/feature-map.json as `map`. With no filter it lists every flow.",
|
|
1412
1418
|
"update-feature-map": "Applies one change to a feature map, such as adding, renaming or removing a feature, or setting, renaming or removing a flow, and returns the updated map.",
|
|
1413
1419
|
"get-team-rules": "Returns the Markdown rules the user's Better Design team wrote for how their product is built and reviewed, or a note that the team has none yet.",
|
|
1414
1420
|
"review-ui-code": "Reviews React component source and CSS and returns findings with line numbers and fixes: native elements that bypass the installed design system, undefined font variables, hardcoded colors and raw palette classes, generated-UI patterns, and WCAG faults such as missing alt text or accessible names. The same input always returns the same findings.",
|
|
1421
|
+
"send-feedback": "Sends Better Design a report of a bug, a missing feature or wrong output in its tools or API: the kind of problem, the tool or endpoint, a one-sentence summary, and optionally what was tried, expected and returned. A person reads every report. It is available only on connections that use a Better Design API key.",
|
|
1415
1422
|
"create-figma-prototype": "Reads a frame from the Figma file that has the Better Design plugin open and returns it as one HTML page built from the design system's components, with a report of every layer that could not be mapped. Name a frame, or omit the name to use the current selection."
|
|
1416
1423
|
};
|
|
1417
1424
|
}
|
|
@@ -1891,6 +1898,7 @@ function buildKitOutputSchema(enableShopifyKit = true) {
|
|
|
1891
1898
|
componentExampleHtml: z5.string().min(1).optional().describe("HTML target only. Starter markup that demonstrates the required .ds wrapper and ds-* component classes."),
|
|
1892
1899
|
tokensJson: z5.string().min(1).optional().describe("DTCG target only. The design tokens as a W3C Design Tokens (DTCG) document. Write it to tokens.json; every token tool reads this format, including Style Dictionary, Tokens Studio, Terrazzo, and the Figma plugins that create variables."),
|
|
1893
1900
|
nativeFiles: z5.array(z5.object({ path: z5.string().min(1), content: z5.string().min(1) })).optional().describe("React Native and SwiftUI targets only. Write each file verbatim at its path: this system's stored theme and material, and the primitives that read it."),
|
|
1901
|
+
screenshotFiles: z5.array(z5.object({ path: z5.string().min(1), content: z5.string().min(1) })).optional().describe("Store screenshots target only. Write each file verbatim at its path, then follow buildInstructions to capture screens, write the slides and render the PNG files."),
|
|
1894
1902
|
unsupportedTokens: z5.array(z5.object({
|
|
1895
1903
|
name: z5.string().min(1),
|
|
1896
1904
|
value: z5.string().min(1),
|
|
@@ -4270,12 +4278,15 @@ var init_review_rules_description = __esm({
|
|
|
4270
4278
|
"content",
|
|
4271
4279
|
"comprehension",
|
|
4272
4280
|
"ai-features",
|
|
4281
|
+
"coverage-gaps",
|
|
4273
4282
|
"all"
|
|
4274
4283
|
];
|
|
4275
4284
|
REVIEW_RULES_DESCRIPTION = `Retrieves accessibility (WCAG 2.1), visual design, animation, content, comprehension, and AI feature review rules for analyzing code.
|
|
4276
4285
|
|
|
4277
4286
|
You MUST call this after writing UI code to self-review your output, then fix every critical and serious issue BEFORE presenting the result.
|
|
4278
4287
|
|
|
4288
|
+
It returns rules to check against. It approves nothing: a clean self-review against these rules is not visual approval, because the rules are read against source, not against what renders.
|
|
4289
|
+
|
|
4279
4290
|
${REDESIGN_SCOPE_CONTRACT}
|
|
4280
4291
|
|
|
4281
4292
|
${DESIGN_TRADEOFF_RULE}
|
|
@@ -4318,6 +4329,8 @@ ${DESIGN_TRADEOFF_RULE}
|
|
|
4318
4329
|
- Critical: Output saved or sent without the user accepting it, silent failures, no manual path when AI is unavailable, user data read before consent, invented citations or unearned certainty
|
|
4319
4330
|
- Serious: AI output with no visible marker, long runs with no sign or a Stop that deletes partial output, refusals that name no topic and offer no alternative, feedback controls with no destination
|
|
4320
4331
|
|
|
4332
|
+
Every rule carries an ID (\`rule/\u2026\`), a source and an exceptions line. Cite the ID in each finding. The \`coverage-gaps\` category lists the surfaces that have no rule yet.
|
|
4333
|
+
|
|
4321
4334
|
Use maxTokens to limit rule content (recommended: 5000\u201310000). Required workflow guidance is always returned in full.`;
|
|
4322
4335
|
}
|
|
4323
4336
|
});
|
|
@@ -5156,6 +5169,22 @@ These rules were read against source. They cannot see layout. Three checks remai
|
|
|
5156
5169
|
2. Call \`inspect-spacing\` on the rendered DOM at 1440, 1728 and 390 wide. The first call returns a capture script and measures nothing; the layout is measured only when a second call, with the captured states, returns status \`completed\`. Rules read code; only measurement catches a label sitting 9px off its own circle, an active state that is 2px wider than its siblings and shifts the row, or a sidebar that leaves page background beside it on a wide screen.
|
|
5157
5170
|
3. For dashboards, deploy the exact source to a public HTTPS preview, then call \`finalize-dashboard-review\` with that URL, the exact complete and visible source, unchanged fixture, and installed component source. Better Design's own browser captures the preview at 390, 1440, and 1728, content-addresses the live deployment, derives the factual interpretation, and issues the only valid review receipt. No trusted browser capture means incomplete. An invalid deployment revision, invalid geometry, or hidden fixture field also blocks approval. Any later source or deployment edit voids the receipt. Then confirm every nav item, link, tab and button works.
|
|
5158
5171
|
|
|
5172
|
+
## Presentations
|
|
5173
|
+
|
|
5174
|
+
A deck passes only on rendered evidence. For every slide:
|
|
5175
|
+
|
|
5176
|
+
1. Call \`check-comprehension\` with \`surface: "slide"\`. Keep one idea per slide and fix every density finding.
|
|
5177
|
+
2. Render the deck and review a screenshot of every slide: the title reads first, one element holds the eye, and nothing competes with it. Text checks and \`inspect-spacing\` cannot see this, so no score stands in for the screenshot.
|
|
5178
|
+
|
|
5179
|
+
## Check the rules below in two passes
|
|
5180
|
+
|
|
5181
|
+
An agent that reviews its own screen in one pass tends to approve it. Split the review:
|
|
5182
|
+
|
|
5183
|
+
1. Per rule: give each rule section below (each \`# \` heading) to its own subagent, in a clean context that holds only that section and the source. A host without subagents checks one section at a time and writes down its findings before it reads the next.
|
|
5184
|
+
2. Skeptic pass: give every flag to one fresh subagent that tries to disprove it. It keeps a flag only when it can cite the file and line that break the rule.
|
|
5185
|
+
|
|
5186
|
+
Cite the rule ID with every finding: the \`rule/\u2026\` line under each rule heading. Read its Exceptions line before you flag. A surface listed under coverage gaps has no rule yet, so say that instead of inventing one.
|
|
5187
|
+
|
|
5159
5188
|
Fix every critical and serious finding before you present.`;
|
|
5160
5189
|
}
|
|
5161
5190
|
});
|
|
@@ -5179,6 +5208,8 @@ function looksLikeCard(candidate) {
|
|
|
5179
5208
|
}
|
|
5180
5209
|
function redactSecrets(value) {
|
|
5181
5210
|
let out = value.replace(/\u0000/g, "");
|
|
5211
|
+
for (const pattern of CREDENTIAL_VALUE_PATTERNS) out = out.replace(pattern, "[redacted]");
|
|
5212
|
+
out = out.replace(JWT_PATTERN, "$1[redacted]");
|
|
5182
5213
|
for (const pattern of SECRET_PATTERNS) out = out.replace(pattern, "[redacted]");
|
|
5183
5214
|
out = out.replace(EMAIL_PATTERN, "[redacted-email]");
|
|
5184
5215
|
out = out.replace(
|
|
@@ -5197,7 +5228,7 @@ function redactUrl(raw) {
|
|
|
5197
5228
|
return "[redacted]";
|
|
5198
5229
|
}
|
|
5199
5230
|
}
|
|
5200
|
-
var SECRET_PATTERNS, EMAIL_PATTERN, CARD_CANDIDATE_PATTERN, PHONE_PATTERNS;
|
|
5231
|
+
var SECRET_PATTERNS, CREDENTIAL_VALUE_PATTERNS, JWT_PATTERN, EMAIL_PATTERN, CARD_CANDIDATE_PATTERN, PHONE_PATTERNS;
|
|
5201
5232
|
var init_redact = __esm({
|
|
5202
5233
|
"../shared/src/redact.ts"() {
|
|
5203
5234
|
"use strict";
|
|
@@ -5213,9 +5244,9 @@ var init_redact = __esm({
|
|
|
5213
5244
|
/\bwhsec_[A-Za-z0-9]{16,}/g,
|
|
5214
5245
|
/\bAIza[A-Za-z0-9_-]{30,}/g,
|
|
5215
5246
|
/\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
|
|
5216
|
-
/\
|
|
5217
|
-
|
|
5218
|
-
|
|
5247
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g
|
|
5248
|
+
];
|
|
5249
|
+
CREDENTIAL_VALUE_PATTERNS = [
|
|
5219
5250
|
// key=value forms for the names that carry credentials. A quoted value is
|
|
5220
5251
|
// taken whole, so a passphrase with spaces and a short password both go:
|
|
5221
5252
|
// the old form needed eight characters and no spaces, which let
|
|
@@ -5223,8 +5254,13 @@ var init_redact = __esm({
|
|
|
5223
5254
|
// through into a corpus held for 180 days. [BD-697]
|
|
5224
5255
|
/(?:\b|_)(?:api[_-]?key|access[_-]?token|refresh[_-]?token|secret(?:[_-]?key)?|password|passwd|authorization)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[A-Za-z0-9._~+/-]+)/gi,
|
|
5225
5256
|
// Authorization headers separate the scheme from the token with a space.
|
|
5226
|
-
/\b(?:bearer|basic|token)\s+[A-Za-z0-9._~+/-]{12,}=*/gi
|
|
5257
|
+
/\b(?:bearer|basic|token)\s+[A-Za-z0-9._~+/-]{12,}=*/gi,
|
|
5258
|
+
// A token in a query string, such as the claim link an agent gets when its
|
|
5259
|
+
// free budget runs out. That link hands the agent's keys to whoever opens
|
|
5260
|
+
// it. [BD-956]
|
|
5261
|
+
/[?&]token=[A-Za-z0-9_-]{16,}/gi
|
|
5227
5262
|
];
|
|
5263
|
+
JWT_PATTERN = /(?<![A-Za-z0-9_-])(?=[A-Za-z0-9_-]*\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-])([A-Za-z0-9_-]*?)(?<![A-Za-z0-9_])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+/g;
|
|
5228
5264
|
EMAIL_PATTERN = /(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
5229
5265
|
CARD_CANDIDATE_PATTERN = /\b(?:\d[ -]?){12,18}\d\b/g;
|
|
5230
5266
|
PHONE_PATTERNS = [
|
|
@@ -5358,7 +5394,7 @@ var init_extract = __esm({
|
|
|
5358
5394
|
});
|
|
5359
5395
|
|
|
5360
5396
|
// ../shared/src/mcp/comprehension/lexicon.ts
|
|
5361
|
-
var TECHNICAL_TERMS, PRODUCT_TERMS, KNOWN_ACRONYMS, VAGUE_ACTIONS, MAX_SENTENCE_WORDS, MAX_SCREEN_WORDS, MAX_ACTIONS, EXPLAIN_BUDGET_SECONDS;
|
|
5397
|
+
var TECHNICAL_TERMS, PRODUCT_TERMS, KNOWN_ACRONYMS, VAGUE_ACTIONS, MAX_SENTENCE_WORDS, MAX_SCREEN_WORDS, MAX_ACTIONS, EXPLAIN_BUDGET_SECONDS, MAX_SLIDE_WORDS, MAX_SLIDE_LINES, SLIDE_NAME;
|
|
5362
5398
|
var init_lexicon = __esm({
|
|
5363
5399
|
"../shared/src/mcp/comprehension/lexicon.ts"() {
|
|
5364
5400
|
"use strict";
|
|
@@ -5467,6 +5503,9 @@ var init_lexicon = __esm({
|
|
|
5467
5503
|
MAX_SCREEN_WORDS = 60;
|
|
5468
5504
|
MAX_ACTIONS = 3;
|
|
5469
5505
|
EXPLAIN_BUDGET_SECONDS = 15;
|
|
5506
|
+
MAX_SLIDE_WORDS = 40;
|
|
5507
|
+
MAX_SLIDE_LINES = 5;
|
|
5508
|
+
SLIDE_NAME = /\b(slides?|deck|presentation)\b/i;
|
|
5470
5509
|
}
|
|
5471
5510
|
});
|
|
5472
5511
|
|
|
@@ -5541,7 +5580,7 @@ function estimateExplainSeconds(words2, insiderTermCount, actionCount) {
|
|
|
5541
5580
|
const choosing = Math.max(0, actionCount - 1) * 3;
|
|
5542
5581
|
return Math.round(speaking + defining + choosing);
|
|
5543
5582
|
}
|
|
5544
|
-
function fifteenSecondFindings(words2, actions, seconds) {
|
|
5583
|
+
function fifteenSecondFindings(words2, actions, seconds, surface) {
|
|
5545
5584
|
const findings = [];
|
|
5546
5585
|
if (seconds > EXPLAIN_BUDGET_SECONDS) {
|
|
5547
5586
|
findings.push({
|
|
@@ -5552,7 +5591,7 @@ function fifteenSecondFindings(words2, actions, seconds) {
|
|
|
5552
5591
|
fix: "Cut to one idea and one next step. Move the rest behind a link or a later screen."
|
|
5553
5592
|
});
|
|
5554
5593
|
}
|
|
5555
|
-
if (words2 > MAX_SCREEN_WORDS) {
|
|
5594
|
+
if (surface === "screen" && words2 > MAX_SCREEN_WORDS) {
|
|
5556
5595
|
findings.push({
|
|
5557
5596
|
test: "fifteen-second",
|
|
5558
5597
|
severity: "serious",
|
|
@@ -5613,7 +5652,30 @@ function screenshotFindings(copy, words2) {
|
|
|
5613
5652
|
}
|
|
5614
5653
|
return findings;
|
|
5615
5654
|
}
|
|
5655
|
+
function slideFindings(words2, body) {
|
|
5656
|
+
const findings = [];
|
|
5657
|
+
if (words2 > MAX_SLIDE_WORDS) {
|
|
5658
|
+
findings.push({
|
|
5659
|
+
test: "slide",
|
|
5660
|
+
severity: "serious",
|
|
5661
|
+
problem: `The slide shows ${words2} words, so the audience reads instead of listening.`,
|
|
5662
|
+
evidence: `${words2} words of visible copy`,
|
|
5663
|
+
fix: `Keep a slide under about ${MAX_SLIDE_WORDS} words: a title that states the point and a few short lines.`
|
|
5664
|
+
});
|
|
5665
|
+
}
|
|
5666
|
+
if (body.length > MAX_SLIDE_LINES) {
|
|
5667
|
+
findings.push({
|
|
5668
|
+
test: "slide",
|
|
5669
|
+
severity: "serious",
|
|
5670
|
+
problem: `The slide stacks ${body.length} lines of copy, which usually means more than one idea.`,
|
|
5671
|
+
evidence: body.join(" | "),
|
|
5672
|
+
fix: "Give each idea its own slide, with the point in the title."
|
|
5673
|
+
});
|
|
5674
|
+
}
|
|
5675
|
+
return findings;
|
|
5676
|
+
}
|
|
5616
5677
|
function analyzeComprehension(input) {
|
|
5678
|
+
const surface = input.surface ?? (SLIDE_NAME.test(input.screen) ? "slide" : "screen");
|
|
5617
5679
|
const copy = resolveCopy(input);
|
|
5618
5680
|
const known = new Set((input.knownTerms ?? []).map((term) => term.trim().toLowerCase()));
|
|
5619
5681
|
const visible = [copy.headline ?? "", ...copy.body, ...copy.actions].join(". ");
|
|
@@ -5622,13 +5684,16 @@ function analyzeComprehension(input) {
|
|
|
5622
5684
|
const estimatedExplainSeconds = estimateExplainSeconds(words2, insiderTerms.length, copy.actions.length);
|
|
5623
5685
|
const findings = [
|
|
5624
5686
|
...momTestFindings(visible, known, insiderTerms),
|
|
5625
|
-
...fifteenSecondFindings(words2, copy.actions, estimatedExplainSeconds),
|
|
5626
|
-
...screenshotFindings(copy, words2)
|
|
5687
|
+
...fifteenSecondFindings(words2, copy.actions, estimatedExplainSeconds, surface),
|
|
5688
|
+
...screenshotFindings(copy, words2),
|
|
5689
|
+
...surface === "slide" ? slideFindings(words2, copy.body) : []
|
|
5627
5690
|
];
|
|
5691
|
+
const blocking = findings.some((finding3) => finding3.severity !== "minor");
|
|
5628
5692
|
const longestSentence = sentences(visible).sort((a, b) => countWords(b) - countWords(a))[0] ?? "";
|
|
5629
5693
|
return {
|
|
5630
5694
|
screen: input.screen,
|
|
5631
|
-
|
|
5695
|
+
surface,
|
|
5696
|
+
verdict: blocking ? "revise" : surface === "slide" ? "needs-screenshot" : "pass",
|
|
5632
5697
|
findings,
|
|
5633
5698
|
copy: { headline: copy.headline, body: copy.body, actions: copy.actions },
|
|
5634
5699
|
stats: {
|
|
@@ -5672,7 +5737,8 @@ function formatStats(report) {
|
|
|
5672
5737
|
"",
|
|
5673
5738
|
`- Visible copy: ${words2} words across ${actionCount} action${actionCount === 1 ? "" : "s"}.`,
|
|
5674
5739
|
`- Insider words found: ${insiderTerms.length > 0 ? insiderTerms.join(", ") : "none"}.`,
|
|
5675
|
-
`- Rough explain time: about ${estimatedExplainSeconds} seconds. This is a heuristic budget from word count, insider words and choices, not a measurement of real users
|
|
5740
|
+
`- Rough explain time: about ${estimatedExplainSeconds} seconds. This is a heuristic budget from word count, insider words and choices, not a measurement of real users.`,
|
|
5741
|
+
"- It read text only: the copy passed in, or the text in the markup. It cannot see screenshots, text inside images, or visual hierarchy."
|
|
5676
5742
|
].join("\n");
|
|
5677
5743
|
}
|
|
5678
5744
|
function formatComprehensionReport(report) {
|
|
@@ -5680,8 +5746,11 @@ function formatComprehensionReport(report) {
|
|
|
5680
5746
|
(totals, finding3) => ({ ...totals, [finding3.severity]: totals[finding3.severity] + 1 }),
|
|
5681
5747
|
{ critical: 0, serious: 0, minor: 0 }
|
|
5682
5748
|
);
|
|
5683
|
-
const verdict = report.verdict === "
|
|
5684
|
-
|
|
5749
|
+
const { line: verdict, next } = report.verdict === "revise" ? {
|
|
5750
|
+
line: `Revise before presenting. ${counts.critical} critical, ${counts.serious} serious, ${counts.minor} minor.`,
|
|
5751
|
+
next: "Rewrite the copy so it survives every test, then call check-comprehension again on the new copy. Do not present the screen while a critical or serious finding stands."
|
|
5752
|
+
} : VERDICTS[report.verdict];
|
|
5753
|
+
const slideSection = report.surface === "slide" ? ["", formatTestSection(report, "slide")] : [];
|
|
5685
5754
|
return [
|
|
5686
5755
|
`# Comprehension check: ${report.screen}`,
|
|
5687
5756
|
"",
|
|
@@ -5692,6 +5761,7 @@ function formatComprehensionReport(report) {
|
|
|
5692
5761
|
formatTestSection(report, "fifteen-second"),
|
|
5693
5762
|
"",
|
|
5694
5763
|
formatTestSection(report, "screenshot"),
|
|
5764
|
+
...slideSection,
|
|
5695
5765
|
"",
|
|
5696
5766
|
formatStats(report),
|
|
5697
5767
|
"",
|
|
@@ -5700,16 +5770,27 @@ function formatComprehensionReport(report) {
|
|
|
5700
5770
|
next
|
|
5701
5771
|
].join("\n");
|
|
5702
5772
|
}
|
|
5703
|
-
var TEST_TITLES, SEVERITY_ORDER;
|
|
5773
|
+
var TEST_TITLES, SEVERITY_ORDER, VERDICTS;
|
|
5704
5774
|
var init_format = __esm({
|
|
5705
5775
|
"../shared/src/mcp/comprehension/format.ts"() {
|
|
5706
5776
|
"use strict";
|
|
5707
5777
|
TEST_TITLES = {
|
|
5708
5778
|
mom: "Mom test: would someone outside the team understand this?",
|
|
5709
5779
|
"fifteen-second": "15 second test: can you explain the screen in 15 seconds?",
|
|
5710
|
-
screenshot: "Screenshot test: with zero context, is it clear what this does?"
|
|
5780
|
+
screenshot: "Screenshot test: with zero context, is it clear what this does?",
|
|
5781
|
+
slide: "Slide test: one idea, and few enough words to hear the speaker?"
|
|
5711
5782
|
};
|
|
5712
5783
|
SEVERITY_ORDER = { critical: 0, serious: 1, minor: 2 };
|
|
5784
|
+
VERDICTS = {
|
|
5785
|
+
pass: {
|
|
5786
|
+
line: "Pass. Nothing blocking. Read the copy once more out loud before you present it.",
|
|
5787
|
+
next: "Say in one sentence what a first-time user can do on this screen. If that sentence needs a second sentence to make sense, simplify the screen."
|
|
5788
|
+
},
|
|
5789
|
+
"needs-screenshot": {
|
|
5790
|
+
line: "The text passes. This is not approval: a slide passes only after you review its rendered screenshot.",
|
|
5791
|
+
next: "Render the slide at its real size and look at the screenshot. Confirm it carries one idea, the title reads first, and one element holds the eye. Split or restyle the slide if any of the three fails."
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5713
5794
|
}
|
|
5714
5795
|
});
|
|
5715
5796
|
|
|
@@ -5734,6 +5815,8 @@ function registerComprehensionTools({ server, provider, track, userId }) {
|
|
|
5734
5815
|
|
|
5735
5816
|
Review rules catch what is broken. This catches what is unclear: copy that makes sense to the people who built the product and to nobody else.
|
|
5736
5817
|
|
|
5818
|
+
It reads text only: the copy you pass, or the text it extracts from your markup. It does not see what renders: not screenshots, text inside images, or visual hierarchy. A pass is a text result, not visual approval.
|
|
5819
|
+
|
|
5737
5820
|
Three tests, run on the screen's own words:
|
|
5738
5821
|
1. Mom test. Would a person outside the team understand this? Flags insider vocabulary, unexplained acronyms and sentences too long to follow.
|
|
5739
5822
|
2. 15 second test. If the screen takes longer than 15 seconds to explain, it is too complex. Flags word count, stacked concepts and competing actions.
|
|
@@ -5743,6 +5826,8 @@ Pass either the copy or the markup:
|
|
|
5743
5826
|
- check-comprehension({ screen: "Lesson end card", headline: "Lesson complete", body: ["Claim the June badge by collecting more quest points from your daily, friend and weekend quests."], actions: ["Continue"] })
|
|
5744
5827
|
- check-comprehension({ screen: "Billing page", code: "<section>\u2026the JSX you wrote\u2026</section>" })
|
|
5745
5828
|
|
|
5829
|
+
For a slide, pass surface: "slide" (a screen named slide, deck or presentation gets it by default) and call once per slide. A slide adds a density test, and it never passes on text: approve it only after you review its rendered screenshot.
|
|
5830
|
+
|
|
5746
5831
|
Use knownTerms for words your audience genuinely knows, so the check stops flagging them. Fix every critical and serious finding, then call this again on the rewritten copy.`,
|
|
5747
5832
|
inputSchema: z9.object({
|
|
5748
5833
|
screen: z9.string().trim().min(1).max(120).describe("What this screen is, e.g. 'lesson end card', 'billing page'."),
|
|
@@ -5751,6 +5836,7 @@ Use knownTerms for words your audience genuinely knows, so the check stops flagg
|
|
|
5751
5836
|
actions: z9.array(z9.string().trim().min(1).max(120)).max(20).optional().describe("Visible button and link labels, e.g. ['Claim badge', 'Not now']."),
|
|
5752
5837
|
code: z9.string().max(6e4).optional().describe("JSX, TSX or HTML for the screen. Copy is extracted from it when headline/body/actions are omitted."),
|
|
5753
5838
|
knownTerms: z9.array(z9.string().trim().min(1).max(60)).max(50).optional().describe("Words this audience already knows, so they are not flagged as insider language."),
|
|
5839
|
+
surface: z9.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.'),
|
|
5754
5840
|
context: CONTEXT_ARG
|
|
5755
5841
|
}),
|
|
5756
5842
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -10867,6 +10953,184 @@ ${FAQ_CARD_ITEM}
|
|
|
10867
10953
|
}
|
|
10868
10954
|
});
|
|
10869
10955
|
|
|
10956
|
+
// ../shared/src/mcp/store-screenshots.ts
|
|
10957
|
+
function renderScript() {
|
|
10958
|
+
const sizes = JSON.stringify(STORE_SCREENSHOT_SIZES);
|
|
10959
|
+
const feature = JSON.stringify(PLAY_FEATURE_GRAPHIC);
|
|
10960
|
+
return `// Renders store screenshots from slides.json at the exact store sizes. [Better Design]
|
|
10961
|
+
// Run once per change: node store-screenshots/render.mjs
|
|
10962
|
+
import { chromium } from "playwright";
|
|
10963
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
10964
|
+
import { dirname, join } from "node:path";
|
|
10965
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10966
|
+
|
|
10967
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10968
|
+
const SIZES = ${sizes};
|
|
10969
|
+
const FEATURE = ${feature};
|
|
10970
|
+
const RANGE = ${JSON.stringify(STORE_SLIDE_RANGE)};
|
|
10971
|
+
const deck = JSON.parse(readFileSync(join(here, "slides.json"), "utf8"));
|
|
10972
|
+
|
|
10973
|
+
if (deck.slides.length < RANGE.min || deck.slides.length > RANGE.max) {
|
|
10974
|
+
throw new Error(\`slides.json holds \${deck.slides.length} slides. Use \${RANGE.min} to \${RANGE.max}: Google Play shows at most \${RANGE.max}.\`);
|
|
10975
|
+
}
|
|
10976
|
+
|
|
10977
|
+
// App Store Connect rejects a PNG with an alpha channel, so check the header, not the look.
|
|
10978
|
+
function check(file, size) {
|
|
10979
|
+
const png = readFileSync(file);
|
|
10980
|
+
const width = png.readUInt32BE(16), height = png.readUInt32BE(20), colorType = png[25];
|
|
10981
|
+
if (width !== size.width || height !== size.height) throw new Error(\`\${file} is \${width} x \${height}, not \${size.width} x \${size.height}.\`);
|
|
10982
|
+
if (colorType !== 2) throw new Error(\`\${file} has a transparency layer. Give every slide an opaque background.\`);
|
|
10983
|
+
}
|
|
10984
|
+
|
|
10985
|
+
async function shoot(browser, size, data, file) {
|
|
10986
|
+
const page = await browser.newPage({ viewport: { width: size.width, height: size.height }, deviceScaleFactor: 1 });
|
|
10987
|
+
await page.addInitScript((slide) => { window.__SLIDE__ = slide; }, data);
|
|
10988
|
+
await page.goto(pathToFileURL(join(here, "slide.html")).href);
|
|
10989
|
+
await page.evaluate(async () => {
|
|
10990
|
+
await document.fonts.ready;
|
|
10991
|
+
await Promise.all([...document.images].map((img) => img.decode().catch(() => { throw new Error("Missing capture: " + img.src); })));
|
|
10992
|
+
});
|
|
10993
|
+
await page.screenshot({ path: file, type: "png" });
|
|
10994
|
+
await page.close();
|
|
10995
|
+
check(file, size);
|
|
10996
|
+
}
|
|
10997
|
+
|
|
10998
|
+
const browser = await chromium.launch();
|
|
10999
|
+
try {
|
|
11000
|
+
for (const size of SIZES) {
|
|
11001
|
+
mkdirSync(join(here, "out", size.id), { recursive: true });
|
|
11002
|
+
for (const [i, slide] of deck.slides.entries()) {
|
|
11003
|
+
const file = join(here, "out", size.id, String(i + 1).padStart(2, "0") + ".png");
|
|
11004
|
+
await shoot(browser, size, { kind: "slide", app: deck.app, slide, frame: size.frame }, file);
|
|
11005
|
+
console.log("wrote", file);
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
mkdirSync(join(here, "out", FEATURE.id), { recursive: true });
|
|
11009
|
+
const feature = join(here, "out", FEATURE.id, "feature-graphic.png");
|
|
11010
|
+
await shoot(browser, FEATURE, { kind: "feature", app: deck.app, slide: deck.featureGraphic, frame: "none" }, feature);
|
|
11011
|
+
console.log("wrote", feature);
|
|
11012
|
+
} finally {
|
|
11013
|
+
await browser.close();
|
|
11014
|
+
}
|
|
11015
|
+
`;
|
|
11016
|
+
}
|
|
11017
|
+
function buildStoreScreenshotFiles(tokensCss) {
|
|
11018
|
+
return [
|
|
11019
|
+
{ path: "store-screenshots/theme.css", content: tokensCss },
|
|
11020
|
+
{ path: "store-screenshots/slides.json", content: `${JSON.stringify(EXAMPLE_SLIDES, null, 2)}
|
|
11021
|
+
` },
|
|
11022
|
+
{ path: "store-screenshots/slide.html", content: SLIDE_HTML },
|
|
11023
|
+
{ path: "store-screenshots/render.mjs", content: renderScript() }
|
|
11024
|
+
];
|
|
11025
|
+
}
|
|
11026
|
+
function fencedFile(file) {
|
|
11027
|
+
const longest = Math.max(0, ...[...file.content.matchAll(/`+/g)].map((run) => run[0].length));
|
|
11028
|
+
const fence3 = "`".repeat(Math.max(3, longest + 1));
|
|
11029
|
+
return `## ${file.path}
|
|
11030
|
+
|
|
11031
|
+
${fence3}
|
|
11032
|
+
${file.content.replace(/\n$/, "")}
|
|
11033
|
+
${fence3}`;
|
|
11034
|
+
}
|
|
11035
|
+
function storeScreenshotText(name, files) {
|
|
11036
|
+
return [storeScreenshotInstructions(name), ...files.map(fencedFile)].join("\n\n");
|
|
11037
|
+
}
|
|
11038
|
+
function storeScreenshotInstructions(name) {
|
|
11039
|
+
const sizes = [...STORE_SCREENSHOT_SIZES, PLAY_FEATURE_GRAPHIC].map((size) => `- ${size.label}: ${size.width} x ${size.height}, in \`store-screenshots/out/${size.id}/\``).join("\n");
|
|
11040
|
+
return [
|
|
11041
|
+
`# Store screenshots in ${name}`,
|
|
11042
|
+
"",
|
|
11043
|
+
"1. Write each file in `screenshotFiles` verbatim at its path.",
|
|
11044
|
+
`2. Capture ${STORE_SLIDE_RANGE.min} to ${STORE_SLIDE_RANGE.max} app screens into \`store-screenshots/captures/\`. iOS Simulator: \`xcrun simctl io booted screenshot store-screenshots/captures/01.png\`. Android emulator: \`adb exec-out screencap -p > store-screenshots/captures/01.png\`.`,
|
|
11045
|
+
'3. Edit `slides.json`: one outcome per slide, a headline a person understands in one second, and no claims such as "Best", "#1" or "Free", which Google Play rejects. Check every headline with Better Design\'s `check-comprehension` tool before you render.',
|
|
11046
|
+
"4. Install Playwright once as a dev dependency with the project's package manager, then its browser: `npx playwright install chromium`.",
|
|
11047
|
+
"5. Google Play asks the feature graphic to avoid pure white, black and dark gray. When this system's primary color is one of those, set `--feature-background` and `--feature-foreground` to a brand color pair at the end of `theme.css`.",
|
|
11048
|
+
"6. Run `node store-screenshots/render.mjs`. It stops on any file with the wrong size or a transparency layer.",
|
|
11049
|
+
"",
|
|
11050
|
+
"It writes:",
|
|
11051
|
+
sizes,
|
|
11052
|
+
"",
|
|
11053
|
+
"Google Play screenshots carry no drawn phone, because Google asks for no device imagery. Show the user every PNG before they upload it."
|
|
11054
|
+
].join("\n");
|
|
11055
|
+
}
|
|
11056
|
+
var STORE_SCREENSHOT_SIZES, PLAY_FEATURE_GRAPHIC, STORE_SLIDE_RANGE, EXAMPLE_SLIDES, SLIDE_HTML;
|
|
11057
|
+
var init_store_screenshots = __esm({
|
|
11058
|
+
"../shared/src/mcp/store-screenshots.ts"() {
|
|
11059
|
+
"use strict";
|
|
11060
|
+
STORE_SCREENSHOT_SIZES = [
|
|
11061
|
+
{ id: "iphone-6-9", label: "iPhone 6.9 inch", width: 1320, height: 2868, frame: "phone" },
|
|
11062
|
+
{ id: "play-phone", label: "Google Play phone", width: 1080, height: 1920, frame: "none" }
|
|
11063
|
+
];
|
|
11064
|
+
PLAY_FEATURE_GRAPHIC = {
|
|
11065
|
+
id: "play-feature-graphic",
|
|
11066
|
+
label: "Google Play feature graphic",
|
|
11067
|
+
width: 1024,
|
|
11068
|
+
height: 500,
|
|
11069
|
+
frame: "none"
|
|
11070
|
+
};
|
|
11071
|
+
STORE_SLIDE_RANGE = { min: 3, max: 8 };
|
|
11072
|
+
EXAMPLE_SLIDES = {
|
|
11073
|
+
app: "Your app",
|
|
11074
|
+
slides: [
|
|
11075
|
+
{ headline: "Say the first outcome in five words", subhead: "", capture: "captures/01.png" },
|
|
11076
|
+
{ headline: "Show the second outcome next", subhead: "", capture: "captures/02.png" },
|
|
11077
|
+
{ headline: "End on the reason to stay", subhead: "", capture: "captures/03.png" }
|
|
11078
|
+
],
|
|
11079
|
+
featureGraphic: { headline: "Your app, in one line" }
|
|
11080
|
+
};
|
|
11081
|
+
SLIDE_HTML = `<!doctype html>
|
|
11082
|
+
<html lang="en">
|
|
11083
|
+
<head>
|
|
11084
|
+
<meta charset="utf-8">
|
|
11085
|
+
<link rel="stylesheet" href="theme.css">
|
|
11086
|
+
<style>
|
|
11087
|
+
html, body { margin: 0; width: 100vw; height: 100vh; overflow: hidden; }
|
|
11088
|
+
body { background: var(--background); color: var(--foreground); font-family: var(--font-sans, system-ui, sans-serif);
|
|
11089
|
+
display: flex; flex-direction: column; align-items: center; box-sizing: border-box; padding: 7vh 8vw 0; gap: 3vh; }
|
|
11090
|
+
h1 { margin: 0; font-size: 7.5vw; line-height: 1.12; font-weight: 700; text-align: center; text-wrap: balance; letter-spacing: -0.02em; }
|
|
11091
|
+
p { margin: 0; font-size: 4vw; line-height: 1.35; text-align: center; color: var(--muted-foreground); text-wrap: balance; }
|
|
11092
|
+
.shot { flex: 1; min-height: 0; width: 100%; display: flex; justify-content: center; align-items: flex-start; }
|
|
11093
|
+
.shot img { display: block; height: 100%; max-width: 100%; object-fit: cover; object-position: top; }
|
|
11094
|
+
.phone { height: 104%; aspect-ratio: 9 / 19.5; box-sizing: border-box; padding: 1.6vw; border-radius: 11vw 11vw 0 0;
|
|
11095
|
+
background: var(--foreground); overflow: hidden; }
|
|
11096
|
+
.phone img { width: 100%; height: 100%; border-radius: 9.4vw 9.4vw 0 0; }
|
|
11097
|
+
.bare img { border-radius: calc(var(--radius, 0.75rem) * 3) calc(var(--radius, 0.75rem) * 3) 0 0; }
|
|
11098
|
+
body.feature { background: var(--feature-background, var(--primary)); color: var(--feature-foreground, var(--primary-foreground)); justify-content: center; align-items: flex-start; padding: 0 10vw; gap: 4vh; }
|
|
11099
|
+
body.feature h1 { font-size: 7vw; text-align: left; }
|
|
11100
|
+
body.feature .app { font-size: 3.4vw; font-weight: 600; opacity: 0.85; }
|
|
11101
|
+
</style>
|
|
11102
|
+
</head>
|
|
11103
|
+
<body>
|
|
11104
|
+
<script>
|
|
11105
|
+
// The render script sets window.__SLIDE__ before this page loads.
|
|
11106
|
+
const { kind, app, slide, frame } = window.__SLIDE__;
|
|
11107
|
+
const add = (tag, className, text) => {
|
|
11108
|
+
const el = document.createElement(tag);
|
|
11109
|
+
if (className) el.className = className;
|
|
11110
|
+
if (text) el.textContent = text;
|
|
11111
|
+
return document.body.appendChild(el);
|
|
11112
|
+
};
|
|
11113
|
+
if (kind === "feature") {
|
|
11114
|
+
document.body.classList.add("feature");
|
|
11115
|
+
add("div", "app", app);
|
|
11116
|
+
add("h1", "", slide.headline);
|
|
11117
|
+
} else {
|
|
11118
|
+
add("h1", "", slide.headline);
|
|
11119
|
+
if (slide.subhead) add("p", "", slide.subhead);
|
|
11120
|
+
const shot = add("div", "shot " + (frame === "phone" ? "" : "bare"));
|
|
11121
|
+
const holder = frame === "phone" ? shot.appendChild(document.createElement("div")) : shot;
|
|
11122
|
+
if (frame === "phone") holder.className = "phone";
|
|
11123
|
+
const img = holder.appendChild(document.createElement("img"));
|
|
11124
|
+
img.src = slide.capture;
|
|
11125
|
+
img.alt = "";
|
|
11126
|
+
}
|
|
11127
|
+
</script>
|
|
11128
|
+
</body>
|
|
11129
|
+
</html>
|
|
11130
|
+
`;
|
|
11131
|
+
}
|
|
11132
|
+
});
|
|
11133
|
+
|
|
10870
11134
|
// ../shared/src/registry-css.ts
|
|
10871
11135
|
function parseCssVars(css) {
|
|
10872
11136
|
const out = { theme: {}, light: {}, dark: {} };
|
|
@@ -13892,36 +14156,38 @@ ${fence3}` }],
|
|
|
13892
14156
|
)
|
|
13893
14157
|
}, icons);
|
|
13894
14158
|
});
|
|
14159
|
+
const loadTokenSource = async ({ designSystemId, projectId }) => {
|
|
14160
|
+
if (projectId) {
|
|
14161
|
+
const result = await fetchProjectTokensCss(projectId, apiFetch, authContext);
|
|
14162
|
+
return {
|
|
14163
|
+
tokensCss: result?.tokensCss,
|
|
14164
|
+
name: result?.name ?? `Project ${projectId.slice(0, 8)}`,
|
|
14165
|
+
viewUrl: `${APP_BASE_URL}/projects/${projectId}`,
|
|
14166
|
+
icons: result?.iconLibrary
|
|
14167
|
+
};
|
|
14168
|
+
}
|
|
14169
|
+
if (!designSystemId) return null;
|
|
14170
|
+
const ds = await provider.getDesignSystemById(designSystemId, { includeComponents: false, includeHtml: false });
|
|
14171
|
+
return {
|
|
14172
|
+
tokensCss: await fetchCatalogTokensCss(designSystemId),
|
|
14173
|
+
name: ds?.metadata.title ?? designSystemId,
|
|
14174
|
+
viewUrl: `${APP_BASE_URL}/design-systems/${designSystemId}`,
|
|
14175
|
+
icons: ds ? await catalogKitIcons(ds.metadata, provider) : void 0
|
|
14176
|
+
};
|
|
14177
|
+
};
|
|
13895
14178
|
const getDtcgKit = async ({
|
|
13896
14179
|
designSystemId,
|
|
13897
14180
|
projectId,
|
|
13898
14181
|
context
|
|
13899
14182
|
}) => tracked("get-design-system-kit", { target: "dtcg", designSystemId, projectId, context }, async () => {
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
let viewUrl;
|
|
13903
|
-
let icons;
|
|
13904
|
-
if (projectId) {
|
|
13905
|
-
viewUrl = `${APP_BASE_URL}/projects/${projectId}`;
|
|
13906
|
-
const result = await fetchProjectTokensCss(projectId, apiFetch, authContext);
|
|
13907
|
-
tokensCss = result?.tokensCss;
|
|
13908
|
-
icons = result?.iconLibrary;
|
|
13909
|
-
name = result?.name ?? `Project ${projectId.slice(0, 8)}`;
|
|
13910
|
-
} else if (designSystemId) {
|
|
13911
|
-
viewUrl = `${APP_BASE_URL}/design-systems/${designSystemId}`;
|
|
13912
|
-
const ds = await provider.getDesignSystemById(designSystemId, {
|
|
13913
|
-
includeComponents: false,
|
|
13914
|
-
includeHtml: false
|
|
13915
|
-
});
|
|
13916
|
-
tokensCss = await fetchCatalogTokensCss(designSystemId);
|
|
13917
|
-
name = ds?.metadata.title ?? designSystemId;
|
|
13918
|
-
icons = ds ? await catalogKitIcons(ds.metadata, provider) : void 0;
|
|
13919
|
-
} else {
|
|
14183
|
+
const source = await loadTokenSource({ designSystemId, projectId });
|
|
14184
|
+
if (!source) {
|
|
13920
14185
|
return {
|
|
13921
14186
|
content: [{ type: "text", text: "For target dtcg, pass either a catalog designSystemId (from find-design-system) or a projectId (from create-design-system)." }],
|
|
13922
14187
|
structuredContent: { status: "error", name: "Missing design system", tokensCss: "" }
|
|
13923
14188
|
};
|
|
13924
14189
|
}
|
|
14190
|
+
const { tokensCss, name, viewUrl, icons } = source;
|
|
13925
14191
|
if (!tokensCss) {
|
|
13926
14192
|
return {
|
|
13927
14193
|
content: [{ type: "text", text: `Could not load tokens for "${projectId ?? designSystemId}". A generated project may still be building. Check ${viewUrl}.` }],
|
|
@@ -13947,6 +14213,33 @@ ${fence3}` }],
|
|
|
13947
14213
|
structuredContent: { status: "completed", name, viewUrl, tokensCss, tokensJson }
|
|
13948
14214
|
}, icons);
|
|
13949
14215
|
});
|
|
14216
|
+
const getStoreScreenshots = async ({
|
|
14217
|
+
designSystemId,
|
|
14218
|
+
projectId,
|
|
14219
|
+
context
|
|
14220
|
+
}) => tracked("get-design-system-kit", { target: "store-screenshots", designSystemId, projectId, context }, async () => {
|
|
14221
|
+
const source = await loadTokenSource({ designSystemId, projectId });
|
|
14222
|
+
if (!source?.tokensCss) {
|
|
14223
|
+
const reason = source ? `Could not load tokens for "${projectId ?? designSystemId}". A generated project may still be building. Check ${source.viewUrl}.` : "For target store-screenshots, pass either a catalog designSystemId (from find-design-system) or a projectId (from create-design-system).";
|
|
14224
|
+
return {
|
|
14225
|
+
content: [{ type: "text", text: reason }],
|
|
14226
|
+
structuredContent: { status: "error", name: source?.name ?? "Missing design system", tokensCss: "" }
|
|
14227
|
+
};
|
|
14228
|
+
}
|
|
14229
|
+
const files = buildStoreScreenshotFiles(source.tokensCss);
|
|
14230
|
+
const instructions = storeScreenshotInstructions(source.name);
|
|
14231
|
+
return {
|
|
14232
|
+
content: [{ type: "text", text: storeScreenshotText(source.name, files) }],
|
|
14233
|
+
structuredContent: {
|
|
14234
|
+
status: "completed",
|
|
14235
|
+
name: source.name,
|
|
14236
|
+
viewUrl: source.viewUrl,
|
|
14237
|
+
tokensCss: source.tokensCss,
|
|
14238
|
+
buildInstructions: instructions,
|
|
14239
|
+
screenshotFiles: files
|
|
14240
|
+
}
|
|
14241
|
+
};
|
|
14242
|
+
});
|
|
13950
14243
|
const fetchProjectMobilePrimitives = async (projectId) => {
|
|
13951
14244
|
try {
|
|
13952
14245
|
const res = await apiFetch(`${APP_BASE_URL}/api/projects/${projectId}/mobile/all.json`, { headers: authHeaders(authContext) });
|
|
@@ -14162,9 +14455,10 @@ ${BUILD_TARGET_RULE}`,
|
|
|
14162
14455
|
"html",
|
|
14163
14456
|
...enableShopifyKit ? ["shopify"] : [],
|
|
14164
14457
|
"dtcg",
|
|
14458
|
+
"store-screenshots",
|
|
14165
14459
|
...enableMobileKits ? ["react-native", "swiftui"] : []
|
|
14166
14460
|
]
|
|
14167
|
-
).describe(`Required build target. Use "react" for any host with filesystem + terminal; use "html" only for inline chat canvases/artifacts.${enableShopifyKit ? ' Use "shopify" for Shopify stores and themes.' : ""} Use "dtcg" to hand the tokens to a design tool: it returns a W3C Design Tokens document for Style Dictionary, Tokens Studio, Terrazzo, or a Figma plugin that creates variables. ${enableMobileKits ? ` Use "react-native" for a React Native or Expo app and "swiftui" for an iOS app: each returns this system's stored mobile kit, its own material on native primitives, plus the handoff to the matching guide.` : ""}`),
|
|
14461
|
+
).describe(`Required build target. Use "react" for any host with filesystem + terminal; use "html" only for inline chat canvases/artifacts.${enableShopifyKit ? ' Use "shopify" for Shopify stores and themes.' : ""} Use "dtcg" to hand the tokens to a design tool: it returns a W3C Design Tokens document for Style Dictionary, Tokens Studio, Terrazzo, or a Figma plugin that creates variables. Use "store-screenshots" for App Store and Google Play screenshots in this system: it returns slide files and a render script that writes PNG files at the store sizes. ${enableMobileKits ? ` Use "react-native" for a React Native or Expo app and "swiftui" for an iOS app: each returns this system's stored mobile kit, its own material on native primitives, plus the handoff to the matching guide.` : ""}`),
|
|
14168
14462
|
designSystemId: z14.string().min(1).optional().describe(`Catalog design system ID from find-design-system. Required for target react; for every other target pass this or projectId.`),
|
|
14169
14463
|
projectId: z14.string().min(1).optional().describe(`Generated project ID from create-design-system / get-design-system-status. Supported with every target but react.`),
|
|
14170
14464
|
component: z14.union([z14.string(), z14.array(z14.string())]).optional().describe("React target only. Pass only when the user asks to inspect specific component source code. Omit to scaffold the entire design system."),
|
|
@@ -14180,7 +14474,7 @@ ${BUILD_TARGET_RULE}`,
|
|
|
14180
14474
|
// interactive preview path. [BD-240]
|
|
14181
14475
|
},
|
|
14182
14476
|
async ({ target, designSystemId, projectId, component, library, context }) => {
|
|
14183
|
-
if (target !== "react" && target !== "html" && target !== "shopify" && target !== "dtcg" && target !== "react-native" && target !== "swiftui") {
|
|
14477
|
+
if (target !== "react" && target !== "html" && target !== "shopify" && target !== "dtcg" && target !== "store-screenshots" && target !== "react-native" && target !== "swiftui") {
|
|
14184
14478
|
return {
|
|
14185
14479
|
content: [{ type: "text", text: `Unknown target "${target}".` }],
|
|
14186
14480
|
structuredContent: { status: "error", name: "Unknown target", tokensCss: "" }
|
|
@@ -14192,6 +14486,9 @@ ${BUILD_TARGET_RULE}`,
|
|
|
14192
14486
|
if (target === "dtcg") {
|
|
14193
14487
|
return withRedesignContract(await getDtcgKit({ designSystemId, projectId, context }));
|
|
14194
14488
|
}
|
|
14489
|
+
if (target === "store-screenshots") {
|
|
14490
|
+
return getStoreScreenshots({ designSystemId, projectId, context });
|
|
14491
|
+
}
|
|
14195
14492
|
if (target === "react-native" || target === "swiftui") {
|
|
14196
14493
|
if (!enableMobileKits) {
|
|
14197
14494
|
return {
|
|
@@ -14687,6 +14984,7 @@ var init_design_system_tools = __esm({
|
|
|
14687
14984
|
init_shopify_kit();
|
|
14688
14985
|
init_redesign_contract();
|
|
14689
14986
|
init_selected_icon_handoff();
|
|
14987
|
+
init_store_screenshots();
|
|
14690
14988
|
init_shopify_primitives();
|
|
14691
14989
|
init_preview_html();
|
|
14692
14990
|
init_shared();
|
|
@@ -22772,7 +23070,7 @@ function columnToSchema(column, factory) {
|
|
|
22772
23070
|
}
|
|
22773
23071
|
return schema;
|
|
22774
23072
|
}
|
|
22775
|
-
function numberColumnToSchema(column,
|
|
23073
|
+
function numberColumnToSchema(column, z38, coerce) {
|
|
22776
23074
|
let unsigned = column.getSQLType().includes("unsigned");
|
|
22777
23075
|
let min;
|
|
22778
23076
|
let max;
|
|
@@ -22840,20 +23138,20 @@ function numberColumnToSchema(column, z35, coerce) {
|
|
|
22840
23138
|
min = Number.MIN_SAFE_INTEGER;
|
|
22841
23139
|
max = Number.MAX_SAFE_INTEGER;
|
|
22842
23140
|
}
|
|
22843
|
-
let schema = coerce === true || coerce?.number ?
|
|
23141
|
+
let schema = coerce === true || coerce?.number ? z38.coerce.number() : z38.number();
|
|
22844
23142
|
schema = schema.min(min).max(max);
|
|
22845
23143
|
return integer2 ? schema.int() : schema;
|
|
22846
23144
|
}
|
|
22847
|
-
function bigintColumnToSchema(column,
|
|
23145
|
+
function bigintColumnToSchema(column, z38, coerce) {
|
|
22848
23146
|
const unsigned = column.getSQLType().includes("unsigned");
|
|
22849
23147
|
const min = unsigned ? 0n : CONSTANTS.INT64_MIN;
|
|
22850
23148
|
const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX;
|
|
22851
|
-
const schema = coerce === true || coerce?.bigint ?
|
|
23149
|
+
const schema = coerce === true || coerce?.bigint ? z38.coerce.bigint() : z38.bigint();
|
|
22852
23150
|
return schema.min(min).max(max);
|
|
22853
23151
|
}
|
|
22854
|
-
function stringColumnToSchema(column,
|
|
23152
|
+
function stringColumnToSchema(column, z38, coerce) {
|
|
22855
23153
|
if (isColumnType(column, ["PgUUID"])) {
|
|
22856
|
-
return
|
|
23154
|
+
return z38.string().uuid();
|
|
22857
23155
|
}
|
|
22858
23156
|
let max;
|
|
22859
23157
|
let regex;
|
|
@@ -22885,7 +23183,7 @@ function stringColumnToSchema(column, z35, coerce) {
|
|
|
22885
23183
|
regex = /^[01]+$/;
|
|
22886
23184
|
max = column.dimensions;
|
|
22887
23185
|
}
|
|
22888
|
-
let schema = coerce === true || coerce?.string ?
|
|
23186
|
+
let schema = coerce === true || coerce?.string ? z38.coerce.string() : z38.string();
|
|
22889
23187
|
schema = regex ? schema.regex(regex) : schema;
|
|
22890
23188
|
return max && fixed ? schema.length(max) : max ? schema.max(max) : schema;
|
|
22891
23189
|
}
|
|
@@ -22981,15 +23279,74 @@ var init_drizzle_zod = __esm({
|
|
|
22981
23279
|
}
|
|
22982
23280
|
});
|
|
22983
23281
|
|
|
22984
|
-
// ../shared/src/
|
|
23282
|
+
// ../shared/src/agent-feedback.ts
|
|
22985
23283
|
import { z as z16 } from "zod";
|
|
22986
|
-
var
|
|
23284
|
+
var agentFeedbackKindValues, TEXT_MAX, SURFACE_MAX, SUMMARY_MAX, AGENT_FEEDBACK_CLIENT_MAX, FIELD_DESCRIPTIONS, optionalText, agentFeedbackInputSchema, AGENT_FEEDBACK_CONTRACT;
|
|
23285
|
+
var init_agent_feedback = __esm({
|
|
23286
|
+
"../shared/src/agent-feedback.ts"() {
|
|
23287
|
+
"use strict";
|
|
23288
|
+
agentFeedbackKindValues = [
|
|
23289
|
+
"bug",
|
|
23290
|
+
"missing-feature",
|
|
23291
|
+
"wrong-output",
|
|
23292
|
+
"docs-gap",
|
|
23293
|
+
"other"
|
|
23294
|
+
];
|
|
23295
|
+
TEXT_MAX = 4e3;
|
|
23296
|
+
SURFACE_MAX = 200;
|
|
23297
|
+
SUMMARY_MAX = 500;
|
|
23298
|
+
AGENT_FEEDBACK_CLIENT_MAX = 100;
|
|
23299
|
+
FIELD_DESCRIPTIONS = {
|
|
23300
|
+
kind: "What kind of problem this is.",
|
|
23301
|
+
surface: "The endpoint path or MCP tool name you called.",
|
|
23302
|
+
summary: "One sentence: what is wrong or missing.",
|
|
23303
|
+
attempted: "What you tried to do, and the request you sent.",
|
|
23304
|
+
expected: "What you expected to get back.",
|
|
23305
|
+
actual: "What you got back instead.",
|
|
23306
|
+
client: "Your agent name and version."
|
|
23307
|
+
};
|
|
23308
|
+
optionalText = (description) => z16.string().trim().max(TEXT_MAX).optional().describe(description);
|
|
23309
|
+
agentFeedbackInputSchema = z16.object({
|
|
23310
|
+
kind: z16.enum(agentFeedbackKindValues).describe(FIELD_DESCRIPTIONS.kind),
|
|
23311
|
+
surface: z16.string().trim().min(1).max(SURFACE_MAX).describe(FIELD_DESCRIPTIONS.surface),
|
|
23312
|
+
summary: z16.string().trim().min(1).max(SUMMARY_MAX).describe(FIELD_DESCRIPTIONS.summary),
|
|
23313
|
+
attempted: optionalText(FIELD_DESCRIPTIONS.attempted),
|
|
23314
|
+
expected: optionalText(FIELD_DESCRIPTIONS.expected),
|
|
23315
|
+
actual: optionalText(FIELD_DESCRIPTIONS.actual),
|
|
23316
|
+
client: z16.string().trim().max(AGENT_FEEDBACK_CLIENT_MAX).optional().describe(FIELD_DESCRIPTIONS.client)
|
|
23317
|
+
});
|
|
23318
|
+
AGENT_FEEDBACK_CONTRACT = {
|
|
23319
|
+
method: "POST",
|
|
23320
|
+
path: "/api/v1/feedback",
|
|
23321
|
+
purpose: "Tell Better Design about a bug or a missing feature you hit while calling this API. A person reads every report.",
|
|
23322
|
+
fields: {
|
|
23323
|
+
kind: { required: true, description: FIELD_DESCRIPTIONS.kind, oneOf: agentFeedbackKindValues },
|
|
23324
|
+
surface: { required: true, description: FIELD_DESCRIPTIONS.surface, maxLength: SURFACE_MAX },
|
|
23325
|
+
summary: { required: true, description: FIELD_DESCRIPTIONS.summary, maxLength: SUMMARY_MAX },
|
|
23326
|
+
attempted: { required: false, description: FIELD_DESCRIPTIONS.attempted, maxLength: TEXT_MAX },
|
|
23327
|
+
expected: { required: false, description: FIELD_DESCRIPTIONS.expected, maxLength: TEXT_MAX },
|
|
23328
|
+
actual: { required: false, description: FIELD_DESCRIPTIONS.actual, maxLength: TEXT_MAX },
|
|
23329
|
+
client: { required: false, description: FIELD_DESCRIPTIONS.client, maxLength: AGENT_FEEDBACK_CLIENT_MAX }
|
|
23330
|
+
},
|
|
23331
|
+
notes: [
|
|
23332
|
+
"Send it with your Better Design API key: Authorization: Bearer <key>.",
|
|
23333
|
+
"Do not include secrets or personal data. Known secret shapes are removed before storage.",
|
|
23334
|
+
"Sending feedback never spends credit."
|
|
23335
|
+
]
|
|
23336
|
+
};
|
|
23337
|
+
}
|
|
23338
|
+
});
|
|
23339
|
+
|
|
23340
|
+
// ../shared/src/schema.ts
|
|
23341
|
+
import { z as z17 } from "zod";
|
|
23342
|
+
var vector2, messageRoleEnum, messageTypeEnum, designSystemStatusEnum, onboardingPathValues, onboardingPathSchema, onboardingPathSqlValues, mcpIssueCategoryValues, mcpIssueStatusValues, mcpIssueImprovementStatusValues, mcpIssueImprovementVerdictValues, mcpIssueImprovementStatusSchema, mcpIssueImprovementVerdictSchema, mcpIssueImprovementStatusSqlValues, mcpIssueImprovementVerdictSqlValues, designConfigSchema, projectMemoryDataSchema, projects, messages, projectMemory, fragments, usage, scrapeBlock, resourceAccess, user, signupAlertDeliveries, billingAlertDeliveries, billingEmailDeliveries, TEAM_RULES_MAX_BYTES, teamRules, agentFeedbackStatusValues, agentFeedback, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, emailCampaignSends, emailCampaignSteps, projectEvents, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, projectsRelations, projectMemoryRelations, mcpIssuesRelations, mcpIssueImprovementsRelations, messagesRelations, fragmentsRelations, designSystemsRelations, designSystemComponentsRelations, insertProjectSchema, selectProjectSchema, insertMessageSchema, selectMessageSchema, insertFragmentSchema, selectFragmentSchema, insertProjectMemorySchema, selectProjectMemorySchema, insertDesignSystemSchema, selectDesignSystemSchema, insertDesignSystemComponentSchema, selectDesignSystemComponentSchema, insertFoundationalDocSchema, selectFoundationalDocSchema, insertIconLibrarySchema, selectIconLibrarySchema, insertApiKeySchema, selectApiKeySchema, insertUserSchema, selectUserSchema, insertSignupAlertDeliverySchema, selectSignupAlertDeliverySchema, insertEmailCampaignSendSchema, selectEmailCampaignSendSchema, insertEmailCampaignStepSchema, selectEmailCampaignStepSchema, insertMcpIssueImprovementSchema, selectMcpIssueImprovementSchema, pluginAuthNonces;
|
|
22987
23343
|
var init_schema2 = __esm({
|
|
22988
23344
|
"../shared/src/schema.ts"() {
|
|
22989
23345
|
"use strict";
|
|
22990
23346
|
init_pg_core();
|
|
22991
23347
|
init_drizzle_orm();
|
|
22992
23348
|
init_drizzle_zod();
|
|
23349
|
+
init_agent_feedback();
|
|
22993
23350
|
vector2 = customType({
|
|
22994
23351
|
dataType() {
|
|
22995
23352
|
return "vector(768)";
|
|
@@ -23017,7 +23374,7 @@ var init_schema2 = __esm({
|
|
|
23017
23374
|
"skip",
|
|
23018
23375
|
"legacy"
|
|
23019
23376
|
];
|
|
23020
|
-
onboardingPathSchema =
|
|
23377
|
+
onboardingPathSchema = z17.enum(onboardingPathValues);
|
|
23021
23378
|
onboardingPathSqlValues = sql.raw(
|
|
23022
23379
|
onboardingPathValues.map((path6) => `'${path6}'`).join(", ")
|
|
23023
23380
|
);
|
|
@@ -23040,10 +23397,10 @@ var init_schema2 = __esm({
|
|
|
23040
23397
|
"not_improved",
|
|
23041
23398
|
"inconclusive"
|
|
23042
23399
|
];
|
|
23043
|
-
mcpIssueImprovementStatusSchema =
|
|
23400
|
+
mcpIssueImprovementStatusSchema = z17.enum(
|
|
23044
23401
|
mcpIssueImprovementStatusValues
|
|
23045
23402
|
);
|
|
23046
|
-
mcpIssueImprovementVerdictSchema =
|
|
23403
|
+
mcpIssueImprovementVerdictSchema = z17.enum(
|
|
23047
23404
|
mcpIssueImprovementVerdictValues
|
|
23048
23405
|
);
|
|
23049
23406
|
mcpIssueImprovementStatusSqlValues = sql.raw(
|
|
@@ -23052,38 +23409,38 @@ var init_schema2 = __esm({
|
|
|
23052
23409
|
mcpIssueImprovementVerdictSqlValues = sql.raw(
|
|
23053
23410
|
mcpIssueImprovementVerdictValues.map((verdict) => `'${verdict}'`).join(", ")
|
|
23054
23411
|
);
|
|
23055
|
-
designConfigSchema =
|
|
23056
|
-
colors:
|
|
23057
|
-
primary:
|
|
23058
|
-
secondary:
|
|
23059
|
-
accent:
|
|
23060
|
-
muted:
|
|
23061
|
-
destructive:
|
|
23062
|
-
background:
|
|
23063
|
-
foreground:
|
|
23064
|
-
card:
|
|
23065
|
-
cardForeground:
|
|
23066
|
-
popover:
|
|
23067
|
-
popoverForeground:
|
|
23068
|
-
border:
|
|
23069
|
-
input:
|
|
23070
|
-
ring:
|
|
23412
|
+
designConfigSchema = z17.object({
|
|
23413
|
+
colors: z17.object({
|
|
23414
|
+
primary: z17.string(),
|
|
23415
|
+
secondary: z17.string(),
|
|
23416
|
+
accent: z17.string(),
|
|
23417
|
+
muted: z17.string(),
|
|
23418
|
+
destructive: z17.string(),
|
|
23419
|
+
background: z17.string(),
|
|
23420
|
+
foreground: z17.string(),
|
|
23421
|
+
card: z17.string(),
|
|
23422
|
+
cardForeground: z17.string(),
|
|
23423
|
+
popover: z17.string(),
|
|
23424
|
+
popoverForeground: z17.string(),
|
|
23425
|
+
border: z17.string(),
|
|
23426
|
+
input: z17.string(),
|
|
23427
|
+
ring: z17.string()
|
|
23071
23428
|
}).partial(),
|
|
23072
|
-
radius:
|
|
23073
|
-
font:
|
|
23074
|
-
sans:
|
|
23075
|
-
mono:
|
|
23429
|
+
radius: z17.string(),
|
|
23430
|
+
font: z17.object({
|
|
23431
|
+
sans: z17.string(),
|
|
23432
|
+
mono: z17.string()
|
|
23076
23433
|
}).partial(),
|
|
23077
|
-
icons:
|
|
23078
|
-
libraryId:
|
|
23079
|
-
variant:
|
|
23434
|
+
icons: z17.object({
|
|
23435
|
+
libraryId: z17.string(),
|
|
23436
|
+
variant: z17.string().nullable()
|
|
23080
23437
|
}).partial()
|
|
23081
23438
|
}).partial();
|
|
23082
|
-
projectMemoryDataSchema =
|
|
23083
|
-
lockedIn:
|
|
23084
|
-
rejected:
|
|
23085
|
-
tone:
|
|
23086
|
-
notes:
|
|
23439
|
+
projectMemoryDataSchema = z17.object({
|
|
23440
|
+
lockedIn: z17.array(z17.string()),
|
|
23441
|
+
rejected: z17.array(z17.string()),
|
|
23442
|
+
tone: z17.string().nullable(),
|
|
23443
|
+
notes: z17.string().nullable()
|
|
23087
23444
|
});
|
|
23088
23445
|
projects = pgTable("projects", {
|
|
23089
23446
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
@@ -23314,6 +23671,40 @@ var init_schema2 = __esm({
|
|
|
23314
23671
|
)
|
|
23315
23672
|
]
|
|
23316
23673
|
);
|
|
23674
|
+
billingEmailDeliveries = pgTable(
|
|
23675
|
+
"billing_email_deliveries",
|
|
23676
|
+
{
|
|
23677
|
+
id: text("id").primaryKey(),
|
|
23678
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
23679
|
+
toEmail: text("to_email").notNull(),
|
|
23680
|
+
/** The Stripe subscription the email is about, so the daily cap can count per subscription. */
|
|
23681
|
+
stripeSubscriptionId: text("stripe_subscription_id").notNull(),
|
|
23682
|
+
email: jsonb("email").$type().notNull(),
|
|
23683
|
+
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
|
23684
|
+
status: text("status").$type().default("pending").notNull(),
|
|
23685
|
+
attemptCount: integer("attempt_count").default(0).notNull(),
|
|
23686
|
+
nextAttemptAt: timestamp("next_attempt_at", { withTimezone: true }).defaultNow().notNull(),
|
|
23687
|
+
leaseUntil: timestamp("lease_until", { withTimezone: true }),
|
|
23688
|
+
leaseToken: uuid("lease_token"),
|
|
23689
|
+
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
23690
|
+
resendEmailId: text("resend_email_id"),
|
|
23691
|
+
lastError: text("last_error"),
|
|
23692
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
23693
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
23694
|
+
},
|
|
23695
|
+
(t) => [
|
|
23696
|
+
index("billing_email_deliveries_due_idx").on(t.status, t.nextAttemptAt, t.leaseUntil),
|
|
23697
|
+
index("billing_email_deliveries_subscription_idx").on(t.stripeSubscriptionId, t.createdAt),
|
|
23698
|
+
check(
|
|
23699
|
+
"billing_email_deliveries_status_valid",
|
|
23700
|
+
sql`${t.status} in ('pending', 'processing', 'delivered', 'failed')`
|
|
23701
|
+
),
|
|
23702
|
+
check(
|
|
23703
|
+
"billing_email_deliveries_processing_lease_valid",
|
|
23704
|
+
sql`${t.status} <> 'processing' or (${t.leaseUntil} is not null and ${t.leaseToken} is not null)`
|
|
23705
|
+
)
|
|
23706
|
+
]
|
|
23707
|
+
);
|
|
23317
23708
|
TEAM_RULES_MAX_BYTES = 32 * 1024;
|
|
23318
23709
|
teamRules = pgTable(
|
|
23319
23710
|
"team_rules",
|
|
@@ -23329,6 +23720,43 @@ var init_schema2 = __esm({
|
|
|
23329
23720
|
)
|
|
23330
23721
|
]
|
|
23331
23722
|
);
|
|
23723
|
+
agentFeedbackStatusValues = [
|
|
23724
|
+
"open",
|
|
23725
|
+
"accepted",
|
|
23726
|
+
"rejected",
|
|
23727
|
+
"shipped"
|
|
23728
|
+
];
|
|
23729
|
+
agentFeedback = pgTable(
|
|
23730
|
+
"agent_feedback",
|
|
23731
|
+
{
|
|
23732
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
23733
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
23734
|
+
apiKeyId: text("api_key_id").references(() => apiKeys.id, { onDelete: "set null" }),
|
|
23735
|
+
kind: text("kind", { enum: agentFeedbackKindValues }).notNull(),
|
|
23736
|
+
// The endpoint path or MCP tool name the agent was calling.
|
|
23737
|
+
surface: text("surface").notNull(),
|
|
23738
|
+
summary: text("summary").notNull(),
|
|
23739
|
+
attempted: text("attempted"),
|
|
23740
|
+
expected: text("expected"),
|
|
23741
|
+
actual: text("actual"),
|
|
23742
|
+
// The agent's own name for itself, such as "claude-code/2.1".
|
|
23743
|
+
client: text("client"),
|
|
23744
|
+
// SHA-256 of kind, surface and the normalized summary, so identical
|
|
23745
|
+
// reports group in the admin queue without a model call.
|
|
23746
|
+
fingerprint: text("fingerprint").notNull(),
|
|
23747
|
+
status: text("status", { enum: agentFeedbackStatusValues }).notNull().default("open"),
|
|
23748
|
+
pullRequestUrl: text("pull_request_url"),
|
|
23749
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
23750
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
23751
|
+
},
|
|
23752
|
+
(t) => [
|
|
23753
|
+
index("agent_feedback_status_created_idx").on(t.status, t.createdAt),
|
|
23754
|
+
index("agent_feedback_fingerprint_idx").on(t.fingerprint),
|
|
23755
|
+
// Deleting a user or an API key looks up its reports through these.
|
|
23756
|
+
index("agent_feedback_user_id_idx").on(t.userId),
|
|
23757
|
+
index("agent_feedback_api_key_id_idx").on(t.apiKeyId)
|
|
23758
|
+
]
|
|
23759
|
+
);
|
|
23332
23760
|
billingSubscription = pgTable(
|
|
23333
23761
|
"billing_subscription",
|
|
23334
23762
|
{
|
|
@@ -24135,7 +24563,7 @@ var init_schema2 = __esm({
|
|
|
24135
24563
|
});
|
|
24136
24564
|
|
|
24137
24565
|
// ../shared/src/mcp/tools/design-config-tools.ts
|
|
24138
|
-
import { z as
|
|
24566
|
+
import { z as z18 } from "zod";
|
|
24139
24567
|
function registerDesignConfigTools({
|
|
24140
24568
|
server,
|
|
24141
24569
|
tracked,
|
|
@@ -24186,7 +24614,7 @@ function registerDesignConfigTools({
|
|
|
24186
24614
|
{
|
|
24187
24615
|
title: "Get a Project's Tuned Design Config",
|
|
24188
24616
|
description: `Read the user-tuned design config (exact color tokens, radius, fonts, icon library) of a generated project. Call this BEFORE building or restyling UI for the project, and again after the user tunes values on the project page, so you apply their exact numbers instead of guessing from adjectives.`,
|
|
24189
|
-
inputSchema:
|
|
24617
|
+
inputSchema: z18.object({
|
|
24190
24618
|
projectId: PROJECT_ID_ARG,
|
|
24191
24619
|
context: CONTEXT_ARG
|
|
24192
24620
|
}),
|
|
@@ -24215,7 +24643,7 @@ function registerDesignConfigTools({
|
|
|
24215
24643
|
{
|
|
24216
24644
|
title: "Set a Project's Design Config",
|
|
24217
24645
|
description: `Write exact design-token values (colors, radius, fonts, icon library) onto a generated project. Use this when the user states concrete values ("make primary oklch(0.62 0.11 45)", "radius 12px"). It merges over the current config and persists, so the change survives restarts and every later get-design-config returns it. Saved config is publicly readable by anyone with the fragment config URL. For vague direction ("warmer", "more playful"), iterate with the user toward concrete values first; do not invent numbers on their behalf.`,
|
|
24218
|
-
inputSchema:
|
|
24646
|
+
inputSchema: z18.object({
|
|
24219
24647
|
projectId: PROJECT_ID_ARG,
|
|
24220
24648
|
config: CONFIG_ARG,
|
|
24221
24649
|
context: CONTEXT_ARG
|
|
@@ -24266,7 +24694,7 @@ var init_design_config_tools = __esm({
|
|
|
24266
24694
|
"use strict";
|
|
24267
24695
|
init_schema2();
|
|
24268
24696
|
init_shared();
|
|
24269
|
-
PROJECT_ID_ARG =
|
|
24697
|
+
PROJECT_ID_ARG = z18.string().min(1).describe("The project id returned by create-design-system.");
|
|
24270
24698
|
CONFIG_ARG = designConfigSchema.describe(
|
|
24271
24699
|
"Partial design config to merge over the current one. Only pass the fields the user changed. Colors are oklch() strings (e.g. 'oklch(0.62 0.11 45)'); radius is a CSS length (e.g. '0.75rem'); fonts are family names."
|
|
24272
24700
|
);
|
|
@@ -24274,16 +24702,16 @@ var init_design_config_tools = __esm({
|
|
|
24274
24702
|
});
|
|
24275
24703
|
|
|
24276
24704
|
// ../shared/src/mcp/tools/jig-tools.ts
|
|
24277
|
-
import { z as
|
|
24705
|
+
import { z as z19 } from "zod";
|
|
24278
24706
|
function registerJigTools({ server, tracked }) {
|
|
24279
24707
|
server.registerTool(
|
|
24280
24708
|
"get-jig-guide",
|
|
24281
24709
|
{
|
|
24282
24710
|
title: "Get a Jig for Parameter Work",
|
|
24283
24711
|
description: `Return the setup for a "jig", the tool you build to make the real thing precisely. Two kinds, both here. Use kind "tune-in-place" (default) when you are about to hard-code taste-based values in UI you are building: animation duration/easing/springs/stagger, particles, canvas physics, blur/glow/grain, gradient stops, shadow recipes, parallax speeds. It returns a control panel to wire to those parameters so the USER tunes by eye and you hard-code what they copy back. Use kind "standalone-tool" when the user wants a reusable custom tool instead: a generator, an image effect, a shader playground, an asset maker. That returns the Toolcraft starter kit (MIT), which ships a canvas and an export flow, with optional layers and a timeline. Tuning setups: React recommends DialKit (MIT, no re-render lag) with a dependency-free fallback; "vanilla" and "react-native" get dependency-free scaffolds.`,
|
|
24284
|
-
inputSchema:
|
|
24285
|
-
kind:
|
|
24286
|
-
framework:
|
|
24712
|
+
inputSchema: z19.object({
|
|
24713
|
+
kind: z19.enum(["tune-in-place", "standalone-tool"]).default("tune-in-place").describe("What the user needs. 'tune-in-place' (default): a control panel for parameters in the UI you are building. 'standalone-tool': a separate reusable design tool, scaffolded with Toolcraft."),
|
|
24714
|
+
framework: z19.enum(["react", "vanilla", "react-native"]).optional().describe("Which tune-in-place setup to return. 'react' (default) for React/Next web projects; 'vanilla' for plain DOM or canvas work; 'react-native' for RN/Expo apps. Ignored when kind is 'standalone-tool'."),
|
|
24287
24715
|
context: CONTEXT_ARG
|
|
24288
24716
|
}),
|
|
24289
24717
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -25115,7 +25543,7 @@ var init_brand_sources = __esm({
|
|
|
25115
25543
|
});
|
|
25116
25544
|
|
|
25117
25545
|
// ../shared/src/mcp/tools/extract-tools.ts
|
|
25118
|
-
import { z as
|
|
25546
|
+
import { z as z20 } from "zod";
|
|
25119
25547
|
function authHeaders2(authContext) {
|
|
25120
25548
|
if (!authContext) return {};
|
|
25121
25549
|
const bearer = authContext.kind === "api-key" ? authContext.apiKey : authContext.accessToken;
|
|
@@ -25152,15 +25580,15 @@ JSON.stringify(${buildRoleSampleScript()})
|
|
|
25152
25580
|
- kind "tokens": a design-tokens JSON export ($value entries, W3C format or the Figma variables API shape with 0..1 rgb floats).
|
|
25153
25581
|
|
|
25154
25582
|
Common vendor names (Shopify's --color-button family) are mapped into empty semantic slots automatically and the mapping is reported in the output.`,
|
|
25155
|
-
inputSchema:
|
|
25156
|
-
css:
|
|
25157
|
-
sources:
|
|
25158
|
-
name:
|
|
25159
|
-
kind:
|
|
25160
|
-
content:
|
|
25583
|
+
inputSchema: z20.object({
|
|
25584
|
+
css: z20.string().trim().min(1).max(2e5).optional().describe("Full contents of the project's globals.css (or any CSS holding the design tokens / CSS custom properties). Optional when `sources` is given."),
|
|
25585
|
+
sources: z20.array(z20.object({
|
|
25586
|
+
name: z20.string().trim().min(1).max(120).describe("Label for this source, e.g. 'globals.css', 'live site sample', 'figma variables'."),
|
|
25587
|
+
kind: z20.enum(["css", "computed", "tokens"]).optional().describe("Omit to infer: JSON with $value entries is tokens, JSON keyed by UI roles is computed, anything else is CSS."),
|
|
25588
|
+
content: z20.string().trim().min(1).max(2e5)
|
|
25161
25589
|
})).max(10).optional().describe("Any mix of brand sources, merged in order (later sources win). See the tool description for how to gather each kind."),
|
|
25162
|
-
name:
|
|
25163
|
-
maxTokens:
|
|
25590
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system, e.g. the app or repo name."),
|
|
25591
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25164
25592
|
}),
|
|
25165
25593
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
25166
25594
|
},
|
|
@@ -25191,10 +25619,10 @@ Common vendor names (Shopify's --color-button family) are mapped into empty sema
|
|
|
25191
25619
|
Use this when the user names their site ("match uclips.eu", "use our website's style") or hands you any public URL whose look should be reused. Server side, Better Design fetches the page's stylesheets AND renders the page in a headless browser to sample what role elements actually look like (body, heading, link, button, input, card), then merges both, rendered truth last. That reads the brand even when the site's variable names are unrecognizable.
|
|
25192
25620
|
|
|
25193
25621
|
Slow by nature (a real browser loads the page): expect 10 to 45 seconds. For CSS the user already has locally, prefer extract-design-system, which is instant.`,
|
|
25194
|
-
inputSchema:
|
|
25195
|
-
url:
|
|
25196
|
-
name:
|
|
25197
|
-
maxTokens:
|
|
25622
|
+
inputSchema: z20.object({
|
|
25623
|
+
url: z20.string().trim().min(8).max(2048).describe("Public http(s) URL of the page to read, usually the homepage."),
|
|
25624
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system. Defaults to the site's hostname."),
|
|
25625
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25198
25626
|
}),
|
|
25199
25627
|
// Reads any public page the user names, so the set of hosts it reaches is
|
|
25200
25628
|
// the open web, not Better Design's catalog. It stores nothing. [BD-316]
|
|
@@ -25244,7 +25672,7 @@ var init_extract_tools = __esm({
|
|
|
25244
25672
|
});
|
|
25245
25673
|
|
|
25246
25674
|
// ../shared/src/mcp/tools/illustration-tools.ts
|
|
25247
|
-
import { z as
|
|
25675
|
+
import { z as z21 } from "zod";
|
|
25248
25676
|
function fail(message) {
|
|
25249
25677
|
return {
|
|
25250
25678
|
content: [{ type: "text", text: message }],
|
|
@@ -25290,11 +25718,11 @@ How to call it:
|
|
|
25290
25718
|
4. Stop when \`faults\` is empty.
|
|
25291
25719
|
|
|
25292
25720
|
Markup carrying script, event handlers, remote references, or styles that reach outside the drawing is refused rather than cleaned, so the drawing you fix is the drawing you sent.`,
|
|
25293
|
-
inputSchema:
|
|
25294
|
-
svg:
|
|
25295
|
-
intent:
|
|
25296
|
-
animated:
|
|
25297
|
-
reviewToken:
|
|
25721
|
+
inputSchema: z21.object({
|
|
25722
|
+
svg: z21.string().min(1).describe("The complete svg element you drew."),
|
|
25723
|
+
intent: z21.string().min(1).describe('What it is meant to be, in plain words. For example "an open inbox tray with letters".'),
|
|
25724
|
+
animated: z21.boolean().optional().describe("Set when the drawing was meant to move, so the motion checks apply."),
|
|
25725
|
+
reviewToken: z21.string().optional().describe("The token from a previous review of this drawing. Reviews it again free.")
|
|
25298
25726
|
}),
|
|
25299
25727
|
outputSchema: REVIEW_OUTPUT_SCHEMA,
|
|
25300
25728
|
// A review without a valid reviewToken passes the paywall, so it spends
|
|
@@ -25373,16 +25801,16 @@ var init_illustration_tools = __esm({
|
|
|
25373
25801
|
init_shared();
|
|
25374
25802
|
REVIEW_OUTPUT_SCHEMA = {
|
|
25375
25803
|
/** Empty when nothing is visibly wrong. */
|
|
25376
|
-
faults:
|
|
25804
|
+
faults: z21.array(z21.string()),
|
|
25377
25805
|
/** Group ids in paint order, the parts an animation can target. */
|
|
25378
|
-
layers:
|
|
25806
|
+
layers: z21.array(z21.string()),
|
|
25379
25807
|
/** False when the markup was refused; faults says why. */
|
|
25380
|
-
reviewed:
|
|
25808
|
+
reviewed: z21.boolean(),
|
|
25381
25809
|
/** True when the drawing could not be rendered, so only structure was checked. */
|
|
25382
|
-
unseen:
|
|
25810
|
+
unseen: z21.boolean(),
|
|
25383
25811
|
/** Pass this back to review the same drawing again without spending another credit. */
|
|
25384
|
-
reviewToken:
|
|
25385
|
-
message:
|
|
25812
|
+
reviewToken: z21.string(),
|
|
25813
|
+
message: z21.string()
|
|
25386
25814
|
};
|
|
25387
25815
|
}
|
|
25388
25816
|
});
|
|
@@ -26744,7 +27172,7 @@ ul.evidence { padding-inline-start: 1rem; }
|
|
|
26744
27172
|
});
|
|
26745
27173
|
|
|
26746
27174
|
// ../shared/src/mcp/tools/inspection-tools.ts
|
|
26747
|
-
import { z as
|
|
27175
|
+
import { z as z22 } from "zod";
|
|
26748
27176
|
function coversRecommendedWidth(states, width) {
|
|
26749
27177
|
return states.some(
|
|
26750
27178
|
(state) => Math.abs(state.viewport.width - width) <= WIDTH_TOLERANCE
|
|
@@ -26800,36 +27228,38 @@ function registerInspectionTools({ server, provider, tracked }) {
|
|
|
26800
27228
|
|
|
26801
27229
|
This is the runtime counterpart to get-review-rules. It automatically loads the live visual-design rubric, but it does not guess pixel values from a screenshot. Exact claims come from DOM geometry.
|
|
26802
27230
|
|
|
27231
|
+
It measures geometry only. A score of 100 is not visual approval: it cannot judge hierarchy, density, or whether the design works, so look at a screenshot before you call a screen done.
|
|
27232
|
+
|
|
26803
27233
|
Workflow:
|
|
26804
27234
|
1. Call with no states to receive a read-only browser capture script in structuredContent.captureScript. Use detail: "full" if your client reads only text; it includes the script in text. That call measures nothing.
|
|
26805
27235
|
2. Evaluate that script in each rendered state you need to review: at minimum 1440, 1728 and 390 wide. Also capture named hover, focus, selected, and open states when they change a control's surface or layout.
|
|
26806
27236
|
3. Call again with the captured states and the project's spacing scale when known. The layout counts as measured only when this second call returns status \`completed\`; reading the snapshot yourself is not a measurement. The report and summary start with the highest-priority correction. Read structuredContent.findings or use detail: "full" for every returned finding, measurement, and fix. Structured evidence retains captured geometry and optional screenshots for report renderers.
|
|
26807
27237
|
|
|
26808
27238
|
The capture reads visible layout geometry, computed spacing, corner radii, surface styles, the number of line boxes each text block rendered, and short accessible labels for interactive controls. It does not collect source code, cookies, form values, or network data. Add data-bd-inspect-label, data-bd-symmetry, data-bd-group, data-bd-compare-size, data-bd-align-group, and data-bd-align-edge attributes when the intended relationship cannot be inferred safely.`,
|
|
26809
|
-
inputSchema:
|
|
26810
|
-
states:
|
|
26811
|
-
spacingScale:
|
|
27239
|
+
inputSchema: z22.object({
|
|
27240
|
+
states: z22.array(spacingStateSchema).min(1).max(6).optional().describe("Rendered layout states from the capture script. Omit on the first call to receive the script."),
|
|
27241
|
+
spacingScale: z22.array(finiteNumber.positive()).min(2).max(32).optional().describe("Project spacing tokens in CSS pixels. Defaults to 4, 8, 12, 16, 24, 32, 48, 64, 96, 128."),
|
|
26812
27242
|
tolerance: finiteNumber.min(0).max(4).optional().describe("Pixel tolerance for grouping measurements. Defaults to 1px."),
|
|
26813
|
-
detail:
|
|
27243
|
+
detail: z22.enum(["summary", "full"]).default("summary").describe("Defaults to a short summary alongside the inline report. Use full for the capture script or all findings and measurements in text-only clients."),
|
|
26814
27244
|
context: CONTEXT_ARG
|
|
26815
27245
|
}),
|
|
26816
|
-
outputSchema:
|
|
26817
|
-
status:
|
|
26818
|
-
score:
|
|
26819
|
-
summary:
|
|
26820
|
-
critical:
|
|
26821
|
-
serious:
|
|
26822
|
-
moderate:
|
|
26823
|
-
total:
|
|
26824
|
-
truncated:
|
|
27246
|
+
outputSchema: z22.object({
|
|
27247
|
+
status: z22.enum(["needs-capture", "completed"]),
|
|
27248
|
+
score: z22.number().optional(),
|
|
27249
|
+
summary: z22.object({
|
|
27250
|
+
critical: z22.number(),
|
|
27251
|
+
serious: z22.number(),
|
|
27252
|
+
moderate: z22.number(),
|
|
27253
|
+
total: z22.number(),
|
|
27254
|
+
truncated: z22.number().optional()
|
|
26825
27255
|
}).optional(),
|
|
26826
|
-
findings:
|
|
26827
|
-
capturedWidths:
|
|
26828
|
-
missingWidths:
|
|
26829
|
-
states:
|
|
26830
|
-
rubric:
|
|
26831
|
-
rubricStatus:
|
|
26832
|
-
captureScript:
|
|
27256
|
+
findings: z22.array(findingSchema).optional(),
|
|
27257
|
+
capturedWidths: z22.array(z22.number()).optional(),
|
|
27258
|
+
missingWidths: z22.array(z22.number()).optional(),
|
|
27259
|
+
states: z22.array(overlayStateSchema).optional(),
|
|
27260
|
+
rubric: z22.array(z22.object({ id: z22.string(), title: z22.string() })).optional(),
|
|
27261
|
+
rubricStatus: z22.enum(["loaded", "unavailable"]).optional(),
|
|
27262
|
+
captureScript: z22.string().optional()
|
|
26833
27263
|
}),
|
|
26834
27264
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
26835
27265
|
_meta: {
|
|
@@ -26889,7 +27319,8 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
26889
27319
|
detail === "full" ? `Spacing score: ${result.score}/100.` : "",
|
|
26890
27320
|
coverageLine,
|
|
26891
27321
|
findingsText,
|
|
26892
|
-
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : ""
|
|
27322
|
+
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : "",
|
|
27323
|
+
GEOMETRY_ONLY
|
|
26893
27324
|
].filter(Boolean).join("\n\n");
|
|
26894
27325
|
return {
|
|
26895
27326
|
content: [{ type: "text", text: text3 }],
|
|
@@ -26906,7 +27337,7 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
26906
27337
|
)
|
|
26907
27338
|
);
|
|
26908
27339
|
}
|
|
26909
|
-
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE;
|
|
27340
|
+
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE, GEOMETRY_ONLY;
|
|
26910
27341
|
var init_inspection_tools = __esm({
|
|
26911
27342
|
"../shared/src/mcp/tools/inspection-tools.ts"() {
|
|
26912
27343
|
"use strict";
|
|
@@ -26914,67 +27345,67 @@ var init_inspection_tools = __esm({
|
|
|
26914
27345
|
init_finding_copy();
|
|
26915
27346
|
init_spacing_inspection_html();
|
|
26916
27347
|
init_shared();
|
|
26917
|
-
finiteNumber =
|
|
26918
|
-
rectSchema =
|
|
27348
|
+
finiteNumber = z22.number().finite();
|
|
27349
|
+
rectSchema = z22.object({
|
|
26919
27350
|
x: finiteNumber,
|
|
26920
27351
|
y: finiteNumber,
|
|
26921
27352
|
width: finiteNumber.nonnegative(),
|
|
26922
27353
|
height: finiteNumber.nonnegative()
|
|
26923
27354
|
});
|
|
26924
|
-
edgesSchema =
|
|
27355
|
+
edgesSchema = z22.object({
|
|
26925
27356
|
top: finiteNumber,
|
|
26926
27357
|
right: finiteNumber,
|
|
26927
27358
|
bottom: finiteNumber,
|
|
26928
27359
|
left: finiteNumber
|
|
26929
27360
|
});
|
|
26930
|
-
cornerRadiusAxesSchema =
|
|
27361
|
+
cornerRadiusAxesSchema = z22.object({
|
|
26931
27362
|
horizontal: finiteNumber.nonnegative(),
|
|
26932
27363
|
vertical: finiteNumber.nonnegative()
|
|
26933
27364
|
});
|
|
26934
|
-
cornerRadiusSchema =
|
|
27365
|
+
cornerRadiusSchema = z22.union([
|
|
26935
27366
|
cornerRadiusAxesSchema,
|
|
26936
27367
|
finiteNumber.nonnegative().transform((value) => ({
|
|
26937
27368
|
horizontal: value,
|
|
26938
27369
|
vertical: value
|
|
26939
27370
|
}))
|
|
26940
27371
|
]);
|
|
26941
|
-
cornerRadiiSchema =
|
|
27372
|
+
cornerRadiiSchema = z22.object({
|
|
26942
27373
|
topLeft: cornerRadiusSchema,
|
|
26943
27374
|
topRight: cornerRadiusSchema,
|
|
26944
27375
|
bottomRight: cornerRadiusSchema,
|
|
26945
27376
|
bottomLeft: cornerRadiusSchema
|
|
26946
27377
|
});
|
|
26947
|
-
styleSchema =
|
|
26948
|
-
display:
|
|
26949
|
-
position:
|
|
26950
|
-
flexDirection:
|
|
26951
|
-
flexWrap:
|
|
26952
|
-
alignItems:
|
|
26953
|
-
justifyContent:
|
|
27378
|
+
styleSchema = z22.object({
|
|
27379
|
+
display: z22.string().max(60).optional(),
|
|
27380
|
+
position: z22.string().max(60).optional(),
|
|
27381
|
+
flexDirection: z22.string().max(60).optional(),
|
|
27382
|
+
flexWrap: z22.string().max(60).optional(),
|
|
27383
|
+
alignItems: z22.string().max(60).optional(),
|
|
27384
|
+
justifyContent: z22.string().max(60).optional(),
|
|
26954
27385
|
rowGap: finiteNumber.optional(),
|
|
26955
27386
|
columnGap: finiteNumber.optional(),
|
|
26956
27387
|
padding: edgesSchema.optional(),
|
|
26957
27388
|
margin: edgesSchema.optional(),
|
|
26958
|
-
overflowX:
|
|
26959
|
-
overflowY:
|
|
27389
|
+
overflowX: z22.string().max(60).optional(),
|
|
27390
|
+
overflowY: z22.string().max(60).optional(),
|
|
26960
27391
|
borderRadius: cornerRadiiSchema.optional(),
|
|
26961
|
-
backgroundColor:
|
|
26962
|
-
backgroundImage:
|
|
26963
|
-
boxShadow:
|
|
27392
|
+
backgroundColor: z22.string().max(160).optional(),
|
|
27393
|
+
backgroundImage: z22.string().max(1e3).optional(),
|
|
27394
|
+
boxShadow: z22.string().max(1e3).optional()
|
|
26964
27395
|
});
|
|
26965
|
-
elementSchema =
|
|
26966
|
-
id:
|
|
26967
|
-
selector:
|
|
26968
|
-
tag:
|
|
26969
|
-
role:
|
|
26970
|
-
label:
|
|
26971
|
-
parentId:
|
|
26972
|
-
groupId:
|
|
26973
|
-
alignmentGroup:
|
|
26974
|
-
alignmentEdge:
|
|
26975
|
-
expectedSymmetry:
|
|
26976
|
-
compareSize:
|
|
26977
|
-
interactive:
|
|
27396
|
+
elementSchema = z22.object({
|
|
27397
|
+
id: z22.string().min(1).max(300),
|
|
27398
|
+
selector: z22.string().max(500).optional(),
|
|
27399
|
+
tag: z22.string().max(80).optional(),
|
|
27400
|
+
role: z22.string().max(80).optional(),
|
|
27401
|
+
label: z22.string().max(160).optional(),
|
|
27402
|
+
parentId: z22.string().max(300).optional(),
|
|
27403
|
+
groupId: z22.string().max(300).optional(),
|
|
27404
|
+
alignmentGroup: z22.string().max(300).optional(),
|
|
27405
|
+
alignmentEdge: z22.enum(["left", "right", "top", "bottom", "center-x", "center-y"]).optional(),
|
|
27406
|
+
expectedSymmetry: z22.enum(["horizontal", "vertical", "both"]).optional(),
|
|
27407
|
+
compareSize: z22.enum(["width", "height", "both"]).optional(),
|
|
27408
|
+
interactive: z22.boolean().optional(),
|
|
26978
27409
|
rect: rectSchema,
|
|
26979
27410
|
style: styleSchema.optional(),
|
|
26980
27411
|
lineCount: finiteNumber.int().positive().optional(),
|
|
@@ -26983,44 +27414,45 @@ var init_inspection_tools = __esm({
|
|
|
26983
27414
|
scrollWidth: finiteNumber.nonnegative().optional(),
|
|
26984
27415
|
scrollHeight: finiteNumber.nonnegative().optional()
|
|
26985
27416
|
});
|
|
26986
|
-
screenshotSchema =
|
|
26987
|
-
spacingStateSchema =
|
|
26988
|
-
name:
|
|
26989
|
-
viewport:
|
|
27417
|
+
screenshotSchema = z22.string().max(5e6).regex(/^data:image\/(?:png|jpeg|webp);base64,/, "Use a PNG, JPEG, or WebP data URL.");
|
|
27418
|
+
spacingStateSchema = z22.object({
|
|
27419
|
+
name: z22.string().min(1).max(100),
|
|
27420
|
+
viewport: z22.object({
|
|
26990
27421
|
width: finiteNumber.positive().max(2e4),
|
|
26991
27422
|
height: finiteNumber.positive().max(2e4),
|
|
26992
27423
|
devicePixelRatio: finiteNumber.positive().max(10).optional()
|
|
26993
27424
|
}),
|
|
26994
|
-
elements:
|
|
27425
|
+
elements: z22.array(elementSchema).max(1500),
|
|
26995
27426
|
screenshotDataUrl: screenshotSchema.optional().describe("Optional browser screenshot as a data URL. It remains in the structured evidence for report renderers and is not persisted.")
|
|
26996
27427
|
});
|
|
26997
|
-
evidenceSchema =
|
|
26998
|
-
label:
|
|
26999
|
-
value:
|
|
27000
|
-
unit:
|
|
27428
|
+
evidenceSchema = z22.object({
|
|
27429
|
+
label: z22.string(),
|
|
27430
|
+
value: z22.union([z22.number(), z22.string()]),
|
|
27431
|
+
unit: z22.enum(["px", "count"]).optional()
|
|
27001
27432
|
});
|
|
27002
|
-
findingSchema =
|
|
27003
|
-
id:
|
|
27004
|
-
ruleId:
|
|
27005
|
-
state:
|
|
27006
|
-
severity:
|
|
27007
|
-
message:
|
|
27008
|
-
fix:
|
|
27009
|
-
elementIds:
|
|
27010
|
-
evidence:
|
|
27433
|
+
findingSchema = z22.object({
|
|
27434
|
+
id: z22.string(),
|
|
27435
|
+
ruleId: z22.string(),
|
|
27436
|
+
state: z22.string(),
|
|
27437
|
+
severity: z22.enum(["critical", "serious", "moderate"]),
|
|
27438
|
+
message: z22.string(),
|
|
27439
|
+
fix: z22.string(),
|
|
27440
|
+
elementIds: z22.array(z22.string()),
|
|
27441
|
+
evidence: z22.array(evidenceSchema)
|
|
27011
27442
|
});
|
|
27012
|
-
overlayStateSchema =
|
|
27013
|
-
name:
|
|
27014
|
-
viewport:
|
|
27015
|
-
width:
|
|
27016
|
-
height:
|
|
27017
|
-
devicePixelRatio:
|
|
27443
|
+
overlayStateSchema = z22.object({
|
|
27444
|
+
name: z22.string(),
|
|
27445
|
+
viewport: z22.object({
|
|
27446
|
+
width: z22.number(),
|
|
27447
|
+
height: z22.number(),
|
|
27448
|
+
devicePixelRatio: z22.number().optional()
|
|
27018
27449
|
}),
|
|
27019
|
-
elements:
|
|
27020
|
-
screenshotDataUrl:
|
|
27450
|
+
elements: z22.array(z22.object({ id: z22.string(), label: z22.string().optional(), rect: rectSchema })),
|
|
27451
|
+
screenshotDataUrl: z22.string().optional()
|
|
27021
27452
|
});
|
|
27022
27453
|
RECOMMENDED_VIEWPORT_WIDTHS = [390, 1440, 1728];
|
|
27023
27454
|
WIDTH_TOLERANCE = 40;
|
|
27455
|
+
GEOMETRY_ONLY = "This measured geometry only. A clean result or a score of 100 is not visual approval: it cannot judge hierarchy, density or whether the design works.";
|
|
27024
27456
|
}
|
|
27025
27457
|
});
|
|
27026
27458
|
|
|
@@ -27059,7 +27491,7 @@ var init_inspection_resource = __esm({
|
|
|
27059
27491
|
});
|
|
27060
27492
|
|
|
27061
27493
|
// ../shared/src/mcp/tools/dashboard-review-tools.ts
|
|
27062
|
-
import { z as
|
|
27494
|
+
import { z as z23 } from "zod";
|
|
27063
27495
|
function finding(id, severity, message, fix) {
|
|
27064
27496
|
return { id, severity, message, fix };
|
|
27065
27497
|
}
|
|
@@ -27566,11 +27998,11 @@ function registerDashboardReviewTools({ server, track }) {
|
|
|
27566
27998
|
Pass the complete page module source, the unchanged fixture, the factual interpretation, and the installed Sidebar, StatCard, and Table source files. Never summarize, reconstruct, or submit only the overview component. The tool derives the overview contract from the fixture. It fails incomplete source, rewritten copy, unsupported claims, weak hierarchy, missing or under-composed installed components, raw aside/table markup, the wrong scan order, action color on every rate, and stepper rails on short flows.
|
|
27567
27999
|
|
|
27568
28000
|
FAIL is blocking and returns an MCP tool error. Fix every finding and call the tool again until PASS. Then run check-comprehension and inspect-spacing, and call finalize-dashboard-review. Only its VALID source-bound receipt permits presentation.`,
|
|
27569
|
-
inputSchema:
|
|
27570
|
-
screen:
|
|
27571
|
-
source:
|
|
28001
|
+
inputSchema: z23.object({
|
|
28002
|
+
screen: z23.string().trim().min(1).max(120).describe("What this dashboard is, e.g. 'funnel report overview'."),
|
|
28003
|
+
source: z23.string().min(1).max(12e4).describe("Complete TSX or JSX source for the dashboard page."),
|
|
27572
28004
|
fixture: dashboardFixtureSchema.describe("The request's unchanged fixture object. The first step is the overview; remaining steps are dependent task screens."),
|
|
27573
|
-
interpretation:
|
|
28005
|
+
interpretation: z23.string().trim().min(1).max(2e3).describe("Exact supplied or factually derived takeaway that must appear after records."),
|
|
27574
28006
|
componentSources: dashboardComponentSourcesSchema.optional().describe("Installed Sidebar, StatCard, and Table source files for compound-composition review."),
|
|
27575
28007
|
context: CONTEXT_ARG
|
|
27576
28008
|
}),
|
|
@@ -27618,34 +28050,34 @@ var init_dashboard_review_tools = __esm({
|
|
|
27618
28050
|
"use strict";
|
|
27619
28051
|
init_review_capture();
|
|
27620
28052
|
init_shared();
|
|
27621
|
-
dashboardFixtureSchema =
|
|
27622
|
-
stats:
|
|
27623
|
-
label:
|
|
27624
|
-
value:
|
|
28053
|
+
dashboardFixtureSchema = z23.object({
|
|
28054
|
+
stats: z23.array(z23.object({
|
|
28055
|
+
label: z23.string().trim().min(1).max(300),
|
|
28056
|
+
value: z23.string().trim().min(1).max(300)
|
|
27625
28057
|
})).max(100).optional(),
|
|
27626
|
-
records:
|
|
27627
|
-
title:
|
|
27628
|
-
detail:
|
|
27629
|
-
meta:
|
|
27630
|
-
status:
|
|
28058
|
+
records: z23.array(z23.object({
|
|
28059
|
+
title: z23.string().trim().min(1).max(300),
|
|
28060
|
+
detail: z23.string().trim().min(1).max(1e3),
|
|
28061
|
+
meta: z23.string().trim().min(1).max(300),
|
|
28062
|
+
status: z23.string().trim().min(1).max(300)
|
|
27631
28063
|
})).max(100).optional(),
|
|
27632
|
-
steps:
|
|
27633
|
-
title:
|
|
27634
|
-
description:
|
|
27635
|
-
action:
|
|
28064
|
+
steps: z23.array(z23.object({
|
|
28065
|
+
title: z23.string().trim().min(1).max(300),
|
|
28066
|
+
description: z23.string().trim().min(1).max(1e3),
|
|
28067
|
+
action: z23.string().trim().min(1).max(200)
|
|
27636
28068
|
})).min(2).max(50)
|
|
27637
28069
|
});
|
|
27638
|
-
dashboardComponentSourcesSchema =
|
|
27639
|
-
sidebar:
|
|
27640
|
-
statCard:
|
|
27641
|
-
table:
|
|
28070
|
+
dashboardComponentSourcesSchema = z23.object({
|
|
28071
|
+
sidebar: z23.string().min(1).max(12e4),
|
|
28072
|
+
statCard: z23.string().min(1).max(12e4),
|
|
28073
|
+
table: z23.string().min(1).max(12e4)
|
|
27642
28074
|
});
|
|
27643
28075
|
}
|
|
27644
28076
|
});
|
|
27645
28077
|
|
|
27646
28078
|
// ../shared/src/mcp/tools/final-ui-review-tools.ts
|
|
27647
28079
|
import { createHash as createHash2 } from "crypto";
|
|
27648
|
-
import { z as
|
|
28080
|
+
import { z as z24 } from "zod";
|
|
27649
28081
|
function digest(value) {
|
|
27650
28082
|
return createHash2("sha256").update(value).digest("hex");
|
|
27651
28083
|
}
|
|
@@ -27656,7 +28088,9 @@ function comprehensionFindings(input) {
|
|
|
27656
28088
|
const report = analyzeComprehension({
|
|
27657
28089
|
screen: input.screen,
|
|
27658
28090
|
code: input.visibleSource,
|
|
27659
|
-
knownTerms: input.knownTerms
|
|
28091
|
+
knownTerms: input.knownTerms,
|
|
28092
|
+
// A dashboard is a screen even when its name mentions slides. [BD-593]
|
|
28093
|
+
surface: "screen"
|
|
27660
28094
|
});
|
|
27661
28095
|
return report.findings.flatMap(
|
|
27662
28096
|
(item, index2) => item.severity === "minor" ? [] : [
|
|
@@ -28057,15 +28491,15 @@ function registerFinalUiReviewTools({
|
|
|
28057
28491
|
description: `Issue a source-bound receipt for a completed dashboard.
|
|
28058
28492
|
|
|
28059
28493
|
Submit the public HTTPS preview plus exact source, unchanged data, and installed components. Better Design's own browser captures the deployed page at 390, 1440, and 1728 pixels and content-addresses the live deployment. No trusted browser capture means incomplete. The review blocks weak hierarchy, under-composed components, hidden fixture fields, unclear copy, spacing defects, unsupported interpretations, or source edits.`,
|
|
28060
|
-
inputSchema:
|
|
28061
|
-
screen:
|
|
28062
|
-
previewUrl:
|
|
28494
|
+
inputSchema: z24.object({
|
|
28495
|
+
screen: z24.string().trim().min(1).max(120),
|
|
28496
|
+
previewUrl: z24.string().url().max(2048).refine((value) => new URL(value).protocol === "https:", {
|
|
28063
28497
|
message: "Use a public HTTPS preview URL."
|
|
28064
28498
|
}).describe("Public HTTPS URL for the exact deployed dashboard route."),
|
|
28065
|
-
source:
|
|
28499
|
+
source: z24.string().min(1).max(24e4).describe(
|
|
28066
28500
|
"Complete TSX or JSX page source, including its default export."
|
|
28067
28501
|
),
|
|
28068
|
-
visibleSource:
|
|
28502
|
+
visibleSource: z24.string().min(1).max(12e4).describe(
|
|
28069
28503
|
"Exact visible screen component copied unchanged from source."
|
|
28070
28504
|
),
|
|
28071
28505
|
fixture: dashboardFixtureSchema.describe(
|
|
@@ -28074,7 +28508,7 @@ Submit the public HTTPS preview plus exact source, unchanged data, and installed
|
|
|
28074
28508
|
componentSources: dashboardComponentSourcesSchema.describe(
|
|
28075
28509
|
"Exact installed Sidebar, StatCard, and Table component source files."
|
|
28076
28510
|
),
|
|
28077
|
-
knownTerms:
|
|
28511
|
+
knownTerms: z24.array(z24.string().trim().min(1).max(60)).max(50).optional(),
|
|
28078
28512
|
context: CONTEXT_ARG
|
|
28079
28513
|
}),
|
|
28080
28514
|
annotations: READ_ONLY_OPEN_WORLD_TOOL_ANNOTATIONS
|
|
@@ -28145,7 +28579,7 @@ var init_final_ui_review_tools = __esm({
|
|
|
28145
28579
|
});
|
|
28146
28580
|
|
|
28147
28581
|
// ../shared/src/mcp/tools/widget-tools.ts
|
|
28148
|
-
import { z as
|
|
28582
|
+
import { z as z25 } from "zod";
|
|
28149
28583
|
function hasIntake(args) {
|
|
28150
28584
|
return Boolean(args.widgetType || INTAKE_FIELDS.some(({ key }) => args[key]));
|
|
28151
28585
|
}
|
|
@@ -28268,16 +28702,16 @@ var init_widget_tools = __esm({
|
|
|
28268
28702
|
WIDGET_GUIDE_TOPIC = "better-widgets";
|
|
28269
28703
|
WIDGET_GUIDE_TOOL = "get-widget-guide";
|
|
28270
28704
|
MINIMUM_CONTRACT_HEADING = "## Minimum build contract";
|
|
28271
|
-
WIDGET_GUIDE_INPUT_SCHEMA =
|
|
28272
|
-
widgetType:
|
|
28273
|
-
userGoal:
|
|
28274
|
-
inputs:
|
|
28275
|
-
interaction:
|
|
28276
|
-
transformation:
|
|
28277
|
-
result:
|
|
28278
|
-
hostFramework:
|
|
28279
|
-
visualContext:
|
|
28280
|
-
maxTokens:
|
|
28705
|
+
WIDGET_GUIDE_INPUT_SCHEMA = z25.object({
|
|
28706
|
+
widgetType: z25.enum(BETTER_WIDGET_TYPES).optional().describe("Widget kind: calculator, estimator, converter, generator, quiz, or checklist."),
|
|
28707
|
+
userGoal: z25.string().trim().min(1).max(500).optional().describe("Who will use the widget and the decision or task it helps them complete."),
|
|
28708
|
+
inputs: z25.string().trim().min(1).max(1e3).optional().describe("Required inputs, including units, allowed ranges, and defaults."),
|
|
28709
|
+
interaction: z25.string().trim().min(1).max(750).optional().describe("How readers tune inputs and see feedback, including presets, custom controls, direct manipulation, and exact-value entry."),
|
|
28710
|
+
transformation: z25.string().trim().min(1).max(1500).optional().describe("Formula, scoring rule, conversion, or transformation that produces the result."),
|
|
28711
|
+
result: z25.string().trim().min(1).max(750).optional().describe("What the result shows, explains, and lets the user do next."),
|
|
28712
|
+
hostFramework: z25.string().trim().min(1).max(300).optional().describe("The host framework and the page or component that will contain the widget."),
|
|
28713
|
+
visualContext: z25.string().trim().min(1).max(750).optional().describe("Existing design tokens, components, or brand reference the widget must match."),
|
|
28714
|
+
maxTokens: z25.number().min(2e3).max(5e4).optional().describe("Maximum tokens to return. Truncates content to fit. Recommended: 5000-10000."),
|
|
28281
28715
|
context: CONTEXT_ARG
|
|
28282
28716
|
});
|
|
28283
28717
|
INTAKE_FIELDS = [
|
|
@@ -28331,7 +28765,7 @@ The completed response combines a scoped build brief with Better Design's stable
|
|
|
28331
28765
|
});
|
|
28332
28766
|
|
|
28333
28767
|
// ../shared/src/mcp/feature-map/schema.ts
|
|
28334
|
-
import { z as
|
|
28768
|
+
import { z as z26 } from "zod";
|
|
28335
28769
|
function oneLine(text3) {
|
|
28336
28770
|
return text3.replace(/\s+/g, " ").trim();
|
|
28337
28771
|
}
|
|
@@ -28366,38 +28800,38 @@ var init_schema3 = __esm({
|
|
|
28366
28800
|
"use strict";
|
|
28367
28801
|
FEATURE_MAP_PATH = ".better-design/feature-map.json";
|
|
28368
28802
|
FEATURE_MAP_LIMITS = { features: 200, flows: 100, steps: 50, paths: 200, name: 120 };
|
|
28369
|
-
NAME =
|
|
28370
|
-
PATHS =
|
|
28371
|
-
featureMapStepSchema =
|
|
28803
|
+
NAME = z26.string().trim().min(1).max(FEATURE_MAP_LIMITS.name);
|
|
28804
|
+
PATHS = z26.array(z26.string().trim().min(1).max(500)).max(FEATURE_MAP_LIMITS.paths).default([]);
|
|
28805
|
+
featureMapStepSchema = z26.object({
|
|
28372
28806
|
name: NAME,
|
|
28373
28807
|
/** URL path such as `/settings/billing`. Absent for a dialog or an email. */
|
|
28374
|
-
route:
|
|
28808
|
+
route: z26.string().trim().max(300).optional(),
|
|
28375
28809
|
/** The file that renders the step, such as the route's page file. */
|
|
28376
|
-
screen:
|
|
28810
|
+
screen: z26.string().trim().max(500).optional(),
|
|
28377
28811
|
/** What the user does here, such as "confirms the cancellation". */
|
|
28378
|
-
action:
|
|
28812
|
+
action: z26.string().trim().max(300).optional(),
|
|
28379
28813
|
components: PATHS,
|
|
28380
28814
|
/** API routes, server actions, hooks and queries the step reads or writes. */
|
|
28381
28815
|
data: PATHS
|
|
28382
28816
|
});
|
|
28383
|
-
featureMapFlowSchema =
|
|
28817
|
+
featureMapFlowSchema = z26.object({
|
|
28384
28818
|
id: NAME,
|
|
28385
28819
|
name: NAME,
|
|
28386
|
-
description:
|
|
28387
|
-
steps:
|
|
28820
|
+
description: z26.string().trim().max(500).optional(),
|
|
28821
|
+
steps: z26.array(featureMapStepSchema).min(1).max(FEATURE_MAP_LIMITS.steps)
|
|
28388
28822
|
});
|
|
28389
|
-
featureMapFeatureSchema =
|
|
28823
|
+
featureMapFeatureSchema = z26.object({
|
|
28390
28824
|
id: NAME,
|
|
28391
28825
|
name: NAME,
|
|
28392
|
-
description:
|
|
28393
|
-
flows:
|
|
28826
|
+
description: z26.string().trim().max(500).optional(),
|
|
28827
|
+
flows: z26.array(featureMapFlowSchema).max(FEATURE_MAP_LIMITS.flows),
|
|
28394
28828
|
/** Components that belong to the feature but to no single step. */
|
|
28395
28829
|
components: PATHS
|
|
28396
28830
|
});
|
|
28397
|
-
featureMapSchema =
|
|
28398
|
-
version:
|
|
28399
|
-
product:
|
|
28400
|
-
features:
|
|
28831
|
+
featureMapSchema = z26.object({
|
|
28832
|
+
version: z26.literal(1),
|
|
28833
|
+
product: z26.string().trim().max(120).optional(),
|
|
28834
|
+
features: z26.array(featureMapFeatureSchema).max(FEATURE_MAP_LIMITS.features)
|
|
28401
28835
|
});
|
|
28402
28836
|
}
|
|
28403
28837
|
});
|
|
@@ -28675,7 +29109,7 @@ var init_read = __esm({
|
|
|
28675
29109
|
});
|
|
28676
29110
|
|
|
28677
29111
|
// ../shared/src/mcp/feature-map/update.ts
|
|
28678
|
-
import { z as
|
|
29112
|
+
import { z as z27 } from "zod";
|
|
28679
29113
|
function find(items, ref) {
|
|
28680
29114
|
const r = ref.trim();
|
|
28681
29115
|
const index2 = items.findIndex((i) => i.id === r || i.name.toLowerCase() === r.toLowerCase());
|
|
@@ -28738,21 +29172,21 @@ var init_update2 = __esm({
|
|
|
28738
29172
|
"../shared/src/mcp/feature-map/update.ts"() {
|
|
28739
29173
|
"use strict";
|
|
28740
29174
|
init_schema3();
|
|
28741
|
-
NAME2 =
|
|
28742
|
-
REF =
|
|
29175
|
+
NAME2 = z27.string().trim().min(1).max(120);
|
|
29176
|
+
REF = z27.string().trim().min(1).max(120).describe("The id or exact name of an existing feature or flow.");
|
|
28743
29177
|
FLOW_INPUT = featureMapFlowSchema.omit({ id: true });
|
|
28744
|
-
featureMapChangeSchema =
|
|
28745
|
-
|
|
28746
|
-
op:
|
|
29178
|
+
featureMapChangeSchema = z27.discriminatedUnion("op", [
|
|
29179
|
+
z27.object({
|
|
29180
|
+
op: z27.literal("add-feature"),
|
|
28747
29181
|
name: NAME2,
|
|
28748
|
-
description:
|
|
28749
|
-
flows:
|
|
29182
|
+
description: z27.string().trim().max(500).optional(),
|
|
29183
|
+
flows: z27.array(FLOW_INPUT).max(100).optional()
|
|
28750
29184
|
}),
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28755
|
-
|
|
29185
|
+
z27.object({ op: z27.literal("rename-feature"), feature: REF, name: NAME2 }),
|
|
29186
|
+
z27.object({ op: z27.literal("remove-feature"), feature: REF }),
|
|
29187
|
+
z27.object({ op: z27.literal("set-flow"), feature: REF, flow: FLOW_INPUT }),
|
|
29188
|
+
z27.object({ op: z27.literal("rename-flow"), feature: REF, flow: REF, name: NAME2 }),
|
|
29189
|
+
z27.object({ op: z27.literal("remove-flow"), feature: REF, flow: REF })
|
|
28756
29190
|
]);
|
|
28757
29191
|
idTaken = (items, id, except2) => items.some((item, i) => item.id === id && i !== except2);
|
|
28758
29192
|
toFlow = (f) => ({ id: slugify(f.name), ...f });
|
|
@@ -28771,7 +29205,7 @@ var init_feature_map = __esm({
|
|
|
28771
29205
|
});
|
|
28772
29206
|
|
|
28773
29207
|
// ../shared/src/mcp/tools/feature-map-tools.ts
|
|
28774
|
-
import { z as
|
|
29208
|
+
import { z as z28 } from "zod";
|
|
28775
29209
|
function registerFeatureMapTools({ server, tracked }) {
|
|
28776
29210
|
server.registerTool(
|
|
28777
29211
|
"build-feature-map",
|
|
@@ -28782,9 +29216,9 @@ function registerFeatureMapTools({ server, tracked }) {
|
|
|
28782
29216
|
Run \`git ls-files\` in the repository and pass every path as \`files\`. Routes become screens (Next.js app and pages routers, Remix and React Router flat routes, SvelteKit, Nuxt), each chain of nested routes becomes a flow (/settings/billing/cancel is Settings \u2192 Billing \u2192 Cancel), and components, API routes, hooks and server actions join the step whose name they share.
|
|
28783
29217
|
|
|
28784
29218
|
The result is a first draft. Write it to ${FEATURE_MAP_PATH}, show the user the flows, and ask which steps are missing: a dialog, an email or a background job has no route, so add those with update-feature-map (op "set-flow").`,
|
|
28785
|
-
inputSchema:
|
|
28786
|
-
files:
|
|
28787
|
-
product:
|
|
29219
|
+
inputSchema: z28.object({
|
|
29220
|
+
files: z28.array(z28.string().max(500)).min(1).max(5e4).describe("Every file path in the repository, relative to its root, as printed by `git ls-files`."),
|
|
29221
|
+
product: z28.string().trim().max(120).optional().describe("The product's name, stored at the top of the map."),
|
|
28788
29222
|
context: CONTEXT_ARG
|
|
28789
29223
|
}),
|
|
28790
29224
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -28814,11 +29248,11 @@ ${serializeFeatureMap(map).trimEnd()}
|
|
|
28814
29248
|
description: `Find the flows a change touches, with the screen, route, components and data files of every step. ${WHEN_TO_USE}
|
|
28815
29249
|
|
|
28816
29250
|
Pass \`flow\` ("cancel account"), \`feature\` ("billing") or \`file\` (a path you are about to edit) to narrow it. With no query it lists every flow.`,
|
|
28817
|
-
inputSchema:
|
|
29251
|
+
inputSchema: z28.object({
|
|
28818
29252
|
map: MAP_ARG,
|
|
28819
|
-
feature:
|
|
28820
|
-
flow:
|
|
28821
|
-
file:
|
|
29253
|
+
feature: z28.string().trim().max(120).optional().describe("A feature id or part of its name."),
|
|
29254
|
+
flow: z28.string().trim().max(120).optional().describe("A flow id or part of its name."),
|
|
29255
|
+
file: z28.string().trim().max(500).optional().describe("A file path or route; returns every flow that uses it."),
|
|
28822
29256
|
context: CONTEXT_ARG
|
|
28823
29257
|
}),
|
|
28824
29258
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -28844,7 +29278,7 @@ Changes, one per call:
|
|
|
28844
29278
|
- { op: "remove-flow", feature, flow }
|
|
28845
29279
|
|
|
28846
29280
|
\`feature\` and \`flow\` take an id or an exact name. Write the returned file to ${FEATURE_MAP_PATH}.`,
|
|
28847
|
-
inputSchema:
|
|
29281
|
+
inputSchema: z28.object({
|
|
28848
29282
|
map: MAP_ARG,
|
|
28849
29283
|
change: featureMapChangeSchema,
|
|
28850
29284
|
context: CONTEXT_ARG
|
|
@@ -28866,7 +29300,7 @@ var init_feature_map_tools = __esm({
|
|
|
28866
29300
|
"use strict";
|
|
28867
29301
|
init_feature_map();
|
|
28868
29302
|
init_shared();
|
|
28869
|
-
MAP_ARG =
|
|
29303
|
+
MAP_ARG = z28.string().min(1).max(2e6).describe(`The exact contents of ${FEATURE_MAP_PATH}.`);
|
|
28870
29304
|
text2 = (body, isError = false) => ({
|
|
28871
29305
|
content: [{ type: "text", text: body }],
|
|
28872
29306
|
isError
|
|
@@ -28887,8 +29321,185 @@ var init_feature_map_tools = __esm({
|
|
|
28887
29321
|
}
|
|
28888
29322
|
});
|
|
28889
29323
|
|
|
29324
|
+
// ../shared/src/mcp/inspiration.ts
|
|
29325
|
+
import { z as z29 } from "zod";
|
|
29326
|
+
function describeSet(set) {
|
|
29327
|
+
const heading = `## ${set.title}${set.category ? ` (${set.category})` : ""}`;
|
|
29328
|
+
if (!set.notes) return heading;
|
|
29329
|
+
const written = /* @__PURE__ */ new Date(`${set.notes.writtenAt}T00:00:00Z`);
|
|
29330
|
+
const date2 = Number.isNaN(written.getTime()) ? set.notes.writtenAt : NOTE_DATE.format(written);
|
|
29331
|
+
return [
|
|
29332
|
+
heading,
|
|
29333
|
+
`Headlines: ${set.notes.headline}`,
|
|
29334
|
+
`Slide order: ${set.notes.order}`,
|
|
29335
|
+
`Framing: ${set.notes.framing}`,
|
|
29336
|
+
`Color: ${set.notes.color}`,
|
|
29337
|
+
`Better Design's notes, written by a vision model from these screenshots on ${date2}. The listing may have changed since.`
|
|
29338
|
+
].join("\n");
|
|
29339
|
+
}
|
|
29340
|
+
var INSPIRATION_TYPES, MAX_INSPIRATION_LIMIT, noteSchema, inspirationNotesFileSchema, indexFileSchema, indexEntrySchema, inspirationIndexSchema, NOTE_DATE;
|
|
29341
|
+
var init_inspiration = __esm({
|
|
29342
|
+
"../shared/src/mcp/inspiration.ts"() {
|
|
29343
|
+
"use strict";
|
|
29344
|
+
INSPIRATION_TYPES = ["app-store-screenshots"];
|
|
29345
|
+
MAX_INSPIRATION_LIMIT = 6;
|
|
29346
|
+
noteSchema = z29.object({
|
|
29347
|
+
slug: z29.string(),
|
|
29348
|
+
/** The day the note was written, as YYYY-MM-DD. A listing can change after it. */
|
|
29349
|
+
writtenAt: z29.string(),
|
|
29350
|
+
headline: z29.string(),
|
|
29351
|
+
order: z29.string(),
|
|
29352
|
+
framing: z29.string(),
|
|
29353
|
+
color: z29.string()
|
|
29354
|
+
});
|
|
29355
|
+
inspirationNotesFileSchema = z29.object({
|
|
29356
|
+
model: z29.string(),
|
|
29357
|
+
notes: z29.array(noteSchema)
|
|
29358
|
+
});
|
|
29359
|
+
indexFileSchema = z29.object({
|
|
29360
|
+
key: z29.string(),
|
|
29361
|
+
type: z29.string(),
|
|
29362
|
+
width: z29.number(),
|
|
29363
|
+
height: z29.number()
|
|
29364
|
+
});
|
|
29365
|
+
indexEntrySchema = z29.object({
|
|
29366
|
+
title: z29.string(),
|
|
29367
|
+
slug: z29.string(),
|
|
29368
|
+
category: z29.string().nullish(),
|
|
29369
|
+
files: z29.array(indexFileSchema)
|
|
29370
|
+
});
|
|
29371
|
+
inspirationIndexSchema = z29.object({
|
|
29372
|
+
entries: z29.array(indexEntrySchema)
|
|
29373
|
+
});
|
|
29374
|
+
NOTE_DATE = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
|
|
29375
|
+
}
|
|
29376
|
+
});
|
|
29377
|
+
|
|
29378
|
+
// ../shared/src/mcp/tools/inspiration-tools.ts
|
|
29379
|
+
import { z as z30 } from "zod";
|
|
29380
|
+
function describeSets(result) {
|
|
29381
|
+
const lines = result.sets.map((set, i) => {
|
|
29382
|
+
const category = set.category ? `, ${set.category}` : "";
|
|
29383
|
+
return `${i + 1}. ${set.title}${category}: ${set.images.length} screenshot${set.images.length === 1 ? "" : "s"} below`;
|
|
29384
|
+
});
|
|
29385
|
+
return [
|
|
29386
|
+
`# App Store screenshot sets`,
|
|
29387
|
+
"",
|
|
29388
|
+
...lines,
|
|
29389
|
+
"",
|
|
29390
|
+
"Each image follows the set it belongs to. Read each set for its headline style, slide order, device framing and color, then propose two or three directions for the user's own screenshots and let the user pick one."
|
|
29391
|
+
].join("\n");
|
|
29392
|
+
}
|
|
29393
|
+
function noMatch(result, category) {
|
|
29394
|
+
const asked = category ? `No App Store screenshot sets match the category "${category}".` : "The library holds no App Store screenshot sets yet.";
|
|
29395
|
+
return `${asked} Categories in the library: ${result.categories.join(", ")}.`;
|
|
29396
|
+
}
|
|
29397
|
+
function registerInspirationTools({ server, tracked }, readInspiration2) {
|
|
29398
|
+
server.registerTool(
|
|
29399
|
+
"get-inspiration",
|
|
29400
|
+
{
|
|
29401
|
+
title: "Get Inspiration",
|
|
29402
|
+
description: `Send real reference designs from Better Design's library as images, so you can study how shipped products solved the same design problem before you design the user's own.
|
|
29403
|
+
|
|
29404
|
+
Use it before designing App Store or Google Play screenshots: pass \`type: "app-store-screenshots"\`, and a \`category\` such as "Health & Fitness" when the user's app has one. Each set arrives as its title and category, followed by its first screenshots.
|
|
29405
|
+
|
|
29406
|
+
Then propose two or three directions drawn from what the sets do (headline style, slide order, device framing, color), and let the user choose before you build anything.`,
|
|
29407
|
+
inputSchema: z30.object({
|
|
29408
|
+
type: z30.enum(INSPIRATION_TYPES).describe('The kind of reference to send. Today: "app-store-screenshots".'),
|
|
29409
|
+
category: z30.string().trim().min(1).max(60).optional().describe('Optional App Store category, for example "Productivity" or "Health & Fitness". Ignores case.'),
|
|
29410
|
+
limit: z30.number().int().min(1).max(MAX_INSPIRATION_LIMIT).optional().describe(`How many sets to send, 1 to ${MAX_INSPIRATION_LIMIT}. Defaults to 3.`),
|
|
29411
|
+
context: CONTEXT_ARG
|
|
29412
|
+
}),
|
|
29413
|
+
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
29414
|
+
},
|
|
29415
|
+
async ({ type, category, limit, context }) => tracked("get-inspiration", { type, category: category ?? null, limit: limit ?? null, context }, async () => {
|
|
29416
|
+
if (!readInspiration2) return { content: [{ type: "text", text: NOT_AVAILABLE }] };
|
|
29417
|
+
const result = await readInspiration2({ type, category, limit });
|
|
29418
|
+
if (result.sets.length === 0) {
|
|
29419
|
+
return { content: [{ type: "text", text: noMatch(result, category) }] };
|
|
29420
|
+
}
|
|
29421
|
+
return {
|
|
29422
|
+
content: [
|
|
29423
|
+
{ type: "text", text: describeSets(result) },
|
|
29424
|
+
...result.sets.flatMap((set) => [
|
|
29425
|
+
{ type: "text", text: describeSet(set) },
|
|
29426
|
+
...set.images.map((image) => ({
|
|
29427
|
+
type: "image",
|
|
29428
|
+
data: image.data,
|
|
29429
|
+
mimeType: image.mimeType
|
|
29430
|
+
}))
|
|
29431
|
+
])
|
|
29432
|
+
]
|
|
29433
|
+
};
|
|
29434
|
+
})
|
|
29435
|
+
);
|
|
29436
|
+
}
|
|
29437
|
+
var NOT_AVAILABLE;
|
|
29438
|
+
var init_inspiration_tools = __esm({
|
|
29439
|
+
"../shared/src/mcp/tools/inspiration-tools.ts"() {
|
|
29440
|
+
"use strict";
|
|
29441
|
+
init_shared();
|
|
29442
|
+
init_inspiration();
|
|
29443
|
+
NOT_AVAILABLE = "The inspiration library is not available from this server. Try again with the hosted Better Design MCP server or the latest `better-design` npm package.";
|
|
29444
|
+
}
|
|
29445
|
+
});
|
|
29446
|
+
|
|
29447
|
+
// ../shared/src/mcp/tools/feedback-tools.ts
|
|
29448
|
+
function createFeedbackSender(apiFetch, apiKey) {
|
|
29449
|
+
return async (report) => {
|
|
29450
|
+
const res = await apiFetch(`${APP_BASE_URL}/api/v1/feedback`, {
|
|
29451
|
+
method: "POST",
|
|
29452
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
29453
|
+
body: JSON.stringify(report),
|
|
29454
|
+
signal: AbortSignal.timeout(15e3)
|
|
29455
|
+
});
|
|
29456
|
+
const body = await res.json().catch(() => ({}));
|
|
29457
|
+
if (!res.ok || !body.id) throw new Error(body.message ?? `HTTP ${res.status}`);
|
|
29458
|
+
return { id: body.id, status: body.status ?? "open", message: body.message ?? "" };
|
|
29459
|
+
};
|
|
29460
|
+
}
|
|
29461
|
+
function registerFeedbackTools({ server, tracked }, sendFeedback) {
|
|
29462
|
+
if (!sendFeedback) return;
|
|
29463
|
+
server.registerTool(
|
|
29464
|
+
"send-feedback",
|
|
29465
|
+
{
|
|
29466
|
+
title: "Send Feedback",
|
|
29467
|
+
description: `Tell Better Design about a bug, a missing feature or wrong output you hit while using its tools or API. A person reads every report and approves any fix.
|
|
29468
|
+
|
|
29469
|
+
Send one report per problem, once you have finished what the user asked. Say what you tried, what you expected and what came back. Leave out secrets, personal data and the user's private code. Sending a report never spends credit.`,
|
|
29470
|
+
inputSchema: reportSchema,
|
|
29471
|
+
// Each call stores one new report and publishes nothing. See
|
|
29472
|
+
// `docs/mcp-tool-annotations.md`.
|
|
29473
|
+
annotations: {
|
|
29474
|
+
readOnlyHint: false,
|
|
29475
|
+
destructiveHint: false,
|
|
29476
|
+
idempotentHint: false,
|
|
29477
|
+
openWorldHint: false
|
|
29478
|
+
}
|
|
29479
|
+
},
|
|
29480
|
+
// Usage telemetry records the kind only. The report itself goes to the queue.
|
|
29481
|
+
async (report) => tracked("send-feedback", { kind: report.kind }, async () => {
|
|
29482
|
+
const info = server.server.getClientVersion();
|
|
29483
|
+
const client = info ? `${info.name}/${info.version}`.slice(0, AGENT_FEEDBACK_CLIENT_MAX) : void 0;
|
|
29484
|
+
const receipt = await sendFeedback({ ...report, client });
|
|
29485
|
+
return {
|
|
29486
|
+
content: [{ type: "text", text: `Report ${receipt.id} sent. ${receipt.message}` }]
|
|
29487
|
+
};
|
|
29488
|
+
})
|
|
29489
|
+
);
|
|
29490
|
+
}
|
|
29491
|
+
var reportSchema;
|
|
29492
|
+
var init_feedback_tools = __esm({
|
|
29493
|
+
"../shared/src/mcp/tools/feedback-tools.ts"() {
|
|
29494
|
+
"use strict";
|
|
29495
|
+
init_agent_feedback();
|
|
29496
|
+
init_shared();
|
|
29497
|
+
reportSchema = agentFeedbackInputSchema.omit({ client: true });
|
|
29498
|
+
}
|
|
29499
|
+
});
|
|
29500
|
+
|
|
28890
29501
|
// ../shared/src/mcp/tools.ts
|
|
28891
|
-
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2) {
|
|
29502
|
+
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2, sendFeedback) {
|
|
28892
29503
|
const { track, tracked } = createTracking(server, onToolUsage);
|
|
28893
29504
|
const deps = {
|
|
28894
29505
|
server,
|
|
@@ -28914,6 +29525,8 @@ function registerAllTools(server, provider, onToolUsage, authContext, options =
|
|
|
28914
29525
|
registerInspectionTools(deps);
|
|
28915
29526
|
registerFeatureMapTools(deps);
|
|
28916
29527
|
registerTeamRulesTools(deps);
|
|
29528
|
+
registerInspirationTools(deps, readInspiration2);
|
|
29529
|
+
registerFeedbackTools(deps, sendFeedback);
|
|
28917
29530
|
registerPreviewResource(deps);
|
|
28918
29531
|
registerInspectionResource(deps);
|
|
28919
29532
|
}
|
|
@@ -28937,6 +29550,8 @@ var init_tools = __esm({
|
|
|
28937
29550
|
init_widget_tools();
|
|
28938
29551
|
init_feature_map_tools();
|
|
28939
29552
|
init_team_rules_tools();
|
|
29553
|
+
init_inspiration_tools();
|
|
29554
|
+
init_feedback_tools();
|
|
28940
29555
|
init_shared();
|
|
28941
29556
|
}
|
|
28942
29557
|
});
|
|
@@ -28946,7 +29561,7 @@ var MCP_IMPLEMENTATION_VERSION;
|
|
|
28946
29561
|
var init_version2 = __esm({
|
|
28947
29562
|
"../shared/src/mcp/version.ts"() {
|
|
28948
29563
|
"use strict";
|
|
28949
|
-
MCP_IMPLEMENTATION_VERSION = "0.
|
|
29564
|
+
MCP_IMPLEMENTATION_VERSION = "0.34.0";
|
|
28950
29565
|
}
|
|
28951
29566
|
});
|
|
28952
29567
|
|
|
@@ -28979,6 +29594,7 @@ __export(mcp_exports, {
|
|
|
28979
29594
|
analyzeDashboardStructure: () => analyzeDashboardStructure,
|
|
28980
29595
|
buildShopifyPrimitiveSet: () => buildShopifyPrimitiveSet,
|
|
28981
29596
|
canonicalMcpToolName: () => canonicalMcpToolName,
|
|
29597
|
+
createFeedbackSender: () => createFeedbackSender,
|
|
28982
29598
|
createMcpServer: () => createMcpServer,
|
|
28983
29599
|
declareAdvertisedSchemaDialect: () => declareAdvertisedSchemaDialect,
|
|
28984
29600
|
deriveDashboardInterpretation: () => deriveDashboardInterpretation,
|
|
@@ -29011,7 +29627,9 @@ function createMcpServer(options) {
|
|
|
29011
29627
|
apiFetch = globalThis.fetch,
|
|
29012
29628
|
userId,
|
|
29013
29629
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
29014
|
-
directoryListing = false
|
|
29630
|
+
directoryListing = false,
|
|
29631
|
+
readInspiration: readInspiration2,
|
|
29632
|
+
sendFeedback
|
|
29015
29633
|
} = options;
|
|
29016
29634
|
const server = new McpServer(
|
|
29017
29635
|
{ name, version: version2 },
|
|
@@ -29038,7 +29656,9 @@ function createMcpServer(options) {
|
|
|
29038
29656
|
},
|
|
29039
29657
|
apiFetch,
|
|
29040
29658
|
userId,
|
|
29041
|
-
captureDashboardBrowser2
|
|
29659
|
+
captureDashboardBrowser2,
|
|
29660
|
+
readInspiration2,
|
|
29661
|
+
sendFeedback
|
|
29042
29662
|
);
|
|
29043
29663
|
return server;
|
|
29044
29664
|
}
|
|
@@ -29055,6 +29675,7 @@ var init_mcp = __esm({
|
|
|
29055
29675
|
init_advertised_schema();
|
|
29056
29676
|
init_tool_names();
|
|
29057
29677
|
init_principle_tools();
|
|
29678
|
+
init_feedback_tools();
|
|
29058
29679
|
init_instructions();
|
|
29059
29680
|
init_session_guidance();
|
|
29060
29681
|
init_shared();
|
|
@@ -29078,10 +29699,12 @@ __export(api_client_exports, {
|
|
|
29078
29699
|
getSessionGuidance: () => getSessionGuidance,
|
|
29079
29700
|
getTeamRules: () => getTeamRules,
|
|
29080
29701
|
listPrinciples: () => listPrinciples,
|
|
29702
|
+
readInspiration: () => readInspiration,
|
|
29081
29703
|
searchDesignSystems: () => searchDesignSystems,
|
|
29082
29704
|
searchIconLibraries: () => searchIconLibraries,
|
|
29083
29705
|
searchIcons: () => searchIcons,
|
|
29084
29706
|
searchPrinciples: () => searchPrinciples,
|
|
29707
|
+
sendAgentFeedback: () => sendAgentFeedback,
|
|
29085
29708
|
trackMcpInstalled: () => trackMcpInstalled,
|
|
29086
29709
|
trackToolUsage: () => trackToolUsage
|
|
29087
29710
|
});
|
|
@@ -29115,6 +29738,15 @@ function captureDashboardBrowser(input) {
|
|
|
29115
29738
|
timeoutMs: 9e4
|
|
29116
29739
|
});
|
|
29117
29740
|
}
|
|
29741
|
+
function readInspiration(input) {
|
|
29742
|
+
const params = new URLSearchParams({ type: input.type });
|
|
29743
|
+
if (input.category) params.set("category", input.category);
|
|
29744
|
+
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
29745
|
+
return request(`/v1/inspiration?${params}`, { timeoutMs: 3e4 });
|
|
29746
|
+
}
|
|
29747
|
+
function sendAgentFeedback(report) {
|
|
29748
|
+
return request("/v1/feedback", { method: "POST", body: report });
|
|
29749
|
+
}
|
|
29118
29750
|
async function searchDesignSystems(query, limit = 5) {
|
|
29119
29751
|
return request(
|
|
29120
29752
|
`/v1/design-systems/search?q=${encodeURIComponent(query)}&limit=${limit}`
|
|
@@ -31091,7 +31723,7 @@ var init_engine = __esm({
|
|
|
31091
31723
|
});
|
|
31092
31724
|
|
|
31093
31725
|
// ../shared/src/lint/config.ts
|
|
31094
|
-
import { z as
|
|
31726
|
+
import { z as z31 } from "zod";
|
|
31095
31727
|
function mergeReviewConfig(override) {
|
|
31096
31728
|
if (!override) return DEFAULT_REVIEW_CONFIG;
|
|
31097
31729
|
return {
|
|
@@ -31149,47 +31781,47 @@ var init_config = __esm({
|
|
|
31149
31781
|
semanticTokens: { enabled: true }
|
|
31150
31782
|
}
|
|
31151
31783
|
};
|
|
31152
|
-
reviewFlowStepSchema =
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
31156
|
-
|
|
31784
|
+
reviewFlowStepSchema = z31.discriminatedUnion("action", [
|
|
31785
|
+
z31.object({ action: z31.literal("click"), selector: z31.string() }),
|
|
31786
|
+
z31.object({ action: z31.literal("type"), selector: z31.string(), value: z31.string() }),
|
|
31787
|
+
z31.object({ action: z31.literal("press"), key: z31.string() }),
|
|
31788
|
+
z31.object({ action: z31.literal("wait"), milliseconds: z31.number().finite().nonnegative() })
|
|
31157
31789
|
]);
|
|
31158
|
-
visualReviewConfigSchema =
|
|
31159
|
-
enabled:
|
|
31160
|
-
model:
|
|
31161
|
-
enabled:
|
|
31162
|
-
maxScreenshots:
|
|
31790
|
+
visualReviewConfigSchema = z31.object({
|
|
31791
|
+
enabled: z31.boolean().optional(),
|
|
31792
|
+
model: z31.object({
|
|
31793
|
+
enabled: z31.boolean().optional(),
|
|
31794
|
+
maxScreenshots: z31.number().int().nonnegative().optional()
|
|
31163
31795
|
}).optional(),
|
|
31164
|
-
routes:
|
|
31165
|
-
viewports:
|
|
31166
|
-
flows:
|
|
31167
|
-
name:
|
|
31168
|
-
persona:
|
|
31169
|
-
path:
|
|
31170
|
-
steps:
|
|
31796
|
+
routes: z31.array(z31.string()).optional(),
|
|
31797
|
+
viewports: z31.array(z31.enum(["desktop", "mobile", "reflow"])).optional(),
|
|
31798
|
+
flows: z31.array(z31.object({
|
|
31799
|
+
name: z31.string(),
|
|
31800
|
+
persona: z31.string(),
|
|
31801
|
+
path: z31.string().optional(),
|
|
31802
|
+
steps: z31.array(reviewFlowStepSchema)
|
|
31171
31803
|
})).optional(),
|
|
31172
|
-
componentGallery:
|
|
31173
|
-
enabled:
|
|
31174
|
-
componentSelector:
|
|
31175
|
-
canvasSelector:
|
|
31176
|
-
labelSelector:
|
|
31177
|
-
maxComponents:
|
|
31804
|
+
componentGallery: z31.object({
|
|
31805
|
+
enabled: z31.boolean().optional(),
|
|
31806
|
+
componentSelector: z31.string().optional(),
|
|
31807
|
+
canvasSelector: z31.string().optional(),
|
|
31808
|
+
labelSelector: z31.string().optional(),
|
|
31809
|
+
maxComponents: z31.number().int().nonnegative().optional()
|
|
31178
31810
|
}).optional()
|
|
31179
31811
|
});
|
|
31180
|
-
reviewConfigOverrideSchema =
|
|
31181
|
-
designSystem:
|
|
31812
|
+
reviewConfigOverrideSchema = z31.object({
|
|
31813
|
+
designSystem: z31.string().optional(),
|
|
31182
31814
|
visual: visualReviewConfigSchema.optional(),
|
|
31183
|
-
rules:
|
|
31184
|
-
elements:
|
|
31185
|
-
tokens:
|
|
31186
|
-
wcag:
|
|
31187
|
-
enabled:
|
|
31188
|
-
severity:
|
|
31815
|
+
rules: z31.object({
|
|
31816
|
+
elements: z31.record(z31.string(), z31.object({ use: z31.string(), from: z31.string() })).optional(),
|
|
31817
|
+
tokens: z31.record(z31.string(), z31.string()).optional(),
|
|
31818
|
+
wcag: z31.object({
|
|
31819
|
+
enabled: z31.boolean().optional(),
|
|
31820
|
+
severity: z31.enum(["all", "critical+serious"]).optional()
|
|
31189
31821
|
}).optional(),
|
|
31190
|
-
slop:
|
|
31191
|
-
copy:
|
|
31192
|
-
semanticTokens:
|
|
31822
|
+
slop: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31823
|
+
copy: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31824
|
+
semanticTokens: z31.object({ enabled: z31.boolean().optional() }).optional()
|
|
31193
31825
|
}).optional()
|
|
31194
31826
|
});
|
|
31195
31827
|
}
|
|
@@ -31565,7 +32197,7 @@ __export(review_tool_exports, {
|
|
|
31565
32197
|
isReviewableSourcePath: () => isReviewableSourcePath,
|
|
31566
32198
|
registerReviewTool: () => registerReviewTool
|
|
31567
32199
|
});
|
|
31568
|
-
import { z as
|
|
32200
|
+
import { z as z32 } from "zod";
|
|
31569
32201
|
function isReviewableSourcePath(path6) {
|
|
31570
32202
|
return /\.(tsx|jsx|ts|js|css)$/i.test(path6);
|
|
31571
32203
|
}
|
|
@@ -31589,11 +32221,11 @@ get-review-rules gives you the judgment rules to apply yourself. This tool is th
|
|
|
31589
32221
|
This source reviewer cannot judge rendered hierarchy or semantic color roles. After it passes, inspect the rendered dashboard and confirm title \u2192 metrics \u2192 records \u2192 interpretation, distinct action and status colors, and that a stepper begins only after a task starts.
|
|
31590
32222
|
|
|
31591
32223
|
Fix every critical and serious finding, then call it again on the fixed source. Findings feed the same loop as a human code review: read, fix, re-check.`,
|
|
31592
|
-
inputSchema:
|
|
31593
|
-
files:
|
|
31594
|
-
|
|
31595
|
-
path:
|
|
31596
|
-
content:
|
|
32224
|
+
inputSchema: z32.object({
|
|
32225
|
+
files: z32.array(
|
|
32226
|
+
z32.object({
|
|
32227
|
+
path: z32.string().trim().min(1).max(400).describe("Repo-relative file path, e.g. components/signup-form.tsx."),
|
|
32228
|
+
content: z32.string().max(MAX_FILE_CHARS).describe("The file's full source.")
|
|
31597
32229
|
})
|
|
31598
32230
|
).min(1).max(MAX_FILES).describe("The .tsx, .jsx, .ts, .js, and .css files to review, as written."),
|
|
31599
32231
|
context: CONTEXT_ARG
|
|
@@ -31654,7 +32286,7 @@ review-ui-code({ files: [{ path: "components/signup-form.tsx", content: "..." },
|
|
|
31654
32286
|
});
|
|
31655
32287
|
|
|
31656
32288
|
// ../shared/src/mcp/figma-prototype.ts
|
|
31657
|
-
import { z as
|
|
32289
|
+
import { z as z33 } from "zod";
|
|
31658
32290
|
function escapeHtml(value) {
|
|
31659
32291
|
return value.replace(/[&<>"]/g, (char2) => ESCAPES.get(char2) ?? char2);
|
|
31660
32292
|
}
|
|
@@ -31688,63 +32320,63 @@ var colorSchema, paintSchema, sizingSchema, baseSchema, prototypeNodeSchema, pro
|
|
|
31688
32320
|
var init_figma_prototype = __esm({
|
|
31689
32321
|
"../shared/src/mcp/figma-prototype.ts"() {
|
|
31690
32322
|
"use strict";
|
|
31691
|
-
colorSchema =
|
|
31692
|
-
paintSchema =
|
|
31693
|
-
sizingSchema =
|
|
31694
|
-
baseSchema =
|
|
31695
|
-
name:
|
|
31696
|
-
width:
|
|
31697
|
-
height:
|
|
31698
|
-
x:
|
|
31699
|
-
y:
|
|
31700
|
-
sizing:
|
|
31701
|
-
absolute:
|
|
31702
|
-
opacity:
|
|
32323
|
+
colorSchema = z33.object({ r: z33.number(), g: z33.number(), b: z33.number(), a: z33.number() });
|
|
32324
|
+
paintSchema = z33.object({ variable: z33.string().optional(), color: colorSchema.optional() });
|
|
32325
|
+
sizingSchema = z33.enum(["FIXED", "HUG", "FILL"]);
|
|
32326
|
+
baseSchema = z33.object({
|
|
32327
|
+
name: z33.string(),
|
|
32328
|
+
width: z33.number(),
|
|
32329
|
+
height: z33.number(),
|
|
32330
|
+
x: z33.number(),
|
|
32331
|
+
y: z33.number(),
|
|
32332
|
+
sizing: z33.object({ horizontal: sizingSchema, vertical: sizingSchema }).optional(),
|
|
32333
|
+
absolute: z33.boolean().optional(),
|
|
32334
|
+
opacity: z33.number().optional()
|
|
31703
32335
|
});
|
|
31704
|
-
prototypeNodeSchema =
|
|
32336
|
+
prototypeNodeSchema = z33.lazy(() => z33.discriminatedUnion("kind", [
|
|
31705
32337
|
baseSchema.extend({
|
|
31706
|
-
kind:
|
|
31707
|
-
auditKey:
|
|
31708
|
-
systemId:
|
|
31709
|
-
texts:
|
|
31710
|
-
mainTexts:
|
|
32338
|
+
kind: z33.literal("instance"),
|
|
32339
|
+
auditKey: z33.string(),
|
|
32340
|
+
systemId: z33.string(),
|
|
32341
|
+
texts: z33.array(z33.string()),
|
|
32342
|
+
mainTexts: z33.array(z33.string())
|
|
31711
32343
|
}),
|
|
31712
32344
|
baseSchema.extend({
|
|
31713
|
-
kind:
|
|
31714
|
-
layout:
|
|
31715
|
-
direction:
|
|
31716
|
-
gap:
|
|
31717
|
-
padding:
|
|
31718
|
-
primary:
|
|
31719
|
-
counter:
|
|
31720
|
-
wrap:
|
|
32345
|
+
kind: z33.literal("frame"),
|
|
32346
|
+
layout: z33.object({
|
|
32347
|
+
direction: z33.enum(["HORIZONTAL", "VERTICAL"]),
|
|
32348
|
+
gap: z33.number(),
|
|
32349
|
+
padding: z33.tuple([z33.number(), z33.number(), z33.number(), z33.number()]),
|
|
32350
|
+
primary: z33.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]),
|
|
32351
|
+
counter: z33.enum(["MIN", "CENTER", "MAX", "BASELINE"]),
|
|
32352
|
+
wrap: z33.boolean().optional()
|
|
31721
32353
|
}).optional(),
|
|
31722
32354
|
fill: paintSchema.optional(),
|
|
31723
|
-
stroke:
|
|
31724
|
-
radius:
|
|
31725
|
-
clips:
|
|
31726
|
-
children:
|
|
32355
|
+
stroke: z33.object({ paint: paintSchema, weight: z33.number() }).optional(),
|
|
32356
|
+
radius: z33.number().optional(),
|
|
32357
|
+
clips: z33.boolean().optional(),
|
|
32358
|
+
children: z33.array(prototypeNodeSchema)
|
|
31727
32359
|
}),
|
|
31728
32360
|
baseSchema.extend({
|
|
31729
|
-
kind:
|
|
31730
|
-
characters:
|
|
31731
|
-
fontSize:
|
|
31732
|
-
fontWeight:
|
|
31733
|
-
lineHeight:
|
|
31734
|
-
letterSpacing:
|
|
31735
|
-
fontFamily:
|
|
31736
|
-
align:
|
|
32361
|
+
kind: z33.literal("text"),
|
|
32362
|
+
characters: z33.string(),
|
|
32363
|
+
fontSize: z33.number(),
|
|
32364
|
+
fontWeight: z33.number(),
|
|
32365
|
+
lineHeight: z33.number().optional(),
|
|
32366
|
+
letterSpacing: z33.number().optional(),
|
|
32367
|
+
fontFamily: z33.string().optional(),
|
|
32368
|
+
align: z33.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]),
|
|
31737
32369
|
fill: paintSchema.optional()
|
|
31738
32370
|
}),
|
|
31739
32371
|
baseSchema.extend({
|
|
31740
|
-
kind:
|
|
31741
|
-
svg:
|
|
31742
|
-
reason:
|
|
32372
|
+
kind: z33.literal("shape"),
|
|
32373
|
+
svg: z33.string(),
|
|
32374
|
+
reason: z33.string()
|
|
31743
32375
|
})
|
|
31744
32376
|
]));
|
|
31745
|
-
prototypeRequestSchema =
|
|
31746
|
-
type:
|
|
31747
|
-
id:
|
|
32377
|
+
prototypeRequestSchema = z33.object({
|
|
32378
|
+
type: z33.literal("better-design-figma-prototype"),
|
|
32379
|
+
id: z33.string(),
|
|
31748
32380
|
frame: prototypeNodeSchema
|
|
31749
32381
|
});
|
|
31750
32382
|
ESCAPES = /* @__PURE__ */ new Map([["&", "&"], ["<", "<"], [">", ">"], ['"', """]]);
|
|
@@ -31930,7 +32562,7 @@ var init_figma_model_prompt = __esm({
|
|
|
31930
32562
|
});
|
|
31931
32563
|
|
|
31932
32564
|
// ../shared/src/mcp/figma-model.ts
|
|
31933
|
-
import { z as
|
|
32565
|
+
import { z as z34 } from "zod";
|
|
31934
32566
|
function fenced(text3, language) {
|
|
31935
32567
|
const match = text3.match(new RegExp("```" + language + "[^\\n]*\\n([\\s\\S]*?)```", "i"));
|
|
31936
32568
|
return match ? match[1] : null;
|
|
@@ -32136,8 +32768,8 @@ var init_figma_model = __esm({
|
|
|
32136
32768
|
init_figma_prototype();
|
|
32137
32769
|
init_html_kit();
|
|
32138
32770
|
init_preview_html();
|
|
32139
|
-
unplacedSchema =
|
|
32140
|
-
unplaced:
|
|
32771
|
+
unplacedSchema = z34.object({
|
|
32772
|
+
unplaced: z34.array(z34.object({ name: z34.string(), reason: z34.string() }))
|
|
32141
32773
|
});
|
|
32142
32774
|
UNSAFE_BLOCKS = /<(script|iframe|object|embed|noscript)\b[\s\S]*?(<\/\1[^>]*>|$)/gi;
|
|
32143
32775
|
UNSAFE_TAGS = /<\/?(meta|link|base|form|style)\b[^>]*(>|$)/gi;
|
|
@@ -32238,7 +32870,7 @@ var init_figma_model_handoff = __esm({
|
|
|
32238
32870
|
});
|
|
32239
32871
|
|
|
32240
32872
|
// ../shared/src/mcp/figma-model-providers.ts
|
|
32241
|
-
import { z as
|
|
32873
|
+
import { z as z35 } from "zod";
|
|
32242
32874
|
function serverLabel(url) {
|
|
32243
32875
|
try {
|
|
32244
32876
|
const parsed = new URL(url);
|
|
@@ -32331,19 +32963,19 @@ var init_figma_model_providers = __esm({
|
|
|
32331
32963
|
DEFAULT_LOCAL_MODEL = "gemma4:latest";
|
|
32332
32964
|
DEFAULT_LOCAL_BASE_URL = "http://localhost:11434/v1";
|
|
32333
32965
|
MAX_OUTPUT_TOKENS = 16e3;
|
|
32334
|
-
chatReplySchema =
|
|
32335
|
-
choices:
|
|
32336
|
-
message:
|
|
32337
|
-
finish_reason:
|
|
32966
|
+
chatReplySchema = z35.object({
|
|
32967
|
+
choices: z35.array(z35.object({
|
|
32968
|
+
message: z35.object({ content: z35.string().nullable().optional() }),
|
|
32969
|
+
finish_reason: z35.string().nullable().optional()
|
|
32338
32970
|
})).min(1),
|
|
32339
|
-
usage:
|
|
32971
|
+
usage: z35.object({ prompt_tokens: z35.number(), completion_tokens: z35.number() }).optional()
|
|
32340
32972
|
});
|
|
32341
|
-
errorCodeSchema =
|
|
32973
|
+
errorCodeSchema = z35.object({ code: z35.string() });
|
|
32342
32974
|
}
|
|
32343
32975
|
});
|
|
32344
32976
|
|
|
32345
32977
|
// ../shared/src/mcp/figma-relay.ts
|
|
32346
|
-
import { z as
|
|
32978
|
+
import { z as z36 } from "zod";
|
|
32347
32979
|
function noPluginMessage() {
|
|
32348
32980
|
return "No Figma plugin is connected. Open the Figma file, run Plugins, Development, Better Design, and leave the panel open.";
|
|
32349
32981
|
}
|
|
@@ -32357,35 +32989,35 @@ var init_figma_relay = __esm({
|
|
|
32357
32989
|
"use strict";
|
|
32358
32990
|
init_figma_prototype();
|
|
32359
32991
|
FIGMA_RELAY_PORT = 4397;
|
|
32360
|
-
relayRequestSchema =
|
|
32361
|
-
id:
|
|
32992
|
+
relayRequestSchema = z36.object({
|
|
32993
|
+
id: z36.string(),
|
|
32362
32994
|
/** A frame name as it reads in Figma. Omitted means the current selection. */
|
|
32363
|
-
frame:
|
|
32995
|
+
frame: z36.string().optional(),
|
|
32364
32996
|
/**
|
|
32365
32997
|
* `model` asks for the serialized frame and its PNG instead of a rendered
|
|
32366
32998
|
* page, so a model can rebuild a frame that holds no library component.
|
|
32367
32999
|
* Omitted means the library path, unchanged. [BD-866]
|
|
32368
33000
|
*/
|
|
32369
|
-
mode:
|
|
33001
|
+
mode: z36.enum(["library", "model"]).optional()
|
|
32370
33002
|
});
|
|
32371
|
-
relayResultSchema =
|
|
32372
|
-
id:
|
|
32373
|
-
html:
|
|
32374
|
-
error:
|
|
32375
|
-
components:
|
|
32376
|
-
report:
|
|
32377
|
-
frame:
|
|
33003
|
+
relayResultSchema = z36.object({
|
|
33004
|
+
id: z36.string(),
|
|
33005
|
+
html: z36.string().optional(),
|
|
33006
|
+
error: z36.string().optional(),
|
|
33007
|
+
components: z36.number().optional(),
|
|
33008
|
+
report: z36.array(z36.object({ name: z36.string(), reason: z36.string() })).default([]),
|
|
33009
|
+
frame: z36.string().optional(),
|
|
32378
33010
|
/** The frames the plugin found, so a missing name can say what is there. */
|
|
32379
|
-
frames:
|
|
33011
|
+
frames: z36.array(z36.string()).optional(),
|
|
32380
33012
|
/** Model mode: the serialized frame, for the model to rebuild. */
|
|
32381
33013
|
tree: prototypeNodeSchema.optional(),
|
|
32382
33014
|
/** Model mode: the frame's PNG export, base64. */
|
|
32383
|
-
png:
|
|
33015
|
+
png: z36.string().optional()
|
|
32384
33016
|
});
|
|
32385
|
-
relayHelloSchema =
|
|
32386
|
-
file:
|
|
33017
|
+
relayHelloSchema = z36.object({
|
|
33018
|
+
file: z36.string(),
|
|
32387
33019
|
/** The design system the file holds, when the plugin knows it. */
|
|
32388
|
-
system:
|
|
33020
|
+
system: z36.string().optional()
|
|
32389
33021
|
});
|
|
32390
33022
|
POLL_HOLD_MS = 25e3;
|
|
32391
33023
|
REQUEST_TIMEOUT_MS = 3e5;
|
|
@@ -32553,7 +33185,7 @@ __export(figma_prototype_tool_exports, {
|
|
|
32553
33185
|
});
|
|
32554
33186
|
import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
|
|
32555
33187
|
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
32556
|
-
import { z as
|
|
33188
|
+
import { z as z37 } from "zod";
|
|
32557
33189
|
async function ensureRelay() {
|
|
32558
33190
|
if (relay) return relay;
|
|
32559
33191
|
starting ??= startFigmaRelay().then((started) => {
|
|
@@ -32613,30 +33245,30 @@ function registerFigmaPrototypeTool(server) {
|
|
|
32613
33245
|
"yourself: the tool returns a prompt file and the frame's image, you write the answer to a file, and",
|
|
32614
33246
|
"you call this tool again with frameId and answerPath so it checks the page and reports what is missing."
|
|
32615
33247
|
].join(" "),
|
|
32616
|
-
inputSchema:
|
|
32617
|
-
frame:
|
|
32618
|
-
outputPath:
|
|
32619
|
-
mode:
|
|
33248
|
+
inputSchema: z37.object({
|
|
33249
|
+
frame: z37.string().optional().describe("The frame's name as it reads in Figma. Omit to use the current selection."),
|
|
33250
|
+
outputPath: z37.string().optional().describe("Where to write the HTML. Relative paths resolve against the working directory."),
|
|
33251
|
+
mode: z37.enum(["library", "model"]).optional().describe(
|
|
32620
33252
|
"library (the default) maps components from a Better Design library. model asks a model to rebuild a frame from any file."
|
|
32621
33253
|
),
|
|
32622
|
-
system:
|
|
32623
|
-
provider:
|
|
33254
|
+
system: z37.string().optional().describe("Model mode: the catalog design system to build with, as find-design-system names it."),
|
|
33255
|
+
provider: z37.enum(["agent", "openai-compatible"]).optional().describe(
|
|
32624
33256
|
"Model mode: agent (the default) hands the frame to you to rebuild; openai-compatible calls a local server, Ollama by default."
|
|
32625
33257
|
),
|
|
32626
|
-
model:
|
|
32627
|
-
frameId:
|
|
32628
|
-
answerPath:
|
|
33258
|
+
model: z37.string().optional().describe(`Model mode with openai-compatible: the local model id. Defaults to ${DEFAULT_LOCAL_MODEL}.`),
|
|
33259
|
+
frameId: z37.string().optional().describe("Model mode, second call: the frameId the first call returned."),
|
|
33260
|
+
answerPath: z37.string().optional().describe("Model mode, second call: the file holding your answer, as the prompt file asks.")
|
|
32629
33261
|
}),
|
|
32630
|
-
outputSchema:
|
|
32631
|
-
status:
|
|
32632
|
-
message:
|
|
32633
|
-
frameId:
|
|
32634
|
-
promptPath:
|
|
32635
|
-
imagePath:
|
|
32636
|
-
path:
|
|
32637
|
-
components:
|
|
32638
|
-
report:
|
|
32639
|
-
model:
|
|
33262
|
+
outputSchema: z37.object({
|
|
33263
|
+
status: z37.enum(["completed", "failed", "needs-answer"]),
|
|
33264
|
+
message: z37.string(),
|
|
33265
|
+
frameId: z37.string().optional(),
|
|
33266
|
+
promptPath: z37.string().optional(),
|
|
33267
|
+
imagePath: z37.string().optional(),
|
|
33268
|
+
path: z37.string().optional(),
|
|
33269
|
+
components: z37.number().optional(),
|
|
33270
|
+
report: z37.array(z37.object({ name: z37.string(), reason: z37.string() })).optional(),
|
|
33271
|
+
model: z37.string().optional()
|
|
32640
33272
|
}),
|
|
32641
33273
|
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
32642
33274
|
},
|
|
@@ -32858,6 +33490,8 @@ async function startServer() {
|
|
|
32858
33490
|
const { createHttpDataProvider: createHttpDataProvider2 } = await Promise.resolve().then(() => (init_http_data_provider(), http_data_provider_exports));
|
|
32859
33491
|
const {
|
|
32860
33492
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33493
|
+
readInspiration: readInspiration2,
|
|
33494
|
+
sendAgentFeedback: sendAgentFeedback2,
|
|
32861
33495
|
trackToolUsage: trackToolUsage2,
|
|
32862
33496
|
trackMcpInstalled: trackMcpInstalled2
|
|
32863
33497
|
} = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
@@ -32883,7 +33517,9 @@ async function startServer() {
|
|
|
32883
33517
|
name: "DesignSystems",
|
|
32884
33518
|
version: MCP_IMPLEMENTATION_VERSION2,
|
|
32885
33519
|
onToolUsage,
|
|
32886
|
-
captureDashboardBrowser: captureDashboardBrowser2
|
|
33520
|
+
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33521
|
+
readInspiration: readInspiration2,
|
|
33522
|
+
sendFeedback: sendAgentFeedback2
|
|
32887
33523
|
});
|
|
32888
33524
|
registerReviewTool2(server, onToolUsage);
|
|
32889
33525
|
registerFigmaPrototypeTool2(server);
|