rivet-design 0.28.0 → 0.28.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 (54) hide show
  1. package/README.md +2 -6
  2. package/README.npm.md +2 -6
  3. package/dist/agent-variants/WorktreeOrchestrator.js +6 -4
  4. package/dist/cli/commands/open.js +4 -2
  5. package/dist/cli/commands/serve.js +1 -0
  6. package/dist/jest.globalSetup.d.ts +3 -0
  7. package/dist/jest.globalSetup.js +19 -0
  8. package/dist/proxy-middleware/proxy-config.d.ts +0 -1
  9. package/dist/proxy-middleware/proxy-config.js +6 -34
  10. package/dist/routes/agentVariants.js +20 -173
  11. package/dist/routes/static.js +8 -2
  12. package/dist/server.d.ts +1 -1
  13. package/dist/server.js +13 -25
  14. package/dist/services/AuthService.js +7 -61
  15. package/dist/services/templates/viteReactTs.js +1 -1
  16. package/dist/types/rivet-page-types.d.ts +48 -0
  17. package/dist/types/rivet-page-types.js +6 -0
  18. package/dist/utils/rivetPage.d.ts +8 -0
  19. package/dist/utils/rivetPage.js +24 -0
  20. package/dist/utils/rivetPage.testHelpers.d.ts +6 -0
  21. package/dist/utils/rivetPage.testHelpers.js +18 -0
  22. package/dist/utils/rivetPageDocument.d.ts +7 -0
  23. package/dist/utils/rivetPageDocument.js +41 -0
  24. package/dist/utils/scriptJson.d.ts +2 -0
  25. package/dist/utils/scriptJson.js +9 -0
  26. package/dist/utils/uiDistPath.d.ts +2 -0
  27. package/dist/utils/uiDistPath.js +22 -0
  28. package/package.json +21 -28
  29. package/src/ui/dist/assets/LibraryEntry-B8-RtQpK.js +2 -0
  30. package/src/ui/dist/assets/google-g-CpxN0HWb.png +0 -0
  31. package/src/ui/dist/assets/main-11-ME6E3.css +1 -0
  32. package/src/ui/dist/assets/main-BGCGsyhl.js +204 -0
  33. package/src/ui/dist/index.html +3 -3
  34. package/src/ui/dist/pages/empty-project.html +53 -0
  35. package/src/ui/dist/pages/rivet-page.html +53 -0
  36. package/dist/routes/emptyStatePage.d.ts +0 -2
  37. package/dist/routes/emptyStatePage.js +0 -589
  38. package/src/ui/dist/assets/LibraryEntry-BE8nZneH.js +0 -37
  39. package/src/ui/dist/assets/logo-animation/clasp.png +0 -0
  40. package/src/ui/dist/assets/logo-animation/eraser.png +0 -0
  41. package/src/ui/dist/assets/logo-animation/playdough.png +0 -0
  42. package/src/ui/dist/assets/logo-animation/sea/bubblephone.png +0 -0
  43. package/src/ui/dist/assets/logo-animation/sea/cart.png +0 -0
  44. package/src/ui/dist/assets/logo-animation/sea/chair.png +0 -0
  45. package/src/ui/dist/assets/logo-animation/sea/desk.png +0 -0
  46. package/src/ui/dist/assets/logo-animation/sea/mug.png +0 -0
  47. package/src/ui/dist/assets/logo-animation/sea/pitcher.png +0 -0
  48. package/src/ui/dist/assets/logo-animation/sea/pot.png +0 -0
  49. package/src/ui/dist/assets/logo-animation/sea/startac.png +0 -0
  50. package/src/ui/dist/assets/logo-animation/sea/terracotta-1.png +0 -0
  51. package/src/ui/dist/assets/logo-animation/sea/terracotta-2.png +0 -0
  52. package/src/ui/dist/assets/logo-animation/sea/vhsplant.png +0 -0
  53. package/src/ui/dist/assets/main-1EDrDqgn.js +0 -498
  54. package/src/ui/dist/assets/main-qgqnlUkI.css +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rivet
2
2
 
3
- Rivet helps you explore multiple design directions for your web app. It opens a local visual editor, generates parallel variants, lets you refine with comments, and can send the chosen direction back into your project.
3
+ Rivet is a design agent for your coding agent. It explores dozens of design directions in parallel so you can pick the one that fits. It opens a local visual editor, generates parallel variants, and lets you refine with comments.
4
4
 
5
5
  ## Install
6
6
 
@@ -81,8 +81,6 @@ rivet variants start --project /path/to/app --instruction "try three hero layout
81
81
  rivet wait <workId> --project /path/to/app --timeout 120
82
82
  rivet variants list --project /path/to/app
83
83
  rivet variants get <projectId>/<sessionId>:<variantId> --detail
84
- rivet changes request --project /path/to/app --instruction "make the header coral" --element "#header"
85
- rivet changes pull --project /path/to/app
86
84
  rivet update
87
85
  rivet stop --project /path/to/app
88
86
  ```
@@ -103,12 +101,10 @@ rivet open --project /path/to/app --caller-agent cursor --caller-model <model> -
103
101
  ## How It Works
104
102
 
105
103
  1. `rivet open` starts or attaches to the local Rivet editor server for a project. It can attach to an existing dev server or start the app's standard `dev`, `start`, or `serve` script when possible.
106
- 2. The editor shows your app and variant previews in the browser. Agents use `rivet variants ...` and `rivet changes ...` to enqueue work.
104
+ 2. The editor shows your app and variant previews in the browser. Agents use `rivet variants ...` to enqueue work.
107
105
  3. Use `rivet wait`, `rivet status`, or `rivet status --watch` to observe progress.
108
106
  4. Copy the chosen direction’s context and ask your coding agent to implement it in your project. Rivet preserves the prototype; your agent owns integration and verification.
109
107
 
110
- If Rivet reports pull mode, staged work can be drained with `rivet changes pull --project <path>`, applied by the agent, then acknowledged with `rivet changes pull --project <path> --ack <leaseId>`.
111
-
112
108
  ## Supported Frameworks
113
109
 
114
110
  - Next.js
package/README.npm.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rivet
2
2
 
3
- Rivet helps you explore multiple design directions for your web app. It opens a local visual editor, generates parallel variants, lets you refine with comments, and can send the chosen direction back into your project.
3
+ Rivet is a design agent for your coding agent. It explores dozens of design directions in parallel so you can pick the one that fits. It opens a local visual editor, generates parallel variants, and lets you refine with comments.
4
4
 
5
5
  ## Install
6
6
 
@@ -81,8 +81,6 @@ rivet variants start --project /path/to/app --instruction "try three hero layout
81
81
  rivet wait <workId> --project /path/to/app --timeout 120
82
82
  rivet variants list --project /path/to/app
83
83
  rivet variants get <projectId>/<sessionId>:<variantId> --detail
84
- rivet changes request --project /path/to/app --instruction "make the header coral" --element "#header"
85
- rivet changes pull --project /path/to/app
86
84
  rivet update
87
85
  rivet stop --project /path/to/app
88
86
  ```
@@ -103,12 +101,10 @@ rivet open --project /path/to/app --caller-agent cursor --caller-model <model> -
103
101
  ## How It Works
104
102
 
105
103
  1. `rivet open` starts or attaches to the local Rivet editor server for a project. It can attach to an existing dev server or start the app's standard `dev`, `start`, or `serve` script when possible.
106
- 2. The editor shows your app and variant previews in the browser. Agents use `rivet variants ...` and `rivet changes ...` to enqueue work.
104
+ 2. The editor shows your app and variant previews in the browser. Agents use `rivet variants ...` to enqueue work.
107
105
  3. Use `rivet wait`, `rivet status`, or `rivet status --watch` to observe progress.
108
106
  4. Copy the chosen direction’s context and ask your coding agent to implement it in your project. Rivet preserves the prototype; your agent owns integration and verification.
109
107
 
110
- If Rivet reports pull mode, staged work can be drained with `rivet changes pull --project <path>`, applied by the agent, then acknowledged with `rivet changes pull --project <path> --ack <leaseId>`.
111
-
112
108
  ## Supported Frameworks
113
109
 
114
110
  - Next.js
@@ -2743,10 +2743,12 @@ class AgentVariantsOrchestrator {
2743
2743
  await Promise.all(this.workspaceInventory
2744
2744
  .getWorktrees(sessionId)
2745
2745
  .map((record) => DependencyPreparationService_1.previewDependencies.settle(record.worktreePath)));
2746
- await Promise.race([
2747
- this.settleHistoryPersists(),
2748
- new Promise((resolve) => setTimeout(resolve, SETTLE_PERSISTS_TEARDOWN_TIMEOUT_MS)),
2749
- ]);
2746
+ await new Promise((resolve) => {
2747
+ const timer = setTimeout(resolve, SETTLE_PERSISTS_TEARDOWN_TIMEOUT_MS);
2748
+ void this.settleHistoryPersists()
2749
+ .finally(() => clearTimeout(timer))
2750
+ .then(resolve);
2751
+ });
2750
2752
  await this.previewRuntime.closeSession(sessionId);
2751
2753
  await this.workspaceInventory.disposeAssignments({ sessionId });
2752
2754
  this.resources.delete(sessionId);
@@ -70,6 +70,7 @@ const SERVER_START_TIMEOUT_ENV = 'RIVET_SERVER_START_TIMEOUT_MS';
70
70
  const LOCK_WAIT_TIMEOUT_ENV = 'RIVET_LOCK_WAIT_TIMEOUT_MS';
71
71
  const OPEN_RETRY_DELAY_MS = 1_000;
72
72
  const APP_HEALTH_TIMEOUT_MS = 10_000;
73
+ const APP_HEALTH_TIMEOUT_ENV = 'RIVET_APP_HEALTH_TIMEOUT_MS';
73
74
  const RETRY_OPEN_RECOVERY = {
74
75
  isRecoverable: true,
75
76
  nextAction: { action: 'retry', delayMs: OPEN_RETRY_DELAY_MS },
@@ -247,6 +248,7 @@ const ensureProjectRuntime = async (context, io, dependencies = {}) => {
247
248
  const env = dependencies.env ?? process.env;
248
249
  const serverStartTimeoutMs = timeoutFromEnv(env, SERVER_START_TIMEOUT_ENV, DEFAULT_SERVER_START_TIMEOUT_MS);
249
250
  const lockWaitTimeoutMs = timeoutFromEnv(env, LOCK_WAIT_TIMEOUT_ENV, DEFAULT_LOCK_WAIT_TIMEOUT_MS);
251
+ const appHealthTimeoutMs = timeoutFromEnv(env, APP_HEALTH_TIMEOUT_ENV, Math.min(serverStartTimeoutMs, APP_HEALTH_TIMEOUT_MS));
250
252
  const validation = await (0, exports.validateProjectOpen)({
251
253
  context,
252
254
  io,
@@ -292,7 +294,7 @@ const ensureProjectRuntime = async (context, io, dependencies = {}) => {
292
294
  }, RETRY_OPEN_RECOVERY);
293
295
  }
294
296
  if (replacement.outcome === 'attach') {
295
- const readinessError = await verifyRuntimeReadiness(existing, Math.min(serverStartTimeoutMs, APP_HEALTH_TIMEOUT_MS));
297
+ const readinessError = await verifyRuntimeReadiness(existing, appHealthTimeoutMs);
296
298
  if (!readinessError) {
297
299
  return finishProjectOpen(await openedData(existing, true), io);
298
300
  }
@@ -346,7 +348,7 @@ const ensureProjectRuntime = async (context, io, dependencies = {}) => {
346
348
  ...(0, serverVersion_1.versionEnvelopeFields)(attached.handshake),
347
349
  });
348
350
  }
349
- const readinessError = await verifyRuntimeReadiness(attached, Math.min(serverStartTimeoutMs, APP_HEALTH_TIMEOUT_MS));
351
+ const readinessError = await verifyRuntimeReadiness(attached, appHealthTimeoutMs);
350
352
  if (readinessError)
351
353
  return readinessError;
352
354
  const data = await openedData(attached, false);
@@ -200,6 +200,7 @@ const handleServe = async (args, io) => {
200
200
  previewPort,
201
201
  agentApplyMode: 'external',
202
202
  rivetPort,
203
+ resolveProjectName: (targetPath) => library.registry.findByPath(targetPath)?.name,
203
204
  resolveLibraryOrigin: () => {
204
205
  const hub = hubDiscovery.read();
205
206
  return hub?.port && (0, pid_1.isPidAlive)(hub.pid)
@@ -0,0 +1,3 @@
1
+ declare const ensureRivetPageShells: () => void;
2
+ export default ensureRivetPageShells;
3
+ //# sourceMappingURL=jest.globalSetup.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const rivetPage_1 = require("./utils/rivetPage");
9
+ const ensureRivetPageShells = () => {
10
+ fs_1.default.mkdirSync(rivetPage_1.RIVET_PAGE_SHELL_DIRECTORY, { recursive: true });
11
+ for (const shellFile of rivetPage_1.RIVET_PAGE_SHELL_FILES) {
12
+ const builtShell = path_1.default.join(rivetPage_1.RIVET_PAGE_SHELL_DIRECTORY, shellFile);
13
+ if (!fs_1.default.existsSync(builtShell)) {
14
+ fs_1.default.copyFileSync(path_1.default.join(__dirname, 'ui', shellFile), builtShell);
15
+ }
16
+ }
17
+ };
18
+ exports.default = ensureRivetPageShells;
19
+ //# sourceMappingURL=jest.globalSetup.js.map
@@ -14,7 +14,6 @@ export declare const resolveTargetAddressedPreview: (args: {
14
14
  resolveVariantPreviewPort?: ResolveVariantPreviewPort;
15
15
  }) => TargetAddressedPreview | null;
16
16
  export declare const markPreviewBridgeDocumentUncacheable: (proxyRes: IncomingMessage) => void;
17
- export declare const buildPreviewHoldingPage: () => string;
18
17
  export declare const createUserDevServerProxy: (userPort: number, userHost?: string, options?: {
19
18
  isPreviewBridgeEnabled?: boolean;
20
19
  expectedPreviewParentOrigin?: string;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createUserDevServerProxy = exports.buildPreviewHoldingPage = exports.markPreviewBridgeDocumentUncacheable = exports.resolveTargetAddressedPreview = void 0;
3
+ exports.createUserDevServerProxy = exports.markPreviewBridgeDocumentUncacheable = exports.resolveTargetAddressedPreview = void 0;
4
4
  const http_proxy_middleware_1 = require("http-proxy-middleware");
5
5
  const crypto_1 = require("crypto");
6
6
  const logger_1 = require("../utils/logger");
7
+ const rivetPage_1 = require("../utils/rivetPage");
7
8
  const htmlDocument_1 = require("../utils/htmlDocument");
8
9
  const preview_connections_1 = require("./preview-connections");
9
10
  const preview_bridge_1 = require("./preview-bridge");
@@ -74,36 +75,6 @@ const markPreviewBridgeDocumentUncacheable = (proxyRes) => {
74
75
  proxyRes.headers['cache-control'] = 'no-cache, no-store';
75
76
  };
76
77
  exports.markPreviewBridgeDocumentUncacheable = markPreviewBridgeDocumentUncacheable;
77
- const buildPreviewHoldingPage = () => `<!doctype html>
78
- <html lang="en">
79
- <head>
80
- <meta charset="utf-8" />
81
- <meta name="viewport" content="width=device-width, initial-scale=1" />
82
- <title>Preview — waiting for dev server</title>
83
- <style>
84
- html, body { height: 100%; margin: 0; }
85
- body {
86
- display: flex; align-items: center; justify-content: center;
87
- background: #0f0f10; color: #a1a1aa;
88
- font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
89
- }
90
- .wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 24px; text-align: center; }
91
- .spinner {
92
- width: 22px; height: 22px; border: 2px solid #2a2a2e; border-top-color: #a1a1aa;
93
- border-radius: 50%; animation: rivet-preview-spin 0.8s linear infinite;
94
- }
95
- @keyframes rivet-preview-spin { to { transform: rotate(360deg); } }
96
- @media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
97
- </style>
98
- </head>
99
- <body>
100
- <div class="wrap">
101
- <div class="spinner" aria-hidden="true"></div>
102
- <div>Waiting for the preview dev server…</div>
103
- </div>
104
- </body>
105
- </html>`;
106
- exports.buildPreviewHoldingPage = buildPreviewHoldingPage;
107
78
  const createUserDevServerProxy = (userPort, userHost = 'localhost', options = {}) => {
108
79
  let variantTarget = null;
109
80
  const requestTargets = new WeakMap();
@@ -311,9 +282,10 @@ const createUserDevServerProxy = (userPort, userHost = 'localhost', options = {}
311
282
  if ('writeHead' in res && !res.headersSent) {
312
283
  if (isPreviewBridgeEnabled && (0, preview_bridge_1.isHtmlDocumentRequest)(req)) {
313
284
  log.debug(`Serving preview holding page — no upstream yet (userPort=${userPort}).`);
314
- const body = (0, preview_bridge_1.injectPreviewBridge)((0, exports.buildPreviewHoldingPage)(), {
315
- expectedParentOrigin: options.expectedPreviewParentOrigin,
316
- });
285
+ const body = (0, preview_bridge_1.injectPreviewBridge)((0, rivetPage_1.renderRivetPage)({
286
+ page: { kind: 'preview-waiting' },
287
+ head: { title: 'Preview — waiting for dev server' },
288
+ }), { expectedParentOrigin: options.expectedPreviewParentOrigin });
317
289
  res.writeHead(200, {
318
290
  'Content-Type': 'text/html; charset=utf-8',
319
291
  'Cache-Control': 'no-cache, no-store',
@@ -23,6 +23,7 @@ const express_1 = require("express");
23
23
  const v3_1 = require("zod/v3");
24
24
  const api_responses_1 = require("../types/api-responses");
25
25
  const logger_1 = require("../utils/logger");
26
+ const rivetPage_1 = require("../utils/rivetPage");
26
27
  const agent_variants_1 = require("../agent-variants");
27
28
  const changeBatchClassification_1 = require("../mcp/changeBatchClassification");
28
29
  const createAgentVariantsOrchestrator_1 = require("../services/createAgentVariantsOrchestrator");
@@ -167,22 +168,6 @@ const HISTORY_STATUSES = new Set([
167
168
  ]);
168
169
  const VARIANT_SHARE_TITLE_FALLBACK = 'Prototype';
169
170
  const CANVAS_SHARE_TITLE_FALLBACK = 'Prototype canvas';
170
- const escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
171
- switch (char) {
172
- case '&':
173
- return '&amp;';
174
- case '<':
175
- return '&lt;';
176
- case '>':
177
- return '&gt;';
178
- case '"':
179
- return '&quot;';
180
- case "'":
181
- return '&#39;';
182
- default:
183
- return char;
184
- }
185
- });
186
171
  const getVariantShareTitle = (manifest) => {
187
172
  const title = [manifest.label, manifest.brief, manifest.sessionPrompt].find((candidate) => candidate.trim().length > 0);
188
173
  return (0, PrototypeDeployService_1.normalizePrototypeShareTitle)(title) ?? VARIANT_SHARE_TITLE_FALLBACK;
@@ -190,165 +175,27 @@ const getVariantShareTitle = (manifest) => {
190
175
  const getCanvasShareTitle = (sessionPrompt) => {
191
176
  return ((0, PrototypeDeployService_1.normalizePrototypeShareTitle)(sessionPrompt) ?? CANVAS_SHARE_TITLE_FALLBACK);
192
177
  };
193
- const CANVAS_IFRAME_SANDBOX = 'allow-scripts allow-forms allow-popups';
194
178
  const RIVET_SOCIAL_IMAGE_URL = 'https://rivet.design/images/rivet-social.png';
195
179
  const RIVET_DEFAULT_SHARE_DESCRIPTION = 'Built with Rivet — the visual editor for design';
196
180
  const UNSUPPORTED_PROTOTYPE_DEPLOY_MESSAGE = "This prototype can't be deployed — only buildable app directions or static previews are supported.";
197
- const buildCanvasShareHtml = (args) => {
198
- const prompt = args.sessionPrompt.trim();
199
- const variantCards = args.variants
200
- .map((variant, index) => {
201
- const label = variant.label.trim() || `Direction ${index + 1}`;
202
- const brief = variant.brief.trim();
203
- return `<article class="variant-card">
204
- <header>
205
- <div>
206
- <p class="eyebrow">Direction ${index + 1}</p>
207
- <h2>${escapeHtml(label)}</h2>
208
- </div>
209
- <a href="${escapeHtml(variant.shareUrl)}" target="_blank" rel="noreferrer">Open</a>
210
- </header>
211
- ${brief ? `<p class="brief">${escapeHtml(brief)}</p>` : ''}
212
- <iframe title="${escapeHtml(label)}" src="${escapeHtml(variant.shareUrl)}" loading="lazy" sandbox="${CANVAS_IFRAME_SANDBOX}"></iframe>
213
- </article>`;
214
- })
215
- .join('\n');
216
- const canvasTitle = `Rivet Canvas — ${args.variants.length} directions`;
217
- const escapedTitle = escapeHtml(canvasTitle);
218
- const escapedDesc = escapeHtml(RIVET_DEFAULT_SHARE_DESCRIPTION);
219
- return `<!doctype html>
220
- <html lang="en">
221
- <head>
222
- <meta charset="utf-8">
223
- <meta name="viewport" content="width=device-width, initial-scale=1">
224
- <title>${escapedTitle}</title>
225
- <meta name="description" content="${escapedDesc}" />
226
- <meta property="og:type" content="website" />
227
- <meta property="og:title" content="${escapedTitle}" />
228
- <meta property="og:description" content="${escapedDesc}" />
229
- <meta property="og:image" content="${RIVET_SOCIAL_IMAGE_URL}" />
230
- <meta name="twitter:card" content="summary_large_image" />
231
- <meta name="twitter:title" content="${escapedTitle}" />
232
- <meta name="twitter:description" content="${escapedDesc}" />
233
- <meta name="twitter:image" content="${RIVET_SOCIAL_IMAGE_URL}" />
234
- <style>
235
- :root {
236
- color-scheme: light;
237
- --bg: #f6f4ef;
238
- --card: #ffffff;
239
- --ink: #161615;
240
- --muted: #6f6a60;
241
- --border: #ded8cc;
242
- --accent: #ff5a1f;
243
- }
244
- * { box-sizing: border-box; }
245
- body {
246
- margin: 0;
247
- background: var(--bg);
248
- color: var(--ink);
249
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
250
- }
251
- main {
252
- width: min(1440px, calc(100vw - 32px));
253
- margin: 0 auto;
254
- padding: 32px 0 48px;
255
- }
256
- .hero {
257
- display: flex;
258
- align-items: flex-end;
259
- justify-content: space-between;
260
- gap: 24px;
261
- margin-bottom: 24px;
262
- }
263
- h1 {
264
- margin: 0;
265
- font-size: clamp(32px, 5vw, 64px);
266
- letter-spacing: -0.06em;
267
- line-height: 0.95;
268
- }
269
- .prompt {
270
- max-width: 720px;
271
- color: var(--muted);
272
- font-size: 15px;
273
- line-height: 1.5;
274
- }
275
- .grid {
276
- display: grid;
277
- grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
278
- gap: 18px;
279
- }
280
- .variant-card {
281
- overflow: hidden;
282
- border: 1px solid var(--border);
283
- border-radius: 18px;
284
- background: var(--card);
285
- box-shadow: 0 20px 60px rgba(22, 22, 21, 0.08);
286
- }
287
- .variant-card header {
288
- display: flex;
289
- align-items: flex-start;
290
- justify-content: space-between;
291
- gap: 16px;
292
- padding: 18px 18px 0;
293
- }
294
- .eyebrow {
295
- margin: 0 0 6px;
296
- color: var(--accent);
297
- font-size: 11px;
298
- font-weight: 700;
299
- letter-spacing: 0.12em;
300
- text-transform: uppercase;
301
- }
302
- h2 {
303
- margin: 0;
304
- font-size: 20px;
305
- letter-spacing: -0.03em;
306
- }
307
- a {
308
- color: var(--ink);
309
- font-size: 13px;
310
- font-weight: 700;
311
- text-decoration: none;
312
- border-bottom: 1px solid currentColor;
313
- }
314
- .brief {
315
- margin: 10px 18px 16px;
316
- color: var(--muted);
317
- font-size: 13px;
318
- line-height: 1.45;
319
- }
320
- iframe {
321
- display: block;
322
- width: 100%;
323
- height: 720px;
324
- border: 0;
325
- border-top: 1px solid var(--border);
326
- background: white;
327
- }
328
- @media (max-width: 640px) {
329
- main { width: min(100vw - 20px, 1440px); padding-top: 20px; }
330
- .hero { display: block; }
331
- .grid { grid-template-columns: 1fr; }
332
- iframe { height: 560px; }
333
- }
334
- </style>
335
- </head>
336
- <body>
337
- <main>
338
- <section class="hero">
339
- <div>
340
- <p class="eyebrow">Rivet Canvas</p>
341
- <h1>${args.variants.length} directions</h1>
342
- </div>
343
- ${prompt ? `<p class="prompt">${escapeHtml(prompt)}</p>` : ''}
344
- </section>
345
- <section class="grid">
346
- ${variantCards}
347
- </section>
348
- </main>
349
- </body>
350
- </html>`;
351
- };
181
+ const buildCanvasSharePage = (args) => (0, rivetPage_1.renderRivetPage)({
182
+ page: {
183
+ kind: 'canvas-share',
184
+ prompt: args.sessionPrompt.trim(),
185
+ directions: args.variants.map((variant, index) => ({
186
+ label: variant.label.trim() || `Direction ${index + 1}`,
187
+ brief: variant.brief.trim(),
188
+ shareUrl: variant.shareUrl,
189
+ })),
190
+ },
191
+ head: {
192
+ title: `Rivet Canvas — ${args.variants.length} directions`,
193
+ shareCard: {
194
+ description: RIVET_DEFAULT_SHARE_DESCRIPTION,
195
+ imageUrl: RIVET_SOCIAL_IMAGE_URL,
196
+ },
197
+ },
198
+ });
352
199
  const generatedProjectWorkspaceRoot = (projectPath) => {
353
200
  const normalized = path_1.default.normalize(projectPath);
354
201
  const segments = normalized.split(path_1.default.sep);
@@ -1811,7 +1658,7 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
1811
1658
  }
1812
1659
  deployedVariants.push(deployed.variant);
1813
1660
  }
1814
- const html = buildCanvasShareHtml({
1661
+ const html = buildCanvasSharePage({
1815
1662
  sessionPrompt,
1816
1663
  variants: deployedVariants,
1817
1664
  });
@@ -42,7 +42,7 @@ const path_1 = __importDefault(require("path"));
42
42
  const fs_1 = __importDefault(require("fs"));
43
43
  const logger_1 = require("../utils/logger");
44
44
  const preview_bridge_1 = require("../proxy-middleware/preview-bridge");
45
- const emptyStatePage_1 = require("./emptyStatePage");
45
+ const rivetPage_1 = require("../utils/rivetPage");
46
46
  const log = (0, logger_1.createLogger)('StaticRoutes');
47
47
  const createStaticRoutes = (staticService, _projectPath, options = {}) => {
48
48
  const router = (0, express_1.Router)();
@@ -62,7 +62,13 @@ const createStaticRoutes = (staticService, _projectPath, options = {}) => {
62
62
  res.sendFile(entryFileName, { root: basePath });
63
63
  };
64
64
  const sendEmptyState = (res) => {
65
- res.status(200).type('html').send((0, emptyStatePage_1.renderEmptyState)());
65
+ res
66
+ .status(200)
67
+ .type('html')
68
+ .send((0, rivetPage_1.renderRivetPage)({
69
+ page: { kind: 'empty-project' },
70
+ head: { title: 'Empty project' },
71
+ }));
66
72
  };
67
73
  router.get('/static', (_req, res) => {
68
74
  const entryPath = path_1.default.join(basePath, entryFileName);
package/dist/server.d.ts CHANGED
@@ -7,7 +7,6 @@ import type { VariantRunService } from './services/VariantRunService';
7
7
  import type { VariantsRuntime } from './services/VariantsRuntime';
8
8
  export declare const INACTIVE_VARIANTS_EVENT_HEARTBEAT_MS = 30000;
9
9
  export declare const RIVET_CORS_ALLOWED_HEADERS: string[];
10
- export declare const DIST_UI_PATH: string;
11
10
  type RivetUiBootstrap = {
12
11
  agentApplyMode: 'embedded' | 'external' | null;
13
12
  queueAccessToken: string | null;
@@ -58,6 +57,7 @@ export type ServerOptions = Omit<CLIOptions, 'help' | 'version' | 'debug'> & {
58
57
  userHost?: string;
59
58
  beforeShutdown?: () => Promise<void>;
60
59
  resolveLibraryOrigin?: () => string | undefined;
60
+ resolveProjectName?: (projectPath: string) => string | undefined;
61
61
  createLibraryViewUrl?: (view: 'projects' | 'references') => Promise<string | null>;
62
62
  };
63
63
  export type ServerHandle = {
package/dist/server.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.startServer = exports.runServerShutdownTasks = exports.isRivetCorsOriginAllowed = exports.isRivetUiStaticAssetPath = exports.isEmbeddedAgentUiHost = exports.resolveEmbeddedAgentUiOrigin = exports.resolveRivetListenHost = exports.sendInactiveVariantsEventStream = exports.readRivetUiIndexHtml = exports.injectRivetUiBootstrap = exports.DIST_UI_PATH = exports.RIVET_CORS_ALLOWED_HEADERS = exports.INACTIVE_VARIANTS_EVENT_HEARTBEAT_MS = void 0;
39
+ exports.startServer = exports.runServerShutdownTasks = exports.isRivetCorsOriginAllowed = exports.isRivetUiStaticAssetPath = exports.isEmbeddedAgentUiHost = exports.resolveEmbeddedAgentUiOrigin = exports.resolveRivetListenHost = exports.sendInactiveVariantsEventStream = exports.readRivetUiIndexHtml = exports.injectRivetUiBootstrap = exports.RIVET_CORS_ALLOWED_HEADERS = exports.INACTIVE_VARIANTS_EVENT_HEARTBEAT_MS = void 0;
40
40
  const editorSession_1 = require("./cli/editorSession");
41
41
  const editorAccess_1 = require("./routes/editorAccess");
42
42
  const editorAccess_2 = require("./utils/editorAccess");
@@ -77,6 +77,8 @@ const VariantHistoryService_1 = require("./services/VariantHistoryService");
77
77
  const RequestAuthContext_1 = require("./services/RequestAuthContext");
78
78
  const middleware_1 = require("./routes/library/middleware");
79
79
  const errorMessage_1 = require("./utils/errorMessage");
80
+ const scriptJson_1 = require("./utils/scriptJson");
81
+ const uiDistPath_1 = require("./utils/uiDistPath");
80
82
  const pluginContext_1 = require("./hosts/pluginContext");
81
83
  const log = (0, index_core_1.createLogger)('RivetServer');
82
84
  const QUEUE_ACCESS_TOKEN_BYTES = 32;
@@ -93,36 +95,21 @@ exports.RIVET_CORS_ALLOWED_HEADERS = [
93
95
  'x-rivet-queue-token',
94
96
  editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER,
95
97
  ];
96
- const resolveUiPath = () => {
97
- const bundledPath = path_1.default.join(__dirname, 'ui/dist');
98
- const devPath = path_1.default.join(__dirname, '../src/ui/dist');
99
- if (fs_1.default.existsSync(bundledPath)) {
100
- log.debug(`Using bundled UI path: ${bundledPath}`);
101
- return bundledPath;
102
- }
103
- log.debug(`Using development UI path: ${devPath}`);
104
- return devPath;
105
- };
106
- exports.DIST_UI_PATH = resolveUiPath();
107
98
  const NO_USER_DEV_SERVER_PORT = 0;
108
99
  const setNoStoreHeaders = (res) => {
109
100
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
110
101
  res.setHeader('Pragma', 'no-cache');
111
102
  res.setHeader('Expires', '0');
112
103
  };
113
- const escapeScriptJson = (value) => JSON.stringify(value)
114
- .replace(/</g, '\\u003c')
115
- .replace(/\u2028/g, '\\u2028')
116
- .replace(/\u2029/g, '\\u2029');
117
104
  const injectRivetUiBootstrap = (html, bootstrap) => {
118
- const script = `<script id="${RIVET_BOOTSTRAP_SCRIPT_ID}">window.__RIVET_BOOTSTRAP__=${escapeScriptJson(bootstrap)};</script>`;
105
+ const script = `<script id="${RIVET_BOOTSTRAP_SCRIPT_ID}">window.__RIVET_BOOTSTRAP__=${(0, scriptJson_1.escapeScriptJson)(bootstrap)};</script>`;
119
106
  if (html.includes('</head>')) {
120
107
  return html.replace('</head>', `${script}</head>`);
121
108
  }
122
109
  return `${script}${html}`;
123
110
  };
124
111
  exports.injectRivetUiBootstrap = injectRivetUiBootstrap;
125
- const readRivetUiIndexHtml = () => fs_1.default.readFileSync(path_1.default.join(exports.DIST_UI_PATH, RIVET_UI_INDEX_FILENAME), 'utf8');
112
+ const readRivetUiIndexHtml = () => fs_1.default.readFileSync(path_1.default.join(uiDistPath_1.DIST_UI_PATH, RIVET_UI_INDEX_FILENAME), 'utf8');
126
113
  exports.readRivetUiIndexHtml = readRivetUiIndexHtml;
127
114
  const sendInactiveVariantsEventStream = (req, res) => {
128
115
  res.set({
@@ -366,15 +353,15 @@ const startServer = async (options) => {
366
353
  const serve = express_1.default.static(directory, uiStaticOptions);
367
354
  return (req, res, next) => isPreviewRequest(req) ? next() : serve(req, res, next);
368
355
  };
369
- app.use('/assets', serveEditorAssets(path_1.default.join(exports.DIST_UI_PATH, 'assets')));
370
- app.use('/rivet/assets', serveEditorAssets(path_1.default.join(exports.DIST_UI_PATH, 'assets')));
371
- app.use('/fonts', serveEditorAssets(path_1.default.join(exports.DIST_UI_PATH, 'fonts')));
372
- app.use('/rivet/fonts', serveEditorAssets(path_1.default.join(exports.DIST_UI_PATH, 'fonts')));
356
+ app.use('/assets', serveEditorAssets(path_1.default.join(uiDistPath_1.DIST_UI_PATH, 'assets')));
357
+ app.use('/rivet/assets', serveEditorAssets(path_1.default.join(uiDistPath_1.DIST_UI_PATH, 'assets')));
358
+ app.use('/fonts', serveEditorAssets(path_1.default.join(uiDistPath_1.DIST_UI_PATH, 'fonts')));
359
+ app.use('/rivet/fonts', serveEditorAssets(path_1.default.join(uiDistPath_1.DIST_UI_PATH, 'fonts')));
373
360
  app.get('/favicon.ico', (_req, res) => {
374
- res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
361
+ res.sendFile('favicon.ico', { root: uiDistPath_1.DIST_UI_PATH });
375
362
  });
376
363
  app.get('/rivet/favicon.ico', (_req, res) => {
377
- res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
364
+ res.sendFile('favicon.ico', { root: uiDistPath_1.DIST_UI_PATH });
378
365
  });
379
366
  app.get(/^\/rivet\/library(?:\/.*)?$/, (req, res) => {
380
367
  const resolvedLibraryOrigin = options.resolveLibraryOrigin?.();
@@ -391,7 +378,7 @@ const startServer = async (options) => {
391
378
  }
392
379
  sendRivetUiIndex(req, res);
393
380
  });
394
- log.info(`Serving UI assets from: ${exports.DIST_UI_PATH}`);
381
+ log.info(`Serving UI assets from: ${uiDistPath_1.DIST_UI_PATH}`);
395
382
  if (framework === 'static' && !hasUserDevServer) {
396
383
  app.get('/', (req, res, next) => {
397
384
  if (isPreviewRequest(req)) {
@@ -511,6 +498,7 @@ const startServer = async (options) => {
511
498
  ? (agentApplyMode ?? 'external')
512
499
  : null,
513
500
  projectPath: livePath,
501
+ projectName: options.resolveProjectName?.(livePath) ?? path_1.default.basename(livePath),
514
502
  projectId: options.projectId ?? null,
515
503
  libraryOrigin: options.resolveLibraryOrigin?.() ?? null,
516
504
  });