@agent-native/core 0.128.0 → 0.128.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +27 -0
  3. package/corpus/core/package.json +3 -1
  4. package/corpus/core/src/server/builder-design-systems.ts +299 -82
  5. package/corpus/core/src/server/email-template.ts +33 -0
  6. package/corpus/core/src/server/index.ts +8 -0
  7. package/corpus/templates/analytics/_gitignore +2 -0
  8. package/corpus/templates/assets/_gitignore +2 -0
  9. package/corpus/templates/calendar/_gitignore +2 -0
  10. package/corpus/templates/chat/_gitignore +2 -0
  11. package/corpus/templates/clips/AGENTS.md +6 -1
  12. package/corpus/templates/clips/_gitignore +2 -0
  13. package/corpus/templates/clips/actions/complete-transactional-email-summary.ts +56 -0
  14. package/corpus/templates/clips/actions/import-loom-recording.ts +258 -76
  15. package/corpus/templates/clips/actions/lib/direct-video.ts +96 -0
  16. package/corpus/templates/clips/actions/lib/loom-import-job.ts +53 -2
  17. package/corpus/templates/clips/actions/lib/loom-video.ts +2 -62
  18. package/corpus/templates/clips/actions/lib/video-download-limits.ts +62 -0
  19. package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +249 -0
  20. package/corpus/templates/clips/app/components/library/library-grid.tsx +65 -0
  21. package/corpus/templates/clips/app/components/library/library-layout.tsx +55 -0
  22. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +28 -126
  23. package/corpus/templates/clips/app/global.css +23 -0
  24. package/corpus/templates/clips/app/hooks/use-transactional-email-bridge.ts +98 -0
  25. package/corpus/templates/clips/app/i18n/en-US.ts +20 -0
  26. package/corpus/templates/clips/app/routes/_app.tsx +2 -0
  27. package/corpus/templates/clips/app/routes/import.tsx +396 -0
  28. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
  29. package/corpus/templates/clips/app/routes/record.tsx +13 -71
  30. package/corpus/templates/clips/changelog/2026-07-25-two-clip-emails-now-include-a-concise-summary-generated-secu.md +6 -0
  31. package/corpus/templates/clips/data/clips-transactional-emails/config.json +3 -0
  32. package/corpus/templates/clips/server/jobs/transactional-emails.ts +1034 -0
  33. package/corpus/templates/clips/server/lib/transactional-email-store.ts +865 -0
  34. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +270 -0
  35. package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
  36. package/corpus/templates/clips/server/routes/api/view-event.post.ts +59 -2
  37. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
  38. package/corpus/templates/content/AGENTS.md +8 -0
  39. package/corpus/templates/content/_gitignore +2 -0
  40. package/corpus/templates/content/actions/_content-database-personal-view.ts +28 -3
  41. package/corpus/templates/content/actions/_property-utils.ts +32 -2
  42. package/corpus/templates/content/actions/configure-document-property.ts +12 -2
  43. package/corpus/templates/content/actions/delete-document-property.ts +13 -3
  44. package/corpus/templates/content/actions/duplicate-document-property.ts +13 -3
  45. package/corpus/templates/content/actions/list-content-spaces.ts +5 -2
  46. package/corpus/templates/content/actions/list-document-properties.ts +12 -3
  47. package/corpus/templates/content/actions/move-database-item.ts +74 -55
  48. package/corpus/templates/content/actions/reorder-document-property.ts +20 -4
  49. package/corpus/templates/content/actions/set-document-property.ts +10 -1
  50. package/corpus/templates/content/actions/update-content-database-personal-view.ts +7 -0
  51. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +31 -21
  52. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +30 -3
  53. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +8 -1
  54. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +72 -20
  55. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +79 -10
  56. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +8 -1
  57. package/corpus/templates/content/app/components/editor/database/settings.tsx +6 -0
  58. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +246 -26
  59. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +491 -197
  60. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +3 -3
  61. package/corpus/templates/content/app/components/sidebar/sidebar-reorder.tsx +409 -0
  62. package/corpus/templates/content/app/hooks/use-content-database.ts +117 -3
  63. package/corpus/templates/content/app/hooks/use-content-spaces.ts +1 -0
  64. package/corpus/templates/content/app/hooks/use-document-properties.ts +62 -18
  65. package/corpus/templates/content/app/hooks/use-documents.ts +13 -5
  66. package/corpus/templates/content/app/i18n-data.ts +261 -3
  67. package/corpus/templates/content/app/routes/_app.page.$id.tsx +9 -2
  68. package/corpus/templates/content/changelog/2026-07-24-database-page-properties-keep-their-context.md +6 -0
  69. package/corpus/templates/content/changelog/2026-07-26-sidebar-references-can-now-be-personally-reordered.md +6 -0
  70. package/corpus/templates/content/docs/solutions/2026-07-24-database-property-context-switch-diagnosis.md +249 -0
  71. package/corpus/templates/content/docs/solutions/content-sidebar-order-shape.md +389 -0
  72. package/corpus/templates/content/shared/api.ts +28 -0
  73. package/corpus/templates/design/_gitignore +2 -0
  74. package/corpus/templates/design/app/i18n-data.ts +10 -0
  75. package/corpus/templates/design/app/lib/builder-design-system-upload.ts +280 -0
  76. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +90 -54
  77. package/corpus/templates/design/changelog/2026-07-28-figma-fig-uploads-now-show-indexing-progress-and-an-open-in-.md +6 -0
  78. package/corpus/templates/design/server/handlers/design-system-decode-job-status.ts +49 -0
  79. package/corpus/templates/design/server/handlers/design-system-upload-start.ts +87 -0
  80. package/corpus/templates/design/server/handlers/index-design-system-sources.ts +82 -0
  81. package/corpus/templates/design/server/routes/api/design-system-decode-job-status.get.ts +1 -0
  82. package/corpus/templates/design/server/routes/api/design-system-upload-start.post.ts +1 -0
  83. package/corpus/templates/design/server/routes/api/index-design-system-sources.post.ts +1 -0
  84. package/corpus/templates/dispatch/_gitignore +2 -0
  85. package/corpus/templates/forms/_gitignore +2 -0
  86. package/corpus/templates/macros/_gitignore +2 -0
  87. package/corpus/templates/mail/_gitignore +2 -0
  88. package/corpus/templates/plan/_gitignore +2 -0
  89. package/corpus/templates/slides/_gitignore +2 -0
  90. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +126 -53
  91. package/corpus/templates/slides/app/components/design-system/builder-design-system-upload.ts +271 -0
  92. package/corpus/templates/slides/app/components/design-system/builder-index-response.ts +1 -1
  93. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  94. package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +49 -0
  95. package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +87 -0
  96. package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +79 -0
  97. package/corpus/templates/slides/server/routes/api/design-system-decode-job-status.get.ts +1 -0
  98. package/corpus/templates/slides/server/routes/api/design-system-upload-start.post.ts +1 -0
  99. package/corpus/templates/slides/server/routes/api/index-design-system-sources.post.ts +1 -0
  100. package/corpus/templates/tasks/_gitignore +2 -0
  101. package/corpus/toolkit/CHANGELOG.md +6 -0
  102. package/corpus/toolkit/package.json +1 -1
  103. package/corpus/toolkit/src/editor/DragHandle.ts +63 -9
  104. package/dist/collab/routes.d.ts +1 -1
  105. package/dist/collab/struct-routes.d.ts +1 -1
  106. package/dist/observability/routes.d.ts +3 -3
  107. package/dist/progress/routes.d.ts +1 -1
  108. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  109. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  110. package/dist/server/builder-design-systems.d.ts +49 -0
  111. package/dist/server/builder-design-systems.d.ts.map +1 -1
  112. package/dist/server/builder-design-systems.js +221 -69
  113. package/dist/server/builder-design-systems.js.map +1 -1
  114. package/dist/server/email-template.d.ts +7 -0
  115. package/dist/server/email-template.d.ts.map +1 -1
  116. package/dist/server/email-template.js +24 -0
  117. package/dist/server/email-template.js.map +1 -1
  118. package/dist/server/index.d.ts +1 -1
  119. package/dist/server/index.d.ts.map +1 -1
  120. package/dist/server/index.js +1 -1
  121. package/dist/server/index.js.map +1 -1
  122. package/dist/server/transcribe-voice.d.ts +1 -1
  123. package/dist/templates/chat/_gitignore +2 -0
  124. package/package.json +5 -3
  125. package/src/server/builder-design-systems.ts +299 -82
  126. package/src/server/email-template.ts +33 -0
  127. package/src/server/index.ts +8 -0
  128. package/src/templates/chat/_gitignore +2 -0
  129. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +0 -151
  130. package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +0 -1
  131. package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +0 -119
  132. package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +0 -1
@@ -0,0 +1,49 @@
1
+ import {
2
+ FeatureNotConfiguredError,
3
+ fetchBuilderDesignSystemDecodeJobStatus,
4
+ getSession,
5
+ runWithRequestContext,
6
+ } from "@agent-native/core/server";
7
+ import { defineEventHandler, getQuery, setResponseStatus } from "h3";
8
+
9
+ /**
10
+ * Reads a Builder design-system decode job's status. The UI polls this after
11
+ * `/api/index-design-system-sources` returns a jobId, until the `.fig` decode
12
+ * job leaves `pending` and exposes a `branchUrl` (or reports an `error`).
13
+ */
14
+ export const designSystemDecodeJobStatus = defineEventHandler(async (event) => {
15
+ const session = await getSession(event).catch(() => null);
16
+ if (!session?.email) {
17
+ setResponseStatus(event, 401);
18
+ return { error: "Unauthorized" };
19
+ }
20
+
21
+ const jobId = getQuery(event).jobId;
22
+ if (typeof jobId !== "string" || !jobId.trim()) {
23
+ setResponseStatus(event, 400);
24
+ return { error: "jobId is required." };
25
+ }
26
+
27
+ try {
28
+ return await runWithRequestContext(
29
+ { userEmail: session.email, orgId: session.orgId },
30
+ () => fetchBuilderDesignSystemDecodeJobStatus(jobId.trim()),
31
+ );
32
+ } catch (err) {
33
+ if (err instanceof FeatureNotConfiguredError) {
34
+ setResponseStatus(event, 412);
35
+ return {
36
+ error: err.message,
37
+ builderConnectUrl:
38
+ err.builderConnectUrl ?? "/_agent-native/builder/connect",
39
+ };
40
+ }
41
+ setResponseStatus(event, 502);
42
+ return {
43
+ error:
44
+ err instanceof Error
45
+ ? err.message
46
+ : "Failed to read decode job status.",
47
+ };
48
+ }
49
+ });
@@ -0,0 +1,87 @@
1
+ import {
2
+ FeatureNotConfiguredError,
3
+ getSession,
4
+ runWithRequestContext,
5
+ startBuilderDesignSystemUpload,
6
+ } from "@agent-native/core/server";
7
+ import { defineEventHandler, readBody, setResponseStatus } from "h3";
8
+
9
+ const MAX_FIG_BYTES = 512 * 1024 * 1024;
10
+
11
+ interface AttachmentInput {
12
+ name?: unknown;
13
+ mimetype?: unknown;
14
+ declaredSize?: unknown;
15
+ }
16
+
17
+ /**
18
+ * Opens signed resumable-upload slots so the browser can stream large `.fig`
19
+ * bytes straight to storage. Only small JSON metadata rides through the app
20
+ * server; the file bytes never do.
21
+ */
22
+ export const designSystemUploadStart = defineEventHandler(async (event) => {
23
+ const session = await getSession(event).catch(() => null);
24
+ if (!session?.email) {
25
+ setResponseStatus(event, 401);
26
+ return { error: "Unauthorized" };
27
+ }
28
+
29
+ const body = (await readBody(event).catch(() => null)) as {
30
+ attachments?: AttachmentInput[];
31
+ } | null;
32
+ const rawList = Array.isArray(body?.attachments) ? body.attachments : [];
33
+ if (rawList.length === 0) {
34
+ setResponseStatus(event, 400);
35
+ return { error: "No attachments provided." };
36
+ }
37
+
38
+ const attachments: {
39
+ name: string;
40
+ mimetype: string;
41
+ declaredSize: number;
42
+ }[] = [];
43
+ for (const raw of rawList) {
44
+ const name = typeof raw?.name === "string" ? raw.name.trim() : "";
45
+ const mimetype =
46
+ typeof raw?.mimetype === "string" && raw.mimetype.trim()
47
+ ? raw.mimetype.trim()
48
+ : "application/octet-stream";
49
+ const declaredSize = Number(raw?.declaredSize);
50
+ if (!name) {
51
+ setResponseStatus(event, 400);
52
+ return { error: "Attachment name is required." };
53
+ }
54
+ if (!Number.isSafeInteger(declaredSize) || declaredSize <= 0) {
55
+ setResponseStatus(event, 400);
56
+ return { error: "Attachment declaredSize must be a positive integer." };
57
+ }
58
+ if (declaredSize > MAX_FIG_BYTES) {
59
+ setResponseStatus(event, 413);
60
+ return {
61
+ error: `File too large (max ${Math.round(MAX_FIG_BYTES / 1024 / 1024)} MB).`,
62
+ };
63
+ }
64
+ attachments.push({ name, mimetype, declaredSize });
65
+ }
66
+
67
+ try {
68
+ const uploads = await runWithRequestContext(
69
+ { userEmail: session.email, orgId: session.orgId },
70
+ () => startBuilderDesignSystemUpload(attachments),
71
+ );
72
+ return { uploads };
73
+ } catch (err) {
74
+ if (err instanceof FeatureNotConfiguredError) {
75
+ setResponseStatus(event, 412);
76
+ return {
77
+ error: err.message,
78
+ builderConnectUrl:
79
+ err.builderConnectUrl ?? "/_agent-native/builder/connect",
80
+ };
81
+ }
82
+ setResponseStatus(event, 502);
83
+ return {
84
+ error: err instanceof Error ? err.message : "Failed to start upload.",
85
+ };
86
+ }
87
+ });
@@ -0,0 +1,82 @@
1
+ import {
2
+ FeatureNotConfiguredError,
3
+ getSession,
4
+ indexBuilderDesignSystem,
5
+ runWithRequestContext,
6
+ } from "@agent-native/core/server";
7
+ import { defineEventHandler, readBody, setResponseStatus } from "h3";
8
+
9
+ import { upsertBuilderProxyDesignSystem } from "../lib/builder-design-system-proxy.js";
10
+
11
+ /**
12
+ * Finalizes Builder DSI indexing from upload tokens produced by the
13
+ * browser-streamed resumable upload. The file bytes were streamed straight to
14
+ * storage; this endpoint only forwards the opaque tokens.
15
+ */
16
+ export const indexDesignSystemSources = defineEventHandler(async (event) => {
17
+ const session = await getSession(event).catch(() => null);
18
+ if (!session?.email) {
19
+ setResponseStatus(event, 401);
20
+ return { error: "Unauthorized" };
21
+ }
22
+
23
+ const body = (await readBody(event).catch(() => null)) as {
24
+ projectName?: unknown;
25
+ uploadTokens?: unknown;
26
+ } | null;
27
+ const uploadTokens = Array.isArray(body?.uploadTokens)
28
+ ? body.uploadTokens.filter(
29
+ (token): token is string =>
30
+ typeof token === "string" && token.length > 0,
31
+ )
32
+ : [];
33
+ if (uploadTokens.length === 0) {
34
+ setResponseStatus(event, 400);
35
+ return { error: "No uploaded files to index." };
36
+ }
37
+ const projectName =
38
+ typeof body?.projectName === "string"
39
+ ? body.projectName.trim() || undefined
40
+ : undefined;
41
+
42
+ const sources = uploadTokens.map((uploadToken) => ({
43
+ kind: "file" as const,
44
+ uploadToken,
45
+ }));
46
+
47
+ try {
48
+ return await runWithRequestContext(
49
+ { userEmail: session.email, orgId: session.orgId },
50
+ async () => {
51
+ const result = await indexBuilderDesignSystem({ sources, projectName });
52
+ const proxy = await upsertBuilderProxyDesignSystem({
53
+ result,
54
+ ownerEmail: session.email,
55
+ orgId: session.orgId ?? null,
56
+ projectName,
57
+ });
58
+ return {
59
+ ...result,
60
+ ...proxy,
61
+ uploadedFileCount: uploadTokens.length,
62
+ };
63
+ },
64
+ );
65
+ } catch (err) {
66
+ if (err instanceof FeatureNotConfiguredError) {
67
+ setResponseStatus(event, 412);
68
+ return {
69
+ error: err.message,
70
+ builderConnectUrl:
71
+ err.builderConnectUrl ?? "/_agent-native/builder/connect",
72
+ };
73
+ }
74
+ setResponseStatus(event, 502);
75
+ return {
76
+ error:
77
+ err instanceof Error
78
+ ? err.message
79
+ : "Builder design-system indexing failed.",
80
+ };
81
+ }
82
+ });
@@ -0,0 +1 @@
1
+ export { designSystemDecodeJobStatus as default } from "../../handlers/design-system-decode-job-status";
@@ -0,0 +1 @@
1
+ export { designSystemUploadStart as default } from "../../handlers/design-system-upload-start";
@@ -0,0 +1 @@
1
+ export { indexDesignSystemSources as default } from "../../handlers/index-design-system-sources";
@@ -9,8 +9,10 @@ npm-debug.log*
9
9
  pnpm-debug.log*
10
10
 
11
11
  node_modules
12
+ build/
12
13
  dist
13
14
  dist-ssr
15
+ .output/
14
16
  *.local
15
17
 
16
18
  # Editor directories and files
@@ -8,8 +8,10 @@ npm-debug.log*
8
8
  pnpm-debug.log*
9
9
 
10
10
  node_modules
11
+ build/
11
12
  dist
12
13
  dist-ssr
14
+ .output/
13
15
  *.local
14
16
 
15
17
  # Editor directories and files
@@ -8,8 +8,10 @@ npm-debug.log*
8
8
  pnpm-debug.log*
9
9
 
10
10
  node_modules
11
+ build/
11
12
  dist
12
13
  dist-ssr
14
+ .output/
13
15
  *.local
14
16
 
15
17
  # Editor directories and files
@@ -8,8 +8,10 @@ npm-debug.log*
8
8
  pnpm-debug.log*
9
9
 
10
10
  node_modules
11
+ build/
11
12
  dist
12
13
  dist-ssr
14
+ .output/
13
15
  *.local
14
16
 
15
17
  # Editor directories and files
@@ -9,8 +9,10 @@ npm-debug.log*
9
9
  pnpm-debug.log*
10
10
 
11
11
  node_modules
12
+ build/
12
13
  dist
13
14
  dist-ssr
15
+ .output/
14
16
  *.local
15
17
 
16
18
  # Editor directories and files
@@ -8,8 +8,10 @@ npm-debug.log*
8
8
  pnpm-debug.log*
9
9
 
10
10
  node_modules
11
+ build/
11
12
  dist
12
13
  dist-ssr
14
+ .output/
13
15
  *.local
14
16
 
15
17
  # Editor directories and files
@@ -1,5 +1,4 @@
1
1
  import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
2
- import { appApiPath } from "@agent-native/core/client/api-path";
3
2
  import {
4
3
  useActionQuery,
5
4
  useActionMutation,
@@ -36,9 +35,13 @@ import { Label } from "@/components/ui/label";
36
35
  import { ScrollArea } from "@/components/ui/scroll-area";
37
36
  import { Textarea } from "@/components/ui/textarea";
38
37
 
38
+ import {
39
+ uploadAndIndexFigmaFiles,
40
+ pollDecodeJobStatus,
41
+ type DecodeJobStatus,
42
+ } from "./builder-design-system-upload";
39
43
  import {
40
44
  MAX_BUILDER_INDEX_UPLOAD_BYTES,
41
- readBuilderIndexResponse,
42
45
  formatFileSize,
43
46
  type BuilderIndexResult,
44
47
  } from "./builder-index-response";
@@ -117,6 +120,62 @@ export function DesignSystemSetup({
117
120
  const [builderIndexError, setBuilderIndexError] = useState<string | null>(
118
121
  null,
119
122
  );
123
+ const [decodeStatus, setDecodeStatus] = useState<DecodeJobStatus | null>(
124
+ null,
125
+ );
126
+ const decodePollRef = useRef<AbortController | null>(null);
127
+
128
+ const stopDecodePolling = useCallback(() => {
129
+ decodePollRef.current?.abort();
130
+ decodePollRef.current = null;
131
+ }, []);
132
+
133
+ const startDecodePolling = useCallback(
134
+ (jobId: string, indexResult: BuilderIndexResult) => {
135
+ decodePollRef.current?.abort();
136
+ const controller = new AbortController();
137
+ decodePollRef.current = controller;
138
+ setDecodeStatus({
139
+ status: "pending",
140
+ branchUrl: null,
141
+ error: null,
142
+ framesProcessed: 0,
143
+ totalFrames: 0,
144
+ });
145
+ pollDecodeJobStatus(jobId, {
146
+ signal: controller.signal,
147
+ onUpdate: (status) => {
148
+ if (!controller.signal.aborted) setDecodeStatus(status);
149
+ },
150
+ })
151
+ .then((status) => {
152
+ if (controller.signal.aborted) return;
153
+ setDecodeStatus(status);
154
+ setBuilderIndexResult(
155
+ status.branchUrl
156
+ ? { ...indexResult, builderUrl: status.branchUrl }
157
+ : indexResult,
158
+ );
159
+ setBuilderIndexing(false);
160
+ })
161
+ .catch((err: unknown) => {
162
+ if (controller.signal.aborted) return;
163
+ if (err instanceof DOMException && err.name === "AbortError") return;
164
+ setDecodeStatus((prev) => ({
165
+ status: "error",
166
+ branchUrl: prev?.branchUrl ?? null,
167
+ error: err instanceof Error ? err.message : String(err),
168
+ framesProcessed: prev?.framesProcessed ?? 0,
169
+ totalFrames: prev?.totalFrames ?? 0,
170
+ }));
171
+ setBuilderIndexResult(indexResult);
172
+ setBuilderIndexing(false);
173
+ });
174
+ },
175
+ [],
176
+ );
177
+
178
+ useEffect(() => stopDecodePolling, [stopDecodePolling]);
120
179
 
121
180
  const codeInputRef = useRef<HTMLInputElement>(null);
122
181
  const docInputRef = useRef<HTMLInputElement>(null);
@@ -170,8 +229,10 @@ export function DesignSystemSetup({
170
229
  setBuilderIndexing(false);
171
230
  setBuilderIndexResult(null);
172
231
  setBuilderIndexError(null);
232
+ stopDecodePolling();
233
+ setDecodeStatus(null);
173
234
  }
174
- }, [open]);
235
+ }, [open, stopDecodePolling]);
175
236
 
176
237
  const hasAnySources = useMemo(() => {
177
238
  return (
@@ -269,30 +330,34 @@ export function DesignSystemSetup({
269
330
 
270
331
  setBuilderIndexError(null);
271
332
  setBuilderIndexResult(null);
333
+ stopDecodePolling();
334
+ setDecodeStatus(null);
272
335
  setBuilderIndexing(true);
273
336
  try {
274
- const body = new FormData();
275
- body.append("file", file);
276
- const res = await fetch(
277
- appApiPath("/api/index-design-system-with-builder"),
278
- {
279
- method: "POST",
280
- body,
281
- },
282
- );
283
- const parsed = await readBuilderIndexResponse(res);
284
- setBuilderIndexResult(parsed);
337
+ const suggestedTitle =
338
+ file.name
339
+ .replace(/\.fig$/i, "")
340
+ .replace(/[-_]+/g, " ")
341
+ .trim() || "Imported brand";
342
+ const parsed = await uploadAndIndexFigmaFiles([file], {
343
+ projectName: suggestedTitle,
344
+ });
345
+ if (parsed.jobId) {
346
+ startDecodePolling(parsed.jobId, parsed);
347
+ } else {
348
+ setBuilderIndexResult(parsed);
349
+ setBuilderIndexing(false);
350
+ }
285
351
  } catch (err) {
286
352
  setBuilderIndexError(
287
353
  err instanceof Error
288
354
  ? err.message
289
355
  : t("designSystemSetup.figParseFailed"),
290
356
  );
291
- } finally {
292
357
  setBuilderIndexing(false);
293
358
  }
294
359
  },
295
- [t],
360
+ [t, startDecodePolling, stopDecodePolling],
296
361
  );
297
362
 
298
363
  const handleEditSave = async () => {
@@ -543,7 +608,10 @@ export function DesignSystemSetup({
543
608
  ) : (
544
609
  <BuilderIndexPreview
545
610
  result={builderIndexResult}
611
+ decodeStatus={decodeStatus}
546
612
  onReset={() => {
613
+ stopDecodePolling();
614
+ setDecodeStatus(null);
547
615
  setBuilderIndexResult(null);
548
616
  setBuilderIndexError(null);
549
617
  }}
@@ -880,12 +948,22 @@ function TagList({
880
948
 
881
949
  function BuilderIndexPreview({
882
950
  result,
951
+ decodeStatus,
883
952
  onReset,
884
953
  }: {
885
954
  result: BuilderIndexResult;
955
+ decodeStatus: DecodeJobStatus | null;
886
956
  onReset: () => void;
887
957
  }) {
888
958
  const t = useT();
959
+ const decodeDone =
960
+ decodeStatus == null ||
961
+ Boolean(decodeStatus.branchUrl) ||
962
+ decodeStatus.status === "complete";
963
+ const decodeFailed = decodeStatus?.status === "error";
964
+ const decodeText = decodeFailed
965
+ ? t("designSystemSetup.decodeFailed", { error: decodeStatus?.error ?? "" })
966
+ : null;
889
967
  return (
890
968
  <div className="space-y-4 rounded-lg border border-border bg-accent/40 p-4">
891
969
  <div className="flex items-start gap-3">
@@ -905,44 +983,39 @@ function BuilderIndexPreview({
905
983
  </div>
906
984
  </div>
907
985
 
908
- <dl className="grid grid-cols-[112px_minmax(0,1fr)] gap-x-3 gap-y-2 rounded-md border border-border bg-card/50 p-3 text-xs">
909
- <dt className="text-muted-foreground">
910
- {t("designSystemSetup.builderDesignSystemId")}
911
- </dt>
912
- <dd className="truncate font-mono text-foreground/80">
913
- {result.designSystemId}
914
- </dd>
915
- <dt className="text-muted-foreground">
916
- {t("designSystemSetup.builderJobId")}
917
- </dt>
918
- <dd className="truncate font-mono text-foreground/80">
919
- {result.jobId}
920
- </dd>
921
- </dl>
922
-
923
- <div className="flex flex-wrap items-center gap-2 border-t border-border pt-3">
924
- <Button size="sm" asChild className="cursor-pointer">
925
- <a
926
- href={withBuilderUtmTrackingParams(result.builderUrl, {
927
- campaign: "product",
928
- content: "design_system_intelligence",
929
- })}
930
- target="_blank"
931
- rel="noreferrer"
986
+ {decodeText && (
987
+ <div className="border-t border-border pt-3 text-xs text-destructive">
988
+ {decodeText}
989
+ </div>
990
+ )}
991
+
992
+ {(decodeDone || decodeFailed) && (
993
+ <div className="flex flex-wrap items-center gap-2 border-t border-border pt-3">
994
+ {decodeDone && !decodeFailed && (
995
+ <Button size="sm" asChild className="cursor-pointer">
996
+ <a
997
+ href={withBuilderUtmTrackingParams(result.builderUrl, {
998
+ campaign: "product",
999
+ content: "design_system_intelligence",
1000
+ })}
1001
+ target="_blank"
1002
+ rel="noreferrer"
1003
+ >
1004
+ <IconExternalLink className="w-3.5 h-3.5" />
1005
+ {t("designSystemSetup.openInBuilder")}
1006
+ </a>
1007
+ </Button>
1008
+ )}
1009
+ <Button
1010
+ size="sm"
1011
+ variant="ghost"
1012
+ onClick={onReset}
1013
+ className="cursor-pointer"
932
1014
  >
933
- <IconExternalLink className="w-3.5 h-3.5" />
934
- {t("designSystemSetup.openInBuilder")}
935
- </a>
936
- </Button>
937
- <Button
938
- size="sm"
939
- variant="ghost"
940
- onClick={onReset}
941
- className="cursor-pointer"
942
- >
943
- {t("designSystemSetup.chooseAnotherFile")}
944
- </Button>
945
- </div>
1015
+ {t("designSystemSetup.chooseAnotherFile")}
1016
+ </Button>
1017
+ </div>
1018
+ )}
946
1019
  </div>
947
1020
  );
948
1021
  }