better-design 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/index.js +668 -419
- 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.`,
|
|
@@ -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
|
|
@@ -22989,7 +23070,7 @@ function columnToSchema(column, factory) {
|
|
|
22989
23070
|
}
|
|
22990
23071
|
return schema;
|
|
22991
23072
|
}
|
|
22992
|
-
function numberColumnToSchema(column,
|
|
23073
|
+
function numberColumnToSchema(column, z38, coerce) {
|
|
22993
23074
|
let unsigned = column.getSQLType().includes("unsigned");
|
|
22994
23075
|
let min;
|
|
22995
23076
|
let max;
|
|
@@ -23057,20 +23138,20 @@ function numberColumnToSchema(column, z37, coerce) {
|
|
|
23057
23138
|
min = Number.MIN_SAFE_INTEGER;
|
|
23058
23139
|
max = Number.MAX_SAFE_INTEGER;
|
|
23059
23140
|
}
|
|
23060
|
-
let schema = coerce === true || coerce?.number ?
|
|
23141
|
+
let schema = coerce === true || coerce?.number ? z38.coerce.number() : z38.number();
|
|
23061
23142
|
schema = schema.min(min).max(max);
|
|
23062
23143
|
return integer2 ? schema.int() : schema;
|
|
23063
23144
|
}
|
|
23064
|
-
function bigintColumnToSchema(column,
|
|
23145
|
+
function bigintColumnToSchema(column, z38, coerce) {
|
|
23065
23146
|
const unsigned = column.getSQLType().includes("unsigned");
|
|
23066
23147
|
const min = unsigned ? 0n : CONSTANTS.INT64_MIN;
|
|
23067
23148
|
const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX;
|
|
23068
|
-
const schema = coerce === true || coerce?.bigint ?
|
|
23149
|
+
const schema = coerce === true || coerce?.bigint ? z38.coerce.bigint() : z38.bigint();
|
|
23069
23150
|
return schema.min(min).max(max);
|
|
23070
23151
|
}
|
|
23071
|
-
function stringColumnToSchema(column,
|
|
23152
|
+
function stringColumnToSchema(column, z38, coerce) {
|
|
23072
23153
|
if (isColumnType(column, ["PgUUID"])) {
|
|
23073
|
-
return
|
|
23154
|
+
return z38.string().uuid();
|
|
23074
23155
|
}
|
|
23075
23156
|
let max;
|
|
23076
23157
|
let regex;
|
|
@@ -23102,7 +23183,7 @@ function stringColumnToSchema(column, z37, coerce) {
|
|
|
23102
23183
|
regex = /^[01]+$/;
|
|
23103
23184
|
max = column.dimensions;
|
|
23104
23185
|
}
|
|
23105
|
-
let schema = coerce === true || coerce?.string ?
|
|
23186
|
+
let schema = coerce === true || coerce?.string ? z38.coerce.string() : z38.string();
|
|
23106
23187
|
schema = regex ? schema.regex(regex) : schema;
|
|
23107
23188
|
return max && fixed ? schema.length(max) : max ? schema.max(max) : schema;
|
|
23108
23189
|
}
|
|
@@ -23198,15 +23279,74 @@ var init_drizzle_zod = __esm({
|
|
|
23198
23279
|
}
|
|
23199
23280
|
});
|
|
23200
23281
|
|
|
23201
|
-
// ../shared/src/
|
|
23282
|
+
// ../shared/src/agent-feedback.ts
|
|
23202
23283
|
import { z as z16 } from "zod";
|
|
23203
|
-
var
|
|
23284
|
+
var agentFeedbackKindValues, TEXT_MAX, SURFACE_MAX, SUMMARY_MAX, AGENT_FEEDBACK_CLIENT_MAX, FIELD_DESCRIPTIONS, optionalText, agentFeedbackInputSchema, AGENT_FEEDBACK_CONTRACT;
|
|
23285
|
+
var init_agent_feedback = __esm({
|
|
23286
|
+
"../shared/src/agent-feedback.ts"() {
|
|
23287
|
+
"use strict";
|
|
23288
|
+
agentFeedbackKindValues = [
|
|
23289
|
+
"bug",
|
|
23290
|
+
"missing-feature",
|
|
23291
|
+
"wrong-output",
|
|
23292
|
+
"docs-gap",
|
|
23293
|
+
"other"
|
|
23294
|
+
];
|
|
23295
|
+
TEXT_MAX = 4e3;
|
|
23296
|
+
SURFACE_MAX = 200;
|
|
23297
|
+
SUMMARY_MAX = 500;
|
|
23298
|
+
AGENT_FEEDBACK_CLIENT_MAX = 100;
|
|
23299
|
+
FIELD_DESCRIPTIONS = {
|
|
23300
|
+
kind: "What kind of problem this is.",
|
|
23301
|
+
surface: "The endpoint path or MCP tool name you called.",
|
|
23302
|
+
summary: "One sentence: what is wrong or missing.",
|
|
23303
|
+
attempted: "What you tried to do, and the request you sent.",
|
|
23304
|
+
expected: "What you expected to get back.",
|
|
23305
|
+
actual: "What you got back instead.",
|
|
23306
|
+
client: "Your agent name and version."
|
|
23307
|
+
};
|
|
23308
|
+
optionalText = (description) => z16.string().trim().max(TEXT_MAX).optional().describe(description);
|
|
23309
|
+
agentFeedbackInputSchema = z16.object({
|
|
23310
|
+
kind: z16.enum(agentFeedbackKindValues).describe(FIELD_DESCRIPTIONS.kind),
|
|
23311
|
+
surface: z16.string().trim().min(1).max(SURFACE_MAX).describe(FIELD_DESCRIPTIONS.surface),
|
|
23312
|
+
summary: z16.string().trim().min(1).max(SUMMARY_MAX).describe(FIELD_DESCRIPTIONS.summary),
|
|
23313
|
+
attempted: optionalText(FIELD_DESCRIPTIONS.attempted),
|
|
23314
|
+
expected: optionalText(FIELD_DESCRIPTIONS.expected),
|
|
23315
|
+
actual: optionalText(FIELD_DESCRIPTIONS.actual),
|
|
23316
|
+
client: z16.string().trim().max(AGENT_FEEDBACK_CLIENT_MAX).optional().describe(FIELD_DESCRIPTIONS.client)
|
|
23317
|
+
});
|
|
23318
|
+
AGENT_FEEDBACK_CONTRACT = {
|
|
23319
|
+
method: "POST",
|
|
23320
|
+
path: "/api/v1/feedback",
|
|
23321
|
+
purpose: "Tell Better Design about a bug or a missing feature you hit while calling this API. A person reads every report.",
|
|
23322
|
+
fields: {
|
|
23323
|
+
kind: { required: true, description: FIELD_DESCRIPTIONS.kind, oneOf: agentFeedbackKindValues },
|
|
23324
|
+
surface: { required: true, description: FIELD_DESCRIPTIONS.surface, maxLength: SURFACE_MAX },
|
|
23325
|
+
summary: { required: true, description: FIELD_DESCRIPTIONS.summary, maxLength: SUMMARY_MAX },
|
|
23326
|
+
attempted: { required: false, description: FIELD_DESCRIPTIONS.attempted, maxLength: TEXT_MAX },
|
|
23327
|
+
expected: { required: false, description: FIELD_DESCRIPTIONS.expected, maxLength: TEXT_MAX },
|
|
23328
|
+
actual: { required: false, description: FIELD_DESCRIPTIONS.actual, maxLength: TEXT_MAX },
|
|
23329
|
+
client: { required: false, description: FIELD_DESCRIPTIONS.client, maxLength: AGENT_FEEDBACK_CLIENT_MAX }
|
|
23330
|
+
},
|
|
23331
|
+
notes: [
|
|
23332
|
+
"Send it with your Better Design API key: Authorization: Bearer <key>.",
|
|
23333
|
+
"Do not include secrets or personal data. Known secret shapes are removed before storage.",
|
|
23334
|
+
"Sending feedback never spends credit."
|
|
23335
|
+
]
|
|
23336
|
+
};
|
|
23337
|
+
}
|
|
23338
|
+
});
|
|
23339
|
+
|
|
23340
|
+
// ../shared/src/schema.ts
|
|
23341
|
+
import { z as z17 } from "zod";
|
|
23342
|
+
var vector2, messageRoleEnum, messageTypeEnum, designSystemStatusEnum, onboardingPathValues, onboardingPathSchema, onboardingPathSqlValues, mcpIssueCategoryValues, mcpIssueStatusValues, mcpIssueImprovementStatusValues, mcpIssueImprovementVerdictValues, mcpIssueImprovementStatusSchema, mcpIssueImprovementVerdictSchema, mcpIssueImprovementStatusSqlValues, mcpIssueImprovementVerdictSqlValues, designConfigSchema, projectMemoryDataSchema, projects, messages, projectMemory, fragments, usage, scrapeBlock, resourceAccess, user, signupAlertDeliveries, billingAlertDeliveries, billingEmailDeliveries, TEAM_RULES_MAX_BYTES, teamRules, agentFeedbackStatusValues, agentFeedback, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, emailCampaignSends, emailCampaignSteps, projectEvents, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, projectsRelations, projectMemoryRelations, mcpIssuesRelations, mcpIssueImprovementsRelations, messagesRelations, fragmentsRelations, designSystemsRelations, designSystemComponentsRelations, insertProjectSchema, selectProjectSchema, insertMessageSchema, selectMessageSchema, insertFragmentSchema, selectFragmentSchema, insertProjectMemorySchema, selectProjectMemorySchema, insertDesignSystemSchema, selectDesignSystemSchema, insertDesignSystemComponentSchema, selectDesignSystemComponentSchema, insertFoundationalDocSchema, selectFoundationalDocSchema, insertIconLibrarySchema, selectIconLibrarySchema, insertApiKeySchema, selectApiKeySchema, insertUserSchema, selectUserSchema, insertSignupAlertDeliverySchema, selectSignupAlertDeliverySchema, insertEmailCampaignSendSchema, selectEmailCampaignSendSchema, insertEmailCampaignStepSchema, selectEmailCampaignStepSchema, insertMcpIssueImprovementSchema, selectMcpIssueImprovementSchema, pluginAuthNonces;
|
|
23204
23343
|
var init_schema2 = __esm({
|
|
23205
23344
|
"../shared/src/schema.ts"() {
|
|
23206
23345
|
"use strict";
|
|
23207
23346
|
init_pg_core();
|
|
23208
23347
|
init_drizzle_orm();
|
|
23209
23348
|
init_drizzle_zod();
|
|
23349
|
+
init_agent_feedback();
|
|
23210
23350
|
vector2 = customType({
|
|
23211
23351
|
dataType() {
|
|
23212
23352
|
return "vector(768)";
|
|
@@ -23234,7 +23374,7 @@ var init_schema2 = __esm({
|
|
|
23234
23374
|
"skip",
|
|
23235
23375
|
"legacy"
|
|
23236
23376
|
];
|
|
23237
|
-
onboardingPathSchema =
|
|
23377
|
+
onboardingPathSchema = z17.enum(onboardingPathValues);
|
|
23238
23378
|
onboardingPathSqlValues = sql.raw(
|
|
23239
23379
|
onboardingPathValues.map((path6) => `'${path6}'`).join(", ")
|
|
23240
23380
|
);
|
|
@@ -23257,10 +23397,10 @@ var init_schema2 = __esm({
|
|
|
23257
23397
|
"not_improved",
|
|
23258
23398
|
"inconclusive"
|
|
23259
23399
|
];
|
|
23260
|
-
mcpIssueImprovementStatusSchema =
|
|
23400
|
+
mcpIssueImprovementStatusSchema = z17.enum(
|
|
23261
23401
|
mcpIssueImprovementStatusValues
|
|
23262
23402
|
);
|
|
23263
|
-
mcpIssueImprovementVerdictSchema =
|
|
23403
|
+
mcpIssueImprovementVerdictSchema = z17.enum(
|
|
23264
23404
|
mcpIssueImprovementVerdictValues
|
|
23265
23405
|
);
|
|
23266
23406
|
mcpIssueImprovementStatusSqlValues = sql.raw(
|
|
@@ -23269,38 +23409,38 @@ var init_schema2 = __esm({
|
|
|
23269
23409
|
mcpIssueImprovementVerdictSqlValues = sql.raw(
|
|
23270
23410
|
mcpIssueImprovementVerdictValues.map((verdict) => `'${verdict}'`).join(", ")
|
|
23271
23411
|
);
|
|
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:
|
|
23412
|
+
designConfigSchema = z17.object({
|
|
23413
|
+
colors: z17.object({
|
|
23414
|
+
primary: z17.string(),
|
|
23415
|
+
secondary: z17.string(),
|
|
23416
|
+
accent: z17.string(),
|
|
23417
|
+
muted: z17.string(),
|
|
23418
|
+
destructive: z17.string(),
|
|
23419
|
+
background: z17.string(),
|
|
23420
|
+
foreground: z17.string(),
|
|
23421
|
+
card: z17.string(),
|
|
23422
|
+
cardForeground: z17.string(),
|
|
23423
|
+
popover: z17.string(),
|
|
23424
|
+
popoverForeground: z17.string(),
|
|
23425
|
+
border: z17.string(),
|
|
23426
|
+
input: z17.string(),
|
|
23427
|
+
ring: z17.string()
|
|
23288
23428
|
}).partial(),
|
|
23289
|
-
radius:
|
|
23290
|
-
font:
|
|
23291
|
-
sans:
|
|
23292
|
-
mono:
|
|
23429
|
+
radius: z17.string(),
|
|
23430
|
+
font: z17.object({
|
|
23431
|
+
sans: z17.string(),
|
|
23432
|
+
mono: z17.string()
|
|
23293
23433
|
}).partial(),
|
|
23294
|
-
icons:
|
|
23295
|
-
libraryId:
|
|
23296
|
-
variant:
|
|
23434
|
+
icons: z17.object({
|
|
23435
|
+
libraryId: z17.string(),
|
|
23436
|
+
variant: z17.string().nullable()
|
|
23297
23437
|
}).partial()
|
|
23298
23438
|
}).partial();
|
|
23299
|
-
projectMemoryDataSchema =
|
|
23300
|
-
lockedIn:
|
|
23301
|
-
rejected:
|
|
23302
|
-
tone:
|
|
23303
|
-
notes:
|
|
23439
|
+
projectMemoryDataSchema = z17.object({
|
|
23440
|
+
lockedIn: z17.array(z17.string()),
|
|
23441
|
+
rejected: z17.array(z17.string()),
|
|
23442
|
+
tone: z17.string().nullable(),
|
|
23443
|
+
notes: z17.string().nullable()
|
|
23304
23444
|
});
|
|
23305
23445
|
projects = pgTable("projects", {
|
|
23306
23446
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
@@ -23580,6 +23720,43 @@ var init_schema2 = __esm({
|
|
|
23580
23720
|
)
|
|
23581
23721
|
]
|
|
23582
23722
|
);
|
|
23723
|
+
agentFeedbackStatusValues = [
|
|
23724
|
+
"open",
|
|
23725
|
+
"accepted",
|
|
23726
|
+
"rejected",
|
|
23727
|
+
"shipped"
|
|
23728
|
+
];
|
|
23729
|
+
agentFeedback = pgTable(
|
|
23730
|
+
"agent_feedback",
|
|
23731
|
+
{
|
|
23732
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
23733
|
+
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
23734
|
+
apiKeyId: text("api_key_id").references(() => apiKeys.id, { onDelete: "set null" }),
|
|
23735
|
+
kind: text("kind", { enum: agentFeedbackKindValues }).notNull(),
|
|
23736
|
+
// The endpoint path or MCP tool name the agent was calling.
|
|
23737
|
+
surface: text("surface").notNull(),
|
|
23738
|
+
summary: text("summary").notNull(),
|
|
23739
|
+
attempted: text("attempted"),
|
|
23740
|
+
expected: text("expected"),
|
|
23741
|
+
actual: text("actual"),
|
|
23742
|
+
// The agent's own name for itself, such as "claude-code/2.1".
|
|
23743
|
+
client: text("client"),
|
|
23744
|
+
// SHA-256 of kind, surface and the normalized summary, so identical
|
|
23745
|
+
// reports group in the admin queue without a model call.
|
|
23746
|
+
fingerprint: text("fingerprint").notNull(),
|
|
23747
|
+
status: text("status", { enum: agentFeedbackStatusValues }).notNull().default("open"),
|
|
23748
|
+
pullRequestUrl: text("pull_request_url"),
|
|
23749
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
23750
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
23751
|
+
},
|
|
23752
|
+
(t) => [
|
|
23753
|
+
index("agent_feedback_status_created_idx").on(t.status, t.createdAt),
|
|
23754
|
+
index("agent_feedback_fingerprint_idx").on(t.fingerprint),
|
|
23755
|
+
// Deleting a user or an API key looks up its reports through these.
|
|
23756
|
+
index("agent_feedback_user_id_idx").on(t.userId),
|
|
23757
|
+
index("agent_feedback_api_key_id_idx").on(t.apiKeyId)
|
|
23758
|
+
]
|
|
23759
|
+
);
|
|
23583
23760
|
billingSubscription = pgTable(
|
|
23584
23761
|
"billing_subscription",
|
|
23585
23762
|
{
|
|
@@ -24386,7 +24563,7 @@ var init_schema2 = __esm({
|
|
|
24386
24563
|
});
|
|
24387
24564
|
|
|
24388
24565
|
// ../shared/src/mcp/tools/design-config-tools.ts
|
|
24389
|
-
import { z as
|
|
24566
|
+
import { z as z18 } from "zod";
|
|
24390
24567
|
function registerDesignConfigTools({
|
|
24391
24568
|
server,
|
|
24392
24569
|
tracked,
|
|
@@ -24437,7 +24614,7 @@ function registerDesignConfigTools({
|
|
|
24437
24614
|
{
|
|
24438
24615
|
title: "Get a Project's Tuned Design Config",
|
|
24439
24616
|
description: `Read the user-tuned design config (exact color tokens, radius, fonts, icon library) of a generated project. Call this BEFORE building or restyling UI for the project, and again after the user tunes values on the project page, so you apply their exact numbers instead of guessing from adjectives.`,
|
|
24440
|
-
inputSchema:
|
|
24617
|
+
inputSchema: z18.object({
|
|
24441
24618
|
projectId: PROJECT_ID_ARG,
|
|
24442
24619
|
context: CONTEXT_ARG
|
|
24443
24620
|
}),
|
|
@@ -24466,7 +24643,7 @@ function registerDesignConfigTools({
|
|
|
24466
24643
|
{
|
|
24467
24644
|
title: "Set a Project's Design Config",
|
|
24468
24645
|
description: `Write exact design-token values (colors, radius, fonts, icon library) onto a generated project. Use this when the user states concrete values ("make primary oklch(0.62 0.11 45)", "radius 12px"). It merges over the current config and persists, so the change survives restarts and every later get-design-config returns it. Saved config is publicly readable by anyone with the fragment config URL. For vague direction ("warmer", "more playful"), iterate with the user toward concrete values first; do not invent numbers on their behalf.`,
|
|
24469
|
-
inputSchema:
|
|
24646
|
+
inputSchema: z18.object({
|
|
24470
24647
|
projectId: PROJECT_ID_ARG,
|
|
24471
24648
|
config: CONFIG_ARG,
|
|
24472
24649
|
context: CONTEXT_ARG
|
|
@@ -24517,7 +24694,7 @@ var init_design_config_tools = __esm({
|
|
|
24517
24694
|
"use strict";
|
|
24518
24695
|
init_schema2();
|
|
24519
24696
|
init_shared();
|
|
24520
|
-
PROJECT_ID_ARG =
|
|
24697
|
+
PROJECT_ID_ARG = z18.string().min(1).describe("The project id returned by create-design-system.");
|
|
24521
24698
|
CONFIG_ARG = designConfigSchema.describe(
|
|
24522
24699
|
"Partial design config to merge over the current one. Only pass the fields the user changed. Colors are oklch() strings (e.g. 'oklch(0.62 0.11 45)'); radius is a CSS length (e.g. '0.75rem'); fonts are family names."
|
|
24523
24700
|
);
|
|
@@ -24525,16 +24702,16 @@ var init_design_config_tools = __esm({
|
|
|
24525
24702
|
});
|
|
24526
24703
|
|
|
24527
24704
|
// ../shared/src/mcp/tools/jig-tools.ts
|
|
24528
|
-
import { z as
|
|
24705
|
+
import { z as z19 } from "zod";
|
|
24529
24706
|
function registerJigTools({ server, tracked }) {
|
|
24530
24707
|
server.registerTool(
|
|
24531
24708
|
"get-jig-guide",
|
|
24532
24709
|
{
|
|
24533
24710
|
title: "Get a Jig for Parameter Work",
|
|
24534
24711
|
description: `Return the setup for a "jig", the tool you build to make the real thing precisely. Two kinds, both here. Use kind "tune-in-place" (default) when you are about to hard-code taste-based values in UI you are building: animation duration/easing/springs/stagger, particles, canvas physics, blur/glow/grain, gradient stops, shadow recipes, parallax speeds. It returns a control panel to wire to those parameters so the USER tunes by eye and you hard-code what they copy back. Use kind "standalone-tool" when the user wants a reusable custom tool instead: a generator, an image effect, a shader playground, an asset maker. That returns the Toolcraft starter kit (MIT), which ships a canvas and an export flow, with optional layers and a timeline. Tuning setups: React recommends DialKit (MIT, no re-render lag) with a dependency-free fallback; "vanilla" and "react-native" get dependency-free scaffolds.`,
|
|
24535
|
-
inputSchema:
|
|
24536
|
-
kind:
|
|
24537
|
-
framework:
|
|
24712
|
+
inputSchema: z19.object({
|
|
24713
|
+
kind: z19.enum(["tune-in-place", "standalone-tool"]).default("tune-in-place").describe("What the user needs. 'tune-in-place' (default): a control panel for parameters in the UI you are building. 'standalone-tool': a separate reusable design tool, scaffolded with Toolcraft."),
|
|
24714
|
+
framework: z19.enum(["react", "vanilla", "react-native"]).optional().describe("Which tune-in-place setup to return. 'react' (default) for React/Next web projects; 'vanilla' for plain DOM or canvas work; 'react-native' for RN/Expo apps. Ignored when kind is 'standalone-tool'."),
|
|
24538
24715
|
context: CONTEXT_ARG
|
|
24539
24716
|
}),
|
|
24540
24717
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -25366,7 +25543,7 @@ var init_brand_sources = __esm({
|
|
|
25366
25543
|
});
|
|
25367
25544
|
|
|
25368
25545
|
// ../shared/src/mcp/tools/extract-tools.ts
|
|
25369
|
-
import { z as
|
|
25546
|
+
import { z as z20 } from "zod";
|
|
25370
25547
|
function authHeaders2(authContext) {
|
|
25371
25548
|
if (!authContext) return {};
|
|
25372
25549
|
const bearer = authContext.kind === "api-key" ? authContext.apiKey : authContext.accessToken;
|
|
@@ -25403,15 +25580,15 @@ JSON.stringify(${buildRoleSampleScript()})
|
|
|
25403
25580
|
- kind "tokens": a design-tokens JSON export ($value entries, W3C format or the Figma variables API shape with 0..1 rgb floats).
|
|
25404
25581
|
|
|
25405
25582
|
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:
|
|
25583
|
+
inputSchema: z20.object({
|
|
25584
|
+
css: z20.string().trim().min(1).max(2e5).optional().describe("Full contents of the project's globals.css (or any CSS holding the design tokens / CSS custom properties). Optional when `sources` is given."),
|
|
25585
|
+
sources: z20.array(z20.object({
|
|
25586
|
+
name: z20.string().trim().min(1).max(120).describe("Label for this source, e.g. 'globals.css', 'live site sample', 'figma variables'."),
|
|
25587
|
+
kind: z20.enum(["css", "computed", "tokens"]).optional().describe("Omit to infer: JSON with $value entries is tokens, JSON keyed by UI roles is computed, anything else is CSS."),
|
|
25588
|
+
content: z20.string().trim().min(1).max(2e5)
|
|
25412
25589
|
})).max(10).optional().describe("Any mix of brand sources, merged in order (later sources win). See the tool description for how to gather each kind."),
|
|
25413
|
-
name:
|
|
25414
|
-
maxTokens:
|
|
25590
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system, e.g. the app or repo name."),
|
|
25591
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25415
25592
|
}),
|
|
25416
25593
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
25417
25594
|
},
|
|
@@ -25442,10 +25619,10 @@ Common vendor names (Shopify's --color-button family) are mapped into empty sema
|
|
|
25442
25619
|
Use this when the user names their site ("match uclips.eu", "use our website's style") or hands you any public URL whose look should be reused. Server side, Better Design fetches the page's stylesheets AND renders the page in a headless browser to sample what role elements actually look like (body, heading, link, button, input, card), then merges both, rendered truth last. That reads the brand even when the site's variable names are unrecognizable.
|
|
25443
25620
|
|
|
25444
25621
|
Slow by nature (a real browser loads the page): expect 10 to 45 seconds. For CSS the user already has locally, prefer extract-design-system, which is instant.`,
|
|
25445
|
-
inputSchema:
|
|
25446
|
-
url:
|
|
25447
|
-
name:
|
|
25448
|
-
maxTokens:
|
|
25622
|
+
inputSchema: z20.object({
|
|
25623
|
+
url: z20.string().trim().min(8).max(2048).describe("Public http(s) URL of the page to read, usually the homepage."),
|
|
25624
|
+
name: z20.string().trim().min(1).max(120).optional().describe("Optional label for the extracted system. Defaults to the site's hostname."),
|
|
25625
|
+
maxTokens: z20.number().min(500).max(5e4).optional().describe("Maximum tokens to return. Truncates the token sheet to fit.")
|
|
25449
25626
|
}),
|
|
25450
25627
|
// Reads any public page the user names, so the set of hosts it reaches is
|
|
25451
25628
|
// the open web, not Better Design's catalog. It stores nothing. [BD-316]
|
|
@@ -25495,7 +25672,7 @@ var init_extract_tools = __esm({
|
|
|
25495
25672
|
});
|
|
25496
25673
|
|
|
25497
25674
|
// ../shared/src/mcp/tools/illustration-tools.ts
|
|
25498
|
-
import { z as
|
|
25675
|
+
import { z as z21 } from "zod";
|
|
25499
25676
|
function fail(message) {
|
|
25500
25677
|
return {
|
|
25501
25678
|
content: [{ type: "text", text: message }],
|
|
@@ -25541,11 +25718,11 @@ How to call it:
|
|
|
25541
25718
|
4. Stop when \`faults\` is empty.
|
|
25542
25719
|
|
|
25543
25720
|
Markup carrying script, event handlers, remote references, or styles that reach outside the drawing is refused rather than cleaned, so the drawing you fix is the drawing you sent.`,
|
|
25544
|
-
inputSchema:
|
|
25545
|
-
svg:
|
|
25546
|
-
intent:
|
|
25547
|
-
animated:
|
|
25548
|
-
reviewToken:
|
|
25721
|
+
inputSchema: z21.object({
|
|
25722
|
+
svg: z21.string().min(1).describe("The complete svg element you drew."),
|
|
25723
|
+
intent: z21.string().min(1).describe('What it is meant to be, in plain words. For example "an open inbox tray with letters".'),
|
|
25724
|
+
animated: z21.boolean().optional().describe("Set when the drawing was meant to move, so the motion checks apply."),
|
|
25725
|
+
reviewToken: z21.string().optional().describe("The token from a previous review of this drawing. Reviews it again free.")
|
|
25549
25726
|
}),
|
|
25550
25727
|
outputSchema: REVIEW_OUTPUT_SCHEMA,
|
|
25551
25728
|
// A review without a valid reviewToken passes the paywall, so it spends
|
|
@@ -25624,16 +25801,16 @@ var init_illustration_tools = __esm({
|
|
|
25624
25801
|
init_shared();
|
|
25625
25802
|
REVIEW_OUTPUT_SCHEMA = {
|
|
25626
25803
|
/** Empty when nothing is visibly wrong. */
|
|
25627
|
-
faults:
|
|
25804
|
+
faults: z21.array(z21.string()),
|
|
25628
25805
|
/** Group ids in paint order, the parts an animation can target. */
|
|
25629
|
-
layers:
|
|
25806
|
+
layers: z21.array(z21.string()),
|
|
25630
25807
|
/** False when the markup was refused; faults says why. */
|
|
25631
|
-
reviewed:
|
|
25808
|
+
reviewed: z21.boolean(),
|
|
25632
25809
|
/** True when the drawing could not be rendered, so only structure was checked. */
|
|
25633
|
-
unseen:
|
|
25810
|
+
unseen: z21.boolean(),
|
|
25634
25811
|
/** Pass this back to review the same drawing again without spending another credit. */
|
|
25635
|
-
reviewToken:
|
|
25636
|
-
message:
|
|
25812
|
+
reviewToken: z21.string(),
|
|
25813
|
+
message: z21.string()
|
|
25637
25814
|
};
|
|
25638
25815
|
}
|
|
25639
25816
|
});
|
|
@@ -26995,7 +27172,7 @@ ul.evidence { padding-inline-start: 1rem; }
|
|
|
26995
27172
|
});
|
|
26996
27173
|
|
|
26997
27174
|
// ../shared/src/mcp/tools/inspection-tools.ts
|
|
26998
|
-
import { z as
|
|
27175
|
+
import { z as z22 } from "zod";
|
|
26999
27176
|
function coversRecommendedWidth(states, width) {
|
|
27000
27177
|
return states.some(
|
|
27001
27178
|
(state) => Math.abs(state.viewport.width - width) <= WIDTH_TOLERANCE
|
|
@@ -27051,36 +27228,38 @@ function registerInspectionTools({ server, provider, tracked }) {
|
|
|
27051
27228
|
|
|
27052
27229
|
This is the runtime counterpart to get-review-rules. It automatically loads the live visual-design rubric, but it does not guess pixel values from a screenshot. Exact claims come from DOM geometry.
|
|
27053
27230
|
|
|
27231
|
+
It measures geometry only. A score of 100 is not visual approval: it cannot judge hierarchy, density, or whether the design works, so look at a screenshot before you call a screen done.
|
|
27232
|
+
|
|
27054
27233
|
Workflow:
|
|
27055
27234
|
1. Call with no states to receive a read-only browser capture script in structuredContent.captureScript. Use detail: "full" if your client reads only text; it includes the script in text. That call measures nothing.
|
|
27056
27235
|
2. Evaluate that script in each rendered state you need to review: at minimum 1440, 1728 and 390 wide. Also capture named hover, focus, selected, and open states when they change a control's surface or layout.
|
|
27057
27236
|
3. Call again with the captured states and the project's spacing scale when known. The layout counts as measured only when this second call returns status \`completed\`; reading the snapshot yourself is not a measurement. The report and summary start with the highest-priority correction. Read structuredContent.findings or use detail: "full" for every returned finding, measurement, and fix. Structured evidence retains captured geometry and optional screenshots for report renderers.
|
|
27058
27237
|
|
|
27059
27238
|
The capture reads visible layout geometry, computed spacing, corner radii, surface styles, the number of line boxes each text block rendered, and short accessible labels for interactive controls. It does not collect source code, cookies, form values, or network data. Add data-bd-inspect-label, data-bd-symmetry, data-bd-group, data-bd-compare-size, data-bd-align-group, and data-bd-align-edge attributes when the intended relationship cannot be inferred safely.`,
|
|
27060
|
-
inputSchema:
|
|
27061
|
-
states:
|
|
27062
|
-
spacingScale:
|
|
27239
|
+
inputSchema: z22.object({
|
|
27240
|
+
states: z22.array(spacingStateSchema).min(1).max(6).optional().describe("Rendered layout states from the capture script. Omit on the first call to receive the script."),
|
|
27241
|
+
spacingScale: z22.array(finiteNumber.positive()).min(2).max(32).optional().describe("Project spacing tokens in CSS pixels. Defaults to 4, 8, 12, 16, 24, 32, 48, 64, 96, 128."),
|
|
27063
27242
|
tolerance: finiteNumber.min(0).max(4).optional().describe("Pixel tolerance for grouping measurements. Defaults to 1px."),
|
|
27064
|
-
detail:
|
|
27243
|
+
detail: z22.enum(["summary", "full"]).default("summary").describe("Defaults to a short summary alongside the inline report. Use full for the capture script or all findings and measurements in text-only clients."),
|
|
27065
27244
|
context: CONTEXT_ARG
|
|
27066
27245
|
}),
|
|
27067
|
-
outputSchema:
|
|
27068
|
-
status:
|
|
27069
|
-
score:
|
|
27070
|
-
summary:
|
|
27071
|
-
critical:
|
|
27072
|
-
serious:
|
|
27073
|
-
moderate:
|
|
27074
|
-
total:
|
|
27075
|
-
truncated:
|
|
27246
|
+
outputSchema: z22.object({
|
|
27247
|
+
status: z22.enum(["needs-capture", "completed"]),
|
|
27248
|
+
score: z22.number().optional(),
|
|
27249
|
+
summary: z22.object({
|
|
27250
|
+
critical: z22.number(),
|
|
27251
|
+
serious: z22.number(),
|
|
27252
|
+
moderate: z22.number(),
|
|
27253
|
+
total: z22.number(),
|
|
27254
|
+
truncated: z22.number().optional()
|
|
27076
27255
|
}).optional(),
|
|
27077
|
-
findings:
|
|
27078
|
-
capturedWidths:
|
|
27079
|
-
missingWidths:
|
|
27080
|
-
states:
|
|
27081
|
-
rubric:
|
|
27082
|
-
rubricStatus:
|
|
27083
|
-
captureScript:
|
|
27256
|
+
findings: z22.array(findingSchema).optional(),
|
|
27257
|
+
capturedWidths: z22.array(z22.number()).optional(),
|
|
27258
|
+
missingWidths: z22.array(z22.number()).optional(),
|
|
27259
|
+
states: z22.array(overlayStateSchema).optional(),
|
|
27260
|
+
rubric: z22.array(z22.object({ id: z22.string(), title: z22.string() })).optional(),
|
|
27261
|
+
rubricStatus: z22.enum(["loaded", "unavailable"]).optional(),
|
|
27262
|
+
captureScript: z22.string().optional()
|
|
27084
27263
|
}),
|
|
27085
27264
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
27086
27265
|
_meta: {
|
|
@@ -27140,7 +27319,8 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
27140
27319
|
detail === "full" ? `Spacing score: ${result.score}/100.` : "",
|
|
27141
27320
|
coverageLine,
|
|
27142
27321
|
findingsText,
|
|
27143
|
-
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : ""
|
|
27322
|
+
result.summary.truncated ? `${result.summary.truncated} additional findings exceeded the analysis limit. Inspect smaller sections before treating this review as complete.` : "",
|
|
27323
|
+
GEOMETRY_ONLY
|
|
27144
27324
|
].filter(Boolean).join("\n\n");
|
|
27145
27325
|
return {
|
|
27146
27326
|
content: [{ type: "text", text: text3 }],
|
|
@@ -27157,7 +27337,7 @@ ${SPACING_CAPTURE_SCRIPT}
|
|
|
27157
27337
|
)
|
|
27158
27338
|
);
|
|
27159
27339
|
}
|
|
27160
|
-
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE;
|
|
27340
|
+
var finiteNumber, rectSchema, edgesSchema, cornerRadiusAxesSchema, cornerRadiusSchema, cornerRadiiSchema, styleSchema, elementSchema, screenshotSchema, spacingStateSchema, evidenceSchema, findingSchema, overlayStateSchema, RECOMMENDED_VIEWPORT_WIDTHS, WIDTH_TOLERANCE, GEOMETRY_ONLY;
|
|
27161
27341
|
var init_inspection_tools = __esm({
|
|
27162
27342
|
"../shared/src/mcp/tools/inspection-tools.ts"() {
|
|
27163
27343
|
"use strict";
|
|
@@ -27165,67 +27345,67 @@ var init_inspection_tools = __esm({
|
|
|
27165
27345
|
init_finding_copy();
|
|
27166
27346
|
init_spacing_inspection_html();
|
|
27167
27347
|
init_shared();
|
|
27168
|
-
finiteNumber =
|
|
27169
|
-
rectSchema =
|
|
27348
|
+
finiteNumber = z22.number().finite();
|
|
27349
|
+
rectSchema = z22.object({
|
|
27170
27350
|
x: finiteNumber,
|
|
27171
27351
|
y: finiteNumber,
|
|
27172
27352
|
width: finiteNumber.nonnegative(),
|
|
27173
27353
|
height: finiteNumber.nonnegative()
|
|
27174
27354
|
});
|
|
27175
|
-
edgesSchema =
|
|
27355
|
+
edgesSchema = z22.object({
|
|
27176
27356
|
top: finiteNumber,
|
|
27177
27357
|
right: finiteNumber,
|
|
27178
27358
|
bottom: finiteNumber,
|
|
27179
27359
|
left: finiteNumber
|
|
27180
27360
|
});
|
|
27181
|
-
cornerRadiusAxesSchema =
|
|
27361
|
+
cornerRadiusAxesSchema = z22.object({
|
|
27182
27362
|
horizontal: finiteNumber.nonnegative(),
|
|
27183
27363
|
vertical: finiteNumber.nonnegative()
|
|
27184
27364
|
});
|
|
27185
|
-
cornerRadiusSchema =
|
|
27365
|
+
cornerRadiusSchema = z22.union([
|
|
27186
27366
|
cornerRadiusAxesSchema,
|
|
27187
27367
|
finiteNumber.nonnegative().transform((value) => ({
|
|
27188
27368
|
horizontal: value,
|
|
27189
27369
|
vertical: value
|
|
27190
27370
|
}))
|
|
27191
27371
|
]);
|
|
27192
|
-
cornerRadiiSchema =
|
|
27372
|
+
cornerRadiiSchema = z22.object({
|
|
27193
27373
|
topLeft: cornerRadiusSchema,
|
|
27194
27374
|
topRight: cornerRadiusSchema,
|
|
27195
27375
|
bottomRight: cornerRadiusSchema,
|
|
27196
27376
|
bottomLeft: cornerRadiusSchema
|
|
27197
27377
|
});
|
|
27198
|
-
styleSchema =
|
|
27199
|
-
display:
|
|
27200
|
-
position:
|
|
27201
|
-
flexDirection:
|
|
27202
|
-
flexWrap:
|
|
27203
|
-
alignItems:
|
|
27204
|
-
justifyContent:
|
|
27378
|
+
styleSchema = z22.object({
|
|
27379
|
+
display: z22.string().max(60).optional(),
|
|
27380
|
+
position: z22.string().max(60).optional(),
|
|
27381
|
+
flexDirection: z22.string().max(60).optional(),
|
|
27382
|
+
flexWrap: z22.string().max(60).optional(),
|
|
27383
|
+
alignItems: z22.string().max(60).optional(),
|
|
27384
|
+
justifyContent: z22.string().max(60).optional(),
|
|
27205
27385
|
rowGap: finiteNumber.optional(),
|
|
27206
27386
|
columnGap: finiteNumber.optional(),
|
|
27207
27387
|
padding: edgesSchema.optional(),
|
|
27208
27388
|
margin: edgesSchema.optional(),
|
|
27209
|
-
overflowX:
|
|
27210
|
-
overflowY:
|
|
27389
|
+
overflowX: z22.string().max(60).optional(),
|
|
27390
|
+
overflowY: z22.string().max(60).optional(),
|
|
27211
27391
|
borderRadius: cornerRadiiSchema.optional(),
|
|
27212
|
-
backgroundColor:
|
|
27213
|
-
backgroundImage:
|
|
27214
|
-
boxShadow:
|
|
27392
|
+
backgroundColor: z22.string().max(160).optional(),
|
|
27393
|
+
backgroundImage: z22.string().max(1e3).optional(),
|
|
27394
|
+
boxShadow: z22.string().max(1e3).optional()
|
|
27215
27395
|
});
|
|
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:
|
|
27396
|
+
elementSchema = z22.object({
|
|
27397
|
+
id: z22.string().min(1).max(300),
|
|
27398
|
+
selector: z22.string().max(500).optional(),
|
|
27399
|
+
tag: z22.string().max(80).optional(),
|
|
27400
|
+
role: z22.string().max(80).optional(),
|
|
27401
|
+
label: z22.string().max(160).optional(),
|
|
27402
|
+
parentId: z22.string().max(300).optional(),
|
|
27403
|
+
groupId: z22.string().max(300).optional(),
|
|
27404
|
+
alignmentGroup: z22.string().max(300).optional(),
|
|
27405
|
+
alignmentEdge: z22.enum(["left", "right", "top", "bottom", "center-x", "center-y"]).optional(),
|
|
27406
|
+
expectedSymmetry: z22.enum(["horizontal", "vertical", "both"]).optional(),
|
|
27407
|
+
compareSize: z22.enum(["width", "height", "both"]).optional(),
|
|
27408
|
+
interactive: z22.boolean().optional(),
|
|
27229
27409
|
rect: rectSchema,
|
|
27230
27410
|
style: styleSchema.optional(),
|
|
27231
27411
|
lineCount: finiteNumber.int().positive().optional(),
|
|
@@ -27234,44 +27414,45 @@ var init_inspection_tools = __esm({
|
|
|
27234
27414
|
scrollWidth: finiteNumber.nonnegative().optional(),
|
|
27235
27415
|
scrollHeight: finiteNumber.nonnegative().optional()
|
|
27236
27416
|
});
|
|
27237
|
-
screenshotSchema =
|
|
27238
|
-
spacingStateSchema =
|
|
27239
|
-
name:
|
|
27240
|
-
viewport:
|
|
27417
|
+
screenshotSchema = z22.string().max(5e6).regex(/^data:image\/(?:png|jpeg|webp);base64,/, "Use a PNG, JPEG, or WebP data URL.");
|
|
27418
|
+
spacingStateSchema = z22.object({
|
|
27419
|
+
name: z22.string().min(1).max(100),
|
|
27420
|
+
viewport: z22.object({
|
|
27241
27421
|
width: finiteNumber.positive().max(2e4),
|
|
27242
27422
|
height: finiteNumber.positive().max(2e4),
|
|
27243
27423
|
devicePixelRatio: finiteNumber.positive().max(10).optional()
|
|
27244
27424
|
}),
|
|
27245
|
-
elements:
|
|
27425
|
+
elements: z22.array(elementSchema).max(1500),
|
|
27246
27426
|
screenshotDataUrl: screenshotSchema.optional().describe("Optional browser screenshot as a data URL. It remains in the structured evidence for report renderers and is not persisted.")
|
|
27247
27427
|
});
|
|
27248
|
-
evidenceSchema =
|
|
27249
|
-
label:
|
|
27250
|
-
value:
|
|
27251
|
-
unit:
|
|
27428
|
+
evidenceSchema = z22.object({
|
|
27429
|
+
label: z22.string(),
|
|
27430
|
+
value: z22.union([z22.number(), z22.string()]),
|
|
27431
|
+
unit: z22.enum(["px", "count"]).optional()
|
|
27252
27432
|
});
|
|
27253
|
-
findingSchema =
|
|
27254
|
-
id:
|
|
27255
|
-
ruleId:
|
|
27256
|
-
state:
|
|
27257
|
-
severity:
|
|
27258
|
-
message:
|
|
27259
|
-
fix:
|
|
27260
|
-
elementIds:
|
|
27261
|
-
evidence:
|
|
27433
|
+
findingSchema = z22.object({
|
|
27434
|
+
id: z22.string(),
|
|
27435
|
+
ruleId: z22.string(),
|
|
27436
|
+
state: z22.string(),
|
|
27437
|
+
severity: z22.enum(["critical", "serious", "moderate"]),
|
|
27438
|
+
message: z22.string(),
|
|
27439
|
+
fix: z22.string(),
|
|
27440
|
+
elementIds: z22.array(z22.string()),
|
|
27441
|
+
evidence: z22.array(evidenceSchema)
|
|
27262
27442
|
});
|
|
27263
|
-
overlayStateSchema =
|
|
27264
|
-
name:
|
|
27265
|
-
viewport:
|
|
27266
|
-
width:
|
|
27267
|
-
height:
|
|
27268
|
-
devicePixelRatio:
|
|
27443
|
+
overlayStateSchema = z22.object({
|
|
27444
|
+
name: z22.string(),
|
|
27445
|
+
viewport: z22.object({
|
|
27446
|
+
width: z22.number(),
|
|
27447
|
+
height: z22.number(),
|
|
27448
|
+
devicePixelRatio: z22.number().optional()
|
|
27269
27449
|
}),
|
|
27270
|
-
elements:
|
|
27271
|
-
screenshotDataUrl:
|
|
27450
|
+
elements: z22.array(z22.object({ id: z22.string(), label: z22.string().optional(), rect: rectSchema })),
|
|
27451
|
+
screenshotDataUrl: z22.string().optional()
|
|
27272
27452
|
});
|
|
27273
27453
|
RECOMMENDED_VIEWPORT_WIDTHS = [390, 1440, 1728];
|
|
27274
27454
|
WIDTH_TOLERANCE = 40;
|
|
27455
|
+
GEOMETRY_ONLY = "This measured geometry only. A clean result or a score of 100 is not visual approval: it cannot judge hierarchy, density or whether the design works.";
|
|
27275
27456
|
}
|
|
27276
27457
|
});
|
|
27277
27458
|
|
|
@@ -27310,7 +27491,7 @@ var init_inspection_resource = __esm({
|
|
|
27310
27491
|
});
|
|
27311
27492
|
|
|
27312
27493
|
// ../shared/src/mcp/tools/dashboard-review-tools.ts
|
|
27313
|
-
import { z as
|
|
27494
|
+
import { z as z23 } from "zod";
|
|
27314
27495
|
function finding(id, severity, message, fix) {
|
|
27315
27496
|
return { id, severity, message, fix };
|
|
27316
27497
|
}
|
|
@@ -27817,11 +27998,11 @@ function registerDashboardReviewTools({ server, track }) {
|
|
|
27817
27998
|
Pass the complete page module source, the unchanged fixture, the factual interpretation, and the installed Sidebar, StatCard, and Table source files. Never summarize, reconstruct, or submit only the overview component. The tool derives the overview contract from the fixture. It fails incomplete source, rewritten copy, unsupported claims, weak hierarchy, missing or under-composed installed components, raw aside/table markup, the wrong scan order, action color on every rate, and stepper rails on short flows.
|
|
27818
27999
|
|
|
27819
28000
|
FAIL is blocking and returns an MCP tool error. Fix every finding and call the tool again until PASS. Then run check-comprehension and inspect-spacing, and call finalize-dashboard-review. Only its VALID source-bound receipt permits presentation.`,
|
|
27820
|
-
inputSchema:
|
|
27821
|
-
screen:
|
|
27822
|
-
source:
|
|
28001
|
+
inputSchema: z23.object({
|
|
28002
|
+
screen: z23.string().trim().min(1).max(120).describe("What this dashboard is, e.g. 'funnel report overview'."),
|
|
28003
|
+
source: z23.string().min(1).max(12e4).describe("Complete TSX or JSX source for the dashboard page."),
|
|
27823
28004
|
fixture: dashboardFixtureSchema.describe("The request's unchanged fixture object. The first step is the overview; remaining steps are dependent task screens."),
|
|
27824
|
-
interpretation:
|
|
28005
|
+
interpretation: z23.string().trim().min(1).max(2e3).describe("Exact supplied or factually derived takeaway that must appear after records."),
|
|
27825
28006
|
componentSources: dashboardComponentSourcesSchema.optional().describe("Installed Sidebar, StatCard, and Table source files for compound-composition review."),
|
|
27826
28007
|
context: CONTEXT_ARG
|
|
27827
28008
|
}),
|
|
@@ -27869,34 +28050,34 @@ var init_dashboard_review_tools = __esm({
|
|
|
27869
28050
|
"use strict";
|
|
27870
28051
|
init_review_capture();
|
|
27871
28052
|
init_shared();
|
|
27872
|
-
dashboardFixtureSchema =
|
|
27873
|
-
stats:
|
|
27874
|
-
label:
|
|
27875
|
-
value:
|
|
28053
|
+
dashboardFixtureSchema = z23.object({
|
|
28054
|
+
stats: z23.array(z23.object({
|
|
28055
|
+
label: z23.string().trim().min(1).max(300),
|
|
28056
|
+
value: z23.string().trim().min(1).max(300)
|
|
27876
28057
|
})).max(100).optional(),
|
|
27877
|
-
records:
|
|
27878
|
-
title:
|
|
27879
|
-
detail:
|
|
27880
|
-
meta:
|
|
27881
|
-
status:
|
|
28058
|
+
records: z23.array(z23.object({
|
|
28059
|
+
title: z23.string().trim().min(1).max(300),
|
|
28060
|
+
detail: z23.string().trim().min(1).max(1e3),
|
|
28061
|
+
meta: z23.string().trim().min(1).max(300),
|
|
28062
|
+
status: z23.string().trim().min(1).max(300)
|
|
27882
28063
|
})).max(100).optional(),
|
|
27883
|
-
steps:
|
|
27884
|
-
title:
|
|
27885
|
-
description:
|
|
27886
|
-
action:
|
|
28064
|
+
steps: z23.array(z23.object({
|
|
28065
|
+
title: z23.string().trim().min(1).max(300),
|
|
28066
|
+
description: z23.string().trim().min(1).max(1e3),
|
|
28067
|
+
action: z23.string().trim().min(1).max(200)
|
|
27887
28068
|
})).min(2).max(50)
|
|
27888
28069
|
});
|
|
27889
|
-
dashboardComponentSourcesSchema =
|
|
27890
|
-
sidebar:
|
|
27891
|
-
statCard:
|
|
27892
|
-
table:
|
|
28070
|
+
dashboardComponentSourcesSchema = z23.object({
|
|
28071
|
+
sidebar: z23.string().min(1).max(12e4),
|
|
28072
|
+
statCard: z23.string().min(1).max(12e4),
|
|
28073
|
+
table: z23.string().min(1).max(12e4)
|
|
27893
28074
|
});
|
|
27894
28075
|
}
|
|
27895
28076
|
});
|
|
27896
28077
|
|
|
27897
28078
|
// ../shared/src/mcp/tools/final-ui-review-tools.ts
|
|
27898
28079
|
import { createHash as createHash2 } from "crypto";
|
|
27899
|
-
import { z as
|
|
28080
|
+
import { z as z24 } from "zod";
|
|
27900
28081
|
function digest(value) {
|
|
27901
28082
|
return createHash2("sha256").update(value).digest("hex");
|
|
27902
28083
|
}
|
|
@@ -27907,7 +28088,9 @@ function comprehensionFindings(input) {
|
|
|
27907
28088
|
const report = analyzeComprehension({
|
|
27908
28089
|
screen: input.screen,
|
|
27909
28090
|
code: input.visibleSource,
|
|
27910
|
-
knownTerms: input.knownTerms
|
|
28091
|
+
knownTerms: input.knownTerms,
|
|
28092
|
+
// A dashboard is a screen even when its name mentions slides. [BD-593]
|
|
28093
|
+
surface: "screen"
|
|
27911
28094
|
});
|
|
27912
28095
|
return report.findings.flatMap(
|
|
27913
28096
|
(item, index2) => item.severity === "minor" ? [] : [
|
|
@@ -28308,15 +28491,15 @@ function registerFinalUiReviewTools({
|
|
|
28308
28491
|
description: `Issue a source-bound receipt for a completed dashboard.
|
|
28309
28492
|
|
|
28310
28493
|
Submit the public HTTPS preview plus exact source, unchanged data, and installed components. Better Design's own browser captures the deployed page at 390, 1440, and 1728 pixels and content-addresses the live deployment. No trusted browser capture means incomplete. The review blocks weak hierarchy, under-composed components, hidden fixture fields, unclear copy, spacing defects, unsupported interpretations, or source edits.`,
|
|
28311
|
-
inputSchema:
|
|
28312
|
-
screen:
|
|
28313
|
-
previewUrl:
|
|
28494
|
+
inputSchema: z24.object({
|
|
28495
|
+
screen: z24.string().trim().min(1).max(120),
|
|
28496
|
+
previewUrl: z24.string().url().max(2048).refine((value) => new URL(value).protocol === "https:", {
|
|
28314
28497
|
message: "Use a public HTTPS preview URL."
|
|
28315
28498
|
}).describe("Public HTTPS URL for the exact deployed dashboard route."),
|
|
28316
|
-
source:
|
|
28499
|
+
source: z24.string().min(1).max(24e4).describe(
|
|
28317
28500
|
"Complete TSX or JSX page source, including its default export."
|
|
28318
28501
|
),
|
|
28319
|
-
visibleSource:
|
|
28502
|
+
visibleSource: z24.string().min(1).max(12e4).describe(
|
|
28320
28503
|
"Exact visible screen component copied unchanged from source."
|
|
28321
28504
|
),
|
|
28322
28505
|
fixture: dashboardFixtureSchema.describe(
|
|
@@ -28325,7 +28508,7 @@ Submit the public HTTPS preview plus exact source, unchanged data, and installed
|
|
|
28325
28508
|
componentSources: dashboardComponentSourcesSchema.describe(
|
|
28326
28509
|
"Exact installed Sidebar, StatCard, and Table component source files."
|
|
28327
28510
|
),
|
|
28328
|
-
knownTerms:
|
|
28511
|
+
knownTerms: z24.array(z24.string().trim().min(1).max(60)).max(50).optional(),
|
|
28329
28512
|
context: CONTEXT_ARG
|
|
28330
28513
|
}),
|
|
28331
28514
|
annotations: READ_ONLY_OPEN_WORLD_TOOL_ANNOTATIONS
|
|
@@ -28396,7 +28579,7 @@ var init_final_ui_review_tools = __esm({
|
|
|
28396
28579
|
});
|
|
28397
28580
|
|
|
28398
28581
|
// ../shared/src/mcp/tools/widget-tools.ts
|
|
28399
|
-
import { z as
|
|
28582
|
+
import { z as z25 } from "zod";
|
|
28400
28583
|
function hasIntake(args) {
|
|
28401
28584
|
return Boolean(args.widgetType || INTAKE_FIELDS.some(({ key }) => args[key]));
|
|
28402
28585
|
}
|
|
@@ -28519,16 +28702,16 @@ var init_widget_tools = __esm({
|
|
|
28519
28702
|
WIDGET_GUIDE_TOPIC = "better-widgets";
|
|
28520
28703
|
WIDGET_GUIDE_TOOL = "get-widget-guide";
|
|
28521
28704
|
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:
|
|
28705
|
+
WIDGET_GUIDE_INPUT_SCHEMA = z25.object({
|
|
28706
|
+
widgetType: z25.enum(BETTER_WIDGET_TYPES).optional().describe("Widget kind: calculator, estimator, converter, generator, quiz, or checklist."),
|
|
28707
|
+
userGoal: z25.string().trim().min(1).max(500).optional().describe("Who will use the widget and the decision or task it helps them complete."),
|
|
28708
|
+
inputs: z25.string().trim().min(1).max(1e3).optional().describe("Required inputs, including units, allowed ranges, and defaults."),
|
|
28709
|
+
interaction: z25.string().trim().min(1).max(750).optional().describe("How readers tune inputs and see feedback, including presets, custom controls, direct manipulation, and exact-value entry."),
|
|
28710
|
+
transformation: z25.string().trim().min(1).max(1500).optional().describe("Formula, scoring rule, conversion, or transformation that produces the result."),
|
|
28711
|
+
result: z25.string().trim().min(1).max(750).optional().describe("What the result shows, explains, and lets the user do next."),
|
|
28712
|
+
hostFramework: z25.string().trim().min(1).max(300).optional().describe("The host framework and the page or component that will contain the widget."),
|
|
28713
|
+
visualContext: z25.string().trim().min(1).max(750).optional().describe("Existing design tokens, components, or brand reference the widget must match."),
|
|
28714
|
+
maxTokens: z25.number().min(2e3).max(5e4).optional().describe("Maximum tokens to return. Truncates content to fit. Recommended: 5000-10000."),
|
|
28532
28715
|
context: CONTEXT_ARG
|
|
28533
28716
|
});
|
|
28534
28717
|
INTAKE_FIELDS = [
|
|
@@ -28582,7 +28765,7 @@ The completed response combines a scoped build brief with Better Design's stable
|
|
|
28582
28765
|
});
|
|
28583
28766
|
|
|
28584
28767
|
// ../shared/src/mcp/feature-map/schema.ts
|
|
28585
|
-
import { z as
|
|
28768
|
+
import { z as z26 } from "zod";
|
|
28586
28769
|
function oneLine(text3) {
|
|
28587
28770
|
return text3.replace(/\s+/g, " ").trim();
|
|
28588
28771
|
}
|
|
@@ -28617,38 +28800,38 @@ var init_schema3 = __esm({
|
|
|
28617
28800
|
"use strict";
|
|
28618
28801
|
FEATURE_MAP_PATH = ".better-design/feature-map.json";
|
|
28619
28802
|
FEATURE_MAP_LIMITS = { features: 200, flows: 100, steps: 50, paths: 200, name: 120 };
|
|
28620
|
-
NAME =
|
|
28621
|
-
PATHS =
|
|
28622
|
-
featureMapStepSchema =
|
|
28803
|
+
NAME = z26.string().trim().min(1).max(FEATURE_MAP_LIMITS.name);
|
|
28804
|
+
PATHS = z26.array(z26.string().trim().min(1).max(500)).max(FEATURE_MAP_LIMITS.paths).default([]);
|
|
28805
|
+
featureMapStepSchema = z26.object({
|
|
28623
28806
|
name: NAME,
|
|
28624
28807
|
/** URL path such as `/settings/billing`. Absent for a dialog or an email. */
|
|
28625
|
-
route:
|
|
28808
|
+
route: z26.string().trim().max(300).optional(),
|
|
28626
28809
|
/** The file that renders the step, such as the route's page file. */
|
|
28627
|
-
screen:
|
|
28810
|
+
screen: z26.string().trim().max(500).optional(),
|
|
28628
28811
|
/** What the user does here, such as "confirms the cancellation". */
|
|
28629
|
-
action:
|
|
28812
|
+
action: z26.string().trim().max(300).optional(),
|
|
28630
28813
|
components: PATHS,
|
|
28631
28814
|
/** API routes, server actions, hooks and queries the step reads or writes. */
|
|
28632
28815
|
data: PATHS
|
|
28633
28816
|
});
|
|
28634
|
-
featureMapFlowSchema =
|
|
28817
|
+
featureMapFlowSchema = z26.object({
|
|
28635
28818
|
id: NAME,
|
|
28636
28819
|
name: NAME,
|
|
28637
|
-
description:
|
|
28638
|
-
steps:
|
|
28820
|
+
description: z26.string().trim().max(500).optional(),
|
|
28821
|
+
steps: z26.array(featureMapStepSchema).min(1).max(FEATURE_MAP_LIMITS.steps)
|
|
28639
28822
|
});
|
|
28640
|
-
featureMapFeatureSchema =
|
|
28823
|
+
featureMapFeatureSchema = z26.object({
|
|
28641
28824
|
id: NAME,
|
|
28642
28825
|
name: NAME,
|
|
28643
|
-
description:
|
|
28644
|
-
flows:
|
|
28826
|
+
description: z26.string().trim().max(500).optional(),
|
|
28827
|
+
flows: z26.array(featureMapFlowSchema).max(FEATURE_MAP_LIMITS.flows),
|
|
28645
28828
|
/** Components that belong to the feature but to no single step. */
|
|
28646
28829
|
components: PATHS
|
|
28647
28830
|
});
|
|
28648
|
-
featureMapSchema =
|
|
28649
|
-
version:
|
|
28650
|
-
product:
|
|
28651
|
-
features:
|
|
28831
|
+
featureMapSchema = z26.object({
|
|
28832
|
+
version: z26.literal(1),
|
|
28833
|
+
product: z26.string().trim().max(120).optional(),
|
|
28834
|
+
features: z26.array(featureMapFeatureSchema).max(FEATURE_MAP_LIMITS.features)
|
|
28652
28835
|
});
|
|
28653
28836
|
}
|
|
28654
28837
|
});
|
|
@@ -28926,7 +29109,7 @@ var init_read = __esm({
|
|
|
28926
29109
|
});
|
|
28927
29110
|
|
|
28928
29111
|
// ../shared/src/mcp/feature-map/update.ts
|
|
28929
|
-
import { z as
|
|
29112
|
+
import { z as z27 } from "zod";
|
|
28930
29113
|
function find(items, ref) {
|
|
28931
29114
|
const r = ref.trim();
|
|
28932
29115
|
const index2 = items.findIndex((i) => i.id === r || i.name.toLowerCase() === r.toLowerCase());
|
|
@@ -28989,21 +29172,21 @@ var init_update2 = __esm({
|
|
|
28989
29172
|
"../shared/src/mcp/feature-map/update.ts"() {
|
|
28990
29173
|
"use strict";
|
|
28991
29174
|
init_schema3();
|
|
28992
|
-
NAME2 =
|
|
28993
|
-
REF =
|
|
29175
|
+
NAME2 = z27.string().trim().min(1).max(120);
|
|
29176
|
+
REF = z27.string().trim().min(1).max(120).describe("The id or exact name of an existing feature or flow.");
|
|
28994
29177
|
FLOW_INPUT = featureMapFlowSchema.omit({ id: true });
|
|
28995
|
-
featureMapChangeSchema =
|
|
28996
|
-
|
|
28997
|
-
op:
|
|
29178
|
+
featureMapChangeSchema = z27.discriminatedUnion("op", [
|
|
29179
|
+
z27.object({
|
|
29180
|
+
op: z27.literal("add-feature"),
|
|
28998
29181
|
name: NAME2,
|
|
28999
|
-
description:
|
|
29000
|
-
flows:
|
|
29182
|
+
description: z27.string().trim().max(500).optional(),
|
|
29183
|
+
flows: z27.array(FLOW_INPUT).max(100).optional()
|
|
29001
29184
|
}),
|
|
29002
|
-
|
|
29003
|
-
|
|
29004
|
-
|
|
29005
|
-
|
|
29006
|
-
|
|
29185
|
+
z27.object({ op: z27.literal("rename-feature"), feature: REF, name: NAME2 }),
|
|
29186
|
+
z27.object({ op: z27.literal("remove-feature"), feature: REF }),
|
|
29187
|
+
z27.object({ op: z27.literal("set-flow"), feature: REF, flow: FLOW_INPUT }),
|
|
29188
|
+
z27.object({ op: z27.literal("rename-flow"), feature: REF, flow: REF, name: NAME2 }),
|
|
29189
|
+
z27.object({ op: z27.literal("remove-flow"), feature: REF, flow: REF })
|
|
29007
29190
|
]);
|
|
29008
29191
|
idTaken = (items, id, except2) => items.some((item, i) => item.id === id && i !== except2);
|
|
29009
29192
|
toFlow = (f) => ({ id: slugify(f.name), ...f });
|
|
@@ -29022,7 +29205,7 @@ var init_feature_map = __esm({
|
|
|
29022
29205
|
});
|
|
29023
29206
|
|
|
29024
29207
|
// ../shared/src/mcp/tools/feature-map-tools.ts
|
|
29025
|
-
import { z as
|
|
29208
|
+
import { z as z28 } from "zod";
|
|
29026
29209
|
function registerFeatureMapTools({ server, tracked }) {
|
|
29027
29210
|
server.registerTool(
|
|
29028
29211
|
"build-feature-map",
|
|
@@ -29033,9 +29216,9 @@ function registerFeatureMapTools({ server, tracked }) {
|
|
|
29033
29216
|
Run \`git ls-files\` in the repository and pass every path as \`files\`. Routes become screens (Next.js app and pages routers, Remix and React Router flat routes, SvelteKit, Nuxt), each chain of nested routes becomes a flow (/settings/billing/cancel is Settings \u2192 Billing \u2192 Cancel), and components, API routes, hooks and server actions join the step whose name they share.
|
|
29034
29217
|
|
|
29035
29218
|
The result is a first draft. Write it to ${FEATURE_MAP_PATH}, show the user the flows, and ask which steps are missing: a dialog, an email or a background job has no route, so add those with update-feature-map (op "set-flow").`,
|
|
29036
|
-
inputSchema:
|
|
29037
|
-
files:
|
|
29038
|
-
product:
|
|
29219
|
+
inputSchema: z28.object({
|
|
29220
|
+
files: z28.array(z28.string().max(500)).min(1).max(5e4).describe("Every file path in the repository, relative to its root, as printed by `git ls-files`."),
|
|
29221
|
+
product: z28.string().trim().max(120).optional().describe("The product's name, stored at the top of the map."),
|
|
29039
29222
|
context: CONTEXT_ARG
|
|
29040
29223
|
}),
|
|
29041
29224
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29065,11 +29248,11 @@ ${serializeFeatureMap(map).trimEnd()}
|
|
|
29065
29248
|
description: `Find the flows a change touches, with the screen, route, components and data files of every step. ${WHEN_TO_USE}
|
|
29066
29249
|
|
|
29067
29250
|
Pass \`flow\` ("cancel account"), \`feature\` ("billing") or \`file\` (a path you are about to edit) to narrow it. With no query it lists every flow.`,
|
|
29068
|
-
inputSchema:
|
|
29251
|
+
inputSchema: z28.object({
|
|
29069
29252
|
map: MAP_ARG,
|
|
29070
|
-
feature:
|
|
29071
|
-
flow:
|
|
29072
|
-
file:
|
|
29253
|
+
feature: z28.string().trim().max(120).optional().describe("A feature id or part of its name."),
|
|
29254
|
+
flow: z28.string().trim().max(120).optional().describe("A flow id or part of its name."),
|
|
29255
|
+
file: z28.string().trim().max(500).optional().describe("A file path or route; returns every flow that uses it."),
|
|
29073
29256
|
context: CONTEXT_ARG
|
|
29074
29257
|
}),
|
|
29075
29258
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29095,7 +29278,7 @@ Changes, one per call:
|
|
|
29095
29278
|
- { op: "remove-flow", feature, flow }
|
|
29096
29279
|
|
|
29097
29280
|
\`feature\` and \`flow\` take an id or an exact name. Write the returned file to ${FEATURE_MAP_PATH}.`,
|
|
29098
|
-
inputSchema:
|
|
29281
|
+
inputSchema: z28.object({
|
|
29099
29282
|
map: MAP_ARG,
|
|
29100
29283
|
change: featureMapChangeSchema,
|
|
29101
29284
|
context: CONTEXT_ARG
|
|
@@ -29117,7 +29300,7 @@ var init_feature_map_tools = __esm({
|
|
|
29117
29300
|
"use strict";
|
|
29118
29301
|
init_feature_map();
|
|
29119
29302
|
init_shared();
|
|
29120
|
-
MAP_ARG =
|
|
29303
|
+
MAP_ARG = z28.string().min(1).max(2e6).describe(`The exact contents of ${FEATURE_MAP_PATH}.`);
|
|
29121
29304
|
text2 = (body, isError = false) => ({
|
|
29122
29305
|
content: [{ type: "text", text: body }],
|
|
29123
29306
|
isError
|
|
@@ -29139,7 +29322,7 @@ var init_feature_map_tools = __esm({
|
|
|
29139
29322
|
});
|
|
29140
29323
|
|
|
29141
29324
|
// ../shared/src/mcp/inspiration.ts
|
|
29142
|
-
import { z as
|
|
29325
|
+
import { z as z29 } from "zod";
|
|
29143
29326
|
function describeSet(set) {
|
|
29144
29327
|
const heading = `## ${set.title}${set.category ? ` (${set.category})` : ""}`;
|
|
29145
29328
|
if (!set.notes) return heading;
|
|
@@ -29160,40 +29343,40 @@ var init_inspiration = __esm({
|
|
|
29160
29343
|
"use strict";
|
|
29161
29344
|
INSPIRATION_TYPES = ["app-store-screenshots"];
|
|
29162
29345
|
MAX_INSPIRATION_LIMIT = 6;
|
|
29163
|
-
noteSchema =
|
|
29164
|
-
slug:
|
|
29346
|
+
noteSchema = z29.object({
|
|
29347
|
+
slug: z29.string(),
|
|
29165
29348
|
/** 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:
|
|
29349
|
+
writtenAt: z29.string(),
|
|
29350
|
+
headline: z29.string(),
|
|
29351
|
+
order: z29.string(),
|
|
29352
|
+
framing: z29.string(),
|
|
29353
|
+
color: z29.string()
|
|
29171
29354
|
});
|
|
29172
|
-
inspirationNotesFileSchema =
|
|
29173
|
-
model:
|
|
29174
|
-
notes:
|
|
29355
|
+
inspirationNotesFileSchema = z29.object({
|
|
29356
|
+
model: z29.string(),
|
|
29357
|
+
notes: z29.array(noteSchema)
|
|
29175
29358
|
});
|
|
29176
|
-
indexFileSchema =
|
|
29177
|
-
key:
|
|
29178
|
-
type:
|
|
29179
|
-
width:
|
|
29180
|
-
height:
|
|
29359
|
+
indexFileSchema = z29.object({
|
|
29360
|
+
key: z29.string(),
|
|
29361
|
+
type: z29.string(),
|
|
29362
|
+
width: z29.number(),
|
|
29363
|
+
height: z29.number()
|
|
29181
29364
|
});
|
|
29182
|
-
indexEntrySchema =
|
|
29183
|
-
title:
|
|
29184
|
-
slug:
|
|
29185
|
-
category:
|
|
29186
|
-
files:
|
|
29365
|
+
indexEntrySchema = z29.object({
|
|
29366
|
+
title: z29.string(),
|
|
29367
|
+
slug: z29.string(),
|
|
29368
|
+
category: z29.string().nullish(),
|
|
29369
|
+
files: z29.array(indexFileSchema)
|
|
29187
29370
|
});
|
|
29188
|
-
inspirationIndexSchema =
|
|
29189
|
-
entries:
|
|
29371
|
+
inspirationIndexSchema = z29.object({
|
|
29372
|
+
entries: z29.array(indexEntrySchema)
|
|
29190
29373
|
});
|
|
29191
29374
|
NOTE_DATE = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
|
|
29192
29375
|
}
|
|
29193
29376
|
});
|
|
29194
29377
|
|
|
29195
29378
|
// ../shared/src/mcp/tools/inspiration-tools.ts
|
|
29196
|
-
import { z as
|
|
29379
|
+
import { z as z30 } from "zod";
|
|
29197
29380
|
function describeSets(result) {
|
|
29198
29381
|
const lines = result.sets.map((set, i) => {
|
|
29199
29382
|
const category = set.category ? `, ${set.category}` : "";
|
|
@@ -29221,10 +29404,10 @@ function registerInspirationTools({ server, tracked }, readInspiration2) {
|
|
|
29221
29404
|
Use it before designing App Store or Google Play screenshots: pass \`type: "app-store-screenshots"\`, and a \`category\` such as "Health & Fitness" when the user's app has one. Each set arrives as its title and category, followed by its first screenshots.
|
|
29222
29405
|
|
|
29223
29406
|
Then propose two or three directions drawn from what the sets do (headline style, slide order, device framing, color), and let the user choose before you build anything.`,
|
|
29224
|
-
inputSchema:
|
|
29225
|
-
type:
|
|
29226
|
-
category:
|
|
29227
|
-
limit:
|
|
29407
|
+
inputSchema: z30.object({
|
|
29408
|
+
type: z30.enum(INSPIRATION_TYPES).describe('The kind of reference to send. Today: "app-store-screenshots".'),
|
|
29409
|
+
category: z30.string().trim().min(1).max(60).optional().describe('Optional App Store category, for example "Productivity" or "Health & Fitness". Ignores case.'),
|
|
29410
|
+
limit: z30.number().int().min(1).max(MAX_INSPIRATION_LIMIT).optional().describe(`How many sets to send, 1 to ${MAX_INSPIRATION_LIMIT}. Defaults to 3.`),
|
|
29228
29411
|
context: CONTEXT_ARG
|
|
29229
29412
|
}),
|
|
29230
29413
|
annotations: READ_ONLY_TOOL_ANNOTATIONS
|
|
@@ -29261,8 +29444,62 @@ var init_inspiration_tools = __esm({
|
|
|
29261
29444
|
}
|
|
29262
29445
|
});
|
|
29263
29446
|
|
|
29447
|
+
// ../shared/src/mcp/tools/feedback-tools.ts
|
|
29448
|
+
function createFeedbackSender(apiFetch, apiKey) {
|
|
29449
|
+
return async (report) => {
|
|
29450
|
+
const res = await apiFetch(`${APP_BASE_URL}/api/v1/feedback`, {
|
|
29451
|
+
method: "POST",
|
|
29452
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
29453
|
+
body: JSON.stringify(report),
|
|
29454
|
+
signal: AbortSignal.timeout(15e3)
|
|
29455
|
+
});
|
|
29456
|
+
const body = await res.json().catch(() => ({}));
|
|
29457
|
+
if (!res.ok || !body.id) throw new Error(body.message ?? `HTTP ${res.status}`);
|
|
29458
|
+
return { id: body.id, status: body.status ?? "open", message: body.message ?? "" };
|
|
29459
|
+
};
|
|
29460
|
+
}
|
|
29461
|
+
function registerFeedbackTools({ server, tracked }, sendFeedback) {
|
|
29462
|
+
if (!sendFeedback) return;
|
|
29463
|
+
server.registerTool(
|
|
29464
|
+
"send-feedback",
|
|
29465
|
+
{
|
|
29466
|
+
title: "Send Feedback",
|
|
29467
|
+
description: `Tell Better Design about a bug, a missing feature or wrong output you hit while using its tools or API. A person reads every report and approves any fix.
|
|
29468
|
+
|
|
29469
|
+
Send one report per problem, once you have finished what the user asked. Say what you tried, what you expected and what came back. Leave out secrets, personal data and the user's private code. Sending a report never spends credit.`,
|
|
29470
|
+
inputSchema: reportSchema,
|
|
29471
|
+
// Each call stores one new report and publishes nothing. See
|
|
29472
|
+
// `docs/mcp-tool-annotations.md`.
|
|
29473
|
+
annotations: {
|
|
29474
|
+
readOnlyHint: false,
|
|
29475
|
+
destructiveHint: false,
|
|
29476
|
+
idempotentHint: false,
|
|
29477
|
+
openWorldHint: false
|
|
29478
|
+
}
|
|
29479
|
+
},
|
|
29480
|
+
// Usage telemetry records the kind only. The report itself goes to the queue.
|
|
29481
|
+
async (report) => tracked("send-feedback", { kind: report.kind }, async () => {
|
|
29482
|
+
const info = server.server.getClientVersion();
|
|
29483
|
+
const client = info ? `${info.name}/${info.version}`.slice(0, AGENT_FEEDBACK_CLIENT_MAX) : void 0;
|
|
29484
|
+
const receipt = await sendFeedback({ ...report, client });
|
|
29485
|
+
return {
|
|
29486
|
+
content: [{ type: "text", text: `Report ${receipt.id} sent. ${receipt.message}` }]
|
|
29487
|
+
};
|
|
29488
|
+
})
|
|
29489
|
+
);
|
|
29490
|
+
}
|
|
29491
|
+
var reportSchema;
|
|
29492
|
+
var init_feedback_tools = __esm({
|
|
29493
|
+
"../shared/src/mcp/tools/feedback-tools.ts"() {
|
|
29494
|
+
"use strict";
|
|
29495
|
+
init_agent_feedback();
|
|
29496
|
+
init_shared();
|
|
29497
|
+
reportSchema = agentFeedbackInputSchema.omit({ client: true });
|
|
29498
|
+
}
|
|
29499
|
+
});
|
|
29500
|
+
|
|
29264
29501
|
// ../shared/src/mcp/tools.ts
|
|
29265
|
-
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2) {
|
|
29502
|
+
function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2, sendFeedback) {
|
|
29266
29503
|
const { track, tracked } = createTracking(server, onToolUsage);
|
|
29267
29504
|
const deps = {
|
|
29268
29505
|
server,
|
|
@@ -29289,6 +29526,7 @@ function registerAllTools(server, provider, onToolUsage, authContext, options =
|
|
|
29289
29526
|
registerFeatureMapTools(deps);
|
|
29290
29527
|
registerTeamRulesTools(deps);
|
|
29291
29528
|
registerInspirationTools(deps, readInspiration2);
|
|
29529
|
+
registerFeedbackTools(deps, sendFeedback);
|
|
29292
29530
|
registerPreviewResource(deps);
|
|
29293
29531
|
registerInspectionResource(deps);
|
|
29294
29532
|
}
|
|
@@ -29313,6 +29551,7 @@ var init_tools = __esm({
|
|
|
29313
29551
|
init_feature_map_tools();
|
|
29314
29552
|
init_team_rules_tools();
|
|
29315
29553
|
init_inspiration_tools();
|
|
29554
|
+
init_feedback_tools();
|
|
29316
29555
|
init_shared();
|
|
29317
29556
|
}
|
|
29318
29557
|
});
|
|
@@ -29322,7 +29561,7 @@ var MCP_IMPLEMENTATION_VERSION;
|
|
|
29322
29561
|
var init_version2 = __esm({
|
|
29323
29562
|
"../shared/src/mcp/version.ts"() {
|
|
29324
29563
|
"use strict";
|
|
29325
|
-
MCP_IMPLEMENTATION_VERSION = "0.
|
|
29564
|
+
MCP_IMPLEMENTATION_VERSION = "0.34.0";
|
|
29326
29565
|
}
|
|
29327
29566
|
});
|
|
29328
29567
|
|
|
@@ -29355,6 +29594,7 @@ __export(mcp_exports, {
|
|
|
29355
29594
|
analyzeDashboardStructure: () => analyzeDashboardStructure,
|
|
29356
29595
|
buildShopifyPrimitiveSet: () => buildShopifyPrimitiveSet,
|
|
29357
29596
|
canonicalMcpToolName: () => canonicalMcpToolName,
|
|
29597
|
+
createFeedbackSender: () => createFeedbackSender,
|
|
29358
29598
|
createMcpServer: () => createMcpServer,
|
|
29359
29599
|
declareAdvertisedSchemaDialect: () => declareAdvertisedSchemaDialect,
|
|
29360
29600
|
deriveDashboardInterpretation: () => deriveDashboardInterpretation,
|
|
@@ -29388,7 +29628,8 @@ function createMcpServer(options) {
|
|
|
29388
29628
|
userId,
|
|
29389
29629
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
29390
29630
|
directoryListing = false,
|
|
29391
|
-
readInspiration: readInspiration2
|
|
29631
|
+
readInspiration: readInspiration2,
|
|
29632
|
+
sendFeedback
|
|
29392
29633
|
} = options;
|
|
29393
29634
|
const server = new McpServer(
|
|
29394
29635
|
{ name, version: version2 },
|
|
@@ -29416,7 +29657,8 @@ function createMcpServer(options) {
|
|
|
29416
29657
|
apiFetch,
|
|
29417
29658
|
userId,
|
|
29418
29659
|
captureDashboardBrowser2,
|
|
29419
|
-
readInspiration2
|
|
29660
|
+
readInspiration2,
|
|
29661
|
+
sendFeedback
|
|
29420
29662
|
);
|
|
29421
29663
|
return server;
|
|
29422
29664
|
}
|
|
@@ -29433,6 +29675,7 @@ var init_mcp = __esm({
|
|
|
29433
29675
|
init_advertised_schema();
|
|
29434
29676
|
init_tool_names();
|
|
29435
29677
|
init_principle_tools();
|
|
29678
|
+
init_feedback_tools();
|
|
29436
29679
|
init_instructions();
|
|
29437
29680
|
init_session_guidance();
|
|
29438
29681
|
init_shared();
|
|
@@ -29461,6 +29704,7 @@ __export(api_client_exports, {
|
|
|
29461
29704
|
searchIconLibraries: () => searchIconLibraries,
|
|
29462
29705
|
searchIcons: () => searchIcons,
|
|
29463
29706
|
searchPrinciples: () => searchPrinciples,
|
|
29707
|
+
sendAgentFeedback: () => sendAgentFeedback,
|
|
29464
29708
|
trackMcpInstalled: () => trackMcpInstalled,
|
|
29465
29709
|
trackToolUsage: () => trackToolUsage
|
|
29466
29710
|
});
|
|
@@ -29500,6 +29744,9 @@ function readInspiration(input) {
|
|
|
29500
29744
|
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
29501
29745
|
return request(`/v1/inspiration?${params}`, { timeoutMs: 3e4 });
|
|
29502
29746
|
}
|
|
29747
|
+
function sendAgentFeedback(report) {
|
|
29748
|
+
return request("/v1/feedback", { method: "POST", body: report });
|
|
29749
|
+
}
|
|
29503
29750
|
async function searchDesignSystems(query, limit = 5) {
|
|
29504
29751
|
return request(
|
|
29505
29752
|
`/v1/design-systems/search?q=${encodeURIComponent(query)}&limit=${limit}`
|
|
@@ -31476,7 +31723,7 @@ var init_engine = __esm({
|
|
|
31476
31723
|
});
|
|
31477
31724
|
|
|
31478
31725
|
// ../shared/src/lint/config.ts
|
|
31479
|
-
import { z as
|
|
31726
|
+
import { z as z31 } from "zod";
|
|
31480
31727
|
function mergeReviewConfig(override) {
|
|
31481
31728
|
if (!override) return DEFAULT_REVIEW_CONFIG;
|
|
31482
31729
|
return {
|
|
@@ -31534,47 +31781,47 @@ var init_config = __esm({
|
|
|
31534
31781
|
semanticTokens: { enabled: true }
|
|
31535
31782
|
}
|
|
31536
31783
|
};
|
|
31537
|
-
reviewFlowStepSchema =
|
|
31538
|
-
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31784
|
+
reviewFlowStepSchema = z31.discriminatedUnion("action", [
|
|
31785
|
+
z31.object({ action: z31.literal("click"), selector: z31.string() }),
|
|
31786
|
+
z31.object({ action: z31.literal("type"), selector: z31.string(), value: z31.string() }),
|
|
31787
|
+
z31.object({ action: z31.literal("press"), key: z31.string() }),
|
|
31788
|
+
z31.object({ action: z31.literal("wait"), milliseconds: z31.number().finite().nonnegative() })
|
|
31542
31789
|
]);
|
|
31543
|
-
visualReviewConfigSchema =
|
|
31544
|
-
enabled:
|
|
31545
|
-
model:
|
|
31546
|
-
enabled:
|
|
31547
|
-
maxScreenshots:
|
|
31790
|
+
visualReviewConfigSchema = z31.object({
|
|
31791
|
+
enabled: z31.boolean().optional(),
|
|
31792
|
+
model: z31.object({
|
|
31793
|
+
enabled: z31.boolean().optional(),
|
|
31794
|
+
maxScreenshots: z31.number().int().nonnegative().optional()
|
|
31548
31795
|
}).optional(),
|
|
31549
|
-
routes:
|
|
31550
|
-
viewports:
|
|
31551
|
-
flows:
|
|
31552
|
-
name:
|
|
31553
|
-
persona:
|
|
31554
|
-
path:
|
|
31555
|
-
steps:
|
|
31796
|
+
routes: z31.array(z31.string()).optional(),
|
|
31797
|
+
viewports: z31.array(z31.enum(["desktop", "mobile", "reflow"])).optional(),
|
|
31798
|
+
flows: z31.array(z31.object({
|
|
31799
|
+
name: z31.string(),
|
|
31800
|
+
persona: z31.string(),
|
|
31801
|
+
path: z31.string().optional(),
|
|
31802
|
+
steps: z31.array(reviewFlowStepSchema)
|
|
31556
31803
|
})).optional(),
|
|
31557
|
-
componentGallery:
|
|
31558
|
-
enabled:
|
|
31559
|
-
componentSelector:
|
|
31560
|
-
canvasSelector:
|
|
31561
|
-
labelSelector:
|
|
31562
|
-
maxComponents:
|
|
31804
|
+
componentGallery: z31.object({
|
|
31805
|
+
enabled: z31.boolean().optional(),
|
|
31806
|
+
componentSelector: z31.string().optional(),
|
|
31807
|
+
canvasSelector: z31.string().optional(),
|
|
31808
|
+
labelSelector: z31.string().optional(),
|
|
31809
|
+
maxComponents: z31.number().int().nonnegative().optional()
|
|
31563
31810
|
}).optional()
|
|
31564
31811
|
});
|
|
31565
|
-
reviewConfigOverrideSchema =
|
|
31566
|
-
designSystem:
|
|
31812
|
+
reviewConfigOverrideSchema = z31.object({
|
|
31813
|
+
designSystem: z31.string().optional(),
|
|
31567
31814
|
visual: visualReviewConfigSchema.optional(),
|
|
31568
|
-
rules:
|
|
31569
|
-
elements:
|
|
31570
|
-
tokens:
|
|
31571
|
-
wcag:
|
|
31572
|
-
enabled:
|
|
31573
|
-
severity:
|
|
31815
|
+
rules: z31.object({
|
|
31816
|
+
elements: z31.record(z31.string(), z31.object({ use: z31.string(), from: z31.string() })).optional(),
|
|
31817
|
+
tokens: z31.record(z31.string(), z31.string()).optional(),
|
|
31818
|
+
wcag: z31.object({
|
|
31819
|
+
enabled: z31.boolean().optional(),
|
|
31820
|
+
severity: z31.enum(["all", "critical+serious"]).optional()
|
|
31574
31821
|
}).optional(),
|
|
31575
|
-
slop:
|
|
31576
|
-
copy:
|
|
31577
|
-
semanticTokens:
|
|
31822
|
+
slop: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31823
|
+
copy: z31.object({ enabled: z31.boolean().optional() }).optional(),
|
|
31824
|
+
semanticTokens: z31.object({ enabled: z31.boolean().optional() }).optional()
|
|
31578
31825
|
}).optional()
|
|
31579
31826
|
});
|
|
31580
31827
|
}
|
|
@@ -31950,7 +32197,7 @@ __export(review_tool_exports, {
|
|
|
31950
32197
|
isReviewableSourcePath: () => isReviewableSourcePath,
|
|
31951
32198
|
registerReviewTool: () => registerReviewTool
|
|
31952
32199
|
});
|
|
31953
|
-
import { z as
|
|
32200
|
+
import { z as z32 } from "zod";
|
|
31954
32201
|
function isReviewableSourcePath(path6) {
|
|
31955
32202
|
return /\.(tsx|jsx|ts|js|css)$/i.test(path6);
|
|
31956
32203
|
}
|
|
@@ -31974,11 +32221,11 @@ get-review-rules gives you the judgment rules to apply yourself. This tool is th
|
|
|
31974
32221
|
This source reviewer cannot judge rendered hierarchy or semantic color roles. After it passes, inspect the rendered dashboard and confirm title \u2192 metrics \u2192 records \u2192 interpretation, distinct action and status colors, and that a stepper begins only after a task starts.
|
|
31975
32222
|
|
|
31976
32223
|
Fix every critical and serious finding, then call it again on the fixed source. Findings feed the same loop as a human code review: read, fix, re-check.`,
|
|
31977
|
-
inputSchema:
|
|
31978
|
-
files:
|
|
31979
|
-
|
|
31980
|
-
path:
|
|
31981
|
-
content:
|
|
32224
|
+
inputSchema: z32.object({
|
|
32225
|
+
files: z32.array(
|
|
32226
|
+
z32.object({
|
|
32227
|
+
path: z32.string().trim().min(1).max(400).describe("Repo-relative file path, e.g. components/signup-form.tsx."),
|
|
32228
|
+
content: z32.string().max(MAX_FILE_CHARS).describe("The file's full source.")
|
|
31982
32229
|
})
|
|
31983
32230
|
).min(1).max(MAX_FILES).describe("The .tsx, .jsx, .ts, .js, and .css files to review, as written."),
|
|
31984
32231
|
context: CONTEXT_ARG
|
|
@@ -32039,7 +32286,7 @@ review-ui-code({ files: [{ path: "components/signup-form.tsx", content: "..." },
|
|
|
32039
32286
|
});
|
|
32040
32287
|
|
|
32041
32288
|
// ../shared/src/mcp/figma-prototype.ts
|
|
32042
|
-
import { z as
|
|
32289
|
+
import { z as z33 } from "zod";
|
|
32043
32290
|
function escapeHtml(value) {
|
|
32044
32291
|
return value.replace(/[&<>"]/g, (char2) => ESCAPES.get(char2) ?? char2);
|
|
32045
32292
|
}
|
|
@@ -32073,63 +32320,63 @@ var colorSchema, paintSchema, sizingSchema, baseSchema, prototypeNodeSchema, pro
|
|
|
32073
32320
|
var init_figma_prototype = __esm({
|
|
32074
32321
|
"../shared/src/mcp/figma-prototype.ts"() {
|
|
32075
32322
|
"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:
|
|
32323
|
+
colorSchema = z33.object({ r: z33.number(), g: z33.number(), b: z33.number(), a: z33.number() });
|
|
32324
|
+
paintSchema = z33.object({ variable: z33.string().optional(), color: colorSchema.optional() });
|
|
32325
|
+
sizingSchema = z33.enum(["FIXED", "HUG", "FILL"]);
|
|
32326
|
+
baseSchema = z33.object({
|
|
32327
|
+
name: z33.string(),
|
|
32328
|
+
width: z33.number(),
|
|
32329
|
+
height: z33.number(),
|
|
32330
|
+
x: z33.number(),
|
|
32331
|
+
y: z33.number(),
|
|
32332
|
+
sizing: z33.object({ horizontal: sizingSchema, vertical: sizingSchema }).optional(),
|
|
32333
|
+
absolute: z33.boolean().optional(),
|
|
32334
|
+
opacity: z33.number().optional()
|
|
32088
32335
|
});
|
|
32089
|
-
prototypeNodeSchema =
|
|
32336
|
+
prototypeNodeSchema = z33.lazy(() => z33.discriminatedUnion("kind", [
|
|
32090
32337
|
baseSchema.extend({
|
|
32091
|
-
kind:
|
|
32092
|
-
auditKey:
|
|
32093
|
-
systemId:
|
|
32094
|
-
texts:
|
|
32095
|
-
mainTexts:
|
|
32338
|
+
kind: z33.literal("instance"),
|
|
32339
|
+
auditKey: z33.string(),
|
|
32340
|
+
systemId: z33.string(),
|
|
32341
|
+
texts: z33.array(z33.string()),
|
|
32342
|
+
mainTexts: z33.array(z33.string())
|
|
32096
32343
|
}),
|
|
32097
32344
|
baseSchema.extend({
|
|
32098
|
-
kind:
|
|
32099
|
-
layout:
|
|
32100
|
-
direction:
|
|
32101
|
-
gap:
|
|
32102
|
-
padding:
|
|
32103
|
-
primary:
|
|
32104
|
-
counter:
|
|
32105
|
-
wrap:
|
|
32345
|
+
kind: z33.literal("frame"),
|
|
32346
|
+
layout: z33.object({
|
|
32347
|
+
direction: z33.enum(["HORIZONTAL", "VERTICAL"]),
|
|
32348
|
+
gap: z33.number(),
|
|
32349
|
+
padding: z33.tuple([z33.number(), z33.number(), z33.number(), z33.number()]),
|
|
32350
|
+
primary: z33.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]),
|
|
32351
|
+
counter: z33.enum(["MIN", "CENTER", "MAX", "BASELINE"]),
|
|
32352
|
+
wrap: z33.boolean().optional()
|
|
32106
32353
|
}).optional(),
|
|
32107
32354
|
fill: paintSchema.optional(),
|
|
32108
|
-
stroke:
|
|
32109
|
-
radius:
|
|
32110
|
-
clips:
|
|
32111
|
-
children:
|
|
32355
|
+
stroke: z33.object({ paint: paintSchema, weight: z33.number() }).optional(),
|
|
32356
|
+
radius: z33.number().optional(),
|
|
32357
|
+
clips: z33.boolean().optional(),
|
|
32358
|
+
children: z33.array(prototypeNodeSchema)
|
|
32112
32359
|
}),
|
|
32113
32360
|
baseSchema.extend({
|
|
32114
|
-
kind:
|
|
32115
|
-
characters:
|
|
32116
|
-
fontSize:
|
|
32117
|
-
fontWeight:
|
|
32118
|
-
lineHeight:
|
|
32119
|
-
letterSpacing:
|
|
32120
|
-
fontFamily:
|
|
32121
|
-
align:
|
|
32361
|
+
kind: z33.literal("text"),
|
|
32362
|
+
characters: z33.string(),
|
|
32363
|
+
fontSize: z33.number(),
|
|
32364
|
+
fontWeight: z33.number(),
|
|
32365
|
+
lineHeight: z33.number().optional(),
|
|
32366
|
+
letterSpacing: z33.number().optional(),
|
|
32367
|
+
fontFamily: z33.string().optional(),
|
|
32368
|
+
align: z33.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]),
|
|
32122
32369
|
fill: paintSchema.optional()
|
|
32123
32370
|
}),
|
|
32124
32371
|
baseSchema.extend({
|
|
32125
|
-
kind:
|
|
32126
|
-
svg:
|
|
32127
|
-
reason:
|
|
32372
|
+
kind: z33.literal("shape"),
|
|
32373
|
+
svg: z33.string(),
|
|
32374
|
+
reason: z33.string()
|
|
32128
32375
|
})
|
|
32129
32376
|
]));
|
|
32130
|
-
prototypeRequestSchema =
|
|
32131
|
-
type:
|
|
32132
|
-
id:
|
|
32377
|
+
prototypeRequestSchema = z33.object({
|
|
32378
|
+
type: z33.literal("better-design-figma-prototype"),
|
|
32379
|
+
id: z33.string(),
|
|
32133
32380
|
frame: prototypeNodeSchema
|
|
32134
32381
|
});
|
|
32135
32382
|
ESCAPES = /* @__PURE__ */ new Map([["&", "&"], ["<", "<"], [">", ">"], ['"', """]]);
|
|
@@ -32315,7 +32562,7 @@ var init_figma_model_prompt = __esm({
|
|
|
32315
32562
|
});
|
|
32316
32563
|
|
|
32317
32564
|
// ../shared/src/mcp/figma-model.ts
|
|
32318
|
-
import { z as
|
|
32565
|
+
import { z as z34 } from "zod";
|
|
32319
32566
|
function fenced(text3, language) {
|
|
32320
32567
|
const match = text3.match(new RegExp("```" + language + "[^\\n]*\\n([\\s\\S]*?)```", "i"));
|
|
32321
32568
|
return match ? match[1] : null;
|
|
@@ -32521,8 +32768,8 @@ var init_figma_model = __esm({
|
|
|
32521
32768
|
init_figma_prototype();
|
|
32522
32769
|
init_html_kit();
|
|
32523
32770
|
init_preview_html();
|
|
32524
|
-
unplacedSchema =
|
|
32525
|
-
unplaced:
|
|
32771
|
+
unplacedSchema = z34.object({
|
|
32772
|
+
unplaced: z34.array(z34.object({ name: z34.string(), reason: z34.string() }))
|
|
32526
32773
|
});
|
|
32527
32774
|
UNSAFE_BLOCKS = /<(script|iframe|object|embed|noscript)\b[\s\S]*?(<\/\1[^>]*>|$)/gi;
|
|
32528
32775
|
UNSAFE_TAGS = /<\/?(meta|link|base|form|style)\b[^>]*(>|$)/gi;
|
|
@@ -32623,7 +32870,7 @@ var init_figma_model_handoff = __esm({
|
|
|
32623
32870
|
});
|
|
32624
32871
|
|
|
32625
32872
|
// ../shared/src/mcp/figma-model-providers.ts
|
|
32626
|
-
import { z as
|
|
32873
|
+
import { z as z35 } from "zod";
|
|
32627
32874
|
function serverLabel(url) {
|
|
32628
32875
|
try {
|
|
32629
32876
|
const parsed = new URL(url);
|
|
@@ -32716,19 +32963,19 @@ var init_figma_model_providers = __esm({
|
|
|
32716
32963
|
DEFAULT_LOCAL_MODEL = "gemma4:latest";
|
|
32717
32964
|
DEFAULT_LOCAL_BASE_URL = "http://localhost:11434/v1";
|
|
32718
32965
|
MAX_OUTPUT_TOKENS = 16e3;
|
|
32719
|
-
chatReplySchema =
|
|
32720
|
-
choices:
|
|
32721
|
-
message:
|
|
32722
|
-
finish_reason:
|
|
32966
|
+
chatReplySchema = z35.object({
|
|
32967
|
+
choices: z35.array(z35.object({
|
|
32968
|
+
message: z35.object({ content: z35.string().nullable().optional() }),
|
|
32969
|
+
finish_reason: z35.string().nullable().optional()
|
|
32723
32970
|
})).min(1),
|
|
32724
|
-
usage:
|
|
32971
|
+
usage: z35.object({ prompt_tokens: z35.number(), completion_tokens: z35.number() }).optional()
|
|
32725
32972
|
});
|
|
32726
|
-
errorCodeSchema =
|
|
32973
|
+
errorCodeSchema = z35.object({ code: z35.string() });
|
|
32727
32974
|
}
|
|
32728
32975
|
});
|
|
32729
32976
|
|
|
32730
32977
|
// ../shared/src/mcp/figma-relay.ts
|
|
32731
|
-
import { z as
|
|
32978
|
+
import { z as z36 } from "zod";
|
|
32732
32979
|
function noPluginMessage() {
|
|
32733
32980
|
return "No Figma plugin is connected. Open the Figma file, run Plugins, Development, Better Design, and leave the panel open.";
|
|
32734
32981
|
}
|
|
@@ -32742,35 +32989,35 @@ var init_figma_relay = __esm({
|
|
|
32742
32989
|
"use strict";
|
|
32743
32990
|
init_figma_prototype();
|
|
32744
32991
|
FIGMA_RELAY_PORT = 4397;
|
|
32745
|
-
relayRequestSchema =
|
|
32746
|
-
id:
|
|
32992
|
+
relayRequestSchema = z36.object({
|
|
32993
|
+
id: z36.string(),
|
|
32747
32994
|
/** A frame name as it reads in Figma. Omitted means the current selection. */
|
|
32748
|
-
frame:
|
|
32995
|
+
frame: z36.string().optional(),
|
|
32749
32996
|
/**
|
|
32750
32997
|
* `model` asks for the serialized frame and its PNG instead of a rendered
|
|
32751
32998
|
* page, so a model can rebuild a frame that holds no library component.
|
|
32752
32999
|
* Omitted means the library path, unchanged. [BD-866]
|
|
32753
33000
|
*/
|
|
32754
|
-
mode:
|
|
33001
|
+
mode: z36.enum(["library", "model"]).optional()
|
|
32755
33002
|
});
|
|
32756
|
-
relayResultSchema =
|
|
32757
|
-
id:
|
|
32758
|
-
html:
|
|
32759
|
-
error:
|
|
32760
|
-
components:
|
|
32761
|
-
report:
|
|
32762
|
-
frame:
|
|
33003
|
+
relayResultSchema = z36.object({
|
|
33004
|
+
id: z36.string(),
|
|
33005
|
+
html: z36.string().optional(),
|
|
33006
|
+
error: z36.string().optional(),
|
|
33007
|
+
components: z36.number().optional(),
|
|
33008
|
+
report: z36.array(z36.object({ name: z36.string(), reason: z36.string() })).default([]),
|
|
33009
|
+
frame: z36.string().optional(),
|
|
32763
33010
|
/** The frames the plugin found, so a missing name can say what is there. */
|
|
32764
|
-
frames:
|
|
33011
|
+
frames: z36.array(z36.string()).optional(),
|
|
32765
33012
|
/** Model mode: the serialized frame, for the model to rebuild. */
|
|
32766
33013
|
tree: prototypeNodeSchema.optional(),
|
|
32767
33014
|
/** Model mode: the frame's PNG export, base64. */
|
|
32768
|
-
png:
|
|
33015
|
+
png: z36.string().optional()
|
|
32769
33016
|
});
|
|
32770
|
-
relayHelloSchema =
|
|
32771
|
-
file:
|
|
33017
|
+
relayHelloSchema = z36.object({
|
|
33018
|
+
file: z36.string(),
|
|
32772
33019
|
/** The design system the file holds, when the plugin knows it. */
|
|
32773
|
-
system:
|
|
33020
|
+
system: z36.string().optional()
|
|
32774
33021
|
});
|
|
32775
33022
|
POLL_HOLD_MS = 25e3;
|
|
32776
33023
|
REQUEST_TIMEOUT_MS = 3e5;
|
|
@@ -32938,7 +33185,7 @@ __export(figma_prototype_tool_exports, {
|
|
|
32938
33185
|
});
|
|
32939
33186
|
import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
|
|
32940
33187
|
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
32941
|
-
import { z as
|
|
33188
|
+
import { z as z37 } from "zod";
|
|
32942
33189
|
async function ensureRelay() {
|
|
32943
33190
|
if (relay) return relay;
|
|
32944
33191
|
starting ??= startFigmaRelay().then((started) => {
|
|
@@ -32998,30 +33245,30 @@ function registerFigmaPrototypeTool(server) {
|
|
|
32998
33245
|
"yourself: the tool returns a prompt file and the frame's image, you write the answer to a file, and",
|
|
32999
33246
|
"you call this tool again with frameId and answerPath so it checks the page and reports what is missing."
|
|
33000
33247
|
].join(" "),
|
|
33001
|
-
inputSchema:
|
|
33002
|
-
frame:
|
|
33003
|
-
outputPath:
|
|
33004
|
-
mode:
|
|
33248
|
+
inputSchema: z37.object({
|
|
33249
|
+
frame: z37.string().optional().describe("The frame's name as it reads in Figma. Omit to use the current selection."),
|
|
33250
|
+
outputPath: z37.string().optional().describe("Where to write the HTML. Relative paths resolve against the working directory."),
|
|
33251
|
+
mode: z37.enum(["library", "model"]).optional().describe(
|
|
33005
33252
|
"library (the default) maps components from a Better Design library. model asks a model to rebuild a frame from any file."
|
|
33006
33253
|
),
|
|
33007
|
-
system:
|
|
33008
|
-
provider:
|
|
33254
|
+
system: z37.string().optional().describe("Model mode: the catalog design system to build with, as find-design-system names it."),
|
|
33255
|
+
provider: z37.enum(["agent", "openai-compatible"]).optional().describe(
|
|
33009
33256
|
"Model mode: agent (the default) hands the frame to you to rebuild; openai-compatible calls a local server, Ollama by default."
|
|
33010
33257
|
),
|
|
33011
|
-
model:
|
|
33012
|
-
frameId:
|
|
33013
|
-
answerPath:
|
|
33258
|
+
model: z37.string().optional().describe(`Model mode with openai-compatible: the local model id. Defaults to ${DEFAULT_LOCAL_MODEL}.`),
|
|
33259
|
+
frameId: z37.string().optional().describe("Model mode, second call: the frameId the first call returned."),
|
|
33260
|
+
answerPath: z37.string().optional().describe("Model mode, second call: the file holding your answer, as the prompt file asks.")
|
|
33014
33261
|
}),
|
|
33015
|
-
outputSchema:
|
|
33016
|
-
status:
|
|
33017
|
-
message:
|
|
33018
|
-
frameId:
|
|
33019
|
-
promptPath:
|
|
33020
|
-
imagePath:
|
|
33021
|
-
path:
|
|
33022
|
-
components:
|
|
33023
|
-
report:
|
|
33024
|
-
model:
|
|
33262
|
+
outputSchema: z37.object({
|
|
33263
|
+
status: z37.enum(["completed", "failed", "needs-answer"]),
|
|
33264
|
+
message: z37.string(),
|
|
33265
|
+
frameId: z37.string().optional(),
|
|
33266
|
+
promptPath: z37.string().optional(),
|
|
33267
|
+
imagePath: z37.string().optional(),
|
|
33268
|
+
path: z37.string().optional(),
|
|
33269
|
+
components: z37.number().optional(),
|
|
33270
|
+
report: z37.array(z37.object({ name: z37.string(), reason: z37.string() })).optional(),
|
|
33271
|
+
model: z37.string().optional()
|
|
33025
33272
|
}),
|
|
33026
33273
|
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
33027
33274
|
},
|
|
@@ -33244,6 +33491,7 @@ async function startServer() {
|
|
|
33244
33491
|
const {
|
|
33245
33492
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33246
33493
|
readInspiration: readInspiration2,
|
|
33494
|
+
sendAgentFeedback: sendAgentFeedback2,
|
|
33247
33495
|
trackToolUsage: trackToolUsage2,
|
|
33248
33496
|
trackMcpInstalled: trackMcpInstalled2
|
|
33249
33497
|
} = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
@@ -33270,7 +33518,8 @@ async function startServer() {
|
|
|
33270
33518
|
version: MCP_IMPLEMENTATION_VERSION2,
|
|
33271
33519
|
onToolUsage,
|
|
33272
33520
|
captureDashboardBrowser: captureDashboardBrowser2,
|
|
33273
|
-
readInspiration: readInspiration2
|
|
33521
|
+
readInspiration: readInspiration2,
|
|
33522
|
+
sendFeedback: sendAgentFeedback2
|
|
33274
33523
|
});
|
|
33275
33524
|
registerReviewTool2(server, onToolUsage);
|
|
33276
33525
|
registerFigmaPrototypeTool2(server);
|