better-design 0.33.0 → 0.35.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 +709 -435
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1252,6 +1252,7 @@ var init_tool_names = __esm({
|
|
|
1252
1252
|
"review-dashboard-structure",
|
|
1253
1253
|
"review-illustration",
|
|
1254
1254
|
"review-ui-code",
|
|
1255
|
+
"send-feedback",
|
|
1255
1256
|
"set-design-config",
|
|
1256
1257
|
"update-feature-map"
|
|
1257
1258
|
];
|
|
@@ -1290,6 +1291,7 @@ var init_tool_names = __esm({
|
|
|
1290
1291
|
"review-dashboard-structure",
|
|
1291
1292
|
"review-illustration",
|
|
1292
1293
|
"review-ui-code",
|
|
1294
|
+
"send-feedback",
|
|
1293
1295
|
"set-design-config",
|
|
1294
1296
|
"update-feature-map"
|
|
1295
1297
|
]);
|
|
@@ -1389,9 +1391,9 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1389
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.",
|
|
1390
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.",
|
|
1391
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.",
|
|
1392
|
-
"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.",
|
|
1393
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.",
|
|
1394
|
-
"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.',
|
|
1395
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.",
|
|
1396
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.",
|
|
1397
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.`,
|
|
@@ -1401,7 +1403,7 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1401
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.",
|
|
1402
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.",
|
|
1403
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.',
|
|
1404
|
-
"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.",
|
|
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 the user picked, `userConfirmedCustom` records that the user chose fully custom, and `brandCss` builds from an existing brand token sheet, such as the output of extract-design-system. With none of the three, it generates nothing and returns the top catalog matches to show the user. 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.",
|
|
1405
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.",
|
|
1406
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.",
|
|
1407
1409
|
"get-design-config": "Returns the design config saved on a generated project: exact color tokens, radius, fonts and icon library, including values the user tuned on the project page.",
|
|
@@ -1410,12 +1412,13 @@ Better Design provides design systems, design principles and UI review checks fo
|
|
|
1410
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.",
|
|
1411
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.",
|
|
1412
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.",
|
|
1413
|
-
"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.",
|
|
1414
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`.",
|
|
1415
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.",
|
|
1416
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.",
|
|
1417
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.",
|
|
1418
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.",
|
|
1419
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."
|
|
1420
1423
|
};
|
|
1421
1424
|
}
|
|
@@ -4275,12 +4278,15 @@ var init_review_rules_description = __esm({
|
|
|
4275
4278
|
"content",
|
|
4276
4279
|
"comprehension",
|
|
4277
4280
|
"ai-features",
|
|
4281
|
+
"coverage-gaps",
|
|
4278
4282
|
"all"
|
|
4279
4283
|
];
|
|
4280
4284
|
REVIEW_RULES_DESCRIPTION = `Retrieves accessibility (WCAG 2.1), visual design, animation, content, comprehension, and AI feature review rules for analyzing code.
|
|
4281
4285
|
|
|
4282
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.
|
|
4283
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
|
+
|
|
4284
4290
|
${REDESIGN_SCOPE_CONTRACT}
|
|
4285
4291
|
|
|
4286
4292
|
${DESIGN_TRADEOFF_RULE}
|
|
@@ -4323,6 +4329,8 @@ ${DESIGN_TRADEOFF_RULE}
|
|
|
4323
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
|
|
4324
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
|
|
4325
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
|
+
|
|
4326
4334
|
Use maxTokens to limit rule content (recommended: 5000\u201310000). Required workflow guidance is always returned in full.`;
|
|
4327
4335
|
}
|
|
4328
4336
|
});
|
|
@@ -5161,6 +5169,22 @@ These rules were read against source. They cannot see layout. Three checks remai
|
|
|
5161
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.
|
|
5162
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.
|
|
5163
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
|
+
|
|
5164
5188
|
Fix every critical and serious finding before you present.`;
|
|
5165
5189
|
}
|
|
5166
5190
|
});
|
|
@@ -5184,6 +5208,8 @@ function looksLikeCard(candidate) {
|
|
|
5184
5208
|
}
|
|
5185
5209
|
function redactSecrets(value) {
|
|
5186
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]");
|
|
5187
5213
|
for (const pattern of SECRET_PATTERNS) out = out.replace(pattern, "[redacted]");
|
|
5188
5214
|
out = out.replace(EMAIL_PATTERN, "[redacted-email]");
|
|
5189
5215
|
out = out.replace(
|
|
@@ -5202,7 +5228,7 @@ function redactUrl(raw) {
|
|
|
5202
5228
|
return "[redacted]";
|
|
5203
5229
|
}
|
|
5204
5230
|
}
|
|
5205
|
-
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;
|
|
5206
5232
|
var init_redact = __esm({
|
|
5207
5233
|
"../shared/src/redact.ts"() {
|
|
5208
5234
|
"use strict";
|
|
@@ -5218,9 +5244,9 @@ var init_redact = __esm({
|
|
|
5218
5244
|
/\bwhsec_[A-Za-z0-9]{16,}/g,
|
|
5219
5245
|
/\bAIza[A-Za-z0-9_-]{30,}/g,
|
|
5220
5246
|
/\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
|
|
5221
|
-
/\
|
|
5222
|
-
|
|
5223
|
-
|
|
5247
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g
|
|
5248
|
+
];
|
|
5249
|
+
CREDENTIAL_VALUE_PATTERNS = [
|
|
5224
5250
|
// key=value forms for the names that carry credentials. A quoted value is
|
|
5225
5251
|
// taken whole, so a passphrase with spaces and a short password both go:
|
|
5226
5252
|
// the old form needed eight characters and no spaces, which let
|
|
@@ -5228,8 +5254,13 @@ var init_redact = __esm({
|
|
|
5228
5254
|
// through into a corpus held for 180 days. [BD-697]
|
|
5229
5255
|
/(?:\b|_)(?:api[_-]?key|access[_-]?token|refresh[_-]?token|secret(?:[_-]?key)?|password|passwd|authorization)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[A-Za-z0-9._~+/-]+)/gi,
|
|
5230
5256
|
// Authorization headers separate the scheme from the token with a space.
|
|
5231
|
-
/\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
|
|
5232
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;
|
|
5233
5264
|
EMAIL_PATTERN = /(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
5234
5265
|
CARD_CANDIDATE_PATTERN = /\b(?:\d[ -]?){12,18}\d\b/g;
|
|
5235
5266
|
PHONE_PATTERNS = [
|
|
@@ -5363,7 +5394,7 @@ var init_extract = __esm({
|
|
|
5363
5394
|
});
|
|
5364
5395
|
|
|
5365
5396
|
// ../shared/src/mcp/comprehension/lexicon.ts
|
|
5366
|
-
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;
|
|
5367
5398
|
var init_lexicon = __esm({
|
|
5368
5399
|
"../shared/src/mcp/comprehension/lexicon.ts"() {
|
|
5369
5400
|
"use strict";
|
|
@@ -5472,6 +5503,9 @@ var init_lexicon = __esm({
|
|
|
5472
5503
|
MAX_SCREEN_WORDS = 60;
|
|
5473
5504
|
MAX_ACTIONS = 3;
|
|
5474
5505
|
EXPLAIN_BUDGET_SECONDS = 15;
|
|
5506
|
+
MAX_SLIDE_WORDS = 40;
|
|
5507
|
+
MAX_SLIDE_LINES = 5;
|
|
5508
|
+
SLIDE_NAME = /\b(slides?|deck|presentation)\b/i;
|
|
5475
5509
|
}
|
|
5476
5510
|
});
|
|
5477
5511
|
|
|
@@ -5546,7 +5580,7 @@ function estimateExplainSeconds(words2, insiderTermCount, actionCount) {
|
|
|
5546
5580
|
const choosing = Math.max(0, actionCount - 1) * 3;
|
|
5547
5581
|
return Math.round(speaking + defining + choosing);
|
|
5548
5582
|
}
|
|
5549
|
-
function fifteenSecondFindings(words2, actions, seconds) {
|
|
5583
|
+
function fifteenSecondFindings(words2, actions, seconds, surface) {
|
|
5550
5584
|
const findings = [];
|
|
5551
5585
|
if (seconds > EXPLAIN_BUDGET_SECONDS) {
|
|
5552
5586
|
findings.push({
|
|
@@ -5557,7 +5591,7 @@ function fifteenSecondFindings(words2, actions, seconds) {
|
|
|
5557
5591
|
fix: "Cut to one idea and one next step. Move the rest behind a link or a later screen."
|
|
5558
5592
|
});
|
|
5559
5593
|
}
|
|
5560
|
-
if (words2 > MAX_SCREEN_WORDS) {
|
|
5594
|
+
if (surface === "screen" && words2 > MAX_SCREEN_WORDS) {
|
|
5561
5595
|
findings.push({
|
|
5562
5596
|
test: "fifteen-second",
|
|
5563
5597
|
severity: "serious",
|
|
@@ -5618,7 +5652,30 @@ function screenshotFindings(copy, words2) {
|
|
|
5618
5652
|
}
|
|
5619
5653
|
return findings;
|
|
5620
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
|
+
}
|
|
5621
5677
|
function analyzeComprehension(input) {
|
|
5678
|
+
const surface = input.surface ?? (SLIDE_NAME.test(input.screen) ? "slide" : "screen");
|
|
5622
5679
|
const copy = resolveCopy(input);
|
|
5623
5680
|
const known = new Set((input.knownTerms ?? []).map((term) => term.trim().toLowerCase()));
|
|
5624
5681
|
const visible = [copy.headline ?? "", ...copy.body, ...copy.actions].join(". ");
|
|
@@ -5627,13 +5684,16 @@ function analyzeComprehension(input) {
|
|
|
5627
5684
|
const estimatedExplainSeconds = estimateExplainSeconds(words2, insiderTerms.length, copy.actions.length);
|
|
5628
5685
|
const findings = [
|
|
5629
5686
|
...momTestFindings(visible, known, insiderTerms),
|
|
5630
|
-
...fifteenSecondFindings(words2, copy.actions, estimatedExplainSeconds),
|
|
5631
|
-
...screenshotFindings(copy, words2)
|
|
5687
|
+
...fifteenSecondFindings(words2, copy.actions, estimatedExplainSeconds, surface),
|
|
5688
|
+
...screenshotFindings(copy, words2),
|
|
5689
|
+
...surface === "slide" ? slideFindings(words2, copy.body) : []
|
|
5632
5690
|
];
|
|
5691
|
+
const blocking = findings.some((finding3) => finding3.severity !== "minor");
|
|
5633
5692
|
const longestSentence = sentences(visible).sort((a, b) => countWords(b) - countWords(a))[0] ?? "";
|
|
5634
5693
|
return {
|
|
5635
5694
|
screen: input.screen,
|
|
5636
|
-
|
|
5695
|
+
surface,
|
|
5696
|
+
verdict: blocking ? "revise" : surface === "slide" ? "needs-screenshot" : "pass",
|
|
5637
5697
|
findings,
|
|
5638
5698
|
copy: { headline: copy.headline, body: copy.body, actions: copy.actions },
|
|
5639
5699
|
stats: {
|
|
@@ -5677,7 +5737,8 @@ function formatStats(report) {
|
|
|
5677
5737
|
"",
|
|
5678
5738
|
`- Visible copy: ${words2} words across ${actionCount} action${actionCount === 1 ? "" : "s"}.`,
|
|
5679
5739
|
`- Insider words found: ${insiderTerms.length > 0 ? insiderTerms.join(", ") : "none"}.`,
|
|
5680
|
-
`- 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."
|
|
5681
5742
|
].join("\n");
|
|
5682
5743
|
}
|
|
5683
5744
|
function formatComprehensionReport(report) {
|
|
@@ -5685,8 +5746,11 @@ function formatComprehensionReport(report) {
|
|
|
5685
5746
|
(totals, finding3) => ({ ...totals, [finding3.severity]: totals[finding3.severity] + 1 }),
|
|
5686
5747
|
{ critical: 0, serious: 0, minor: 0 }
|
|
5687
5748
|
);
|
|
5688
|
-
const verdict = report.verdict === "
|
|
5689
|
-
|
|
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")] : [];
|
|
5690
5754
|
return [
|
|
5691
5755
|
`# Comprehension check: ${report.screen}`,
|
|
5692
5756
|
"",
|
|
@@ -5697,6 +5761,7 @@ function formatComprehensionReport(report) {
|
|
|
5697
5761
|
formatTestSection(report, "fifteen-second"),
|
|
5698
5762
|
"",
|
|
5699
5763
|
formatTestSection(report, "screenshot"),
|
|
5764
|
+
...slideSection,
|
|
5700
5765
|
"",
|
|
5701
5766
|
formatStats(report),
|
|
5702
5767
|
"",
|
|
@@ -5705,16 +5770,27 @@ function formatComprehensionReport(report) {
|
|
|
5705
5770
|
next
|
|
5706
5771
|
].join("\n");
|
|
5707
5772
|
}
|
|
5708
|
-
var TEST_TITLES, SEVERITY_ORDER;
|
|
5773
|
+
var TEST_TITLES, SEVERITY_ORDER, VERDICTS;
|
|
5709
5774
|
var init_format = __esm({
|
|
5710
5775
|
"../shared/src/mcp/comprehension/format.ts"() {
|
|
5711
5776
|
"use strict";
|
|
5712
5777
|
TEST_TITLES = {
|
|
5713
5778
|
mom: "Mom test: would someone outside the team understand this?",
|
|
5714
5779
|
"fifteen-second": "15 second test: can you explain the screen in 15 seconds?",
|
|
5715
|
-
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?"
|
|
5716
5782
|
};
|
|
5717
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
|
+
};
|
|
5718
5794
|
}
|
|
5719
5795
|
});
|
|
5720
5796
|
|
|
@@ -5739,6 +5815,8 @@ function registerComprehensionTools({ server, provider, track, userId }) {
|
|
|
5739
5815
|
|
|
5740
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.
|
|
5741
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
|
+
|
|
5742
5820
|
Three tests, run on the screen's own words:
|
|
5743
5821
|
1. Mom test. Would a person outside the team understand this? Flags insider vocabulary, unexplained acronyms and sentences too long to follow.
|
|
5744
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.
|
|
@@ -5748,6 +5826,8 @@ Pass either the copy or the markup:
|
|
|
5748
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"] })
|
|
5749
5827
|
- check-comprehension({ screen: "Billing page", code: "<section>\u2026the JSX you wrote\u2026</section>" })
|
|
5750
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
|
+
|
|
5751
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.`,
|
|
5752
5832
|
inputSchema: z9.object({
|
|
5753
5833
|
screen: z9.string().trim().min(1).max(120).describe("What this screen is, e.g. 'lesson end card', 'billing page'."),
|
|
@@ -5756,6 +5836,7 @@ Use knownTerms for words your audience genuinely knows, so the check stops flagg
|
|
|
5756
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']."),
|
|
5757
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."),
|
|
5758
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.'),
|
|
5759
5840
|
context: CONTEXT_ARG
|
|
5760
5841
|
}),
|
|
5761
5842
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -13885,6 +13966,32 @@ Examples:
|
|
|
13885
13966
|
- "fintech startup" \u2192 Stripe design system
|
|
13886
13967
|
- "playful education app" \u2192 Duolingo design system
|
|
13887
13968
|
- "minimal developer tools" \u2192 Linear design system`;
|
|
13969
|
+
const formatCatalogMatches = (results) => results.map(
|
|
13970
|
+
(r) => [
|
|
13971
|
+
// Markdown link so the NAME is clickable in the host (Claude Code,
|
|
13972
|
+
// ChatGPT, claude.ai all render markdown links). Keep it intact when
|
|
13973
|
+
// presenting so the user can open the preview in one click.
|
|
13974
|
+
`### [${r.title}](${APP_BASE_URL}/design-systems/${r.id})`,
|
|
13975
|
+
r.description,
|
|
13976
|
+
`_${r.personality.join(", ")}_ \xB7 id: \`${r.id}\``
|
|
13977
|
+
].join("\n")
|
|
13978
|
+
).join("\n\n");
|
|
13979
|
+
const askForBaseChoice = async (prompt) => {
|
|
13980
|
+
const heading = "# Choose a starting point first\n\nNothing was generated.";
|
|
13981
|
+
try {
|
|
13982
|
+
const results = await provider.searchDesignSystems(prompt, 4);
|
|
13983
|
+
if (results.length === 0) {
|
|
13984
|
+
return `${heading} No catalog system matched this prompt. Ask the user whether to go fully custom, then call create-design-system again with \`userConfirmedCustom: true\`.`;
|
|
13985
|
+
}
|
|
13986
|
+
return `${heading} Show the user these options, keeping each name as its clickable link, and ask which to start from or whether to go fully custom. Then call create-design-system again with the chosen \`id\` as \`baseDesignSystemId\`, or with \`userConfirmedCustom: true\` if the user chose fully custom. Do not pick for the user.
|
|
13987
|
+
|
|
13988
|
+
---
|
|
13989
|
+
|
|
13990
|
+
${formatCatalogMatches(results)}`;
|
|
13991
|
+
} catch {
|
|
13992
|
+
return `${heading} The catalog search failed, so call \`find-design-system\` with the app description, show the user the top matches, and ask which to start from or whether to go fully custom.`;
|
|
13993
|
+
}
|
|
13994
|
+
};
|
|
13888
13995
|
const makeSearchHandler = (toolName, includeBuildRules = false) => async ({ query }) => {
|
|
13889
13996
|
const start = Date.now();
|
|
13890
13997
|
const finish = (response, topResults, isError = false, errorMessage) => {
|
|
@@ -13912,27 +14019,20 @@ ${includeBuildRules ? BUILD_TARGET_RULE : DESIGN_TRADEOFF_RULE}`
|
|
|
13912
14019
|
topResults
|
|
13913
14020
|
);
|
|
13914
14021
|
}
|
|
13915
|
-
const
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
// ChatGPT, claude.ai all render markdown links). Keep it intact when
|
|
13919
|
-
// presenting so the user can open the preview in one click.
|
|
13920
|
-
`### [${r.title}](${APP_BASE_URL}/design-systems/${r.id})`,
|
|
13921
|
-
`${r.description}`,
|
|
13922
|
-
`_${r.personality.join(", ")}_ \xB7 id: \`${r.id}\``
|
|
13923
|
-
].join("\n")
|
|
13924
|
-
).join("\n\n");
|
|
14022
|
+
const productPagePointer = COMMERCIAL_QUERY.test(query) ? `
|
|
14023
|
+
|
|
14024
|
+
${PRODUCT_PAGE_POINTER}` : "";
|
|
13925
14025
|
return finish(
|
|
13926
14026
|
{
|
|
13927
14027
|
content: [{
|
|
13928
14028
|
type: "text",
|
|
13929
14029
|
text: `# Catalog matches
|
|
13930
14030
|
|
|
13931
|
-
Keep any already selected design system and its assigned icons; do not reopen that choice unless requested. If none is selected, present these as options, keeping each name as its clickable markdown link (no scores) so they can open the preview. Explain each option's expected buyer fit and trade-offs using known project context; do not claim conversion effects without evidence. Recommend by buyer, never by the product's material or subject (a steel tool does not need a steel-dark UI). For ecommerce and DTC product pages, default to light, warm, photography-led systems unless the brand already owns a dark identity. Ask which to start their brand from, or to go fully custom, then pass the chosen \`id\` as \`baseDesignSystemId\` to create-design-system
|
|
14031
|
+
Keep any already selected design system and its assigned icons; do not reopen that choice unless requested. If none is selected, present these as options, keeping each name as its clickable markdown link (no scores) so they can open the preview. Explain each option's expected buyer fit and trade-offs using known project context; do not claim conversion effects without evidence. Recommend by buyer, never by the product's material or subject (a steel tool does not need a steel-dark UI). For ecommerce and DTC product pages, default to light, warm, photography-led systems unless the brand already owns a dark identity. Ask which to start their brand from, or to go fully custom, then pass the chosen \`id\` as \`baseDesignSystemId\` to create-design-system, or pass \`userConfirmedCustom: true\` if they choose fully custom.${productPagePointer}
|
|
13932
14032
|
|
|
13933
14033
|
---
|
|
13934
14034
|
|
|
13935
|
-
${
|
|
14035
|
+
${formatCatalogMatches(results)}
|
|
13936
14036
|
|
|
13937
14037
|
---
|
|
13938
14038
|
|
|
@@ -14624,7 +14724,7 @@ _${installNote}_`,
|
|
|
14624
14724
|
|
|
14625
14725
|
${DESIGN_TRADEOFF_RULE}
|
|
14626
14726
|
|
|
14627
|
-
**Use this for a new custom brand, even when the user never mentions design or branding** (a design system IS the app's brand). Keep an already selected system and its assigned icons unless the user requests a different direction; a redesign alone does not require regenerating the brand. When no system is selected, first call \`find-design-system\` with the app description, show the user the top ~4 matches, and ask which to start from (passed here as \`baseDesignSystemId\`) or whether to go fully custom. Then derive the \`prompt\` from the app (name, domain, vibe), generate, show it with \`get-design-system-status\`, and build with that brand. Also use it when the user explicitly wants a **custom** design system instead of installing an existing one via \`get-design-system-kit\`.
|
|
14727
|
+
**Use this for a new custom brand, even when the user never mentions design or branding** (a design system IS the app's brand). Keep an already selected system and its assigned icons unless the user requests a different direction; a redesign alone does not require regenerating the brand. When no system is selected, first call \`find-design-system\` with the app description, show the user the top ~4 matches, and ask which to start from (passed here as \`baseDesignSystemId\`) or whether to go fully custom (passed here as \`userConfirmedCustom: true\`). A call with neither, and no \`brandCss\`, generates nothing: it returns those matches for you to show the user. Then derive the \`prompt\` from the app (name, domain, vibe), generate, show it with \`get-design-system-status\`, and build with that brand. Also use it when the user explicitly wants a **custom** design system instead of installing an existing one via \`get-design-system-kit\`.
|
|
14628
14728
|
|
|
14629
14729
|
**When the user has an existing brand** (their site, Figma file, or stylesheet): run \`extract-design-system\` or \`extract-from-url\` first and pass the resulting token sheet (the fenced \`\`\`css block, verbatim: the \`:root { ... }\` and optional \`.dark { ... }\` blocks) as \`brandCss\`. The generation is then pinned to that brand: no catalog aesthetic is retrieved, the sheet's colors, fonts and radii override everything, and later design-system switches are refused. Copy the block exactly; do not retype or trim it.
|
|
14630
14730
|
|
|
@@ -14637,6 +14737,7 @@ Share the project URL with the user right away so they can watch the generation
|
|
|
14637
14737
|
inputSchema: z14.object({
|
|
14638
14738
|
prompt: z14.string().min(1).max(1e4).describe("Describe the design system the user wants. E.g. 'A warm, minimal design system for a personal finance app, soft pastels, rounded corners' or 'A brutalist developer tool aesthetic with sharp edges and monospace type'."),
|
|
14639
14739
|
baseDesignSystemId: z14.string().optional().describe("Optional. The catalog id the USER picked from the `find-design-system` matches you showed them, used to bias generation toward that aesthetic (e.g. a theme tweak on top of Linear). Leave empty only when the user chose fully custom."),
|
|
14740
|
+
userConfirmedCustom: z14.boolean().optional().describe("Set true only when the user saw the find-design-system matches and chose fully custom. Without it, baseDesignSystemId or brandCss, the tool returns the matches and generates nothing."),
|
|
14640
14741
|
brandCss: z14.string().max(5e4).optional().describe("Optional. The user's extracted brand token sheet, verbatim: the fenced css block from extract-design-system / extract-from-url (`:root { --background: ...; }` plus optional `.dark { ... }`). When set, generation builds FROM this brand instead of a catalog look-alike, and baseDesignSystemId is ignored (a catalog base would leak its identity past the brand tokens).")
|
|
14641
14742
|
}),
|
|
14642
14743
|
// Each call starts a new generation and returns a new project id, so it
|
|
@@ -14650,7 +14751,10 @@ Share the project URL with the user right away so they can watch the generation
|
|
|
14650
14751
|
openWorldHint: true
|
|
14651
14752
|
}
|
|
14652
14753
|
},
|
|
14653
|
-
async ({ prompt, baseDesignSystemId, brandCss }) => tracked("create-design-system", { query: prompt, hasBase: Boolean(baseDesignSystemId), hasBrand: Boolean(brandCss) }, async () => {
|
|
14754
|
+
async ({ prompt, baseDesignSystemId, brandCss, userConfirmedCustom }) => tracked("create-design-system", { query: prompt, hasBase: Boolean(baseDesignSystemId), hasBrand: Boolean(brandCss), userConfirmedCustom: Boolean(userConfirmedCustom) }, async () => {
|
|
14755
|
+
if (!baseDesignSystemId && !brandCss && !userConfirmedCustom) {
|
|
14756
|
+
return { content: [{ type: "text", text: await askForBaseChoice(prompt) }] };
|
|
14757
|
+
}
|
|
14654
14758
|
const startUrl = `${APP_BASE_URL}/api/projects/anonymous`;
|
|
14655
14759
|
let projectId;
|
|
14656
14760
|
const requestHeaders = { "Content-Type": "application/json" };
|
|
@@ -14890,7 +14994,7 @@ Check again in ~30 seconds. Watch it live on the project page: ${projectUrl}` }]
|
|
|
14890
14994
|
})
|
|
14891
14995
|
);
|
|
14892
14996
|
}
|
|
14893
|
-
var SHADCN_INIT_IF_MISSING, SAFE_COMPONENT_NAME_RE, DS_INDEX_SCHEMA;
|
|
14997
|
+
var SHADCN_INIT_IF_MISSING, SAFE_COMPONENT_NAME_RE, DS_INDEX_SCHEMA, COMMERCIAL_QUERY, PRODUCT_PAGE_POINTER;
|
|
14894
14998
|
var init_design_system_tools = __esm({
|
|
14895
14999
|
"../shared/src/mcp/tools/design-system-tools.ts"() {
|
|
14896
15000
|
"use strict";
|
|
@@ -14914,6 +15018,8 @@ var init_design_system_tools = __esm({
|
|
|
14914
15018
|
DS_INDEX_SCHEMA = z14.object({
|
|
14915
15019
|
baseComponents: z14.array(z14.object({ name: z14.string() })).optional()
|
|
14916
15020
|
});
|
|
15021
|
+
COMMERCIAL_QUERY = /\b(product pages?|pdp|shops?|(?<!app )stores?|storefront|e-?commerce|webshop|dtc|d2c|shopify|checkout|buy box)\b|[$€£]\s?\d/i;
|
|
15022
|
+
PRODUCT_PAGE_POINTER = 'This looks like a product page or a store. Before you build, call `get-ui-principle({ topic: "product-pages" })` for section order by buyer objection, the squint test and buyer-matched tone. For a catalog or checkout, also call `get-ux-principle` with topic `commerce-catalog` or `commerce-checkout`.';
|
|
14917
15023
|
}
|
|
14918
15024
|
});
|
|
14919
15025
|
|
|
@@ -22989,7 +23095,7 @@ function columnToSchema(column, factory) {
|
|
|
22989
23095
|
}
|
|
22990
23096
|
return schema;
|
|
22991
23097
|
}
|
|
22992
|
-
function numberColumnToSchema(column,
|
|
23098
|
+
function numberColumnToSchema(column, z38, coerce) {
|
|
22993
23099
|
let unsigned = column.getSQLType().includes("unsigned");
|
|
22994
23100
|
let min;
|
|
22995
23101
|
let max;
|
|
@@ -23057,20 +23163,20 @@ function numberColumnToSchema(column, z37, coerce) {
|
|
|
23057
23163
|
min = Number.MIN_SAFE_INTEGER;
|
|
23058
23164
|
max = Number.MAX_SAFE_INTEGER;
|
|
23059
23165
|
}
|
|
23060
|
-
let schema = coerce === true || coerce?.number ?
|
|
23166
|
+
let schema = coerce === true || coerce?.number ? z38.coerce.number() : z38.number();
|
|
23061
23167
|
schema = schema.min(min).max(max);
|
|
23062
23168
|
return integer2 ? schema.int() : schema;
|
|
23063
23169
|
}
|
|
23064
|
-
function bigintColumnToSchema(column,
|
|
23170
|
+
function bigintColumnToSchema(column, z38, coerce) {
|
|
23065
23171
|
const unsigned = column.getSQLType().includes("unsigned");
|
|
23066
23172
|
const min = unsigned ? 0n : CONSTANTS.INT64_MIN;
|
|
23067
23173
|
const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX;
|
|
23068
|
-
const schema = coerce === true || coerce?.bigint ?
|
|
23174
|
+
const schema = coerce === true || coerce?.bigint ? z38.coerce.bigint() : z38.bigint();
|
|
23069
23175
|
return schema.min(min).max(max);
|
|
23070
23176
|
}
|
|
23071
|
-
function stringColumnToSchema(column,
|
|
23177
|
+
function stringColumnToSchema(column, z38, coerce) {
|
|
23072
23178
|
if (isColumnType(column, ["PgUUID"])) {
|
|
23073
|
-
return
|
|
23179
|
+
return z38.string().uuid();
|
|
23074
23180
|
}
|
|
23075
23181
|
let max;
|
|
23076
23182
|
let regex;
|
|
@@ -23102,7 +23208,7 @@ function stringColumnToSchema(column, z37, coerce) {
|
|
|
23102
23208
|
regex = /^[01]+$/;
|
|
23103
23209
|
max = column.dimensions;
|
|
23104
23210
|
}
|
|
23105
|
-
let schema = coerce === true || coerce?.string ?
|
|
23211
|
+
let schema = coerce === true || coerce?.string ? z38.coerce.string() : z38.string();
|
|
23106
23212
|
schema = regex ? schema.regex(regex) : schema;
|
|
23107
23213
|
return max && fixed ? schema.length(max) : max ? schema.max(max) : schema;
|
|
23108
23214
|
}
|
|
@@ -23198,15 +23304,74 @@ var init_drizzle_zod = __esm({
|
|
|
23198
23304
|
}
|
|
23199
23305
|
});
|
|
23200
23306
|
|
|
23201
|
-
// ../shared/src/
|
|
23307
|
+
// ../shared/src/agent-feedback.ts
|
|
23202
23308
|
import { z as z16 } from "zod";
|
|
23203
|
-
var
|
|
23309
|
+
var agentFeedbackKindValues, TEXT_MAX, SURFACE_MAX, SUMMARY_MAX, AGENT_FEEDBACK_CLIENT_MAX, FIELD_DESCRIPTIONS, optionalText, agentFeedbackInputSchema, AGENT_FEEDBACK_CONTRACT;
|
|
23310
|
+
var init_agent_feedback = __esm({
|
|
23311
|
+
"../shared/src/agent-feedback.ts"() {
|
|
23312
|
+
"use strict";
|
|
23313
|
+
agentFeedbackKindValues = [
|
|
23314
|
+
"bug",
|
|
23315
|
+
"missing-feature",
|
|
23316
|
+
"wrong-output",
|
|
23317
|
+
"docs-gap",
|
|
23318
|
+
"other"
|
|
23319
|
+
];
|
|
23320
|
+
TEXT_MAX = 4e3;
|
|
23321
|
+
SURFACE_MAX = 200;
|
|
23322
|
+
SUMMARY_MAX = 500;
|
|
23323
|
+
AGENT_FEEDBACK_CLIENT_MAX = 100;
|
|
23324
|
+
FIELD_DESCRIPTIONS = {
|
|
23325
|
+
kind: "What kind of problem this is.",
|
|
23326
|
+
surface: "The endpoint path or MCP tool name you called.",
|
|
23327
|
+
summary: "One sentence: what is wrong or missing.",
|
|
23328
|
+
attempted: "What you tried to do, and the request you sent.",
|
|
23329
|
+
expected: "What you expected to get back.",
|
|
23330
|
+
actual: "What you got back instead.",
|
|
23331
|
+
client: "Your agent name and version."
|
|
23332
|
+
};
|
|
23333
|
+
optionalText = (description) => z16.string().trim().max(TEXT_MAX).optional().describe(description);
|
|
23334
|
+
agentFeedbackInputSchema = z16.object({
|
|
23335
|
+
kind: z16.enum(agentFeedbackKindValues).describe(FIELD_DESCRIPTIONS.kind),
|
|
23336
|
+
surface: z16.string().trim().min(1).max(SURFACE_MAX).describe(FIELD_DESCRIPTIONS.surface),
|
|
23337
|
+
summary: z16.string().trim().min(1).max(SUMMARY_MAX).describe(FIELD_DESCRIPTIONS.summary),
|
|
23338
|
+
attempted: optionalText(FIELD_DESCRIPTIONS.attempted),
|
|
23339
|
+
expected: optionalText(FIELD_DESCRIPTIONS.expected),
|
|
23340
|
+
actual: optionalText(FIELD_DESCRIPTIONS.actual),
|
|
23341
|
+
client: z16.string().trim().max(AGENT_FEEDBACK_CLIENT_MAX).optional().describe(FIELD_DESCRIPTIONS.client)
|
|
23342
|
+
});
|
|
23343
|
+
AGENT_FEEDBACK_CONTRACT = {
|
|
23344
|
+
method: "POST",
|
|
23345
|
+
path: "/api/v1/feedback",
|
|
23346
|
+
purpose: "Tell Better Design about a bug or a missing feature you hit while calling this API. A person reads every report.",
|
|
23347
|
+
fields: {
|
|
23348
|
+
kind: { required: true, description: FIELD_DESCRIPTIONS.kind, oneOf: agentFeedbackKindValues },
|
|
23349
|
+
surface: { required: true, description: FIELD_DESCRIPTIONS.surface, maxLength: SURFACE_MAX },
|
|
23350
|
+
summary: { required: true, description: FIELD_DESCRIPTIONS.summary, maxLength: SUMMARY_MAX },
|
|
23351
|
+
attempted: { required: false, description: FIELD_DESCRIPTIONS.attempted, maxLength: TEXT_MAX },
|
|
23352
|
+
expected: { required: false, description: FIELD_DESCRIPTIONS.expected, maxLength: TEXT_MAX },
|
|
23353
|
+
actual: { required: false, description: FIELD_DESCRIPTIONS.actual, maxLength: TEXT_MAX },
|
|
23354
|
+
client: { required: false, description: FIELD_DESCRIPTIONS.client, maxLength: AGENT_FEEDBACK_CLIENT_MAX }
|
|
23355
|
+
},
|
|
23356
|
+
notes: [
|
|
23357
|
+
"Send it with your Better Design API key: Authorization: Bearer <key>.",
|
|
23358
|
+
"Do not include secrets or personal data. Known secret shapes are removed before storage.",
|
|
23359
|
+
"Sending feedback never spends credit."
|
|
23360
|
+
]
|
|
23361
|
+
};
|
|
23362
|
+
}
|
|
23363
|
+
});
|
|
23364
|
+
|
|
23365
|
+
// ../shared/src/schema.ts
|
|
23366
|
+
import { z as z17 } from "zod";
|
|
23367
|
+
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;
|
|
23204
23368
|
var init_schema2 = __esm({
|
|
23205
23369
|
"../shared/src/schema.ts"() {
|
|
23206
23370
|
"use strict";
|
|
23207
23371
|
init_pg_core();
|
|
23208
23372
|
init_drizzle_orm();
|
|
23209
23373
|
init_drizzle_zod();
|
|
23374
|
+
init_agent_feedback();
|
|
23210
23375
|
vector2 = customType({
|
|
23211
23376
|
dataType() {
|
|
23212
23377
|
return "vector(768)";
|
|
@@ -23234,7 +23399,7 @@ var init_schema2 = __esm({
|
|
|
23234
23399
|
"skip",
|
|
23235
23400
|
"legacy"
|
|
23236
23401
|
];
|
|
23237
|
-
onboardingPathSchema =
|
|
23402
|
+
onboardingPathSchema = z17.enum(onboardingPathValues);
|
|
23238
23403
|
onboardingPathSqlValues = sql.raw(
|
|
23239
23404
|
onboardingPathValues.map((path6) => `'${path6}'`).join(", ")
|
|
23240
23405
|
);
|
|
@@ -23257,10 +23422,10 @@ var init_schema2 = __esm({
|
|
|
23257
23422
|
"not_improved",
|
|
23258
23423
|
"inconclusive"
|
|
23259
23424
|
];
|
|
23260
|
-
mcpIssueImprovementStatusSchema =
|
|
23425
|
+
mcpIssueImprovementStatusSchema = z17.enum(
|
|
23261
23426
|
mcpIssueImprovementStatusValues
|
|
23262
23427
|
);
|
|
23263
|
-
mcpIssueImprovementVerdictSchema =
|
|
23428
|
+
mcpIssueImprovementVerdictSchema = z17.enum(
|
|
23264
23429
|
mcpIssueImprovementVerdictValues
|
|
23265
23430
|
);
|
|
23266
23431
|
mcpIssueImprovementStatusSqlValues = sql.raw(
|
|
@@ -23269,38 +23434,38 @@ var init_schema2 = __esm({
|
|
|
23269
23434
|
mcpIssueImprovementVerdictSqlValues = sql.raw(
|
|
23270
23435
|
mcpIssueImprovementVerdictValues.map((verdict) => `'${verdict}'`).join(", ")
|
|
23271
23436
|
);
|
|
23272
|
-
designConfigSchema =
|
|
23273
|
-
colors:
|
|
23274
|
-
primary:
|
|
23275
|
-
secondary:
|
|
23276
|
-
accent:
|
|
23277
|
-
muted:
|
|
23278
|
-
destructive:
|
|
23279
|
-
background:
|
|
23280
|
-
foreground:
|
|
23281
|
-
card:
|
|
23282
|
-
cardForeground:
|
|
23283
|
-
popover:
|
|
23284
|
-
popoverForeground:
|
|
23285
|
-
border:
|
|
23286
|
-
input:
|
|
23287
|
-
ring:
|
|
23437
|
+
designConfigSchema = z17.object({
|
|
23438
|
+
colors: z17.object({
|
|
23439
|
+
primary: z17.string(),
|
|
23440
|
+
secondary: z17.string(),
|
|
23441
|
+
accent: z17.string(),
|
|
23442
|
+
muted: z17.string(),
|
|
23443
|
+
destructive: z17.string(),
|
|
23444
|
+
background: z17.string(),
|
|
23445
|
+
foreground: z17.string(),
|
|
23446
|
+
card: z17.string(),
|
|
23447
|
+
cardForeground: z17.string(),
|
|
23448
|
+
popover: z17.string(),
|
|
23449
|
+
popoverForeground: z17.string(),
|
|
23450
|
+
border: z17.string(),
|
|
23451
|
+
input: z17.string(),
|
|
23452
|
+
ring: z17.string()
|
|
23288
23453
|
}).partial(),
|
|
23289
|
-
radius:
|
|
23290
|
-
font:
|
|
23291
|
-
sans:
|
|
23292
|
-
mono:
|
|
23454
|
+
radius: z17.string(),
|
|
23455
|
+
font: z17.object({
|
|
23456
|
+
sans: z17.string(),
|
|
23457
|
+
mono: z17.string()
|
|
23293
23458
|
}).partial(),
|
|
23294
|
-
icons:
|
|
23295
|
-
libraryId:
|
|
23296
|
-
variant:
|
|
23459
|
+
icons: z17.object({
|
|
23460
|
+
libraryId: z17.string(),
|
|
23461
|
+
variant: z17.string().nullable()
|
|
23297
23462
|
}).partial()
|
|
23298
23463
|
}).partial();
|
|
23299
|
-
projectMemoryDataSchema =
|
|
23300
|
-
lockedIn:
|
|
23301
|
-
rejected:
|
|
23302
|
-
tone:
|
|
23303
|
-
notes:
|
|
23464
|
+
projectMemoryDataSchema = z17.object({
|
|
23465
|
+
lockedIn: z17.array(z17.string()),
|
|
23466
|
+
rejected: z17.array(z17.string()),
|
|
23467
|
+
tone: z17.string().nullable(),
|
|
23468
|
+
notes: z17.string().nullable()
|
|
23304
23469
|
});
|
|
23305
23470
|
projects = pgTable("projects", {
|
|
23306
23471
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
@@ -23580,6 +23745,43 @@ var init_schema2 = __esm({
|
|
|
23580
23745
|
)
|
|
23581
23746
|
]
|
|
23582
23747
|
);
|
|
23748
|
+
agentFeedbackStatusValues = [
|
|
23749
|
+
"open",
|
|
23750
|
+
"accepted",
|
|
23751
|
+
"rejected",
|
|
23752
|
+
"shipped"
|
|
23753
|
+
];
|
|
23754
|
+
agentFeedback = pgTable(
|
|
23755
|
+
"agent_feedback",
|
|
23756
|
+
{
|
|
23757
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
23758
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
23759
|
+
apiKeyId: text("api_key_id").references(() => apiKeys.id, { onDelete: "set null" }),
|
|
23760
|
+
kind: text("kind", { enum: agentFeedbackKindValues }).notNull(),
|
|
23761
|
+
// The endpoint path or MCP tool name the agent was calling.
|
|
23762
|
+
surface: text("surface").notNull(),
|
|
23763
|
+
summary: text("summary").notNull(),
|
|
23764
|
+
attempted: text("attempted"),
|
|
23765
|
+
expected: text("expected"),
|
|
23766
|
+
actual: text("actual"),
|
|
23767
|
+
// The agent's own name for itself, such as "claude-code/2.1".
|
|
23768
|
+
client: text("client"),
|
|
23769
|
+
// SHA-256 of kind, surface and the normalized summary, so identical
|
|
23770
|
+
// reports group in the admin queue without a model call.
|
|
23771
|
+
fingerprint: text("fingerprint").notNull(),
|
|
23772
|
+
status: text("status", { enum: agentFeedbackStatusValues }).notNull().default("open"),
|
|
23773
|
+
pullRequestUrl: text("pull_request_url"),
|
|
23774
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
23775
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
23776
|
+
},
|
|
23777
|
+
(t) => [
|
|
23778
|
+
index("agent_feedback_status_created_idx").on(t.status, t.createdAt),
|
|
23779
|
+
index("agent_feedback_fingerprint_idx").on(t.fingerprint),
|
|
23780
|
+
// Deleting a user or an API key looks up its reports through these.
|
|
23781
|
+
index("agent_feedback_user_id_idx").on(t.userId),
|
|
23782
|
+
index("agent_feedback_api_key_id_idx").on(t.apiKeyId)
|
|
23783
|
+
]
|
|
23784
|
+
);
|
|
23583
23785
|
billingSubscription = pgTable(
|
|
23584
23786
|
"billing_subscription",
|
|
23585
23787
|
{
|
|
@@ -24386,7 +24588,7 @@ var init_schema2 = __esm({
|
|
|
24386
24588
|
});
|
|
24387
24589
|
|
|
24388
24590
|
// ../shared/src/mcp/tools/design-config-tools.ts
|
|
24389
|
-
import { z as
|
|
24591
|
+
import { z as z18 } from "zod";
|
|
24390
24592
|
function registerDesignConfigTools({
|
|
24391
24593
|
server,
|
|
24392
24594
|
tracked,
|
|
@@ -24437,7 +24639,7 @@ function registerDesignConfigTools({
|
|
|
24437
24639
|
{
|
|
24438
24640
|
title: "Get a Project's Tuned Design Config",
|
|
24439
24641
|
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.`,
|
|
24440
|
-
inputSchema:
|
|
24642
|
+
inputSchema: z18.object({
|
|
24441
24643
|
projectId: PROJECT_ID_ARG,
|
|
24442
24644
|
context: CONTEXT_ARG
|
|
24443
24645
|
}),
|
|
@@ -24466,7 +24668,7 @@ function registerDesignConfigTools({
|
|
|
24466
24668
|
{
|
|
24467
24669
|
title: "Set a Project's Design Config",
|
|
24468
24670
|
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.`,
|
|
24469
|
-
inputSchema:
|
|
24671
|
+
inputSchema: z18.object({
|
|
24470
24672
|
projectId: PROJECT_ID_ARG,
|
|
24471
24673
|
config: CONFIG_ARG,
|
|
24472
24674
|
context: CONTEXT_ARG
|
|
@@ -24517,7 +24719,7 @@ var init_design_config_tools = __esm({
|
|
|
24517
24719
|
"use strict";
|
|
24518
24720
|
init_schema2();
|
|
24519
24721
|
init_shared();
|
|
24520
|
-
PROJECT_ID_ARG =
|
|
24722
|
+
PROJECT_ID_ARG = z18.string().min(1).describe("The project id returned by create-design-system.");
|
|
24521
24723
|
CONFIG_ARG = designConfigSchema.describe(
|
|
24522
24724
|
"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."
|
|
24523
24725
|
);
|
|
@@ -24525,16 +24727,16 @@ var init_design_config_tools = __esm({
|
|
|
24525
24727
|
});
|
|
24526
24728
|
|
|
24527
24729
|
// ../shared/src/mcp/tools/jig-tools.ts
|
|
24528
|
-
import { z as
|
|
24730
|
+
import { z as z19 } from "zod";
|
|
24529
24731
|
function registerJigTools({ server, tracked }) {
|
|
24530
24732
|
server.registerTool(
|
|
24531
24733
|
"get-jig-guide",
|
|
24532
24734
|
{
|
|
24533
24735
|
title: "Get a Jig for Parameter Work",
|
|
24534
24736
|
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.`,
|
|
24535
|
-
inputSchema:
|
|
24536
|
-
kind:
|
|
24537
|
-
framework:
|
|
24737
|
+
inputSchema: z19.object({
|
|
24738
|
+
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."),
|
|
24739
|
+
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'."),
|
|
24538
24740
|
context: CONTEXT_ARG
|
|
24539
24741
|
}),
|
|
24540
24742
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -25366,7 +25568,7 @@ var init_brand_sources = __esm({
|
|
|
25366
25568
|
});
|
|
25367
25569
|
|
|
25368
25570
|
// ../shared/src/mcp/tools/extract-tools.ts
|
|
25369
|
-
import { z as
|
|
25571
|
+
import { z as z20 } from "zod";
|
|
25370
25572
|
function authHeaders2(authContext) {
|
|
25371
25573
|
if (!authContext) return {};
|
|
25372
25574
|
const bearer = authContext.kind === "api-key" ? authContext.apiKey : authContext.accessToken;
|
|
@@ -25403,15 +25605,15 @@ JSON.stringify(${buildRoleSampleScript()})
|
|
|
25403
25605
|
- kind "tokens": a design-tokens JSON export ($value entries, W3C format or the Figma variables API shape with 0..1 rgb floats).
|
|
25404
25606
|
|
|
25405
25607
|
Common vendor names (Shopify's --color-button family) are mapped into empty semantic slots automatically and the mapping is reported in the output.`,
|
|
25406
|
-
inputSchema:
|
|
25407
|
-
css:
|
|
25408
|
-
sources:
|
|
25409
|
-
name:
|
|
25410
|
-
kind:
|
|
25411
|
-
content:
|
|
25608
|
+
inputSchema: z20.object({
|
|
25609
|
+
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."),
|
|
25610
|
+
sources: z20.array(z20.object({
|
|
25611
|
+
name: z20.string().trim().min(1).max(120).describe("Label for this source, e.g. 'globals.css', 'live site sample', 'figma variables'."),
|
|
25612
|
+
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."),
|
|
25613
|
+
content: z20.string().trim().min(1).max(2e5)
|
|
25412
25614
|
})).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."),
|
|
25413
|
-
name:
|
|
25414
|
-
maxTokens:
|
|
25615
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system, e.g. the app or repo name."),
|
|
25616
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25415
25617
|
}),
|
|
25416
25618
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
25417
25619
|
},
|
|
@@ -25442,10 +25644,10 @@ Common vendor names (Shopify's --color-button family) are mapped into empty sema
|
|
|
25442
25644
|
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.
|
|
25443
25645
|
|
|
25444
25646
|
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.`,
|
|
25445
|
-
inputSchema:
|
|
25446
|
-
url:
|
|
25447
|
-
name:
|
|
25448
|
-
maxTokens:
|
|
25647
|
+
inputSchema: z20.object({
|
|
25648
|
+
url: z20.string().trim().min(8).max(2048).describe("Public http(s) URL of the page to read, usually the homepage."),
|
|
25649
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system. Defaults to the site's hostname."),
|
|
25650
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25449
25651
|
}),
|
|
25450
25652
|
// Reads any public page the user names, so the set of hosts it reaches is
|
|
25451
25653
|
// the open web, not Better Design's catalog. It stores nothing. [BD-316]
|
|
@@ -25495,7 +25697,7 @@ var init_extract_tools = __esm({
|
|
|
25495
25697
|
});
|
|
25496
25698
|
|
|
25497
25699
|
// ../shared/src/mcp/tools/illustration-tools.ts
|
|
25498
|
-
import { z as
|
|
25700
|
+
import { z as z21 } from "zod";
|
|
25499
25701
|
function fail(message) {
|
|
25500
25702
|
return {
|
|
25501
25703
|
content: [{ type: "text", text: message }],
|
|
@@ -25541,11 +25743,11 @@ How to call it:
|
|
|
25541
25743
|
4. Stop when \`faults\` is empty.
|
|
25542
25744
|
|
|
25543
25745
|
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.`,
|
|
25544
|
-
inputSchema:
|
|
25545
|
-
svg:
|
|
25546
|
-
intent:
|
|
25547
|
-
animated:
|
|
25548
|
-
reviewToken:
|
|
25746
|
+
inputSchema: z21.object({
|
|
25747
|
+
svg: z21.string().min(1).describe("The complete svg element you drew."),
|
|
25748
|
+
intent: z21.string().min(1).describe('What it is meant to be, in plain words. For example "an open inbox tray with letters".'),
|
|
25749
|
+
animated: z21.boolean().optional().describe("Set when the drawing was meant to move, so the motion checks apply."),
|
|
25750
|
+
reviewToken: z21.string().optional().describe("The token from a previous review of this drawing. Reviews it again free.")
|
|
25549
25751
|
}),
|
|
25550
25752
|
outputSchema: REVIEW_OUTPUT_SCHEMA,
|
|
25551
25753
|
// A review without a valid reviewToken passes the paywall, so it spends
|
|
@@ -25624,16 +25826,16 @@ var init_illustration_tools = __esm({
|
|
|
25624
25826
|
init_shared();
|
|
25625
25827
|
REVIEW_OUTPUT_SCHEMA = {
|
|
25626
25828
|
/** Empty when nothing is visibly wrong. */
|
|
25627
|
-
faults:
|
|
25829
|
+
faults: z21.array(z21.string()),
|
|
25628
25830
|
/** Group ids in paint order, the parts an animation can target. */
|
|
25629
|
-
layers:
|
|
25831
|
+
layers: z21.array(z21.string()),
|
|
25630
25832
|
/** False when the markup was refused; faults says why. */
|
|
25631
|
-
reviewed:
|
|
25833
|
+
reviewed: z21.boolean(),
|
|
25632
25834
|
/** True when the drawing could not be rendered, so only structure was checked. */
|
|
25633
|
-
unseen:
|
|
25835
|
+
unseen: z21.boolean(),
|
|
25634
25836
|
/** Pass this back to review the same drawing again without spending another credit. */
|
|
25635
|
-
reviewToken:
|
|
25636
|
-
message:
|
|
25837
|
+
reviewToken: z21.string(),
|
|
25838
|
+
message: z21.string()
|
|
25637
25839
|
};
|
|
25638
25840
|
}
|
|
25639
25841
|
});
|
|
@@ -26995,7 +27197,7 @@ ul.evidence { padding-inline-start: 1rem; }
|
|
|
26995
27197
|
});
|
|
26996
27198
|
|
|
26997
27199
|
// ../shared/src/mcp/tools/inspection-tools.ts
|
|
26998
|
-
import { z as
|
|
27200
|
+
import { z as z22 } from "zod";
|
|
26999
27201
|
function coversRecommendedWidth(states, width) {
|
|
27000
27202
|
return states.some(
|
|
27001
27203
|
(state) => Math.abs(state.viewport.width - width) <= WIDTH_TOLERANCE
|
|
@@ -27051,36 +27253,38 @@ function registerInspectionTools({ server, provider, tracked }) {
|
|
|
27051
27253
|
|
|
27052
27254
|
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.
|
|
27053
27255
|
|
|
27256
|
+
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.
|
|
27257
|
+
|
|
27054
27258
|
Workflow:
|
|
27055
27259
|
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.
|
|
27056
27260
|
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.
|
|
27057
27261
|
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.
|
|
27058
27262
|
|
|
27059
27263
|
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.`,
|
|
27060
|
-
inputSchema:
|
|
27061
|
-
states:
|
|
27062
|
-
spacingScale:
|
|
27264
|
+
inputSchema: z22.object({
|
|
27265
|
+
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."),
|
|
27266
|
+
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."),
|
|
27063
27267
|
tolerance: finiteNumber.min(0).max(4).optional().describe("Pixel tolerance for grouping measurements. Defaults to 1px."),
|
|
27064
|
-
detail:
|
|
27268
|
+
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."),
|
|
27065
27269
|
context: CONTEXT_ARG
|
|
27066
27270
|
}),
|
|
27067
|
-
outputSchema:
|
|
27068
|
-
status:
|
|
27069
|
-
score:
|
|
27070
|
-
summary:
|
|
27071
|
-
critical:
|
|
27072
|
-
serious:
|
|
27073
|
-
moderate:
|
|
27074
|
-
total:
|
|
27075
|
-
truncated:
|
|
27271
|
+
outputSchema: z22.object({
|
|
27272
|
+
status: z22.enum(["needs-capture", "completed"]),
|
|
27273
|
+
score: z22.number().optional(),
|
|
27274
|
+
summary: z22.object({
|
|
27275
|
+
critical: z22.number(),
|
|
27276
|
+
serious: z22.number(),
|
|
27277
|
+
moderate: z22.number(),
|
|
27278
|
+
total: z22.number(),
|
|
27279
|
+
truncated: z22.number().optional()
|
|
27076
27280
|
}).optional(),
|
|
27077
|
-
findings:
|
|
27078
|
-
capturedWidths:
|
|
27079
|
-
missingWidths:
|
|
27080
|
-
states:
|
|
27081
|
-
rubric:
|
|
27082
|
-
rubricStatus:
|
|
27083
|
-
captureScript:
|
|
27281
|
+
findings: z22.array(findingSchema).optional(),
|
|
27282
|
+
capturedWidths: z22.array(z22.number()).optional(),
|
|
27283
|
+
missingWidths: z22.array(z22.number()).optional(),
|
|
27284
|
+
states: z22.array(overlayStateSchema).optional(),
|
|
27285
|
+
rubric: z22.array(z22.object({ id: z22.string(), title: z22.string() })).optional(),
|
|
27286
|
+
rubricStatus: z22.enum(["loaded", "unavailable"]).optional(),
|
|
27287
|
+
captureScript: z22.string().optional()
|
|
27084
27288
|
}),
|
|
27085
27289
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
27086
27290
|
_meta: {
|
|
@@ -27140,7 +27344,8 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
27140
27344
|
detail === "full" ? `Spacing score: ${result.score}/100.` : "",
|
|
27141
27345
|
coverageLine,
|
|
27142
27346
|
findingsText,
|
|
27143
|
-
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : ""
|
|
27347
|
+
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : "",
|
|
27348
|
+
GEOMETRY_ONLY
|
|
27144
27349
|
].filter(Boolean).join("\n\n");
|
|
27145
27350
|
return {
|
|
27146
27351
|
content: [{ type: "text", text: text3 }],
|
|
@@ -27157,7 +27362,7 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
27157
27362
|
)
|
|
27158
27363
|
);
|
|
27159
27364
|
}
|
|
27160
|
-
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE;
|
|
27365
|
+
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE, GEOMETRY_ONLY;
|
|
27161
27366
|
var init_inspection_tools = __esm({
|
|
27162
27367
|
"../shared/src/mcp/tools/inspection-tools.ts"() {
|
|
27163
27368
|
"use strict";
|
|
@@ -27165,67 +27370,67 @@ var init_inspection_tools = __esm({
|
|
|
27165
27370
|
init_finding_copy();
|
|
27166
27371
|
init_spacing_inspection_html();
|
|
27167
27372
|
init_shared();
|
|
27168
|
-
finiteNumber =
|
|
27169
|
-
rectSchema =
|
|
27373
|
+
finiteNumber = z22.number().finite();
|
|
27374
|
+
rectSchema = z22.object({
|
|
27170
27375
|
x: finiteNumber,
|
|
27171
27376
|
y: finiteNumber,
|
|
27172
27377
|
width: finiteNumber.nonnegative(),
|
|
27173
27378
|
height: finiteNumber.nonnegative()
|
|
27174
27379
|
});
|
|
27175
|
-
edgesSchema =
|
|
27380
|
+
edgesSchema = z22.object({
|
|
27176
27381
|
top: finiteNumber,
|
|
27177
27382
|
right: finiteNumber,
|
|
27178
27383
|
bottom: finiteNumber,
|
|
27179
27384
|
left: finiteNumber
|
|
27180
27385
|
});
|
|
27181
|
-
cornerRadiusAxesSchema =
|
|
27386
|
+
cornerRadiusAxesSchema = z22.object({
|
|
27182
27387
|
horizontal: finiteNumber.nonnegative(),
|
|
27183
27388
|
vertical: finiteNumber.nonnegative()
|
|
27184
27389
|
});
|
|
27185
|
-
cornerRadiusSchema =
|
|
27390
|
+
cornerRadiusSchema = z22.union([
|
|
27186
27391
|
cornerRadiusAxesSchema,
|
|
27187
27392
|
finiteNumber.nonnegative().transform((value) => ({
|
|
27188
27393
|
horizontal: value,
|
|
27189
27394
|
vertical: value
|
|
27190
27395
|
}))
|
|
27191
27396
|
]);
|
|
27192
|
-
cornerRadiiSchema =
|
|
27397
|
+
cornerRadiiSchema = z22.object({
|
|
27193
27398
|
topLeft: cornerRadiusSchema,
|
|
27194
27399
|
topRight: cornerRadiusSchema,
|
|
27195
27400
|
bottomRight: cornerRadiusSchema,
|
|
27196
27401
|
bottomLeft: cornerRadiusSchema
|
|
27197
27402
|
});
|
|
27198
|
-
styleSchema =
|
|
27199
|
-
display:
|
|
27200
|
-
position:
|
|
27201
|
-
flexDirection:
|
|
27202
|
-
flexWrap:
|
|
27203
|
-
alignItems:
|
|
27204
|
-
justifyContent:
|
|
27403
|
+
styleSchema = z22.object({
|
|
27404
|
+
display: z22.string().max(60).optional(),
|
|
27405
|
+
position: z22.string().max(60).optional(),
|
|
27406
|
+
flexDirection: z22.string().max(60).optional(),
|
|
27407
|
+
flexWrap: z22.string().max(60).optional(),
|
|
27408
|
+
alignItems: z22.string().max(60).optional(),
|
|
27409
|
+
justifyContent: z22.string().max(60).optional(),
|
|
27205
27410
|
rowGap: finiteNumber.optional(),
|
|
27206
27411
|
columnGap: finiteNumber.optional(),
|
|
27207
27412
|
padding: edgesSchema.optional(),
|
|
27208
27413
|
margin: edgesSchema.optional(),
|
|
27209
|
-
overflowX:
|
|
27210
|
-
overflowY:
|
|
27414
|
+
overflowX: z22.string().max(60).optional(),
|
|
27415
|
+
overflowY: z22.string().max(60).optional(),
|
|
27211
27416
|
borderRadius: cornerRadiiSchema.optional(),
|
|
27212
|
-
backgroundColor:
|
|
27213
|
-
backgroundImage:
|
|
27214
|
-
boxShadow:
|
|
27417
|
+
backgroundColor: z22.string().max(160).optional(),
|
|
27418
|
+
backgroundImage: z22.string().max(1e3).optional(),
|
|
27419
|
+
boxShadow: z22.string().max(1e3).optional()
|
|
27215
27420
|
});
|
|
27216
|
-
elementSchema =
|
|
27217
|
-
id:
|
|
27218
|
-
selector:
|
|
27219
|
-
tag:
|
|
27220
|
-
role:
|
|
27221
|
-
label:
|
|
27222
|
-
parentId:
|
|
27223
|
-
groupId:
|
|
27224
|
-
alignmentGroup:
|
|
27225
|
-
alignmentEdge:
|
|
27226
|
-
expectedSymmetry:
|
|
27227
|
-
compareSize:
|
|
27228
|
-
interactive:
|
|
27421
|
+
elementSchema = z22.object({
|
|
27422
|
+
id: z22.string().min(1).max(300),
|
|
27423
|
+
selector: z22.string().max(500).optional(),
|
|
27424
|
+
tag: z22.string().max(80).optional(),
|
|
27425
|
+
role: z22.string().max(80).optional(),
|
|
27426
|
+
label: z22.string().max(160).optional(),
|
|
27427
|
+
parentId: z22.string().max(300).optional(),
|
|
27428
|
+
groupId: z22.string().max(300).optional(),
|
|
27429
|
+
alignmentGroup: z22.string().max(300).optional(),
|
|
27430
|
+
alignmentEdge: z22.enum(["left", "right", "top", "bottom", "center-x", "center-y"]).optional(),
|
|
27431
|
+
expectedSymmetry: z22.enum(["horizontal", "vertical", "both"]).optional(),
|
|
27432
|
+
compareSize: z22.enum(["width", "height", "both"]).optional(),
|
|
27433
|
+
interactive: z22.boolean().optional(),
|
|
27229
27434
|
rect: rectSchema,
|
|
27230
27435
|
style: styleSchema.optional(),
|
|
27231
27436
|
lineCount: finiteNumber.int().positive().optional(),
|
|
@@ -27234,44 +27439,45 @@ var init_inspection_tools = __esm({
|
|
|
27234
27439
|
scrollWidth: finiteNumber.nonnegative().optional(),
|
|
27235
27440
|
scrollHeight: finiteNumber.nonnegative().optional()
|
|
27236
27441
|
});
|
|
27237
|
-
screenshotSchema =
|
|
27238
|
-
spacingStateSchema =
|
|
27239
|
-
name:
|
|
27240
|
-
viewport:
|
|
27442
|
+
screenshotSchema = z22.string().max(5e6).regex(/^data:image\/(?:png|jpeg|webp);base64,/, "Use a PNG, JPEG, or WebP data URL.");
|
|
27443
|
+
spacingStateSchema = z22.object({
|
|
27444
|
+
name: z22.string().min(1).max(100),
|
|
27445
|
+
viewport: z22.object({
|
|
27241
27446
|
width: finiteNumber.positive().max(2e4),
|
|
27242
27447
|
height: finiteNumber.positive().max(2e4),
|
|
27243
27448
|
devicePixelRatio: finiteNumber.positive().max(10).optional()
|
|
27244
27449
|
}),
|
|
27245
|
-
elements:
|
|
27450
|
+
elements: z22.array(elementSchema).max(1500),
|
|
27246
27451
|
screenshotDataUrl: screenshotSchema.optional().describe("Optional browser screenshot as a data URL. It remains in the structured evidence for report renderers and is not persisted.")
|
|
27247
27452
|
});
|
|
27248
|
-
evidenceSchema =
|
|
27249
|
-
label:
|
|
27250
|
-
value:
|
|
27251
|
-
unit:
|
|
27453
|
+
evidenceSchema = z22.object({
|
|
27454
|
+
label: z22.string(),
|
|
27455
|
+
value: z22.union([z22.number(), z22.string()]),
|
|
27456
|
+
unit: z22.enum(["px", "count"]).optional()
|
|
27252
27457
|
});
|
|
27253
|
-
findingSchema =
|
|
27254
|
-
id:
|
|
27255
|
-
ruleId:
|
|
27256
|
-
state:
|
|
27257
|
-
severity:
|
|
27258
|
-
message:
|
|
27259
|
-
fix:
|
|
27260
|
-
elementIds:
|
|
27261
|
-
evidence:
|
|
27458
|
+
findingSchema = z22.object({
|
|
27459
|
+
id: z22.string(),
|
|
27460
|
+
ruleId: z22.string(),
|
|
27461
|
+
state: z22.string(),
|
|
27462
|
+
severity: z22.enum(["critical", "serious", "moderate"]),
|
|
27463
|
+
message: z22.string(),
|
|
27464
|
+
fix: z22.string(),
|
|
27465
|
+
elementIds: z22.array(z22.string()),
|
|
27466
|
+
evidence: z22.array(evidenceSchema)
|
|
27262
27467
|
});
|
|
27263
|
-
overlayStateSchema =
|
|
27264
|
-
name:
|
|
27265
|
-
viewport:
|
|
27266
|
-
width:
|
|
27267
|
-
height:
|
|
27268
|
-
devicePixelRatio:
|
|
27468
|
+
overlayStateSchema = z22.object({
|
|
27469
|
+
name: z22.string(),
|
|
27470
|
+
viewport: z22.object({
|
|
27471
|
+
width: z22.number(),
|
|
27472
|
+
height: z22.number(),
|
|
27473
|
+
devicePixelRatio: z22.number().optional()
|
|
27269
27474
|
}),
|
|
27270
|
-
elements:
|
|
27271
|
-
screenshotDataUrl:
|
|
27475
|
+
elements: z22.array(z22.object({ id: z22.string(), label: z22.string().optional(), rect: rectSchema })),
|
|
27476
|
+
screenshotDataUrl: z22.string().optional()
|
|
27272
27477
|
});
|
|
27273
27478
|
RECOMMENDED_VIEWPORT_WIDTHS = [390, 1440, 1728];
|
|
27274
27479
|
WIDTH_TOLERANCE = 40;
|
|
27480
|
+
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.";
|
|
27275
27481
|
}
|
|
27276
27482
|
});
|
|
27277
27483
|
|
|
@@ -27310,7 +27516,7 @@ var init_inspection_resource = __esm({
|
|
|
27310
27516
|
});
|
|
27311
27517
|
|
|
27312
27518
|
// ../shared/src/mcp/tools/dashboard-review-tools.ts
|
|
27313
|
-
import { z as
|
|
27519
|
+
import { z as z23 } from "zod";
|
|
27314
27520
|
function finding(id, severity, message, fix) {
|
|
27315
27521
|
return { id, severity, message, fix };
|
|
27316
27522
|
}
|
|
@@ -27817,11 +28023,11 @@ function registerDashboardReviewTools({ server, track }) {
|
|
|
27817
28023
|
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.
|
|
27818
28024
|
|
|
27819
28025
|
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.`,
|
|
27820
|
-
inputSchema:
|
|
27821
|
-
screen:
|
|
27822
|
-
source:
|
|
28026
|
+
inputSchema: z23.object({
|
|
28027
|
+
screen: z23.string().trim().min(1).max(120).describe("What this dashboard is, e.g. 'funnel report overview'."),
|
|
28028
|
+
source: z23.string().min(1).max(12e4).describe("Complete TSX or JSX source for the dashboard page."),
|
|
27823
28029
|
fixture: dashboardFixtureSchema.describe("The request's unchanged fixture object. The first step is the overview; remaining steps are dependent task screens."),
|
|
27824
|
-
interpretation:
|
|
28030
|
+
interpretation: z23.string().trim().min(1).max(2e3).describe("Exact supplied or factually derived takeaway that must appear after records."),
|
|
27825
28031
|
componentSources: dashboardComponentSourcesSchema.optional().describe("Installed Sidebar, StatCard, and Table source files for compound-composition review."),
|
|
27826
28032
|
context: CONTEXT_ARG
|
|
27827
28033
|
}),
|
|
@@ -27869,34 +28075,34 @@ var init_dashboard_review_tools = __esm({
|
|
|
27869
28075
|
"use strict";
|
|
27870
28076
|
init_review_capture();
|
|
27871
28077
|
init_shared();
|
|
27872
|
-
dashboardFixtureSchema =
|
|
27873
|
-
stats:
|
|
27874
|
-
label:
|
|
27875
|
-
value:
|
|
28078
|
+
dashboardFixtureSchema = z23.object({
|
|
28079
|
+
stats: z23.array(z23.object({
|
|
28080
|
+
label: z23.string().trim().min(1).max(300),
|
|
28081
|
+
value: z23.string().trim().min(1).max(300)
|
|
27876
28082
|
})).max(100).optional(),
|
|
27877
|
-
records:
|
|
27878
|
-
title:
|
|
27879
|
-
detail:
|
|
27880
|
-
meta:
|
|
27881
|
-
status:
|
|
28083
|
+
records: z23.array(z23.object({
|
|
28084
|
+
title: z23.string().trim().min(1).max(300),
|
|
28085
|
+
detail: z23.string().trim().min(1).max(1e3),
|
|
28086
|
+
meta: z23.string().trim().min(1).max(300),
|
|
28087
|
+
status: z23.string().trim().min(1).max(300)
|
|
27882
28088
|
})).max(100).optional(),
|
|
27883
|
-
steps:
|
|
27884
|
-
title:
|
|
27885
|
-
description:
|
|
27886
|
-
action:
|
|
28089
|
+
steps: z23.array(z23.object({
|
|
28090
|
+
title: z23.string().trim().min(1).max(300),
|
|
28091
|
+
description: z23.string().trim().min(1).max(1e3),
|
|
28092
|
+
action: z23.string().trim().min(1).max(200)
|
|
27887
28093
|
})).min(2).max(50)
|
|
27888
28094
|
});
|
|
27889
|
-
dashboardComponentSourcesSchema =
|
|
27890
|
-
sidebar:
|
|
27891
|
-
statCard:
|
|
27892
|
-
table:
|
|
28095
|
+
dashboardComponentSourcesSchema = z23.object({
|
|
28096
|
+
sidebar: z23.string().min(1).max(12e4),
|
|
28097
|
+
statCard: z23.string().min(1).max(12e4),
|
|
28098
|
+
table: z23.string().min(1).max(12e4)
|
|
27893
28099
|
});
|
|
27894
28100
|
}
|
|
27895
28101
|
});
|
|
27896
28102
|
|
|
27897
28103
|
// ../shared/src/mcp/tools/final-ui-review-tools.ts
|
|
27898
28104
|
import { createHash as createHash2 } from "crypto";
|
|
27899
|
-
import { z as
|
|
28105
|
+
import { z as z24 } from "zod";
|
|
27900
28106
|
function digest(value) {
|
|
27901
28107
|
return createHash2("sha256").update(value).digest("hex");
|
|
27902
28108
|
}
|
|
@@ -27907,7 +28113,9 @@ function comprehensionFindings(input) {
|
|
|
27907
28113
|
const report = analyzeComprehension({
|
|
27908
28114
|
screen: input.screen,
|
|
27909
28115
|
code: input.visibleSource,
|
|
27910
|
-
knownTerms: input.knownTerms
|
|
28116
|
+
knownTerms: input.knownTerms,
|
|
28117
|
+
// A dashboard is a screen even when its name mentions slides. [BD-593]
|
|
28118
|
+
surface: "screen"
|
|
27911
28119
|
});
|
|
27912
28120
|
return report.findings.flatMap(
|
|
27913
28121
|
(item, index2) => item.severity === "minor" ? [] : [
|
|
@@ -28308,15 +28516,15 @@ function registerFinalUiReviewTools({
|
|
|
28308
28516
|
description: `Issue a source-bound receipt for a completed dashboard.
|
|
28309
28517
|
|
|
28310
28518
|
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.`,
|
|
28311
|
-
inputSchema:
|
|
28312
|
-
screen:
|
|
28313
|
-
previewUrl:
|
|
28519
|
+
inputSchema: z24.object({
|
|
28520
|
+
screen: z24.string().trim().min(1).max(120),
|
|
28521
|
+
previewUrl: z24.string().url().max(2048).refine((value) => new URL(value).protocol === "https:", {
|
|
28314
28522
|
message: "Use a public HTTPS preview URL."
|
|
28315
28523
|
}).describe("Public HTTPS URL for the exact deployed dashboard route."),
|
|
28316
|
-
source:
|
|
28524
|
+
source: z24.string().min(1).max(24e4).describe(
|
|
28317
28525
|
"Complete TSX or JSX page source, including its default export."
|
|
28318
28526
|
),
|
|
28319
|
-
visibleSource:
|
|
28527
|
+
visibleSource: z24.string().min(1).max(12e4).describe(
|
|
28320
28528
|
"Exact visible screen component copied unchanged from source."
|
|
28321
28529
|
),
|
|
28322
28530
|
fixture: dashboardFixtureSchema.describe(
|
|
@@ -28325,7 +28533,7 @@ Submit the public HTTPS preview plus exact source, unchanged data, and installed
|
|
|
28325
28533
|
componentSources: dashboardComponentSourcesSchema.describe(
|
|
28326
28534
|
"Exact installed Sidebar, StatCard, and Table component source files."
|
|
28327
28535
|
),
|
|
28328
|
-
knownTerms:
|
|
28536
|
+
knownTerms: z24.array(z24.string().trim().min(1).max(60)).max(50).optional(),
|
|
28329
28537
|
context: CONTEXT_ARG
|
|
28330
28538
|
}),
|
|
28331
28539
|
annotations: READ_ONLY_OPEN_WORLD_TOOL_ANNOTATIONS
|
|
@@ -28396,7 +28604,7 @@ var init_final_ui_review_tools = __esm({
|
|
|
28396
28604
|
});
|
|
28397
28605
|
|
|
28398
28606
|
// ../shared/src/mcp/tools/widget-tools.ts
|
|
28399
|
-
import { z as
|
|
28607
|
+
import { z as z25 } from "zod";
|
|
28400
28608
|
function hasIntake(args) {
|
|
28401
28609
|
return Boolean(args.widgetType || INTAKE_FIELDS.some(({ key }) => args[key]));
|
|
28402
28610
|
}
|
|
@@ -28519,16 +28727,16 @@ var init_widget_tools = __esm({
|
|
|
28519
28727
|
WIDGET_GUIDE_TOPIC = "better-widgets";
|
|
28520
28728
|
WIDGET_GUIDE_TOOL = "get-widget-guide";
|
|
28521
28729
|
MINIMUM_CONTRACT_HEADING = "## Minimum build contract";
|
|
28522
|
-
WIDGET_GUIDE_INPUT_SCHEMA =
|
|
28523
|
-
widgetType:
|
|
28524
|
-
userGoal:
|
|
28525
|
-
inputs:
|
|
28526
|
-
interaction:
|
|
28527
|
-
transformation:
|
|
28528
|
-
result:
|
|
28529
|
-
hostFramework:
|
|
28530
|
-
visualContext:
|
|
28531
|
-
maxTokens:
|
|
28730
|
+
WIDGET_GUIDE_INPUT_SCHEMA = z25.object({
|
|
28731
|
+
widgetType: z25.enum(BETTER_WIDGET_TYPES).optional().describe("Widget kind: calculator, estimator, converter, generator, quiz, or checklist."),
|
|
28732
|
+
userGoal: z25.string().trim().min(1).max(500).optional().describe("Who will use the widget and the decision or task it helps them complete."),
|
|
28733
|
+
inputs: z25.string().trim().min(1).max(1e3).optional().describe("Required inputs, including units, allowed ranges, and defaults."),
|
|
28734
|
+
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."),
|
|
28735
|
+
transformation: z25.string().trim().min(1).max(1500).optional().describe("Formula, scoring rule, conversion, or transformation that produces the result."),
|
|
28736
|
+
result: z25.string().trim().min(1).max(750).optional().describe("What the result shows, explains, and lets the user do next."),
|
|
28737
|
+
hostFramework: z25.string().trim().min(1).max(300).optional().describe("The host framework and the page or component that will contain the widget."),
|
|
28738
|
+
visualContext: z25.string().trim().min(1).max(750).optional().describe("Existing design tokens, components, or brand reference the widget must match."),
|
|
28739
|
+
maxTokens: z25.number().min(2e3).max(5e4).optional().describe("Maximum tokens to return. Truncates content to fit. Recommended: 5000-10000."),
|
|
28532
28740
|
context: CONTEXT_ARG
|
|
28533
28741
|
});
|
|
28534
28742
|
INTAKE_FIELDS = [
|
|
@@ -28582,7 +28790,7 @@ The completed response combines a scoped build brief with Better Design's stable
|
|
|
28582
28790
|
});
|
|
28583
28791
|
|
|
28584
28792
|
// ../shared/src/mcp/feature-map/schema.ts
|
|
28585
|
-
import { z as
|
|
28793
|
+
import { z as z26 } from "zod";
|
|
28586
28794
|
function oneLine(text3) {
|
|
28587
28795
|
return text3.replace(/\s+/g, " ").trim();
|
|
28588
28796
|
}
|
|
@@ -28617,38 +28825,38 @@ var init_schema3 = __esm({
|
|
|
28617
28825
|
"use strict";
|
|
28618
28826
|
FEATURE_MAP_PATH = ".better-design/feature-map.json";
|
|
28619
28827
|
FEATURE_MAP_LIMITS = { features: 200, flows: 100, steps: 50, paths: 200, name: 120 };
|
|
28620
|
-
NAME =
|
|
28621
|
-
PATHS =
|
|
28622
|
-
featureMapStepSchema =
|
|
28828
|
+
NAME = z26.string().trim().min(1).max(FEATURE_MAP_LIMITS.name);
|
|
28829
|
+
PATHS = z26.array(z26.string().trim().min(1).max(500)).max(FEATURE_MAP_LIMITS.paths).default([]);
|
|
28830
|
+
featureMapStepSchema = z26.object({
|
|
28623
28831
|
name: NAME,
|
|
28624
28832
|
/** URL path such as `/settings/billing`. Absent for a dialog or an email. */
|
|
28625
|
-
route:
|
|
28833
|
+
route: z26.string().trim().max(300).optional(),
|
|
28626
28834
|
/** The file that renders the step, such as the route's page file. */
|
|
28627
|
-
screen:
|
|
28835
|
+
screen: z26.string().trim().max(500).optional(),
|
|
28628
28836
|
/** What the user does here, such as "confirms the cancellation". */
|
|
28629
|
-
action:
|
|
28837
|
+
action: z26.string().trim().max(300).optional(),
|
|
28630
28838
|
components: PATHS,
|
|
28631
28839
|
/** API routes, server actions, hooks and queries the step reads or writes. */
|
|
28632
28840
|
data: PATHS
|
|
28633
28841
|
});
|
|
28634
|
-
featureMapFlowSchema =
|
|
28842
|
+
featureMapFlowSchema = z26.object({
|
|
28635
28843
|
id: NAME,
|
|
28636
28844
|
name: NAME,
|
|
28637
|
-
description:
|
|
28638
|
-
steps:
|
|
28845
|
+
description: z26.string().trim().max(500).optional(),
|
|
28846
|
+
steps: z26.array(featureMapStepSchema).min(1).max(FEATURE_MAP_LIMITS.steps)
|
|
28639
28847
|
});
|
|
28640
|
-
featureMapFeatureSchema =
|
|
28848
|
+
featureMapFeatureSchema = z26.object({
|
|
28641
28849
|
id: NAME,
|
|
28642
28850
|
name: NAME,
|
|
28643
|
-
description:
|
|
28644
|
-
flows:
|
|
28851
|
+
description: z26.string().trim().max(500).optional(),
|
|
28852
|
+
flows: z26.array(featureMapFlowSchema).max(FEATURE_MAP_LIMITS.flows),
|
|
28645
28853
|
/** Components that belong to the feature but to no single step. */
|
|
28646
28854
|
components: PATHS
|
|
28647
28855
|
});
|
|
28648
|
-
featureMapSchema =
|
|
28649
|
-
version:
|
|
28650
|
-
product:
|
|
28651
|
-
features:
|
|
28856
|
+
featureMapSchema = z26.object({
|
|
28857
|
+
version: z26.literal(1),
|
|
28858
|
+
product: z26.string().trim().max(120).optional(),
|
|
28859
|
+
features: z26.array(featureMapFeatureSchema).max(FEATURE_MAP_LIMITS.features)
|
|
28652
28860
|
});
|
|
28653
28861
|
}
|
|
28654
28862
|
});
|
|
@@ -28926,7 +29134,7 @@ var init_read = __esm({
|
|
|
28926
29134
|
});
|
|
28927
29135
|
|
|
28928
29136
|
// ../shared/src/mcp/feature-map/update.ts
|
|
28929
|
-
import { z as
|
|
29137
|
+
import { z as z27 } from "zod";
|
|
28930
29138
|
function find(items, ref) {
|
|
28931
29139
|
const r = ref.trim();
|
|
28932
29140
|
const index2 = items.findIndex((i) => i.id === r || i.name.toLowerCase() === r.toLowerCase());
|
|
@@ -28989,21 +29197,21 @@ var init_update2 = __esm({
|
|
|
28989
29197
|
"../shared/src/mcp/feature-map/update.ts"() {
|
|
28990
29198
|
"use strict";
|
|
28991
29199
|
init_schema3();
|
|
28992
|
-
NAME2 =
|
|
28993
|
-
REF =
|
|
29200
|
+
NAME2 = z27.string().trim().min(1).max(120);
|
|
29201
|
+
REF = z27.string().trim().min(1).max(120).describe("The id or exact name of an existing feature or flow.");
|
|
28994
29202
|
FLOW_INPUT = featureMapFlowSchema.omit({ id: true });
|
|
28995
|
-
featureMapChangeSchema =
|
|
28996
|
-
|
|
28997
|
-
op:
|
|
29203
|
+
featureMapChangeSchema = z27.discriminatedUnion("op", [
|
|
29204
|
+
z27.object({
|
|
29205
|
+
op: z27.literal("add-feature"),
|
|
28998
29206
|
name: NAME2,
|
|
28999
|
-
description:
|
|
29000
|
-
flows:
|
|
29207
|
+
description: z27.string().trim().max(500).optional(),
|
|
29208
|
+
flows: z27.array(FLOW_INPUT).max(100).optional()
|
|
29001
29209
|
}),
|
|
29002
|
-
|
|
29003
|
-
|
|
29004
|
-
|
|
29005
|
-
|
|
29006
|
-
|
|
29210
|
+
z27.object({ op: z27.literal("rename-feature"), feature: REF, name: NAME2 }),
|
|
29211
|
+
z27.object({ op: z27.literal("remove-feature"), feature: REF }),
|
|
29212
|
+
z27.object({ op: z27.literal("set-flow"), feature: REF, flow: FLOW_INPUT }),
|
|
29213
|
+
z27.object({ op: z27.literal("rename-flow"), feature: REF, flow: REF, name: NAME2 }),
|
|
29214
|
+
z27.object({ op: z27.literal("remove-flow"), feature: REF, flow: REF })
|
|
29007
29215
|
]);
|
|
29008
29216
|
idTaken = (items, id, except2) => items.some((item, i) => item.id === id && i !== except2);
|
|
29009
29217
|
toFlow = (f) => ({ id: slugify(f.name), ...f });
|
|
@@ -29022,7 +29230,7 @@ var init_feature_map = __esm({
|
|
|
29022
29230
|
});
|
|
29023
29231
|
|
|
29024
29232
|
// ../shared/src/mcp/tools/feature-map-tools.ts
|
|
29025
|
-
import { z as
|
|
29233
|
+
import { z as z28 } from "zod";
|
|
29026
29234
|
function registerFeatureMapTools({ server, tracked }) {
|
|
29027
29235
|
server.registerTool(
|
|
29028
29236
|
"build-feature-map",
|
|
@@ -29033,9 +29241,9 @@ function registerFeatureMapTools({ server, tracked }) {
|
|
|
29033
29241
|
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.
|
|
29034
29242
|
|
|
29035
29243
|
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").`,
|
|
29036
|
-
inputSchema:
|
|
29037
|
-
files:
|
|
29038
|
-
product:
|
|
29244
|
+
inputSchema: z28.object({
|
|
29245
|
+
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`."),
|
|
29246
|
+
product: z28.string().trim().max(120).optional().describe("The product's name, stored at the top of the map."),
|
|
29039
29247
|
context: CONTEXT_ARG
|
|
29040
29248
|
}),
|
|
29041
29249
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29065,11 +29273,11 @@ ${serializeFeatureMap(map).trimEnd()}
|
|
|
29065
29273
|
description: `Find the flows a change touches, with the screen, route, components and data files of every step. ${WHEN_TO_USE}
|
|
29066
29274
|
|
|
29067
29275
|
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.`,
|
|
29068
|
-
inputSchema:
|
|
29276
|
+
inputSchema: z28.object({
|
|
29069
29277
|
map: MAP_ARG,
|
|
29070
|
-
feature:
|
|
29071
|
-
flow:
|
|
29072
|
-
file:
|
|
29278
|
+
feature: z28.string().trim().max(120).optional().describe("A feature id or part of its name."),
|
|
29279
|
+
flow: z28.string().trim().max(120).optional().describe("A flow id or part of its name."),
|
|
29280
|
+
file: z28.string().trim().max(500).optional().describe("A file path or route; returns every flow that uses it."),
|
|
29073
29281
|
context: CONTEXT_ARG
|
|
29074
29282
|
}),
|
|
29075
29283
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29095,7 +29303,7 @@ Changes, one per call:
|
|
|
29095
29303
|
- { op: "remove-flow", feature, flow }
|
|
29096
29304
|
|
|
29097
29305
|
\`feature\` and \`flow\` take an id or an exact name. Write the returned file to ${FEATURE_MAP_PATH}.`,
|
|
29098
|
-
inputSchema:
|
|
29306
|
+
inputSchema: z28.object({
|
|
29099
29307
|
map: MAP_ARG,
|
|
29100
29308
|
change: featureMapChangeSchema,
|
|
29101
29309
|
context: CONTEXT_ARG
|
|
@@ -29117,7 +29325,7 @@ var init_feature_map_tools = __esm({
|
|
|
29117
29325
|
"use strict";
|
|
29118
29326
|
init_feature_map();
|
|
29119
29327
|
init_shared();
|
|
29120
|
-
MAP_ARG =
|
|
29328
|
+
MAP_ARG = z28.string().min(1).max(2e6).describe(`The exact contents of ${FEATURE_MAP_PATH}.`);
|
|
29121
29329
|
text2 = (body, isError = false) => ({
|
|
29122
29330
|
content: [{ type: "text", text: body }],
|
|
29123
29331
|
isError
|
|
@@ -29139,7 +29347,7 @@ var init_feature_map_tools = __esm({
|
|
|
29139
29347
|
});
|
|
29140
29348
|
|
|
29141
29349
|
// ../shared/src/mcp/inspiration.ts
|
|
29142
|
-
import { z as
|
|
29350
|
+
import { z as z29 } from "zod";
|
|
29143
29351
|
function describeSet(set) {
|
|
29144
29352
|
const heading = `## ${set.title}${set.category ? ` (${set.category})` : ""}`;
|
|
29145
29353
|
if (!set.notes) return heading;
|
|
@@ -29160,40 +29368,40 @@ var init_inspiration = __esm({
|
|
|
29160
29368
|
"use strict";
|
|
29161
29369
|
INSPIRATION_TYPES = ["app-store-screenshots"];
|
|
29162
29370
|
MAX_INSPIRATION_LIMIT = 6;
|
|
29163
|
-
noteSchema =
|
|
29164
|
-
slug:
|
|
29371
|
+
noteSchema = z29.object({
|
|
29372
|
+
slug: z29.string(),
|
|
29165
29373
|
/** The day the note was written, as YYYY-MM-DD. A listing can change after it. */
|
|
29166
|
-
writtenAt:
|
|
29167
|
-
headline:
|
|
29168
|
-
order:
|
|
29169
|
-
framing:
|
|
29170
|
-
color:
|
|
29374
|
+
writtenAt: z29.string(),
|
|
29375
|
+
headline: z29.string(),
|
|
29376
|
+
order: z29.string(),
|
|
29377
|
+
framing: z29.string(),
|
|
29378
|
+
color: z29.string()
|
|
29171
29379
|
});
|
|
29172
|
-
inspirationNotesFileSchema =
|
|
29173
|
-
model:
|
|
29174
|
-
notes:
|
|
29380
|
+
inspirationNotesFileSchema = z29.object({
|
|
29381
|
+
model: z29.string(),
|
|
29382
|
+
notes: z29.array(noteSchema)
|
|
29175
29383
|
});
|
|
29176
|
-
indexFileSchema =
|
|
29177
|
-
key:
|
|
29178
|
-
type:
|
|
29179
|
-
width:
|
|
29180
|
-
height:
|
|
29384
|
+
indexFileSchema = z29.object({
|
|
29385
|
+
key: z29.string(),
|
|
29386
|
+
type: z29.string(),
|
|
29387
|
+
width: z29.number(),
|
|
29388
|
+
height: z29.number()
|
|
29181
29389
|
});
|
|
29182
|
-
indexEntrySchema =
|
|
29183
|
-
title:
|
|
29184
|
-
slug:
|
|
29185
|
-
category:
|
|
29186
|
-
files:
|
|
29390
|
+
indexEntrySchema = z29.object({
|
|
29391
|
+
title: z29.string(),
|
|
29392
|
+
slug: z29.string(),
|
|
29393
|
+
category: z29.string().nullish(),
|
|
29394
|
+
files: z29.array(indexFileSchema)
|
|
29187
29395
|
});
|
|
29188
|
-
inspirationIndexSchema =
|
|
29189
|
-
entries:
|
|
29396
|
+
inspirationIndexSchema = z29.object({
|
|
29397
|
+
entries: z29.array(indexEntrySchema)
|
|
29190
29398
|
});
|
|
29191
29399
|
NOTE_DATE = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
|
|
29192
29400
|
}
|
|
29193
29401
|
});
|
|
29194
29402
|
|
|
29195
29403
|
// ../shared/src/mcp/tools/inspiration-tools.ts
|
|
29196
|
-
import { z as
|
|
29404
|
+
import { z as z30 } from "zod";
|
|
29197
29405
|
function describeSets(result) {
|
|
29198
29406
|
const lines = result.sets.map((set, i) => {
|
|
29199
29407
|
const category = set.category ? `, ${set.category}` : "";
|
|
@@ -29221,10 +29429,10 @@ function registerInspirationTools({ server, tracked }, readInspiration2) {
|
|
|
29221
29429
|
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.
|
|
29222
29430
|
|
|
29223
29431
|
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.`,
|
|
29224
|
-
inputSchema:
|
|
29225
|
-
type:
|
|
29226
|
-
category:
|
|
29227
|
-
limit:
|
|
29432
|
+
inputSchema: z30.object({
|
|
29433
|
+
type: z30.enum(INSPIRATION_TYPES).describe('The kind of reference to send. Today: "app-store-screenshots".'),
|
|
29434
|
+
category: z30.string().trim().min(1).max(60).optional().describe('Optional App Store category, for example "Productivity" or "Health & Fitness". Ignores case.'),
|
|
29435
|
+
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.`),
|
|
29228
29436
|
context: CONTEXT_ARG
|
|
29229
29437
|
}),
|
|
29230
29438
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29261,8 +29469,62 @@ var init_inspiration_tools = __esm({
|
|
|
29261
29469
|
}
|
|
29262
29470
|
});
|
|
29263
29471
|
|
|
29472
|
+
// ../shared/src/mcp/tools/feedback-tools.ts
|
|
29473
|
+
function createFeedbackSender(apiFetch, apiKey) {
|
|
29474
|
+
return async (report) => {
|
|
29475
|
+
const res = await apiFetch(`${APP_BASE_URL}/api/v1/feedback`, {
|
|
29476
|
+
method: "POST",
|
|
29477
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
29478
|
+
body: JSON.stringify(report),
|
|
29479
|
+
signal: AbortSignal.timeout(15e3)
|
|
29480
|
+
});
|
|
29481
|
+
const body = await res.json().catch(() => ({}));
|
|
29482
|
+
if (!res.ok || !body.id) throw new Error(body.message ?? `HTTP ${res.status}`);
|
|
29483
|
+
return { id: body.id, status: body.status ?? "open", message: body.message ?? "" };
|
|
29484
|
+
};
|
|
29485
|
+
}
|
|
29486
|
+
function registerFeedbackTools({ server, tracked }, sendFeedback) {
|
|
29487
|
+
if (!sendFeedback) return;
|
|
29488
|
+
server.registerTool(
|
|
29489
|
+
"send-feedback",
|
|
29490
|
+
{
|
|
29491
|
+
title: "Send Feedback",
|
|
29492
|
+
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.
|
|
29493
|
+
|
|
29494
|
+
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.`,
|
|
29495
|
+
inputSchema: reportSchema,
|
|
29496
|
+
// Each call stores one new report and publishes nothing. See
|
|
29497
|
+
// `docs/mcp-tool-annotations.md`.
|
|
29498
|
+
annotations: {
|
|
29499
|
+
readOnlyHint: false,
|
|
29500
|
+
destructiveHint: false,
|
|
29501
|
+
idempotentHint: false,
|
|
29502
|
+
openWorldHint: false
|
|
29503
|
+
}
|
|
29504
|
+
},
|
|
29505
|
+
// Usage telemetry records the kind only. The report itself goes to the queue.
|
|
29506
|
+
async (report) => tracked("send-feedback", { kind: report.kind }, async () => {
|
|
29507
|
+
const info = server.server.getClientVersion();
|
|
29508
|
+
const client = info ? `${info.name}/${info.version}`.slice(0, AGENT_FEEDBACK_CLIENT_MAX) : void 0;
|
|
29509
|
+
const receipt = await sendFeedback({ ...report, client });
|
|
29510
|
+
return {
|
|
29511
|
+
content: [{ type: "text", text: `Report ${receipt.id} sent. ${receipt.message}` }]
|
|
29512
|
+
};
|
|
29513
|
+
})
|
|
29514
|
+
);
|
|
29515
|
+
}
|
|
29516
|
+
var reportSchema;
|
|
29517
|
+
var init_feedback_tools = __esm({
|
|
29518
|
+
"../shared/src/mcp/tools/feedback-tools.ts"() {
|
|
29519
|
+
"use strict";
|
|
29520
|
+
init_agent_feedback();
|
|
29521
|
+
init_shared();
|
|
29522
|
+
reportSchema = agentFeedbackInputSchema.omit({ client: true });
|
|
29523
|
+
}
|
|
29524
|
+
});
|
|
29525
|
+
|
|
29264
29526
|
// ../shared/src/mcp/tools.ts
|
|
29265
|
-
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2) {
|
|
29527
|
+
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2, sendFeedback) {
|
|
29266
29528
|
const { track, tracked } = createTracking(server, onToolUsage);
|
|
29267
29529
|
const deps = {
|
|
29268
29530
|
server,
|
|
@@ -29289,6 +29551,7 @@ function registerAllTools(server, provider, onToolUsage, authContext, options =
|
|
|
29289
29551
|
registerFeatureMapTools(deps);
|
|
29290
29552
|
registerTeamRulesTools(deps);
|
|
29291
29553
|
registerInspirationTools(deps, readInspiration2);
|
|
29554
|
+
registerFeedbackTools(deps, sendFeedback);
|
|
29292
29555
|
registerPreviewResource(deps);
|
|
29293
29556
|
registerInspectionResource(deps);
|
|
29294
29557
|
}
|
|
@@ -29313,6 +29576,7 @@ var init_tools = __esm({
|
|
|
29313
29576
|
init_feature_map_tools();
|
|
29314
29577
|
init_team_rules_tools();
|
|
29315
29578
|
init_inspiration_tools();
|
|
29579
|
+
init_feedback_tools();
|
|
29316
29580
|
init_shared();
|
|
29317
29581
|
}
|
|
29318
29582
|
});
|
|
@@ -29322,7 +29586,7 @@ var MCP_IMPLEMENTATION_VERSION;
|
|
|
29322
29586
|
var init_version2 = __esm({
|
|
29323
29587
|
"../shared/src/mcp/version.ts"() {
|
|
29324
29588
|
"use strict";
|
|
29325
|
-
MCP_IMPLEMENTATION_VERSION = "0.
|
|
29589
|
+
MCP_IMPLEMENTATION_VERSION = "0.35.0";
|
|
29326
29590
|
}
|
|
29327
29591
|
});
|
|
29328
29592
|
|
|
@@ -29355,6 +29619,7 @@ __export(mcp_exports, {
|
|
|
29355
29619
|
analyzeDashboardStructure: () => analyzeDashboardStructure,
|
|
29356
29620
|
buildShopifyPrimitiveSet: () => buildShopifyPrimitiveSet,
|
|
29357
29621
|
canonicalMcpToolName: () => canonicalMcpToolName,
|
|
29622
|
+
createFeedbackSender: () => createFeedbackSender,
|
|
29358
29623
|
createMcpServer: () => createMcpServer,
|
|
29359
29624
|
declareAdvertisedSchemaDialect: () => declareAdvertisedSchemaDialect,
|
|
29360
29625
|
deriveDashboardInterpretation: () => deriveDashboardInterpretation,
|
|
@@ -29388,7 +29653,8 @@ function createMcpServer(options) {
|
|
|
29388
29653
|
userId,
|
|
29389
29654
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
29390
29655
|
directoryListing = false,
|
|
29391
|
-
readInspiration: readInspiration2
|
|
29656
|
+
readInspiration: readInspiration2,
|
|
29657
|
+
sendFeedback
|
|
29392
29658
|
} = options;
|
|
29393
29659
|
const server = new McpServer(
|
|
29394
29660
|
{ name, version: version2 },
|
|
@@ -29416,7 +29682,8 @@ function createMcpServer(options) {
|
|
|
29416
29682
|
apiFetch,
|
|
29417
29683
|
userId,
|
|
29418
29684
|
captureDashboardBrowser2,
|
|
29419
|
-
readInspiration2
|
|
29685
|
+
readInspiration2,
|
|
29686
|
+
sendFeedback
|
|
29420
29687
|
);
|
|
29421
29688
|
return server;
|
|
29422
29689
|
}
|
|
@@ -29433,6 +29700,7 @@ var init_mcp = __esm({
|
|
|
29433
29700
|
init_advertised_schema();
|
|
29434
29701
|
init_tool_names();
|
|
29435
29702
|
init_principle_tools();
|
|
29703
|
+
init_feedback_tools();
|
|
29436
29704
|
init_instructions();
|
|
29437
29705
|
init_session_guidance();
|
|
29438
29706
|
init_shared();
|
|
@@ -29461,6 +29729,7 @@ __export(api_client_exports, {
|
|
|
29461
29729
|
searchIconLibraries: () => searchIconLibraries,
|
|
29462
29730
|
searchIcons: () => searchIcons,
|
|
29463
29731
|
searchPrinciples: () => searchPrinciples,
|
|
29732
|
+
sendAgentFeedback: () => sendAgentFeedback,
|
|
29464
29733
|
trackMcpInstalled: () => trackMcpInstalled,
|
|
29465
29734
|
trackToolUsage: () => trackToolUsage
|
|
29466
29735
|
});
|
|
@@ -29500,6 +29769,9 @@ function readInspiration(input) {
|
|
|
29500
29769
|
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
29501
29770
|
return request(`/v1/inspiration?${params}`, { timeoutMs: 3e4 });
|
|
29502
29771
|
}
|
|
29772
|
+
function sendAgentFeedback(report) {
|
|
29773
|
+
return request("/v1/feedback", { method: "POST", body: report });
|
|
29774
|
+
}
|
|
29503
29775
|
async function searchDesignSystems(query, limit = 5) {
|
|
29504
29776
|
return request(
|
|
29505
29777
|
`/v1/design-systems/search?q=${encodeURIComponent(query)}&limit=${limit}`
|
|
@@ -31476,7 +31748,7 @@ var init_engine = __esm({
|
|
|
31476
31748
|
});
|
|
31477
31749
|
|
|
31478
31750
|
// ../shared/src/lint/config.ts
|
|
31479
|
-
import { z as
|
|
31751
|
+
import { z as z31 } from "zod";
|
|
31480
31752
|
function mergeReviewConfig(override) {
|
|
31481
31753
|
if (!override) return DEFAULT_REVIEW_CONFIG;
|
|
31482
31754
|
return {
|
|
@@ -31534,47 +31806,47 @@ var init_config = __esm({
|
|
|
31534
31806
|
semanticTokens: { enabled: true }
|
|
31535
31807
|
}
|
|
31536
31808
|
};
|
|
31537
|
-
reviewFlowStepSchema =
|
|
31538
|
-
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31809
|
+
reviewFlowStepSchema = z31.discriminatedUnion("action", [
|
|
31810
|
+
z31.object({ action: z31.literal("click"), selector: z31.string() }),
|
|
31811
|
+
z31.object({ action: z31.literal("type"), selector: z31.string(), value: z31.string() }),
|
|
31812
|
+
z31.object({ action: z31.literal("press"), key: z31.string() }),
|
|
31813
|
+
z31.object({ action: z31.literal("wait"), milliseconds: z31.number().finite().nonnegative() })
|
|
31542
31814
|
]);
|
|
31543
|
-
visualReviewConfigSchema =
|
|
31544
|
-
enabled:
|
|
31545
|
-
model:
|
|
31546
|
-
enabled:
|
|
31547
|
-
maxScreenshots:
|
|
31815
|
+
visualReviewConfigSchema = z31.object({
|
|
31816
|
+
enabled: z31.boolean().optional(),
|
|
31817
|
+
model: z31.object({
|
|
31818
|
+
enabled: z31.boolean().optional(),
|
|
31819
|
+
maxScreenshots: z31.number().int().nonnegative().optional()
|
|
31548
31820
|
}).optional(),
|
|
31549
|
-
routes:
|
|
31550
|
-
viewports:
|
|
31551
|
-
flows:
|
|
31552
|
-
name:
|
|
31553
|
-
persona:
|
|
31554
|
-
path:
|
|
31555
|
-
steps:
|
|
31821
|
+
routes: z31.array(z31.string()).optional(),
|
|
31822
|
+
viewports: z31.array(z31.enum(["desktop", "mobile", "reflow"])).optional(),
|
|
31823
|
+
flows: z31.array(z31.object({
|
|
31824
|
+
name: z31.string(),
|
|
31825
|
+
persona: z31.string(),
|
|
31826
|
+
path: z31.string().optional(),
|
|
31827
|
+
steps: z31.array(reviewFlowStepSchema)
|
|
31556
31828
|
})).optional(),
|
|
31557
|
-
componentGallery:
|
|
31558
|
-
enabled:
|
|
31559
|
-
componentSelector:
|
|
31560
|
-
canvasSelector:
|
|
31561
|
-
labelSelector:
|
|
31562
|
-
maxComponents:
|
|
31829
|
+
componentGallery: z31.object({
|
|
31830
|
+
enabled: z31.boolean().optional(),
|
|
31831
|
+
componentSelector: z31.string().optional(),
|
|
31832
|
+
canvasSelector: z31.string().optional(),
|
|
31833
|
+
labelSelector: z31.string().optional(),
|
|
31834
|
+
maxComponents: z31.number().int().nonnegative().optional()
|
|
31563
31835
|
}).optional()
|
|
31564
31836
|
});
|
|
31565
|
-
reviewConfigOverrideSchema =
|
|
31566
|
-
designSystem:
|
|
31837
|
+
reviewConfigOverrideSchema = z31.object({
|
|
31838
|
+
designSystem: z31.string().optional(),
|
|
31567
31839
|
visual: visualReviewConfigSchema.optional(),
|
|
31568
|
-
rules:
|
|
31569
|
-
elements:
|
|
31570
|
-
tokens:
|
|
31571
|
-
wcag:
|
|
31572
|
-
enabled:
|
|
31573
|
-
severity:
|
|
31840
|
+
rules: z31.object({
|
|
31841
|
+
elements: z31.record(z31.string(), z31.object({ use: z31.string(), from: z31.string() })).optional(),
|
|
31842
|
+
tokens: z31.record(z31.string(), z31.string()).optional(),
|
|
31843
|
+
wcag: z31.object({
|
|
31844
|
+
enabled: z31.boolean().optional(),
|
|
31845
|
+
severity: z31.enum(["all", "critical+serious"]).optional()
|
|
31574
31846
|
}).optional(),
|
|
31575
|
-
slop:
|
|
31576
|
-
copy:
|
|
31577
|
-
semanticTokens:
|
|
31847
|
+
slop: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31848
|
+
copy: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31849
|
+
semanticTokens: z31.object({ enabled: z31.boolean().optional() }).optional()
|
|
31578
31850
|
}).optional()
|
|
31579
31851
|
});
|
|
31580
31852
|
}
|
|
@@ -31950,7 +32222,7 @@ __export(review_tool_exports, {
|
|
|
31950
32222
|
isReviewableSourcePath: () => isReviewableSourcePath,
|
|
31951
32223
|
registerReviewTool: () => registerReviewTool
|
|
31952
32224
|
});
|
|
31953
|
-
import { z as
|
|
32225
|
+
import { z as z32 } from "zod";
|
|
31954
32226
|
function isReviewableSourcePath(path6) {
|
|
31955
32227
|
return /\.(tsx|jsx|ts|js|css)$/i.test(path6);
|
|
31956
32228
|
}
|
|
@@ -31974,11 +32246,11 @@ get-review-rules gives you the judgment rules to apply yourself. This tool is th
|
|
|
31974
32246
|
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.
|
|
31975
32247
|
|
|
31976
32248
|
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.`,
|
|
31977
|
-
inputSchema:
|
|
31978
|
-
files:
|
|
31979
|
-
|
|
31980
|
-
path:
|
|
31981
|
-
content:
|
|
32249
|
+
inputSchema: z32.object({
|
|
32250
|
+
files: z32.array(
|
|
32251
|
+
z32.object({
|
|
32252
|
+
path: z32.string().trim().min(1).max(400).describe("Repo-relative file path, e.g. components/signup-form.tsx."),
|
|
32253
|
+
content: z32.string().max(MAX_FILE_CHARS).describe("The file's full source.")
|
|
31982
32254
|
})
|
|
31983
32255
|
).min(1).max(MAX_FILES).describe("The .tsx, .jsx, .ts, .js, and .css files to review, as written."),
|
|
31984
32256
|
context: CONTEXT_ARG
|
|
@@ -32039,7 +32311,7 @@ review-ui-code({ files: [{ path: "components/signup-form.tsx", content: "..." },
|
|
|
32039
32311
|
});
|
|
32040
32312
|
|
|
32041
32313
|
// ../shared/src/mcp/figma-prototype.ts
|
|
32042
|
-
import { z as
|
|
32314
|
+
import { z as z33 } from "zod";
|
|
32043
32315
|
function escapeHtml(value) {
|
|
32044
32316
|
return value.replace(/[&<>"]/g, (char2) => ESCAPES.get(char2) ?? char2);
|
|
32045
32317
|
}
|
|
@@ -32073,63 +32345,63 @@ var colorSchema, paintSchema, sizingSchema, baseSchema, prototypeNodeSchema, pro
|
|
|
32073
32345
|
var init_figma_prototype = __esm({
|
|
32074
32346
|
"../shared/src/mcp/figma-prototype.ts"() {
|
|
32075
32347
|
"use strict";
|
|
32076
|
-
colorSchema =
|
|
32077
|
-
paintSchema =
|
|
32078
|
-
sizingSchema =
|
|
32079
|
-
baseSchema =
|
|
32080
|
-
name:
|
|
32081
|
-
width:
|
|
32082
|
-
height:
|
|
32083
|
-
x:
|
|
32084
|
-
y:
|
|
32085
|
-
sizing:
|
|
32086
|
-
absolute:
|
|
32087
|
-
opacity:
|
|
32348
|
+
colorSchema = z33.object({ r: z33.number(), g: z33.number(), b: z33.number(), a: z33.number() });
|
|
32349
|
+
paintSchema = z33.object({ variable: z33.string().optional(), color: colorSchema.optional() });
|
|
32350
|
+
sizingSchema = z33.enum(["FIXED", "HUG", "FILL"]);
|
|
32351
|
+
baseSchema = z33.object({
|
|
32352
|
+
name: z33.string(),
|
|
32353
|
+
width: z33.number(),
|
|
32354
|
+
height: z33.number(),
|
|
32355
|
+
x: z33.number(),
|
|
32356
|
+
y: z33.number(),
|
|
32357
|
+
sizing: z33.object({ horizontal: sizingSchema, vertical: sizingSchema }).optional(),
|
|
32358
|
+
absolute: z33.boolean().optional(),
|
|
32359
|
+
opacity: z33.number().optional()
|
|
32088
32360
|
});
|
|
32089
|
-
prototypeNodeSchema =
|
|
32361
|
+
prototypeNodeSchema = z33.lazy(() => z33.discriminatedUnion("kind", [
|
|
32090
32362
|
baseSchema.extend({
|
|
32091
|
-
kind:
|
|
32092
|
-
auditKey:
|
|
32093
|
-
systemId:
|
|
32094
|
-
texts:
|
|
32095
|
-
mainTexts:
|
|
32363
|
+
kind: z33.literal("instance"),
|
|
32364
|
+
auditKey: z33.string(),
|
|
32365
|
+
systemId: z33.string(),
|
|
32366
|
+
texts: z33.array(z33.string()),
|
|
32367
|
+
mainTexts: z33.array(z33.string())
|
|
32096
32368
|
}),
|
|
32097
32369
|
baseSchema.extend({
|
|
32098
|
-
kind:
|
|
32099
|
-
layout:
|
|
32100
|
-
direction:
|
|
32101
|
-
gap:
|
|
32102
|
-
padding:
|
|
32103
|
-
primary:
|
|
32104
|
-
counter:
|
|
32105
|
-
wrap:
|
|
32370
|
+
kind: z33.literal("frame"),
|
|
32371
|
+
layout: z33.object({
|
|
32372
|
+
direction: z33.enum(["HORIZONTAL", "VERTICAL"]),
|
|
32373
|
+
gap: z33.number(),
|
|
32374
|
+
padding: z33.tuple([z33.number(), z33.number(), z33.number(), z33.number()]),
|
|
32375
|
+
primary: z33.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]),
|
|
32376
|
+
counter: z33.enum(["MIN", "CENTER", "MAX", "BASELINE"]),
|
|
32377
|
+
wrap: z33.boolean().optional()
|
|
32106
32378
|
}).optional(),
|
|
32107
32379
|
fill: paintSchema.optional(),
|
|
32108
|
-
stroke:
|
|
32109
|
-
radius:
|
|
32110
|
-
clips:
|
|
32111
|
-
children:
|
|
32380
|
+
stroke: z33.object({ paint: paintSchema, weight: z33.number() }).optional(),
|
|
32381
|
+
radius: z33.number().optional(),
|
|
32382
|
+
clips: z33.boolean().optional(),
|
|
32383
|
+
children: z33.array(prototypeNodeSchema)
|
|
32112
32384
|
}),
|
|
32113
32385
|
baseSchema.extend({
|
|
32114
|
-
kind:
|
|
32115
|
-
characters:
|
|
32116
|
-
fontSize:
|
|
32117
|
-
fontWeight:
|
|
32118
|
-
lineHeight:
|
|
32119
|
-
letterSpacing:
|
|
32120
|
-
fontFamily:
|
|
32121
|
-
align:
|
|
32386
|
+
kind: z33.literal("text"),
|
|
32387
|
+
characters: z33.string(),
|
|
32388
|
+
fontSize: z33.number(),
|
|
32389
|
+
fontWeight: z33.number(),
|
|
32390
|
+
lineHeight: z33.number().optional(),
|
|
32391
|
+
letterSpacing: z33.number().optional(),
|
|
32392
|
+
fontFamily: z33.string().optional(),
|
|
32393
|
+
align: z33.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]),
|
|
32122
32394
|
fill: paintSchema.optional()
|
|
32123
32395
|
}),
|
|
32124
32396
|
baseSchema.extend({
|
|
32125
|
-
kind:
|
|
32126
|
-
svg:
|
|
32127
|
-
reason:
|
|
32397
|
+
kind: z33.literal("shape"),
|
|
32398
|
+
svg: z33.string(),
|
|
32399
|
+
reason: z33.string()
|
|
32128
32400
|
})
|
|
32129
32401
|
]));
|
|
32130
|
-
prototypeRequestSchema =
|
|
32131
|
-
type:
|
|
32132
|
-
id:
|
|
32402
|
+
prototypeRequestSchema = z33.object({
|
|
32403
|
+
type: z33.literal("better-design-figma-prototype"),
|
|
32404
|
+
id: z33.string(),
|
|
32133
32405
|
frame: prototypeNodeSchema
|
|
32134
32406
|
});
|
|
32135
32407
|
ESCAPES = /* @__PURE__ */ new Map([["&", "&"], ["<", "<"], [">", ">"], ['"', """]]);
|
|
@@ -32315,7 +32587,7 @@ var init_figma_model_prompt = __esm({
|
|
|
32315
32587
|
});
|
|
32316
32588
|
|
|
32317
32589
|
// ../shared/src/mcp/figma-model.ts
|
|
32318
|
-
import { z as
|
|
32590
|
+
import { z as z34 } from "zod";
|
|
32319
32591
|
function fenced(text3, language) {
|
|
32320
32592
|
const match = text3.match(new RegExp("```" + language + "[^\\n]*\\n([\\s\\S]*?)```", "i"));
|
|
32321
32593
|
return match ? match[1] : null;
|
|
@@ -32521,8 +32793,8 @@ var init_figma_model = __esm({
|
|
|
32521
32793
|
init_figma_prototype();
|
|
32522
32794
|
init_html_kit();
|
|
32523
32795
|
init_preview_html();
|
|
32524
|
-
unplacedSchema =
|
|
32525
|
-
unplaced:
|
|
32796
|
+
unplacedSchema = z34.object({
|
|
32797
|
+
unplaced: z34.array(z34.object({ name: z34.string(), reason: z34.string() }))
|
|
32526
32798
|
});
|
|
32527
32799
|
UNSAFE_BLOCKS = /<(script|iframe|object|embed|noscript)\b[\s\S]*?(<\/\1[^>]*>|$)/gi;
|
|
32528
32800
|
UNSAFE_TAGS = /<\/?(meta|link|base|form|style)\b[^>]*(>|$)/gi;
|
|
@@ -32623,7 +32895,7 @@ var init_figma_model_handoff = __esm({
|
|
|
32623
32895
|
});
|
|
32624
32896
|
|
|
32625
32897
|
// ../shared/src/mcp/figma-model-providers.ts
|
|
32626
|
-
import { z as
|
|
32898
|
+
import { z as z35 } from "zod";
|
|
32627
32899
|
function serverLabel(url) {
|
|
32628
32900
|
try {
|
|
32629
32901
|
const parsed = new URL(url);
|
|
@@ -32716,19 +32988,19 @@ var init_figma_model_providers = __esm({
|
|
|
32716
32988
|
DEFAULT_LOCAL_MODEL = "gemma4:latest";
|
|
32717
32989
|
DEFAULT_LOCAL_BASE_URL = "http://localhost:11434/v1";
|
|
32718
32990
|
MAX_OUTPUT_TOKENS = 16e3;
|
|
32719
|
-
chatReplySchema =
|
|
32720
|
-
choices:
|
|
32721
|
-
message:
|
|
32722
|
-
finish_reason:
|
|
32991
|
+
chatReplySchema = z35.object({
|
|
32992
|
+
choices: z35.array(z35.object({
|
|
32993
|
+
message: z35.object({ content: z35.string().nullable().optional() }),
|
|
32994
|
+
finish_reason: z35.string().nullable().optional()
|
|
32723
32995
|
})).min(1),
|
|
32724
|
-
usage:
|
|
32996
|
+
usage: z35.object({ prompt_tokens: z35.number(), completion_tokens: z35.number() }).optional()
|
|
32725
32997
|
});
|
|
32726
|
-
errorCodeSchema =
|
|
32998
|
+
errorCodeSchema = z35.object({ code: z35.string() });
|
|
32727
32999
|
}
|
|
32728
33000
|
});
|
|
32729
33001
|
|
|
32730
33002
|
// ../shared/src/mcp/figma-relay.ts
|
|
32731
|
-
import { z as
|
|
33003
|
+
import { z as z36 } from "zod";
|
|
32732
33004
|
function noPluginMessage() {
|
|
32733
33005
|
return "No Figma plugin is connected. Open the Figma file, run Plugins, Development, Better Design, and leave the panel open.";
|
|
32734
33006
|
}
|
|
@@ -32742,35 +33014,35 @@ var init_figma_relay = __esm({
|
|
|
32742
33014
|
"use strict";
|
|
32743
33015
|
init_figma_prototype();
|
|
32744
33016
|
FIGMA_RELAY_PORT = 4397;
|
|
32745
|
-
relayRequestSchema =
|
|
32746
|
-
id:
|
|
33017
|
+
relayRequestSchema = z36.object({
|
|
33018
|
+
id: z36.string(),
|
|
32747
33019
|
/** A frame name as it reads in Figma. Omitted means the current selection. */
|
|
32748
|
-
frame:
|
|
33020
|
+
frame: z36.string().optional(),
|
|
32749
33021
|
/**
|
|
32750
33022
|
* `model` asks for the serialized frame and its PNG instead of a rendered
|
|
32751
33023
|
* page, so a model can rebuild a frame that holds no library component.
|
|
32752
33024
|
* Omitted means the library path, unchanged. [BD-866]
|
|
32753
33025
|
*/
|
|
32754
|
-
mode:
|
|
33026
|
+
mode: z36.enum(["library", "model"]).optional()
|
|
32755
33027
|
});
|
|
32756
|
-
relayResultSchema =
|
|
32757
|
-
id:
|
|
32758
|
-
html:
|
|
32759
|
-
error:
|
|
32760
|
-
components:
|
|
32761
|
-
report:
|
|
32762
|
-
frame:
|
|
33028
|
+
relayResultSchema = z36.object({
|
|
33029
|
+
id: z36.string(),
|
|
33030
|
+
html: z36.string().optional(),
|
|
33031
|
+
error: z36.string().optional(),
|
|
33032
|
+
components: z36.number().optional(),
|
|
33033
|
+
report: z36.array(z36.object({ name: z36.string(), reason: z36.string() })).default([]),
|
|
33034
|
+
frame: z36.string().optional(),
|
|
32763
33035
|
/** The frames the plugin found, so a missing name can say what is there. */
|
|
32764
|
-
frames:
|
|
33036
|
+
frames: z36.array(z36.string()).optional(),
|
|
32765
33037
|
/** Model mode: the serialized frame, for the model to rebuild. */
|
|
32766
33038
|
tree: prototypeNodeSchema.optional(),
|
|
32767
33039
|
/** Model mode: the frame's PNG export, base64. */
|
|
32768
|
-
png:
|
|
33040
|
+
png: z36.string().optional()
|
|
32769
33041
|
});
|
|
32770
|
-
relayHelloSchema =
|
|
32771
|
-
file:
|
|
33042
|
+
relayHelloSchema = z36.object({
|
|
33043
|
+
file: z36.string(),
|
|
32772
33044
|
/** The design system the file holds, when the plugin knows it. */
|
|
32773
|
-
system:
|
|
33045
|
+
system: z36.string().optional()
|
|
32774
33046
|
});
|
|
32775
33047
|
POLL_HOLD_MS = 25e3;
|
|
32776
33048
|
REQUEST_TIMEOUT_MS = 3e5;
|
|
@@ -32938,7 +33210,7 @@ __export(figma_prototype_tool_exports, {
|
|
|
32938
33210
|
});
|
|
32939
33211
|
import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
|
|
32940
33212
|
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
32941
|
-
import { z as
|
|
33213
|
+
import { z as z37 } from "zod";
|
|
32942
33214
|
async function ensureRelay() {
|
|
32943
33215
|
if (relay) return relay;
|
|
32944
33216
|
starting ??= startFigmaRelay().then((started) => {
|
|
@@ -32998,30 +33270,30 @@ function registerFigmaPrototypeTool(server) {
|
|
|
32998
33270
|
"yourself: the tool returns a prompt file and the frame's image, you write the answer to a file, and",
|
|
32999
33271
|
"you call this tool again with frameId and answerPath so it checks the page and reports what is missing."
|
|
33000
33272
|
].join(" "),
|
|
33001
|
-
inputSchema:
|
|
33002
|
-
frame:
|
|
33003
|
-
outputPath:
|
|
33004
|
-
mode:
|
|
33273
|
+
inputSchema: z37.object({
|
|
33274
|
+
frame: z37.string().optional().describe("The frame's name as it reads in Figma. Omit to use the current selection."),
|
|
33275
|
+
outputPath: z37.string().optional().describe("Where to write the HTML. Relative paths resolve against the working directory."),
|
|
33276
|
+
mode: z37.enum(["library", "model"]).optional().describe(
|
|
33005
33277
|
"library (the default) maps components from a Better Design library. model asks a model to rebuild a frame from any file."
|
|
33006
33278
|
),
|
|
33007
|
-
system:
|
|
33008
|
-
provider:
|
|
33279
|
+
system: z37.string().optional().describe("Model mode: the catalog design system to build with, as find-design-system names it."),
|
|
33280
|
+
provider: z37.enum(["agent", "openai-compatible"]).optional().describe(
|
|
33009
33281
|
"Model mode: agent (the default) hands the frame to you to rebuild; openai-compatible calls a local server, Ollama by default."
|
|
33010
33282
|
),
|
|
33011
|
-
model:
|
|
33012
|
-
frameId:
|
|
33013
|
-
answerPath:
|
|
33283
|
+
model: z37.string().optional().describe(`Model mode with openai-compatible: the local model id. Defaults to ${DEFAULT_LOCAL_MODEL}.`),
|
|
33284
|
+
frameId: z37.string().optional().describe("Model mode, second call: the frameId the first call returned."),
|
|
33285
|
+
answerPath: z37.string().optional().describe("Model mode, second call: the file holding your answer, as the prompt file asks.")
|
|
33014
33286
|
}),
|
|
33015
|
-
outputSchema:
|
|
33016
|
-
status:
|
|
33017
|
-
message:
|
|
33018
|
-
frameId:
|
|
33019
|
-
promptPath:
|
|
33020
|
-
imagePath:
|
|
33021
|
-
path:
|
|
33022
|
-
components:
|
|
33023
|
-
report:
|
|
33024
|
-
model:
|
|
33287
|
+
outputSchema: z37.object({
|
|
33288
|
+
status: z37.enum(["completed", "failed", "needs-answer"]),
|
|
33289
|
+
message: z37.string(),
|
|
33290
|
+
frameId: z37.string().optional(),
|
|
33291
|
+
promptPath: z37.string().optional(),
|
|
33292
|
+
imagePath: z37.string().optional(),
|
|
33293
|
+
path: z37.string().optional(),
|
|
33294
|
+
components: z37.number().optional(),
|
|
33295
|
+
report: z37.array(z37.object({ name: z37.string(), reason: z37.string() })).optional(),
|
|
33296
|
+
model: z37.string().optional()
|
|
33025
33297
|
}),
|
|
33026
33298
|
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
33027
33299
|
},
|
|
@@ -33244,6 +33516,7 @@ async function startServer() {
|
|
|
33244
33516
|
const {
|
|
33245
33517
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33246
33518
|
readInspiration: readInspiration2,
|
|
33519
|
+
sendAgentFeedback: sendAgentFeedback2,
|
|
33247
33520
|
trackToolUsage: trackToolUsage2,
|
|
33248
33521
|
trackMcpInstalled: trackMcpInstalled2
|
|
33249
33522
|
} = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
@@ -33270,7 +33543,8 @@ async function startServer() {
|
|
|
33270
33543
|
version: MCP_IMPLEMENTATION_VERSION2,
|
|
33271
33544
|
onToolUsage,
|
|
33272
33545
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33273
|
-
readInspiration: readInspiration2
|
|
33546
|
+
readInspiration: readInspiration2,
|
|
33547
|
+
sendFeedback: sendAgentFeedback2
|
|
33274
33548
|
});
|
|
33275
33549
|
registerReviewTool2(server, onToolUsage);
|
|
33276
33550
|
registerFigmaPrototypeTool2(server);
|