rivet-design 0.11.8 → 0.11.9

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 (46) hide show
  1. package/dist/config/flags.d.ts +3 -0
  2. package/dist/config/flags.d.ts.map +1 -1
  3. package/dist/config/flags.js +1 -0
  4. package/dist/config/flags.js.map +1 -1
  5. package/dist/index.d.ts +1 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +96 -590
  8. package/dist/index.js.map +1 -1
  9. package/dist/install/harnesses.d.ts +82 -0
  10. package/dist/install/harnesses.d.ts.map +1 -0
  11. package/dist/install/harnesses.js +537 -0
  12. package/dist/install/harnesses.js.map +1 -0
  13. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +25 -0
  14. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  15. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +121 -0
  16. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  17. package/dist/mcp/auth/tools.d.ts.map +1 -1
  18. package/dist/mcp/auth/tools.js +63 -12
  19. package/dist/mcp/auth/tools.js.map +1 -1
  20. package/dist/mcp/server.d.ts.map +1 -1
  21. package/dist/mcp/server.js +2 -7
  22. package/dist/mcp/server.js.map +1 -1
  23. package/dist/onboarding/signin.d.ts +31 -0
  24. package/dist/onboarding/signin.d.ts.map +1 -0
  25. package/dist/onboarding/signin.js +235 -0
  26. package/dist/onboarding/signin.js.map +1 -0
  27. package/dist/routes/agentVariants.d.ts.map +1 -1
  28. package/dist/routes/agentVariants.js +450 -33
  29. package/dist/routes/agentVariants.js.map +1 -1
  30. package/dist/services/PrototypeDeployService.d.ts +2 -0
  31. package/dist/services/PrototypeDeployService.d.ts.map +1 -1
  32. package/dist/services/PrototypeDeployService.js +6 -0
  33. package/dist/services/PrototypeDeployService.js.map +1 -1
  34. package/dist/services/TelemetryService.d.ts +70 -0
  35. package/dist/services/TelemetryService.d.ts.map +1 -1
  36. package/dist/services/TelemetryService.js +85 -0
  37. package/dist/services/TelemetryService.js.map +1 -1
  38. package/dist/services/VariantHistoryService.d.ts +18 -0
  39. package/dist/services/VariantHistoryService.d.ts.map +1 -1
  40. package/dist/services/VariantHistoryService.js +27 -3
  41. package/dist/services/VariantHistoryService.js.map +1 -1
  42. package/package.json +2 -1
  43. package/src/ui/dist/assets/main-BtohezP4.js +641 -0
  44. package/src/ui/dist/assets/{main-CtRINpOq.css → main-IY99De7M.css} +1 -1
  45. package/src/ui/dist/index.html +2 -2
  46. package/src/ui/dist/assets/main-CF-wHnwl.js +0 -641
@@ -78,6 +78,10 @@ const deployBody = v3_1.z.object({
78
78
  variantId: v3_1.z.string().min(1),
79
79
  projectPath: v3_1.z.string().optional(),
80
80
  });
81
+ const canvasDeployBody = v3_1.z.object({
82
+ variantIds: v3_1.z.array(v3_1.z.string().min(1)).min(1),
83
+ projectPath: v3_1.z.string().optional(),
84
+ });
81
85
  const elementRefSchema = v3_1.z.object({
82
86
  xpath: v3_1.z.string(),
83
87
  tagName: v3_1.z.string(),
@@ -119,7 +123,11 @@ const inlineVariantBody = v3_1.z.object({
119
123
  images: v3_1.z.array(v3_1.z.string()).optional(),
120
124
  references: v3_1.z.array(commentReferenceItemSchema).max(8).optional(),
121
125
  variantRequest: v3_1.z.object({
122
- count: v3_1.z.number().int().min(contracts_1.VARIANT_COUNT_MIN).max(contracts_1.VARIANT_COUNT_MAX),
126
+ count: v3_1.z
127
+ .number()
128
+ .int()
129
+ .min(contracts_1.VARIANT_COUNT_MIN)
130
+ .max(contracts_1.VARIANT_COUNT_MAX),
123
131
  }),
124
132
  })
125
133
  .passthrough())
@@ -161,6 +169,169 @@ const HISTORY_STATUSES = new Set([
161
169
  'rejected',
162
170
  'cancelled',
163
171
  ]);
172
+ const escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
173
+ switch (char) {
174
+ case '&':
175
+ return '&amp;';
176
+ case '<':
177
+ return '&lt;';
178
+ case '>':
179
+ return '&gt;';
180
+ case '"':
181
+ return '&quot;';
182
+ case "'":
183
+ return '&#39;';
184
+ default:
185
+ return char;
186
+ }
187
+ });
188
+ const CANVAS_IFRAME_SANDBOX = 'allow-scripts allow-forms allow-popups';
189
+ /**
190
+ * Render a public canvas page that puts every deployed direction in one shareable view.
191
+ */
192
+ const buildCanvasShareHtml = (args) => {
193
+ const prompt = args.sessionPrompt.trim();
194
+ const variantCards = args.variants
195
+ .map((variant, index) => {
196
+ const label = variant.label.trim() || `Direction ${index + 1}`;
197
+ const brief = variant.brief.trim();
198
+ return `<article class="variant-card">
199
+ <header>
200
+ <div>
201
+ <p class="eyebrow">Direction ${index + 1}</p>
202
+ <h2>${escapeHtml(label)}</h2>
203
+ </div>
204
+ <a href="${escapeHtml(variant.shareUrl)}" target="_blank" rel="noreferrer">Open</a>
205
+ </header>
206
+ ${brief ? `<p class="brief">${escapeHtml(brief)}</p>` : ''}
207
+ <iframe title="${escapeHtml(label)}" src="${escapeHtml(variant.shareUrl)}" loading="lazy" sandbox="${CANVAS_IFRAME_SANDBOX}"></iframe>
208
+ </article>`;
209
+ })
210
+ .join('\n');
211
+ return `<!doctype html>
212
+ <html lang="en">
213
+ <head>
214
+ <meta charset="utf-8">
215
+ <meta name="viewport" content="width=device-width, initial-scale=1">
216
+ <title>Rivet Canvas</title>
217
+ <style>
218
+ :root {
219
+ color-scheme: light;
220
+ --bg: #f6f4ef;
221
+ --card: #ffffff;
222
+ --ink: #161615;
223
+ --muted: #6f6a60;
224
+ --border: #ded8cc;
225
+ --accent: #ff5a1f;
226
+ }
227
+ * { box-sizing: border-box; }
228
+ body {
229
+ margin: 0;
230
+ background: var(--bg);
231
+ color: var(--ink);
232
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
233
+ }
234
+ main {
235
+ width: min(1440px, calc(100vw - 32px));
236
+ margin: 0 auto;
237
+ padding: 32px 0 48px;
238
+ }
239
+ .hero {
240
+ display: flex;
241
+ align-items: flex-end;
242
+ justify-content: space-between;
243
+ gap: 24px;
244
+ margin-bottom: 24px;
245
+ }
246
+ h1 {
247
+ margin: 0;
248
+ font-size: clamp(32px, 5vw, 64px);
249
+ letter-spacing: -0.06em;
250
+ line-height: 0.95;
251
+ }
252
+ .prompt {
253
+ max-width: 720px;
254
+ color: var(--muted);
255
+ font-size: 15px;
256
+ line-height: 1.5;
257
+ }
258
+ .grid {
259
+ display: grid;
260
+ grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
261
+ gap: 18px;
262
+ }
263
+ .variant-card {
264
+ overflow: hidden;
265
+ border: 1px solid var(--border);
266
+ border-radius: 18px;
267
+ background: var(--card);
268
+ box-shadow: 0 20px 60px rgba(22, 22, 21, 0.08);
269
+ }
270
+ .variant-card header {
271
+ display: flex;
272
+ align-items: flex-start;
273
+ justify-content: space-between;
274
+ gap: 16px;
275
+ padding: 18px 18px 0;
276
+ }
277
+ .eyebrow {
278
+ margin: 0 0 6px;
279
+ color: var(--accent);
280
+ font-size: 11px;
281
+ font-weight: 700;
282
+ letter-spacing: 0.12em;
283
+ text-transform: uppercase;
284
+ }
285
+ h2 {
286
+ margin: 0;
287
+ font-size: 20px;
288
+ letter-spacing: -0.03em;
289
+ }
290
+ a {
291
+ color: var(--ink);
292
+ font-size: 13px;
293
+ font-weight: 700;
294
+ text-decoration: none;
295
+ border-bottom: 1px solid currentColor;
296
+ }
297
+ .brief {
298
+ margin: 10px 18px 16px;
299
+ color: var(--muted);
300
+ font-size: 13px;
301
+ line-height: 1.45;
302
+ }
303
+ iframe {
304
+ display: block;
305
+ width: 100%;
306
+ height: 720px;
307
+ border: 0;
308
+ border-top: 1px solid var(--border);
309
+ background: white;
310
+ }
311
+ @media (max-width: 640px) {
312
+ main { width: min(100vw - 20px, 1440px); padding-top: 20px; }
313
+ .hero { display: block; }
314
+ .grid { grid-template-columns: 1fr; }
315
+ iframe { height: 560px; }
316
+ }
317
+ </style>
318
+ </head>
319
+ <body>
320
+ <main>
321
+ <section class="hero">
322
+ <div>
323
+ <p class="eyebrow">Rivet Canvas</p>
324
+ <h1>${args.variants.length} directions</h1>
325
+ </div>
326
+ ${prompt ? `<p class="prompt">${escapeHtml(prompt)}</p>` : ''}
327
+ </section>
328
+ <section class="grid">
329
+ ${variantCards}
330
+ </section>
331
+ </main>
332
+ </body>
333
+ </html>`;
334
+ };
164
335
  /** Resolve a Rivet-generated subproject path to the workspace that owns variant history. */
165
336
  const generatedProjectWorkspaceRoot = (projectPath) => {
166
337
  const normalized = path_1.default.normalize(projectPath);
@@ -230,6 +401,67 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
230
401
  }
231
402
  return null;
232
403
  };
404
+ /**
405
+ * Deploy one persisted variant and append the returned public URL to history.
406
+ */
407
+ const deployPersistedVariant = async (args) => {
408
+ const existingDeployments = args.variant.manifest.deployments ?? [];
409
+ const latestDeployment = existingDeployments.length > 0
410
+ ? existingDeployments[existingDeployments.length - 1]
411
+ : undefined;
412
+ if (args.reuseExisting && latestDeployment) {
413
+ return {
414
+ ok: true,
415
+ variant: {
416
+ variantId: args.variant.manifest.variantId,
417
+ label: args.variant.manifest.label,
418
+ brief: args.variant.manifest.brief,
419
+ ...latestDeployment,
420
+ },
421
+ };
422
+ }
423
+ const filesDir = path_1.default.join(args.variant.variantDir, 'files');
424
+ const preview = args.variant.manifest.preview;
425
+ let result;
426
+ if (preview.kind === 'static') {
427
+ // Already a built static bundle (e.g. a history static-preview variant)
428
+ // — upload files/ directly, no build needed.
429
+ result = await deployService.deployStatic(filesDir);
430
+ }
431
+ else if (args.variant.manifest.projectKind === 'fresh' &&
432
+ args.variant.manifest.artifact.kind === 'project') {
433
+ // Buildable fresh Vite app — compile then upload.
434
+ result = await deployService.deploy(filesDir);
435
+ }
436
+ else {
437
+ return {
438
+ ok: false,
439
+ status: 422,
440
+ errorCode: 'RUNTIME_VALIDATION_FAILED',
441
+ message: "This prototype can't be deployed — only fresh app directions or static previews are supported.",
442
+ };
443
+ }
444
+ const deployment = {
445
+ deployId: result.deployId,
446
+ shareUrl: result.shareUrl,
447
+ deployedAt: new Date().toISOString(),
448
+ };
449
+ await variantHistory.recordDeployment({
450
+ projectPath: args.projectPath,
451
+ sessionId: args.sessionId,
452
+ variantId: args.variant.manifest.variantId,
453
+ deployment,
454
+ });
455
+ return {
456
+ ok: true,
457
+ variant: {
458
+ variantId: args.variant.manifest.variantId,
459
+ label: args.variant.manifest.label,
460
+ brief: args.variant.manifest.brief,
461
+ ...deployment,
462
+ },
463
+ };
464
+ };
233
465
  router.post('/variants/inline', async (req, res) => {
234
466
  if (rejectNonLocalRequest(req, res))
235
467
  return;
@@ -368,7 +600,8 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
368
600
  return;
369
601
  }
370
602
  const statusParam = typeof req.query.status === 'string' ? req.query.status : undefined;
371
- if (statusParam && !HISTORY_STATUSES.has(statusParam)) {
603
+ if (statusParam &&
604
+ !HISTORY_STATUSES.has(statusParam)) {
372
605
  res.status(400).json({
373
606
  errorCode: 'SCHEMA_VALIDATION_FAILED',
374
607
  message: `Invalid status '${statusParam}' — expected one of ${[...HISTORY_STATUSES].join(', ')}.`,
@@ -508,14 +741,71 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
508
741
  return;
509
742
  }
510
743
  try {
511
- const port = parsed.data.variantId
512
- ? (orchestrator.getDevServerPort(sessionId, parsed.data.variantId) ??
513
- null)
514
- : null;
744
+ if (!parsed.data.variantId) {
745
+ telemetry?.trackAgentVariantsPreviewSwitchStarted({
746
+ sessionId,
747
+ variantId: null,
748
+ target: 'original',
749
+ previewKind: 'original',
750
+ hasPort: false,
751
+ });
752
+ bridge.switchPreviewPort(null);
753
+ telemetry?.trackAgentVariantsPreviewSwitchCompleted({
754
+ sessionId,
755
+ variantId: null,
756
+ target: 'original',
757
+ previewKind: 'original',
758
+ hasPort: false,
759
+ port: null,
760
+ });
761
+ res.json({ port: null });
762
+ return;
763
+ }
764
+ const port = orchestrator.getDevServerPort(sessionId, parsed.data.variantId);
765
+ telemetry?.trackAgentVariantsPreviewSwitchStarted({
766
+ sessionId,
767
+ variantId: parsed.data.variantId,
768
+ target: 'variant',
769
+ previewKind: 'dev_server',
770
+ hasPort: typeof port === 'number',
771
+ });
772
+ if (typeof port !== 'number') {
773
+ telemetry?.trackAgentVariantsPreviewSwitchFailed({
774
+ sessionId,
775
+ variantId: parsed.data.variantId,
776
+ target: 'variant',
777
+ previewKind: 'dev_server',
778
+ errorCode: 'PREVIEW_NOT_READY',
779
+ reason: 'missing_variant_port',
780
+ hasPort: false,
781
+ });
782
+ res.status(409).json({
783
+ errorCode: 'PREVIEW_NOT_READY',
784
+ message: 'Variant preview is not ready yet',
785
+ });
786
+ return;
787
+ }
515
788
  bridge.switchPreviewPort(port);
789
+ telemetry?.trackAgentVariantsPreviewSwitchCompleted({
790
+ sessionId,
791
+ variantId: parsed.data.variantId,
792
+ target: 'variant',
793
+ previewKind: 'dev_server',
794
+ hasPort: true,
795
+ port,
796
+ });
516
797
  res.json({ port });
517
798
  }
518
799
  catch (err) {
800
+ telemetry?.trackAgentVariantsPreviewSwitchFailed({
801
+ sessionId,
802
+ variantId: parsed.data.variantId,
803
+ target: parsed.data.variantId ? 'variant' : 'original',
804
+ previewKind: parsed.data.variantId ? 'dev_server' : 'original',
805
+ errorCode: 'PREVIEW_SWITCH_FAILED',
806
+ reason: err instanceof Error ? err.message : String(err),
807
+ hasPort: false,
808
+ });
519
809
  sendError(res, err);
520
810
  }
521
811
  });
@@ -539,7 +829,9 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
539
829
  // sub-path. The hasAssets check also avoids the trailing-slash
540
830
  // fragment-link quirk for the common no-assets case.
541
831
  const hasAssets = orchestrator.hasStaticPreviewAssets(sessionId, variantId);
542
- if (hasAssets && !req.originalUrl.includes('?') && !req.originalUrl.endsWith('/')) {
832
+ if (hasAssets &&
833
+ !req.originalUrl.includes('?') &&
834
+ !req.originalUrl.endsWith('/')) {
543
835
  // Document URL must end in `/` so the browser's `document.URL` and
544
836
  // the `<base href>` agree. Otherwise fragment-only links
545
837
  // (`<a href="#section">`) resolve to the base-href URL — different
@@ -789,40 +1081,165 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
789
1081
  });
790
1082
  return;
791
1083
  }
792
- const variant = found.detail;
793
- const filesDir = path_1.default.join(variant.variantDir, 'files');
794
- const preview = variant.manifest.preview;
795
- let result;
796
- if (preview.kind === 'static') {
797
- // Already a built static bundle (e.g. a history static-preview
798
- // variant) — upload files/ directly, no build needed. This is what
799
- // makes deploying a clicked variant from history work.
800
- result = await deployService.deployStatic(filesDir);
1084
+ const deployed = await deployPersistedVariant({
1085
+ projectPath: found.projectPath,
1086
+ sessionId,
1087
+ variant: found.detail,
1088
+ reuseExisting: false,
1089
+ });
1090
+ if (!deployed.ok) {
1091
+ res.status(deployed.status).json({
1092
+ errorCode: deployed.errorCode,
1093
+ message: deployed.message,
1094
+ });
1095
+ return;
801
1096
  }
802
- else if (variant.manifest.projectKind === 'fresh' &&
803
- variant.manifest.artifact.kind === 'project') {
804
- // Buildable fresh Vite app — compile then upload.
805
- result = await deployService.deploy(filesDir);
1097
+ const { deployId, shareUrl, deployedAt } = deployed.variant;
1098
+ res.json({ deployId, shareUrl, deployedAt });
1099
+ }
1100
+ catch (err) {
1101
+ log.error('Prototype deploy failed', err);
1102
+ const message = err instanceof Error ? err.message : 'Deploy failed.';
1103
+ const detail = err && typeof err === 'object' && 'detail' in err
1104
+ ? String(err.detail ?? '')
1105
+ : '';
1106
+ res.status(500).json({
1107
+ errorCode: 'RUNTIME_VALIDATION_FAILED',
1108
+ message,
1109
+ ...(detail ? { detail } : {}),
1110
+ });
1111
+ }
1112
+ });
1113
+ /**
1114
+ * Deploy a fresh-project canvas to the public Prototype Preview Host. The
1115
+ * canvas page embeds every requested direction so one public URL can be used
1116
+ * for review instead of sharing one URL per variant.
1117
+ */
1118
+ router.post('/variants/:sessionId/canvas/deploy', async (req, res) => {
1119
+ if (rejectNonLocalRequest(req, res))
1120
+ return;
1121
+ const sessionId = req.params.sessionId;
1122
+ const flags = await (0, evaluateFlags_1.getFeatureFlags)({
1123
+ userKey: (0, featureFlagUserKey_1.getConfigFeatureFlagUserKey)({ headers: req.headers }),
1124
+ });
1125
+ if (!flags.deployPrototype.isEnabled) {
1126
+ res.status(403).json({
1127
+ errorCode: 'FEATURE_DISABLED',
1128
+ message: 'Prototype deploy is not enabled.',
1129
+ });
1130
+ return;
1131
+ }
1132
+ if (!flags.canvasShare.isEnabled) {
1133
+ res.status(403).json({
1134
+ errorCode: 'FEATURE_DISABLED',
1135
+ message: 'Canvas share is not enabled.',
1136
+ });
1137
+ return;
1138
+ }
1139
+ const parsed = canvasDeployBody.safeParse(req.body);
1140
+ if (!parsed.success) {
1141
+ res.status(400).json({
1142
+ errorCode: 'SCHEMA_VALIDATION_FAILED',
1143
+ message: parsed.error.message,
1144
+ });
1145
+ return;
1146
+ }
1147
+ const projectPath = resolveProjectPath(parsed.data.projectPath);
1148
+ if (!projectPath) {
1149
+ res.status(400).json({
1150
+ errorCode: 'RUNTIME_VALIDATION_FAILED',
1151
+ message: 'No projectPath available — pass projectPath or open a project in the visual editor.',
1152
+ });
1153
+ return;
1154
+ }
1155
+ try {
1156
+ const variantIds = [...new Set(parsed.data.variantIds)];
1157
+ const canvasVariants = [];
1158
+ const deployedVariants = [];
1159
+ let sessionPrompt = '';
1160
+ let canvasHistoryProjectPath = projectPath;
1161
+ for (const variantId of variantIds) {
1162
+ const found = await findHistoryVariant({
1163
+ projectPath,
1164
+ sessionId,
1165
+ variantId,
1166
+ });
1167
+ if (!found) {
1168
+ res.status(404).json({
1169
+ errorCode: 'WORK_ITEM_NOT_FOUND',
1170
+ message: `Variant '${variantId}' not found in history.`,
1171
+ });
1172
+ return;
1173
+ }
1174
+ canvasHistoryProjectPath = found.projectPath;
1175
+ const variant = found.detail;
1176
+ if (variant.manifest.preview.kind !== 'static') {
1177
+ res.status(422).json({
1178
+ errorCode: 'RUNTIME_VALIDATION_FAILED',
1179
+ message: 'Canvas sharing only supports static preview directions. Share buildable app directions individually.',
1180
+ });
1181
+ return;
1182
+ }
1183
+ if (!sessionPrompt) {
1184
+ sessionPrompt = variant.manifest.sessionPrompt;
1185
+ }
1186
+ canvasVariants.push(found);
806
1187
  }
807
- else {
808
- res.status(422).json({
809
- errorCode: 'RUNTIME_VALIDATION_FAILED',
810
- message: 'This prototype can\'t be deployed — only fresh app directions or static previews are supported.',
1188
+ for (const found of canvasVariants) {
1189
+ const variant = found.detail;
1190
+ const deployed = await deployPersistedVariant({
1191
+ projectPath: found.projectPath,
1192
+ sessionId,
1193
+ variant,
1194
+ reuseExisting: false,
811
1195
  });
812
- return;
1196
+ if (!deployed.ok) {
1197
+ res.status(deployed.status).json({
1198
+ errorCode: deployed.errorCode,
1199
+ message: deployed.message,
1200
+ });
1201
+ return;
1202
+ }
1203
+ deployedVariants.push(deployed.variant);
813
1204
  }
814
- const { deployId, shareUrl } = result;
1205
+ const html = buildCanvasShareHtml({
1206
+ sessionPrompt,
1207
+ variants: deployedVariants,
1208
+ });
1209
+ const canvas = await deployService.deployHtml(html);
815
1210
  const deployedAt = new Date().toISOString();
816
- await variantHistory.recordDeployment({
817
- projectPath: found.projectPath,
818
- sessionId,
819
- variantId: parsed.data.variantId,
820
- deployment: { deployId, shareUrl, deployedAt },
1211
+ const canvasDeployment = {
1212
+ deployId: canvas.deployId,
1213
+ shareUrl: canvas.shareUrl,
1214
+ deployedAt,
1215
+ variants: deployedVariants.map((variant) => ({
1216
+ variantId: variant.variantId,
1217
+ deployId: variant.deployId,
1218
+ shareUrl: variant.shareUrl,
1219
+ })),
1220
+ };
1221
+ let historyWarning;
1222
+ try {
1223
+ await variantHistory.recordCanvasDeployment({
1224
+ projectPath: canvasHistoryProjectPath,
1225
+ sessionId,
1226
+ deployment: canvasDeployment,
1227
+ });
1228
+ }
1229
+ catch (err) {
1230
+ historyWarning = 'Canvas history could not be recorded.';
1231
+ log.warn('Canvas deploy history record failed', err);
1232
+ }
1233
+ res.json({
1234
+ deployId: canvas.deployId,
1235
+ shareUrl: canvas.shareUrl,
1236
+ deployedAt,
1237
+ variants: deployedVariants,
1238
+ ...(historyWarning ? { historyWarning } : {}),
821
1239
  });
822
- res.json({ deployId, shareUrl, deployedAt });
823
1240
  }
824
1241
  catch (err) {
825
- log.error('Prototype deploy failed', err);
1242
+ log.error('Canvas deploy failed', err);
826
1243
  const message = err instanceof Error ? err.message : 'Deploy failed.';
827
1244
  const detail = err && typeof err === 'object' && 'detail' in err
828
1245
  ? String(err.detail ?? '')