rivet-design 0.10.2 → 0.10.4
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/dist/mcp/agent-variants/SessionStore.d.ts +23 -0
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +71 -2
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +26 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +297 -139
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +5 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +8 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +26 -2
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +16 -4
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/runLabel.d.ts +18 -0
- package/dist/mcp/agent-variants/runLabel.d.ts.map +1 -0
- package/dist/mcp/agent-variants/runLabel.js +146 -0
- package/dist/mcp/agent-variants/runLabel.js.map +1 -0
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +14 -5
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +0 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +27 -2
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -13
- package/dist/server.js.map +1 -1
- package/dist/services/AuthService.d.ts.map +1 -1
- package/dist/services/AuthService.js +7 -0
- package/dist/services/AuthService.js.map +1 -1
- package/dist/services/VariantHistoryService.d.ts +5 -1
- package/dist/services/VariantHistoryService.d.ts.map +1 -1
- package/dist/services/VariantHistoryService.js +4 -0
- package/dist/services/VariantHistoryService.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +30 -9
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/utils/logger.d.ts +5 -5
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +27 -23
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-COggU55U.js +645 -0
- package/src/ui/dist/assets/main-frxzIoK6.css +1 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-C8QYh4jE.css +0 -1
- package/src/ui/dist/assets/main-CMk2ORlB.js +0 -645
|
@@ -25,6 +25,47 @@ const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
|
25
25
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
26
26
|
const DESIGN_CONTEXT_ROUTE_SEGMENT = 'design-md';
|
|
27
27
|
const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
|
|
28
|
+
// Hard ceiling on worktree provisioning so a slow/large host project can never
|
|
29
|
+
// stall the `start_variants` / `approve_variant_briefs` tool call indefinitely.
|
|
30
|
+
// Existing-project provisioning clones the host working tree once per variant
|
|
31
|
+
// (`git worktree add` + untracked-file copy), so its cost scales with repo
|
|
32
|
+
// size — a big monorepo (or a React Native tree with native build dirs) used to
|
|
33
|
+
// hang the call for 15+ minutes with no signal. With this bound the call fails
|
|
34
|
+
// fast and legibly instead. Per-git-op hangs are already caught by simple-git's
|
|
35
|
+
// block timeout in WorktreeManager; this is the cross-op backstop. Override via
|
|
36
|
+
// env for unusually large repos.
|
|
37
|
+
const DEFAULT_PROVISION_TIMEOUT_MS = 180_000;
|
|
38
|
+
const PROVISION_TIMEOUT_MS = (() => {
|
|
39
|
+
const raw = process.env.RIVET_VARIANTS_PROVISION_TIMEOUT_MS;
|
|
40
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
41
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
42
|
+
? parsed
|
|
43
|
+
: DEFAULT_PROVISION_TIMEOUT_MS;
|
|
44
|
+
})();
|
|
45
|
+
/** Sentinel so callers can distinguish a provisioning timeout from other errors. */
|
|
46
|
+
class ProvisionTimeoutError extends Error {
|
|
47
|
+
timeoutMs;
|
|
48
|
+
constructor(timeoutMs) {
|
|
49
|
+
super(`Worktree provisioning timed out after ${timeoutMs}ms`);
|
|
50
|
+
this.timeoutMs = timeoutMs;
|
|
51
|
+
this.name = 'ProvisionTimeoutError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reject with `ProvisionTimeoutError` if `work` hasn't settled within
|
|
56
|
+
* `timeoutMs`. The underlying work is NOT cancellable (git/fs I/O), so it may
|
|
57
|
+
* keep running after we reject — callers should best-effort clean up partial
|
|
58
|
+
* state. The timer is unref'd so it never keeps the process alive on its own.
|
|
59
|
+
*/
|
|
60
|
+
function withProvisionTimeout(work, timeoutMs) {
|
|
61
|
+
let timer;
|
|
62
|
+
const timeout = new Promise((_, reject) => {
|
|
63
|
+
timer = setTimeout(() => reject(new ProvisionTimeoutError(timeoutMs)), timeoutMs);
|
|
64
|
+
if (typeof timer.unref === 'function')
|
|
65
|
+
timer.unref();
|
|
66
|
+
});
|
|
67
|
+
return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
|
|
68
|
+
}
|
|
28
69
|
/**
|
|
29
70
|
* Allowlist of asset file extensions an agent-planned source may have.
|
|
30
71
|
* `assetPlan` is sized for large local *assets* (3D models, images,
|
|
@@ -238,6 +279,7 @@ class AgentVariantsOrchestrator {
|
|
|
238
279
|
setCommittedDevServerHealth;
|
|
239
280
|
variantHistory;
|
|
240
281
|
startStaticPreviewServerImpl;
|
|
282
|
+
provisionTimeoutMs;
|
|
241
283
|
resources = new Map();
|
|
242
284
|
/**
|
|
243
285
|
* Committed dev servers from prior sessions that survived teardown. The
|
|
@@ -281,6 +323,8 @@ class AgentVariantsOrchestrator {
|
|
|
281
323
|
this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
|
|
282
324
|
this.startStaticPreviewServerImpl =
|
|
283
325
|
deps.startStaticPreviewServer ?? StaticPreviewServer_1.startStaticPreviewServer;
|
|
326
|
+
this.provisionTimeoutMs =
|
|
327
|
+
deps.provisionTimeoutMs ?? PROVISION_TIMEOUT_MS;
|
|
284
328
|
}
|
|
285
329
|
// --- Pure delegations (no side effects) ---------------------------------
|
|
286
330
|
propose(args) {
|
|
@@ -647,7 +691,7 @@ class AgentVariantsOrchestrator {
|
|
|
647
691
|
const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
|
|
648
692
|
return artifact?.content;
|
|
649
693
|
}
|
|
650
|
-
/** Build the
|
|
694
|
+
/** Build the rendered DesignMD document for an artifact link. */
|
|
651
695
|
getDesignContextViewerHtml(sessionId, artifactId) {
|
|
652
696
|
const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
|
|
653
697
|
return artifact ? buildDesignContextViewerDocument(artifact) : undefined;
|
|
@@ -802,6 +846,7 @@ class AgentVariantsOrchestrator {
|
|
|
802
846
|
count,
|
|
803
847
|
target: args.target,
|
|
804
848
|
projectContext,
|
|
849
|
+
runLabel: args.runLabel,
|
|
805
850
|
});
|
|
806
851
|
if (proposeResult.stage !== 'awaiting_briefs' ||
|
|
807
852
|
!proposeResult.briefWorkItem) {
|
|
@@ -1071,6 +1116,39 @@ class AgentVariantsOrchestrator {
|
|
|
1071
1116
|
alreadyTerminal: result.alreadyTerminal,
|
|
1072
1117
|
};
|
|
1073
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Remove a variant from the UI. Hides it from the active snapshot for good
|
|
1121
|
+
* (it's filtered out of progress/summary/terminal accounting too) while
|
|
1122
|
+
* retaining it in `.rivet/variants/` with status `removed` so the work is
|
|
1123
|
+
* kept, never rendered again. Mirrors `cancelVariant`'s shape; idempotent.
|
|
1124
|
+
*/
|
|
1125
|
+
async removeVariant(args) {
|
|
1126
|
+
const result = this.store.removeWorkItem({
|
|
1127
|
+
sessionId: args.sessionId,
|
|
1128
|
+
workItemId: args.variantId,
|
|
1129
|
+
});
|
|
1130
|
+
this.telemetry.track('agent_variants.variant_removed', {
|
|
1131
|
+
source: 'mcp',
|
|
1132
|
+
sessionId: args.sessionId,
|
|
1133
|
+
variantId: args.variantId,
|
|
1134
|
+
finalStatus: result.finalStatus,
|
|
1135
|
+
sessionStage: result.sessionStage,
|
|
1136
|
+
alreadyRemoved: result.alreadyRemoved,
|
|
1137
|
+
});
|
|
1138
|
+
if (!result.alreadyRemoved) {
|
|
1139
|
+
void this.markPersistedVariantRemoved(args.sessionId, args.variantId).catch((err) => {
|
|
1140
|
+
log.warn(`markPersistedVariantRemoved failed for ${args.sessionId}/${args.variantId}`, err);
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
this.emitChange();
|
|
1144
|
+
return {
|
|
1145
|
+
sessionId: args.sessionId,
|
|
1146
|
+
variantId: args.variantId,
|
|
1147
|
+
finalStatus: result.finalStatus,
|
|
1148
|
+
sessionStage: result.sessionStage,
|
|
1149
|
+
alreadyRemoved: result.alreadyRemoved,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1074
1152
|
/**
|
|
1075
1153
|
* User has reviewed the rendered variants in chat and picked one. Look up
|
|
1076
1154
|
* the captured diff, build a VariantPickEnvelope, and enqueue to the
|
|
@@ -1619,17 +1697,76 @@ class AgentVariantsOrchestrator {
|
|
|
1619
1697
|
// --- Side-effect implementations ----------------------------------------
|
|
1620
1698
|
async provisionWorktrees(sessionId, approveResult) {
|
|
1621
1699
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1700
|
+
const variantCount = approveResult.codeGenWorkItemIds.length +
|
|
1701
|
+
(approveResult.scaffoldBaseWorkItemId ? 1 : 0);
|
|
1702
|
+
// Fresh + static_preview provisions nothing (HTML is the deliverable) —
|
|
1703
|
+
// skip the timing/telemetry wrapper so the fast path stays untouched.
|
|
1704
|
+
if (projectContext.kind === 'fresh' && !approveResult.scaffoldBaseWorkItemId) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
const startedAt = Date.now();
|
|
1708
|
+
this.telemetry.track('agent_variants.provisioning_started', {
|
|
1709
|
+
source: 'mcp',
|
|
1710
|
+
sessionId,
|
|
1711
|
+
kind: projectContext.kind,
|
|
1712
|
+
variantCount,
|
|
1713
|
+
});
|
|
1714
|
+
const run = async () => {
|
|
1715
|
+
if (projectContext.kind === 'fresh') {
|
|
1716
|
+
// Fresh + vite_app: provision a Vite skeleton per variant and copy the
|
|
1717
|
+
// agent-planned assetPlan files before the agent leases its code_gen
|
|
1718
|
+
// items. (Background `npm install` is dispatched inside and is NOT
|
|
1719
|
+
// awaited here — the timeout bounds worktree creation, not install.)
|
|
1628
1720
|
await this.provisionFreshWorktrees(sessionId, approveResult, projectContext);
|
|
1721
|
+
return;
|
|
1629
1722
|
}
|
|
1630
|
-
|
|
1723
|
+
await this.provisionExistingWorktrees(sessionId, approveResult);
|
|
1724
|
+
};
|
|
1725
|
+
try {
|
|
1726
|
+
await withProvisionTimeout(run(), this.provisionTimeoutMs);
|
|
1727
|
+
this.telemetry.track('agent_variants.provisioning_completed', {
|
|
1728
|
+
source: 'mcp',
|
|
1729
|
+
sessionId,
|
|
1730
|
+
kind: projectContext.kind,
|
|
1731
|
+
variantCount,
|
|
1732
|
+
durationMs: Date.now() - startedAt,
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
catch (err) {
|
|
1736
|
+
const timedOut = err instanceof ProvisionTimeoutError;
|
|
1737
|
+
this.telemetry.track('agent_variants.provisioning_failed', {
|
|
1738
|
+
source: 'mcp',
|
|
1739
|
+
sessionId,
|
|
1740
|
+
kind: projectContext.kind,
|
|
1741
|
+
variantCount,
|
|
1742
|
+
durationMs: Date.now() - startedAt,
|
|
1743
|
+
timedOut,
|
|
1744
|
+
errorCode: timedOut
|
|
1745
|
+
? 'PROVISION_TIMEOUT'
|
|
1746
|
+
: err instanceof errors_1.AgentVariantsError
|
|
1747
|
+
? err.code
|
|
1748
|
+
: 'PROVISION_FAILED',
|
|
1749
|
+
});
|
|
1750
|
+
// Best-effort cleanup of any worktrees created before the failure so a
|
|
1751
|
+
// timed-out clone doesn't leak half-provisioned dirs. Fire-and-forget so
|
|
1752
|
+
// the provisioning error returns to the caller immediately — never block
|
|
1753
|
+
// the error on teardown, which itself shells out to git (a wedged repo
|
|
1754
|
+
// could otherwise re-hang the tool on cleanup). The cleanup git calls are
|
|
1755
|
+
// bounded by GIT_OPTS in WorktreeManager, and the next-startup orphan
|
|
1756
|
+
// sweep catches anything created after this point.
|
|
1757
|
+
void this.teardownSession(sessionId, 'cancel').catch((teardownErr) => {
|
|
1758
|
+
log.warn(`cleanup after provisioning failure for ${sessionId} failed`, teardownErr);
|
|
1759
|
+
});
|
|
1760
|
+
if (timedOut) {
|
|
1761
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Provisioning ${variantCount} variant worktree(s) timed out after ` +
|
|
1762
|
+
`${Math.round(this.provisionTimeoutMs / 1000)}s. This usually means the ` +
|
|
1763
|
+
`project is very large or slow to clone (e.g. a big monorepo, or a ` +
|
|
1764
|
+
`React Native tree with native build dirs that aren't git-ignored). ` +
|
|
1765
|
+
`Try git-ignoring build artifacts, or generate standalone variants ` +
|
|
1766
|
+
`with create_zero_to_one_project instead of cloning this project.`);
|
|
1767
|
+
}
|
|
1768
|
+
throw err;
|
|
1631
1769
|
}
|
|
1632
|
-
await this.provisionExistingWorktrees(sessionId, approveResult);
|
|
1633
1770
|
}
|
|
1634
1771
|
/**
|
|
1635
1772
|
* Existing-project flow: clone the user's repo N times via git worktree.
|
|
@@ -2120,6 +2257,7 @@ class AgentVariantsOrchestrator {
|
|
|
2120
2257
|
label: input.briefLabel,
|
|
2121
2258
|
brief: input.briefBody,
|
|
2122
2259
|
sessionPrompt,
|
|
2260
|
+
runLabel: this.store.getRunLabel(args.sessionId),
|
|
2123
2261
|
kind: 'diff',
|
|
2124
2262
|
diff: args.diff,
|
|
2125
2263
|
sourceDir,
|
|
@@ -2201,6 +2339,7 @@ class AgentVariantsOrchestrator {
|
|
|
2201
2339
|
label: input.briefLabel,
|
|
2202
2340
|
brief: input.briefBody,
|
|
2203
2341
|
sessionPrompt,
|
|
2342
|
+
runLabel: this.store.getRunLabel(args.sessionId),
|
|
2204
2343
|
kind: 'project-created',
|
|
2205
2344
|
sourceDir,
|
|
2206
2345
|
preview,
|
|
@@ -2285,6 +2424,22 @@ class AgentVariantsOrchestrator {
|
|
|
2285
2424
|
status: 'cancelled',
|
|
2286
2425
|
});
|
|
2287
2426
|
}
|
|
2427
|
+
async markPersistedVariantRemoved(sessionId, variantId) {
|
|
2428
|
+
if (!this.store.hasSession(sessionId))
|
|
2429
|
+
return;
|
|
2430
|
+
const projectPath = await this.resolveHistoryProjectPath(sessionId);
|
|
2431
|
+
if (!projectPath)
|
|
2432
|
+
return;
|
|
2433
|
+
// No-ops (ENOENT) when the variant was never persisted — e.g. removing a
|
|
2434
|
+
// still-pending variant that produced no artifact. Completed variants have
|
|
2435
|
+
// a manifest, which flips to `removed` and is retained on disk.
|
|
2436
|
+
await this.variantHistory.markStatus({
|
|
2437
|
+
projectPath,
|
|
2438
|
+
sessionId,
|
|
2439
|
+
variantId,
|
|
2440
|
+
status: 'removed',
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2288
2443
|
/**
|
|
2289
2444
|
* Resolve the project path that owns `.rivet/variants/` for a session.
|
|
2290
2445
|
* Existing sessions: the user's project (via `resolveEnv`). Fresh sessions:
|
|
@@ -3102,8 +3257,7 @@ const buildDesignContextViewUrl = (sessionId, artifactId) => {
|
|
|
3102
3257
|
};
|
|
3103
3258
|
const buildDesignContextViewerDocument = (artifact) => {
|
|
3104
3259
|
const title = `${artifact.label} DESIGN.md`;
|
|
3105
|
-
const visualHtml = renderDesignMarkdown(artifact.content);
|
|
3106
|
-
const rawMarkdown = escapeHtml(artifact.content);
|
|
3260
|
+
const visualHtml = renderDesignMarkdown(artifact.content, artifact.label);
|
|
3107
3261
|
return `<!doctype html>
|
|
3108
3262
|
<html lang="en">
|
|
3109
3263
|
<head>
|
|
@@ -3112,131 +3266,131 @@ const buildDesignContextViewerDocument = (artifact) => {
|
|
|
3112
3266
|
<title>${escapeHtml(title)}</title>
|
|
3113
3267
|
<style>
|
|
3114
3268
|
:root {
|
|
3115
|
-
color-scheme:
|
|
3116
|
-
--
|
|
3117
|
-
--
|
|
3118
|
-
--
|
|
3119
|
-
--
|
|
3120
|
-
--
|
|
3121
|
-
--
|
|
3122
|
-
--
|
|
3123
|
-
--
|
|
3269
|
+
color-scheme: dark;
|
|
3270
|
+
--font-main: 'Satoshi', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3271
|
+
--font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
3272
|
+
--main: #1C1C20;
|
|
3273
|
+
--main-light: #1E1E22;
|
|
3274
|
+
--main-input: #2E2E2E;
|
|
3275
|
+
--main-border: #232328;
|
|
3276
|
+
--main-hover: #404040;
|
|
3277
|
+
--content: #ffffff;
|
|
3278
|
+
--content-muted: #d1d5db;
|
|
3279
|
+
--content-subtle: #9ca3af;
|
|
3280
|
+
--divider: #4b5563;
|
|
3281
|
+
--primary: #FF3300;
|
|
3282
|
+
--primary-border: #ff6b35;
|
|
3124
3283
|
}
|
|
3125
3284
|
* { box-sizing: border-box; }
|
|
3126
3285
|
body {
|
|
3127
3286
|
margin: 0;
|
|
3128
3287
|
min-height: 100vh;
|
|
3129
|
-
background:
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
color: var(--ink);
|
|
3133
|
-
font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif;
|
|
3288
|
+
background: var(--main);
|
|
3289
|
+
color: var(--content);
|
|
3290
|
+
font-family: var(--font-main);
|
|
3134
3291
|
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
padding: 40px 0;
|
|
3292
|
+
.page-shell {
|
|
3293
|
+
min-height: 100vh;
|
|
3294
|
+
padding: 22px 28px 56px;
|
|
3139
3295
|
}
|
|
3140
|
-
|
|
3296
|
+
.topbar {
|
|
3141
3297
|
display: flex;
|
|
3142
3298
|
align-items: center;
|
|
3143
3299
|
justify-content: space-between;
|
|
3144
|
-
|
|
3145
|
-
margin-bottom: 24px;
|
|
3146
|
-
border-bottom: 1px solid var(--rule);
|
|
3147
|
-
padding-bottom: 18px;
|
|
3148
|
-
}
|
|
3149
|
-
h1 {
|
|
3150
|
-
margin: 0;
|
|
3151
|
-
max-width: 780px;
|
|
3152
|
-
font-size: clamp(2rem, 5vw, 4.5rem);
|
|
3153
|
-
letter-spacing: -0.06em;
|
|
3154
|
-
line-height: 0.92;
|
|
3155
|
-
}
|
|
3156
|
-
.mode-input {
|
|
3157
|
-
position: absolute;
|
|
3158
|
-
opacity: 0;
|
|
3159
|
-
pointer-events: none;
|
|
3300
|
+
margin-bottom: 28px;
|
|
3160
3301
|
}
|
|
3161
|
-
.
|
|
3302
|
+
.brand {
|
|
3162
3303
|
display: inline-flex;
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
border: 1px solid var(--rule);
|
|
3166
|
-
border-radius: 999px;
|
|
3167
|
-
background: rgba(255, 253, 248, 0.72);
|
|
3168
|
-
padding: 4px;
|
|
3169
|
-
box-shadow: 0 12px 30px rgba(61, 44, 26, 0.08);
|
|
3170
|
-
}
|
|
3171
|
-
.mode-toggle label {
|
|
3172
|
-
position: relative;
|
|
3173
|
-
cursor: pointer;
|
|
3174
|
-
border-radius: 999px;
|
|
3175
|
-
padding: 8px 14px;
|
|
3176
|
-
color: var(--muted);
|
|
3177
|
-
font: 600 0.74rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
3178
|
-
letter-spacing: 0.12em;
|
|
3179
|
-
text-transform: uppercase;
|
|
3180
|
-
transition:
|
|
3181
|
-
background 160ms ease,
|
|
3182
|
-
color 160ms ease;
|
|
3183
|
-
}
|
|
3184
|
-
.mode-toggle label:has(input:checked) {
|
|
3185
|
-
background: var(--ink);
|
|
3186
|
-
color: var(--paper);
|
|
3304
|
+
align-items: center;
|
|
3305
|
+
min-height: 28px;
|
|
3187
3306
|
}
|
|
3188
|
-
.
|
|
3307
|
+
.brand-logo {
|
|
3189
3308
|
display: block;
|
|
3309
|
+
width: 64px;
|
|
3310
|
+
height: auto;
|
|
3190
3311
|
}
|
|
3191
|
-
.
|
|
3192
|
-
|
|
3312
|
+
.avatar {
|
|
3313
|
+
width: 28px;
|
|
3314
|
+
height: 28px;
|
|
3315
|
+
border: 1px solid var(--main-border);
|
|
3316
|
+
border-radius: 8px;
|
|
3317
|
+
background: var(--main-light);
|
|
3193
3318
|
}
|
|
3194
|
-
main
|
|
3195
|
-
|
|
3196
|
-
|
|
3319
|
+
main {
|
|
3320
|
+
width: min(1120px, calc(100vw - 56px));
|
|
3321
|
+
margin: 0 auto;
|
|
3197
3322
|
}
|
|
3198
|
-
.
|
|
3323
|
+
.document {
|
|
3199
3324
|
overflow: hidden;
|
|
3200
|
-
border: 1px solid var(--
|
|
3201
|
-
border-radius:
|
|
3202
|
-
background:
|
|
3203
|
-
box-shadow: 0
|
|
3204
|
-
}
|
|
3205
|
-
.
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
font: 700
|
|
3213
|
-
letter-spacing: 0.
|
|
3325
|
+
border: 1px solid var(--main-border);
|
|
3326
|
+
border-radius: 22px;
|
|
3327
|
+
background: var(--main-light);
|
|
3328
|
+
box-shadow: 0 18px 60px rgb(0 0 0 / 18%);
|
|
3329
|
+
}
|
|
3330
|
+
.document-title {
|
|
3331
|
+
padding: 42px 46px 32px;
|
|
3332
|
+
}
|
|
3333
|
+
.eyebrow {
|
|
3334
|
+
margin: 0 0 14px;
|
|
3335
|
+
color: var(--content-subtle);
|
|
3336
|
+
font-size: 0.68rem;
|
|
3337
|
+
font-weight: 700;
|
|
3338
|
+
letter-spacing: 0.16em;
|
|
3214
3339
|
text-transform: uppercase;
|
|
3215
3340
|
}
|
|
3341
|
+
.file-title {
|
|
3342
|
+
margin: 0;
|
|
3343
|
+
color: var(--content);
|
|
3344
|
+
font-size: clamp(2.35rem, 6vw, 4.9rem);
|
|
3345
|
+
font-weight: 600;
|
|
3346
|
+
letter-spacing: -0.07em;
|
|
3347
|
+
line-height: 0.94;
|
|
3348
|
+
}
|
|
3216
3349
|
.visual {
|
|
3217
|
-
|
|
3350
|
+
border-top: 1px solid var(--main-border);
|
|
3351
|
+
max-width: none;
|
|
3352
|
+
margin: 0 auto;
|
|
3353
|
+
padding: 36px 46px 48px;
|
|
3354
|
+
}
|
|
3355
|
+
.visual-inner {
|
|
3356
|
+
max-width: 720px;
|
|
3357
|
+
margin: 0 auto;
|
|
3218
3358
|
}
|
|
3219
3359
|
.visual h1,
|
|
3220
3360
|
.visual h2,
|
|
3221
3361
|
.visual h3 {
|
|
3222
|
-
margin: 1.
|
|
3223
|
-
|
|
3224
|
-
|
|
3362
|
+
margin: 1.2em 0 0.4em;
|
|
3363
|
+
color: var(--content);
|
|
3364
|
+
font-weight: 600;
|
|
3365
|
+
letter-spacing: -0.02em;
|
|
3366
|
+
line-height: 1.15;
|
|
3225
3367
|
}
|
|
3226
3368
|
.visual h1:first-child,
|
|
3227
3369
|
.visual h2:first-child,
|
|
3228
3370
|
.visual h3:first-child {
|
|
3229
3371
|
margin-top: 0;
|
|
3230
3372
|
}
|
|
3231
|
-
.visual h1 { font-size:
|
|
3232
|
-
.visual h2 { font-size: 1.
|
|
3233
|
-
.visual h3 { font-size:
|
|
3373
|
+
.visual h1 { font-size: 1.25rem; }
|
|
3374
|
+
.visual h2 { font-size: 1.1rem; }
|
|
3375
|
+
.visual h3 { font-size: 1rem; }
|
|
3376
|
+
.visual h2 + p,
|
|
3377
|
+
.visual h2 + ul {
|
|
3378
|
+
border-left: 1px solid var(--main-border);
|
|
3379
|
+
padding-left: 14px;
|
|
3380
|
+
}
|
|
3234
3381
|
.visual p,
|
|
3235
3382
|
.visual li,
|
|
3236
3383
|
.visual blockquote {
|
|
3237
|
-
color:
|
|
3238
|
-
font-size:
|
|
3239
|
-
line-height: 1.
|
|
3384
|
+
color: var(--content-muted);
|
|
3385
|
+
font-size: 0.88rem;
|
|
3386
|
+
line-height: 1.6;
|
|
3387
|
+
}
|
|
3388
|
+
.visual strong {
|
|
3389
|
+
color: var(--content);
|
|
3390
|
+
font-weight: 650;
|
|
3391
|
+
}
|
|
3392
|
+
.visual a {
|
|
3393
|
+
color: var(--primary-border);
|
|
3240
3394
|
}
|
|
3241
3395
|
.visual ul {
|
|
3242
3396
|
display: grid;
|
|
@@ -3245,71 +3399,67 @@ const buildDesignContextViewerDocument = (artifact) => {
|
|
|
3245
3399
|
}
|
|
3246
3400
|
.visual blockquote {
|
|
3247
3401
|
margin: 18px 0;
|
|
3248
|
-
border-left:
|
|
3402
|
+
border-left: 3px solid var(--primary);
|
|
3249
3403
|
padding-left: 14px;
|
|
3250
|
-
color: var(--
|
|
3404
|
+
color: var(--content-subtle);
|
|
3251
3405
|
}
|
|
3252
3406
|
.visual code {
|
|
3253
3407
|
border-radius: 6px;
|
|
3254
|
-
|
|
3408
|
+
border: 1px solid var(--main-border);
|
|
3409
|
+
background: var(--main-input);
|
|
3410
|
+
color: var(--content);
|
|
3255
3411
|
padding: 0.12rem 0.34rem;
|
|
3256
|
-
font-family:
|
|
3412
|
+
font-family: var(--font-mono);
|
|
3257
3413
|
font-size: 0.92em;
|
|
3258
3414
|
}
|
|
3259
3415
|
pre {
|
|
3260
3416
|
margin: 0;
|
|
3261
|
-
max-height: calc(100vh - 190px);
|
|
3262
3417
|
overflow: auto;
|
|
3263
|
-
background:
|
|
3264
|
-
color:
|
|
3265
|
-
padding:
|
|
3266
|
-
font: 0.78rem/1.55
|
|
3418
|
+
background: var(--main);
|
|
3419
|
+
color: var(--content-muted);
|
|
3420
|
+
padding: 16px;
|
|
3421
|
+
font: 0.78rem/1.55 var(--font-mono);
|
|
3267
3422
|
white-space: pre-wrap;
|
|
3268
3423
|
word-break: break-word;
|
|
3269
3424
|
}
|
|
3270
3425
|
@media (max-width: 900px) {
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3426
|
+
.page-shell { padding: 18px 12px 34px; }
|
|
3427
|
+
.topbar { margin-bottom: 18px; padding: 0 4px; }
|
|
3428
|
+
main { width: min(100vw - 24px, 760px); }
|
|
3429
|
+
.document { border-radius: 18px; }
|
|
3430
|
+
.document-title { padding: 30px 24px 24px; }
|
|
3431
|
+
.file-title { font-size: clamp(2rem, 12vw, 3.6rem); }
|
|
3432
|
+
.visual { padding: 26px 24px 34px; }
|
|
3274
3433
|
}
|
|
3275
3434
|
</style>
|
|
3276
3435
|
</head>
|
|
3277
3436
|
<body>
|
|
3278
|
-
<
|
|
3279
|
-
<header>
|
|
3280
|
-
<
|
|
3281
|
-
<div class="
|
|
3282
|
-
<label>
|
|
3283
|
-
<input class="mode-input" type="radio" name="designmd-view-mode" id="designmd-visual-mode" checked />
|
|
3284
|
-
<span>Visual</span>
|
|
3285
|
-
</label>
|
|
3286
|
-
<label>
|
|
3287
|
-
<input class="mode-input" type="radio" name="designmd-view-mode" id="designmd-raw-mode" />
|
|
3288
|
-
<span>Raw</span>
|
|
3289
|
-
</label>
|
|
3290
|
-
</div>
|
|
3437
|
+
<div class="page-shell">
|
|
3438
|
+
<header class="topbar" aria-label="Rivet">
|
|
3439
|
+
<div class="brand"><img class="brand-logo" src="/assets/logo.png" alt="Rivet" /></div>
|
|
3440
|
+
<div class="avatar" aria-hidden="true"></div>
|
|
3291
3441
|
</header>
|
|
3292
|
-
<
|
|
3293
|
-
<article class="
|
|
3294
|
-
<div class="
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
<div class="
|
|
3299
|
-
<pre>${rawMarkdown}</pre>
|
|
3442
|
+
<main>
|
|
3443
|
+
<article class="document" id="designmd-visual" aria-label="DesignMD artifact">
|
|
3444
|
+
<div class="document-title">
|
|
3445
|
+
<p class="eyebrow">Design Context</p>
|
|
3446
|
+
<h1 class="file-title">${escapeHtml(title)}</h1>
|
|
3447
|
+
</div>
|
|
3448
|
+
<div class="visual"><div class="visual-inner">${visualHtml}</div></div>
|
|
3300
3449
|
</article>
|
|
3301
|
-
</
|
|
3302
|
-
</
|
|
3450
|
+
</main>
|
|
3451
|
+
</div>
|
|
3303
3452
|
</body>
|
|
3304
3453
|
</html>`;
|
|
3305
3454
|
};
|
|
3306
|
-
const renderDesignMarkdown = (markdown) => {
|
|
3455
|
+
const renderDesignMarkdown = (markdown, artifactLabel) => {
|
|
3307
3456
|
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
3308
3457
|
const output = [];
|
|
3309
3458
|
const paragraph = [];
|
|
3310
3459
|
const listItems = [];
|
|
3311
3460
|
const codeLines = [];
|
|
3312
3461
|
let isCodeBlock = false;
|
|
3462
|
+
let skippedFirstH1 = false;
|
|
3313
3463
|
const flushParagraph = () => {
|
|
3314
3464
|
if (paragraph.length === 0)
|
|
3315
3465
|
return;
|
|
@@ -3361,7 +3511,15 @@ const renderDesignMarkdown = (markdown) => {
|
|
|
3361
3511
|
flushParagraph();
|
|
3362
3512
|
flushList();
|
|
3363
3513
|
const level = Math.min(headingMatch[1].length, 3);
|
|
3364
|
-
|
|
3514
|
+
const headingText = headingMatch[2].trim();
|
|
3515
|
+
const isDuplicateTitle = headingText === artifactLabel ||
|
|
3516
|
+
headingText === `${artifactLabel} DESIGN.md`;
|
|
3517
|
+
// The viewer header already renders the artifact title.
|
|
3518
|
+
if (level === 1 && !skippedFirstH1 && isDuplicateTitle) {
|
|
3519
|
+
skippedFirstH1 = true;
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
output.push(`<h${level}>${renderInlineMarkdown(headingText)}</h${level}>`);
|
|
3365
3523
|
return;
|
|
3366
3524
|
}
|
|
3367
3525
|
const listMatch = trimmed.match(/^[-*]\s+(.+)$/);
|