better-design 0.41.0 → 0.42.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 CHANGED
@@ -45,7 +45,7 @@ the variables in every mode, the text and effect styles, and the components
45
45
  with their variant options. It writes them to one JSON file. You confirm the
46
46
  read in the plugin panel. Pass that file to `create-figma-prototype` as
47
47
  `kitPath` in model mode, and the prototype uses the kit's colours, font and
48
- radius.
48
+ radius. Pass a `projectId` to also keep the kit on that project.
49
49
 
50
50
  A frame from a file that was not built from a Better Design library has no
51
51
  components to map, so pass `mode: "model"` and a catalog `system`. The plugin
package/dist/index.js CHANGED
@@ -12856,7 +12856,7 @@ var init_agent_feedback = __esm({
12856
12856
 
12857
12857
  // ../shared/src/schema.ts
12858
12858
  import { z as z8 } from "zod";
12859
- 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, TEAM_RULE_PROPOSAL_MAX_BYTES, teamRuleProposals, projectRuleKindValues, PROJECT_RULE_MAX_BYTES, PROJECT_RULE_NAME_MAX_CHARS, projectRules, agentFeedbackStatusValues, agentFeedback, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, claimCodes, emailCampaignSends, emailCampaignSteps, projectEvents, adminProjectReadRouteValues, adminProjectReads, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, productOutcomeMetricValues, productOutcomeStatusValues, productOutcomeBlockedReasonValues, mcpIssueImprovementOutcomes, 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;
12859
+ 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, TEAM_RULE_PROPOSAL_MAX_BYTES, teamRuleProposals, projectRuleKindValues, PROJECT_RULE_MAX_BYTES, PROJECT_RULE_NAME_MAX_CHARS, projectRules, projectFigmaKits, agentFeedbackStatusValues, agentFeedback, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, claimCodes, emailCampaignSends, emailCampaignSteps, projectEvents, adminProjectReadRouteValues, adminProjectReads, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, productOutcomeMetricValues, productOutcomeStatusValues, productOutcomeBlockedReasonValues, mcpIssueImprovementOutcomes, 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, accountPages;
12860
12860
  var init_schema2 = __esm({
12861
12861
  "../shared/src/schema.ts"() {
12862
12862
  "use strict";
@@ -13308,6 +13308,13 @@ var init_schema2 = __esm({
13308
13308
  )
13309
13309
  ]
13310
13310
  );
13311
+ projectFigmaKits = pgTable("project_figma_kits", {
13312
+ projectId: uuid("project_id").primaryKey().references(() => projects.id, { onDelete: "cascade" }),
13313
+ /** The Figma file name the kit came from. */
13314
+ file: text("file").notNull(),
13315
+ kit: jsonb("kit").$type().notNull(),
13316
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
13317
+ });
13311
13318
  agentFeedbackStatusValues = [
13312
13319
  "open",
13313
13320
  "accepted",
@@ -14229,6 +14236,25 @@ var init_schema2 = __esm({
14229
14236
  createdAt: timestamp("created_at").defaultNow().notNull(),
14230
14237
  expiresAt: timestamp("expires_at").notNull()
14231
14238
  });
14239
+ accountPages = pgTable(
14240
+ "account_pages",
14241
+ {
14242
+ id: text("id").primaryKey(),
14243
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
14244
+ designSystemId: text("design_system_id").notNull(),
14245
+ website: text("website").notNull(),
14246
+ company: text("company").notNull(),
14247
+ headline: text("headline").notNull(),
14248
+ problem: text("problem"),
14249
+ offer: text("offer").notNull(),
14250
+ /** Up to three short proof points: results, customers or numbers. [BD-1093] */
14251
+ proofs: text("proofs").array(),
14252
+ ctaLabel: text("cta_label").notNull(),
14253
+ ctaUrl: text("cta_url").notNull(),
14254
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
14255
+ },
14256
+ (t) => [index("account_pages_user_id_idx").on(t.userId)]
14257
+ );
14232
14258
  }
14233
14259
  });
14234
14260
 
@@ -30644,7 +30670,7 @@ var MCP_IMPLEMENTATION_VERSION;
30644
30670
  var init_version2 = __esm({
30645
30671
  "../shared/src/mcp/version.ts"() {
30646
30672
  "use strict";
30647
- MCP_IMPLEMENTATION_VERSION = "0.41.0";
30673
+ MCP_IMPLEMENTATION_VERSION = "0.42.0";
30648
30674
  }
30649
30675
  });
30650
30676
 
@@ -30792,6 +30818,7 @@ __export(api_client_exports, {
30792
30818
  listPrinciples: () => listPrinciples,
30793
30819
  proposeTeamRule: () => proposeTeamRule,
30794
30820
  readInspiration: () => readInspiration,
30821
+ saveProjectFigmaKit: () => saveProjectFigmaKit,
30795
30822
  saveProjectRule: () => saveProjectRule,
30796
30823
  saveTeamRules: () => saveTeamRules,
30797
30824
  searchDesignSystems: () => searchDesignSystems,
@@ -30965,6 +30992,9 @@ async function saveProjectRule(projectId, save, replaceExisting) {
30965
30992
  throw error;
30966
30993
  }
30967
30994
  }
30995
+ async function saveProjectFigmaKit(projectId, kit) {
30996
+ return request(`/v1/project-kit/${encodeURIComponent(projectId)}`, { method: "PUT", body: kit });
30997
+ }
30968
30998
  function postAlert(body) {
30969
30999
  return fetch(`${API_URL2}/api/internal/alert`, {
30970
31000
  method: "POST",
@@ -31577,7 +31607,7 @@ var init_code_rules = __esm({
31577
31607
 
31578
31608
  // ../shared/src/lint/length-rules.ts
31579
31609
  function pxMatches(value) {
31580
- const unquoted = value.replace(QUOTED_RE, (quoted2) => " ".repeat(quoted2.length));
31610
+ const unquoted = value.replace(QUOTED_RE, (quoted3) => " ".repeat(quoted3.length));
31581
31611
  return [...unquoted.matchAll(PX_RE)].filter((match) => Number.parseFloat(match[0]) !== 0);
31582
31612
  }
31583
31613
  function pxValues(value) {
@@ -34739,6 +34769,16 @@ review-ui-code({ files: [{ path: "components/signup-form.tsx", content: "..." },
34739
34769
 
34740
34770
  // ../shared/src/mcp/figma-kit.ts
34741
34771
  import { z as z36 } from "zod";
34772
+ function kitCounts(kit) {
34773
+ return {
34774
+ file: kit.file,
34775
+ collections: kit.collections.length,
34776
+ variables: kit.collections.reduce((total, collection) => total + collection.variables.length, 0),
34777
+ textStyles: kit.textStyles.length,
34778
+ effectStyles: kit.effectStyles.length,
34779
+ components: kit.components.length
34780
+ };
34781
+ }
34742
34782
  var colorSchema, kitValueSchema, kitVariableSchema, kitCollectionSchema, kitTextStyleSchema, kitEffectStyleSchema, kitComponentSchema, figmaKitSchema;
34743
34783
  var init_figma_kit = __esm({
34744
34784
  "../shared/src/mcp/figma-kit.ts"() {
@@ -35837,6 +35877,38 @@ var init_figma_model_providers = __esm({
35837
35877
  }
35838
35878
  });
35839
35879
 
35880
+ // ../shared/src/mcp/figma-kit-gaps.ts
35881
+ function describeKitGaps(gaps) {
35882
+ if (gaps.length === 0) return "An agent can use every component set in this kit.";
35883
+ const names = (reason) => {
35884
+ const all = gaps.filter((gap) => gap.reason === reason).map((gap) => quoted2(gap.component));
35885
+ const shown = all.slice(0, NAMES_SHOWN).join(", ");
35886
+ return { count: all.length, list: all.length > NAMES_SHOWN ? `${shown} and ${all.length - NAMES_SHOWN} more` : shown };
35887
+ };
35888
+ const crowded = names("many-variant-properties");
35889
+ const missing = names("no-match-in-code");
35890
+ const renamed = names("name-differs-from-code");
35891
+ const unruled = names("no-use-rule");
35892
+ return [
35893
+ "Before an agent builds with this kit:",
35894
+ crowded.count ? `- ${crowded.count} set(s) have more than ${VARIANT_PROPERTY_LIMIT} variant properties, so an agent may pick the wrong variant: ${crowded.list}.` : "",
35895
+ missing.count ? `- ${missing.count} set(s) have no part in code, so an agent builds them from plain layers: ${missing.list}.` : "",
35896
+ renamed.count ? `- ${renamed.count} set(s) build as a part with another name, so check each one: ${renamed.list}.` : "",
35897
+ unruled.count ? `- ${unruled.count} set(s) have no use rule, so an agent does not know when to use them: ${unruled.list}. Save one with set-team-rules, kind "use".` : ""
35898
+ ].filter(Boolean).join("\n");
35899
+ }
35900
+ var VARIANT_PROPERTY_LIMIT, NAME_MAX_CHARS, quoted2, NAMES_SHOWN;
35901
+ var init_figma_kit_gaps = __esm({
35902
+ "../shared/src/mcp/figma-kit-gaps.ts"() {
35903
+ "use strict";
35904
+ init_project_rules();
35905
+ VARIANT_PROPERTY_LIMIT = 3;
35906
+ NAME_MAX_CHARS = 80;
35907
+ quoted2 = (name) => JSON.stringify(name.replace(/\s+/g, " ").trim().slice(0, NAME_MAX_CHARS));
35908
+ NAMES_SHOWN = 8;
35909
+ }
35910
+ });
35911
+
35840
35912
  // ../shared/src/mcp/figma-relay.ts
35841
35913
  import { z as z40 } from "zod";
35842
35914
  function noPluginMessage() {
@@ -36281,7 +36353,7 @@ ${page.html}` }],
36281
36353
  }
36282
36354
  );
36283
36355
  }
36284
- function registerFigmaKitTool(server) {
36356
+ function registerFigmaKitTool(server, saveKit) {
36285
36357
  server.registerTool(
36286
36358
  "read-figma-kit",
36287
36359
  {
@@ -36291,10 +36363,12 @@ function registerFigmaKitTool(server) {
36291
36363
  "the text and effect styles, and every component with the options of its variant properties.",
36292
36364
  "Writes them as one JSON file and returns the counts. The Better Design plugin must be open in",
36293
36365
  "that file, and the designer confirms the read in the plugin panel. Only the file's own items are",
36294
- "read; a library the file uses is read from its own file."
36366
+ "read; a library the file uses is read from its own file. Pass a projectId to also keep the kit on",
36367
+ "that Better Design project, so later calls build with the client's own names and values."
36295
36368
  ].join(" "),
36296
36369
  inputSchema: z41.object({
36297
- outputPath: z41.string().describe("Where to write the kit JSON. Relative paths resolve against the working directory.")
36370
+ outputPath: z41.string().describe("Where to write the kit JSON. Relative paths resolve against the working directory."),
36371
+ projectId: z41.guid().optional().describe("A Better Design project id. The kit is kept on it, replacing the one it held.")
36298
36372
  }),
36299
36373
  outputSchema: z41.object({
36300
36374
  status: z41.enum(["completed", "failed"]),
@@ -36305,11 +36379,21 @@ function registerFigmaKitTool(server) {
36305
36379
  variables: z41.number().optional(),
36306
36380
  textStyles: z41.number().optional(),
36307
36381
  effectStyles: z41.number().optional(),
36308
- components: z41.number().optional()
36382
+ components: z41.number().optional(),
36383
+ projectId: z41.string().optional(),
36384
+ gaps: z41.array(
36385
+ z41.object({
36386
+ component: z41.string(),
36387
+ page: z41.string(),
36388
+ reason: z41.enum(["many-variant-properties", "no-use-rule", "no-match-in-code", "name-differs-from-code"]),
36389
+ detail: z41.string()
36390
+ })
36391
+ ).optional(),
36392
+ nameMap: z41.array(z41.object({ component: z41.string(), part: z41.string().nullable(), match: z41.enum(["same", "family", "none"]) })).optional()
36309
36393
  }),
36310
36394
  annotations: { readOnlyHint: false, openWorldHint: true }
36311
36395
  },
36312
- async ({ outputPath }) => {
36396
+ async ({ outputPath, projectId }) => {
36313
36397
  const failed = (message2) => ({
36314
36398
  content: [{ type: "text", text: message2 }],
36315
36399
  structuredContent: { status: "failed", message: message2 },
@@ -36319,6 +36403,7 @@ function registerFigmaKitTool(server) {
36319
36403
  if (!started) {
36320
36404
  return failed("Another Better Design session already owns the Figma relay port. Close it and try again.");
36321
36405
  }
36406
+ if (projectId && !saveKit) return failed("This server cannot reach a Better Design project. Run the stdio server with an API key.");
36322
36407
  if (!await waitForPlugin(started)) return failed(noPluginMessage());
36323
36408
  const { answer } = started.queue.ask(void 0, void 0, "kit");
36324
36409
  const result = await answer;
@@ -36329,17 +36414,23 @@ function registerFigmaKitTool(server) {
36329
36414
  await mkdir2(dirname4(path9), { recursive: true });
36330
36415
  await writeFile2(path9, `${JSON.stringify(kit, null, 2)}
36331
36416
  `, "utf8");
36332
- const counts = {
36333
- collections: kit.collections.length,
36334
- variables: kit.collections.reduce((total, collection) => total + collection.variables.length, 0),
36335
- textStyles: kit.textStyles.length,
36336
- effectStyles: kit.effectStyles.length,
36337
- components: kit.components.length
36338
- };
36339
- const message = `${kit.file}: ${counts.components} component(s), ${counts.variables} variable(s) in ${counts.collections} collection(s), ${counts.textStyles} text style(s) and ${counts.effectStyles} effect style(s). Written to ${path9}.`;
36417
+ const { file, ...counts } = kitCounts(kit);
36418
+ let message = `${file}: ${counts.components} component(s), ${counts.variables} variable(s) in ${counts.collections} collection(s), ${counts.textStyles} text style(s) and ${counts.effectStyles} effect style(s). Written to ${path9}.`;
36419
+ let gaps;
36420
+ let nameMap;
36421
+ if (projectId && saveKit) {
36422
+ try {
36423
+ ({ gaps, nameMap } = await saveKit(projectId, kit));
36424
+ message = `${describeKitGaps(gaps)}
36425
+
36426
+ ${message} Kept on project ${projectId}.`;
36427
+ } catch (error) {
36428
+ return failed(`${message} The kit was not kept on project ${projectId}: ${error instanceof Error ? error.message : String(error)}`);
36429
+ }
36430
+ }
36340
36431
  return {
36341
36432
  content: [{ type: "text", text: message }],
36342
- structuredContent: { status: "completed", message, path: path9, file: kit.file, ...counts }
36433
+ structuredContent: { status: "completed", message, path: path9, file, ...counts, projectId, gaps, nameMap }
36343
36434
  };
36344
36435
  }
36345
36436
  );
@@ -36353,6 +36444,8 @@ var init_figma_prototype_tool = __esm({
36353
36444
  init_figma_model();
36354
36445
  init_figma_model_handoff();
36355
36446
  init_figma_model_providers();
36447
+ init_figma_kit();
36448
+ init_figma_kit_gaps();
36356
36449
  init_figma_relay();
36357
36450
  init_figma_relay_server();
36358
36451
  relay = null;
@@ -36469,7 +36562,8 @@ async function startServer() {
36469
36562
  sendAgentFeedback: sendAgentFeedback2,
36470
36563
  confirmAgentClaim: confirmAgentClaim2,
36471
36564
  trackToolUsage: trackToolUsage2,
36472
- trackMcpInstalled: trackMcpInstalled2
36565
+ trackMcpInstalled: trackMcpInstalled2,
36566
+ saveProjectFigmaKit: saveProjectFigmaKit2
36473
36567
  } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
36474
36568
  const { isFirstConnect: isFirstConnect2 } = await Promise.resolve().then(() => (init_install_marker(), install_marker_exports));
36475
36569
  const { mcpDebugLog: mcpDebugLog2 } = await Promise.resolve().then(() => (init_mcp_debug_log(), mcp_debug_log_exports));
@@ -36500,7 +36594,7 @@ async function startServer() {
36500
36594
  });
36501
36595
  registerReviewTool2(server, onToolUsage);
36502
36596
  registerFigmaPrototypeTool2(server);
36503
- registerFigmaKitTool2(server);
36597
+ registerFigmaKitTool2(server, saveProjectFigmaKit2);
36504
36598
  server.server.oninitialized = () => {
36505
36599
  try {
36506
36600
  const info = server.server.getClientVersion?.();