rivet-design 0.9.5 → 0.9.6
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 +10 -2
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +82 -22
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +4 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +430 -35
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +389 -129
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +90 -36
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +32 -13
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +9 -2
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +120 -37
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/services/VariantHistoryService.d.ts +64 -1
- package/dist/services/VariantHistoryService.d.ts.map +1 -1
- package/dist/services/VariantHistoryService.js +148 -18
- package/dist/services/VariantHistoryService.js.map +1 -1
- package/dist/utils/skills/claude-skill.d.ts +1 -1
- package/dist/utils/skills/claude-skill.js +1 -1
- package/dist/utils/skills/cursor-rules.d.ts +1 -1
- package/dist/utils/skills/cursor-rules.js +1 -1
- package/dist/utils/skills/describe-motion-protocol.d.ts +11 -0
- package/dist/utils/skills/describe-motion-protocol.d.ts.map +1 -0
- package/dist/utils/skills/describe-motion-protocol.js +216 -0
- package/dist/utils/skills/describe-motion-protocol.js.map +1 -0
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +23 -17
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-BX1XfsOq.css +1 -0
- package/src/ui/dist/assets/{main-SuZlKEi0.js → main-CO7W1r28.js} +39 -39
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-OdmwI8Od.css +0 -1
|
@@ -22,6 +22,8 @@ const previewQa_1 = require("./previewQa");
|
|
|
22
22
|
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
|
|
23
23
|
const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
24
24
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
25
|
+
const DESIGN_CONTEXT_ROUTE_SEGMENT = 'design-md';
|
|
26
|
+
const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
|
|
25
27
|
/**
|
|
26
28
|
* Allowlist of asset file extensions an agent-planned source may have.
|
|
27
29
|
* `assetPlan` is sized for large local *assets* (3D models, images,
|
|
@@ -39,15 +41,36 @@ const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
|
39
41
|
*/
|
|
40
42
|
const ALLOWED_ASSET_EXTENSIONS = new Set([
|
|
41
43
|
// 3D / models
|
|
42
|
-
'.glb',
|
|
44
|
+
'.glb',
|
|
45
|
+
'.gltf',
|
|
46
|
+
'.obj',
|
|
47
|
+
'.fbx',
|
|
48
|
+
'.usdz',
|
|
43
49
|
// images
|
|
44
|
-
'.png',
|
|
50
|
+
'.png',
|
|
51
|
+
'.jpg',
|
|
52
|
+
'.jpeg',
|
|
53
|
+
'.gif',
|
|
54
|
+
'.webp',
|
|
55
|
+
'.svg',
|
|
56
|
+
'.avif',
|
|
57
|
+
'.bmp',
|
|
58
|
+
'.ico',
|
|
45
59
|
// video
|
|
46
|
-
'.mp4',
|
|
60
|
+
'.mp4',
|
|
61
|
+
'.webm',
|
|
62
|
+
'.mov',
|
|
47
63
|
// audio
|
|
48
|
-
'.mp3',
|
|
64
|
+
'.mp3',
|
|
65
|
+
'.wav',
|
|
66
|
+
'.ogg',
|
|
67
|
+
'.m4a',
|
|
49
68
|
// fonts
|
|
50
|
-
'.woff',
|
|
69
|
+
'.woff',
|
|
70
|
+
'.woff2',
|
|
71
|
+
'.ttf',
|
|
72
|
+
'.otf',
|
|
73
|
+
'.eot',
|
|
51
74
|
// PDFs
|
|
52
75
|
'.pdf',
|
|
53
76
|
]);
|
|
@@ -180,8 +203,7 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
180
203
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved extension '${resolvedExt || '(none)'}' is not on the allowlist (resolved from '${entry.source}').`);
|
|
181
204
|
}
|
|
182
205
|
const normalizedDest = path_1.default.normalize(entry.destination);
|
|
183
|
-
if (normalizedDest.startsWith('..') ||
|
|
184
|
-
path_1.default.isAbsolute(normalizedDest)) {
|
|
206
|
+
if (normalizedDest.startsWith('..') || path_1.default.isAbsolute(normalizedDest)) {
|
|
185
207
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.destination must stay inside the worktree, got '${entry.destination}'`);
|
|
186
208
|
}
|
|
187
209
|
const absDest = path_1.default.join(worktreePath, normalizedDest);
|
|
@@ -306,8 +328,10 @@ class AgentVariantsOrchestrator {
|
|
|
306
328
|
const variants = this.getVariants(sessionId);
|
|
307
329
|
const sessionProjectContext = this.store.getProjectContext(sessionId);
|
|
308
330
|
const projectContext = toActiveProjectContext(sessionProjectContext);
|
|
309
|
-
const destinationPath = projectContext.kind === 'fresh'
|
|
310
|
-
|
|
331
|
+
const destinationPath = projectContext.kind === 'fresh'
|
|
332
|
+
? projectContext.workspacePath
|
|
333
|
+
: undefined;
|
|
334
|
+
const artifacts = buildSessionArtifacts(sessionId, sessionProjectContext);
|
|
311
335
|
return {
|
|
312
336
|
active: true,
|
|
313
337
|
sessionId,
|
|
@@ -429,7 +453,9 @@ class AgentVariantsOrchestrator {
|
|
|
429
453
|
};
|
|
430
454
|
}
|
|
431
455
|
}
|
|
432
|
-
catch {
|
|
456
|
+
catch {
|
|
457
|
+
/* work item may not exist in edge cases */
|
|
458
|
+
}
|
|
433
459
|
}
|
|
434
460
|
if (!preview && port) {
|
|
435
461
|
preview = { kind: 'dev_server', port };
|
|
@@ -440,7 +466,7 @@ class AgentVariantsOrchestrator {
|
|
|
440
466
|
const canView = Boolean(preview) || (isSucceeded && Boolean(port));
|
|
441
467
|
const canCommit = isSucceeded && !qaFailed;
|
|
442
468
|
const commitDisabledReason = qaFailed
|
|
443
|
-
? qa?.summary ?? 'Variant failed QA'
|
|
469
|
+
? (qa?.summary ?? 'Variant failed QA')
|
|
444
470
|
: 'Wait for a successful variant';
|
|
445
471
|
return {
|
|
446
472
|
...variant,
|
|
@@ -486,7 +512,9 @@ class AgentVariantsOrchestrator {
|
|
|
486
512
|
}
|
|
487
513
|
getStaticPreviewHtml(sessionId, workItemId) {
|
|
488
514
|
// Primary: from the staticPreviews Map populated by handleSucceededReport.
|
|
489
|
-
const fromMap = this.resources
|
|
515
|
+
const fromMap = this.resources
|
|
516
|
+
.get(sessionId)
|
|
517
|
+
?.staticPreviews.get(workItemId)?.html;
|
|
490
518
|
if (fromMap)
|
|
491
519
|
return fromMap;
|
|
492
520
|
// Fallback: read directly from the work item's stored output — available
|
|
@@ -501,6 +529,16 @@ class AgentVariantsOrchestrator {
|
|
|
501
529
|
return undefined;
|
|
502
530
|
}
|
|
503
531
|
}
|
|
532
|
+
/** Resolve the DESIGN.md markdown behind an artifact link. */
|
|
533
|
+
getDesignContextMarkdown(sessionId, artifactId) {
|
|
534
|
+
const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
|
|
535
|
+
return artifact?.content;
|
|
536
|
+
}
|
|
537
|
+
/** Build the raw-plus-rendered DesignMD document for an artifact link. */
|
|
538
|
+
getDesignContextViewerHtml(sessionId, artifactId) {
|
|
539
|
+
const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
|
|
540
|
+
return artifact ? buildDesignContextViewerDocument(artifact) : undefined;
|
|
541
|
+
}
|
|
504
542
|
getStaticPreviewByBriefId(sessionId, briefId) {
|
|
505
543
|
const resources = this.resources.get(sessionId);
|
|
506
544
|
if (!resources)
|
|
@@ -559,8 +597,12 @@ class AgentVariantsOrchestrator {
|
|
|
559
597
|
*/
|
|
560
598
|
async startUnified(args) {
|
|
561
599
|
const count = args.briefs?.length ?? args.count ?? 4;
|
|
562
|
-
const projectContext = args.projectContext ?? {
|
|
563
|
-
|
|
600
|
+
const projectContext = args.projectContext ?? {
|
|
601
|
+
kind: 'existing',
|
|
602
|
+
};
|
|
603
|
+
const sourceContext = projectContext.kind === 'fresh'
|
|
604
|
+
? projectContext.sourceContext
|
|
605
|
+
: undefined;
|
|
564
606
|
const isSourceGrounded = Boolean(sourceContext?.sourceUrls?.length) ||
|
|
565
607
|
Boolean(sourceContext?.sourceArtifacts?.length) ||
|
|
566
608
|
Boolean(sourceContext?.sourceIntent) ||
|
|
@@ -835,8 +877,8 @@ class AgentVariantsOrchestrator {
|
|
|
835
877
|
}
|
|
836
878
|
else {
|
|
837
879
|
try {
|
|
838
|
-
historyProjectPath =
|
|
839
|
-
|
|
880
|
+
historyProjectPath = (await this.resolveEnv(args.sessionId))
|
|
881
|
+
?.projectPath;
|
|
840
882
|
}
|
|
841
883
|
catch {
|
|
842
884
|
historyProjectPath = undefined;
|
|
@@ -869,13 +911,15 @@ class AgentVariantsOrchestrator {
|
|
|
869
911
|
}
|
|
870
912
|
const variantSnapshot = this.getVariants(args.sessionId).find((variant) => variant.workItemId === args.variantId);
|
|
871
913
|
if (!variantSnapshot || variantSnapshot.actions?.commit?.enabled !== true) {
|
|
872
|
-
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', variantSnapshot?.actions?.commit?.reason ??
|
|
914
|
+
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', variantSnapshot?.actions?.commit?.reason ??
|
|
915
|
+
'Variant is not committable');
|
|
873
916
|
}
|
|
874
917
|
const input = this.store.getWorkItemInput(args.sessionId, args.variantId);
|
|
875
918
|
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
876
919
|
let payload;
|
|
877
920
|
let envelopeDestination;
|
|
878
921
|
let changedFilesCount;
|
|
922
|
+
let runnablePaths;
|
|
879
923
|
if (projectContext.kind === 'fresh') {
|
|
880
924
|
const destinationPath = projectContext.workspacePath;
|
|
881
925
|
this.assertDestinationAvailable(destinationPath);
|
|
@@ -956,7 +1000,7 @@ class AgentVariantsOrchestrator {
|
|
|
956
1000
|
// chosen project. Best-effort: failures here log and continue so
|
|
957
1001
|
// a partial history never blocks the commit handoff.
|
|
958
1002
|
try {
|
|
959
|
-
this.preserveUnchosenVariants({
|
|
1003
|
+
runnablePaths = this.preserveUnchosenVariants({
|
|
960
1004
|
sessionId: args.sessionId,
|
|
961
1005
|
chosenVariantId: args.variantId,
|
|
962
1006
|
destinationPath,
|
|
@@ -1052,7 +1096,7 @@ class AgentVariantsOrchestrator {
|
|
|
1052
1096
|
diff: record.diff,
|
|
1053
1097
|
target: input.target,
|
|
1054
1098
|
changedFilesCount,
|
|
1055
|
-
note:
|
|
1099
|
+
note: "Variant diff applied to the user's working tree (uncommitted).",
|
|
1056
1100
|
};
|
|
1057
1101
|
envelopeDestination = env.projectPath;
|
|
1058
1102
|
}
|
|
@@ -1087,12 +1131,21 @@ class AgentVariantsOrchestrator {
|
|
|
1087
1131
|
projectPath: historyProjectPath,
|
|
1088
1132
|
projectKind: projectContext.kind,
|
|
1089
1133
|
destinationPath: envelopeDestination,
|
|
1134
|
+
runnablePaths,
|
|
1090
1135
|
}).catch((err) => {
|
|
1091
1136
|
log.warn(`persistVariantHistoryAtCommit failed for session ${args.sessionId}`, err);
|
|
1092
1137
|
});
|
|
1093
1138
|
const enqueueResult = this.adapter.enqueue(envelope);
|
|
1094
1139
|
resources.committedVariantIds.add(args.variantId);
|
|
1095
|
-
|
|
1140
|
+
// For fresh `new-project` commits the committed variant IS the user's
|
|
1141
|
+
// final result — there's no dev server to fall back to. Keep the session
|
|
1142
|
+
// active so SSE keeps pushing snapshots with the chosen variant; the UI's
|
|
1143
|
+
// iframe stays on it instead of unmounting to about:blank (which would
|
|
1144
|
+
// surface a misleading "Preview isn't connected" overlay). For diff /
|
|
1145
|
+
// diff-applied commits the user moves on to their own dev server, so
|
|
1146
|
+
// clearing is correct — that's the original behavior preserved below.
|
|
1147
|
+
if (this.activeSessionId === args.sessionId &&
|
|
1148
|
+
payload.kind !== 'project-created') {
|
|
1096
1149
|
this.activeSessionId = null;
|
|
1097
1150
|
}
|
|
1098
1151
|
const dwellMsFromTerminal = resources.terminalAt
|
|
@@ -1558,19 +1611,28 @@ class AgentVariantsOrchestrator {
|
|
|
1558
1611
|
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
1559
1612
|
if (projectContext.kind !== 'existing')
|
|
1560
1613
|
return;
|
|
1561
|
-
let
|
|
1614
|
+
let env;
|
|
1562
1615
|
try {
|
|
1563
|
-
|
|
1564
|
-
projectPath = env.projectPath;
|
|
1616
|
+
env = await this.resolveEnv(args.sessionId);
|
|
1565
1617
|
}
|
|
1566
1618
|
catch (err) {
|
|
1567
1619
|
log.warn(`persistCompletedExistingVariant: resolveEnv failed for ${args.sessionId}`, err);
|
|
1568
1620
|
return;
|
|
1569
1621
|
}
|
|
1622
|
+
const record = this.resources.get(args.sessionId)?.worktrees.get(args.workItemId);
|
|
1623
|
+
let sourceDir;
|
|
1624
|
+
if (env.framework === 'static' && record) {
|
|
1625
|
+
try {
|
|
1626
|
+
sourceDir = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
|
|
1627
|
+
}
|
|
1628
|
+
catch (err) {
|
|
1629
|
+
log.warn(`persistCompletedExistingVariant: static snapshot path failed for ${args.sessionId}/${args.workItemId}`, err);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1570
1632
|
const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
1571
1633
|
const sessionPrompt = this.store.getPrompt(args.sessionId);
|
|
1572
1634
|
await this.variantHistory.persistVariant({
|
|
1573
|
-
projectPath,
|
|
1635
|
+
projectPath: env.projectPath,
|
|
1574
1636
|
sessionId: args.sessionId,
|
|
1575
1637
|
variantId: args.workItemId,
|
|
1576
1638
|
label: input.briefLabel,
|
|
@@ -1578,6 +1640,10 @@ class AgentVariantsOrchestrator {
|
|
|
1578
1640
|
sessionPrompt,
|
|
1579
1641
|
kind: 'diff',
|
|
1580
1642
|
diff: args.diff,
|
|
1643
|
+
sourceDir,
|
|
1644
|
+
preview: sourceDir
|
|
1645
|
+
? { kind: 'static', entryPath: 'index.html' }
|
|
1646
|
+
: { kind: 'none', reason: 'diff_only' },
|
|
1581
1647
|
changedFilesCount: countDiffFiles(args.diff),
|
|
1582
1648
|
projectKind: 'existing',
|
|
1583
1649
|
});
|
|
@@ -1634,9 +1700,17 @@ class AgentVariantsOrchestrator {
|
|
|
1634
1700
|
// is implementation-detail staging that we copy alongside it. Hardcode
|
|
1635
1701
|
// the count so the history row matches what `commit_variant` reports
|
|
1636
1702
|
// (always 1) instead of double-counting the brief.
|
|
1637
|
-
const changedFilesCount = staticPreview
|
|
1638
|
-
|
|
1639
|
-
:
|
|
1703
|
+
const changedFilesCount = staticPreview ? 1 : countWorktreeFiles(sourceDir);
|
|
1704
|
+
const preview = staticPreview
|
|
1705
|
+
? { kind: 'static', entryPath: 'index.html' }
|
|
1706
|
+
: {
|
|
1707
|
+
kind: 'dev-server',
|
|
1708
|
+
command: {
|
|
1709
|
+
cwd: '.',
|
|
1710
|
+
packageManager: 'npm',
|
|
1711
|
+
script: 'dev',
|
|
1712
|
+
},
|
|
1713
|
+
};
|
|
1640
1714
|
try {
|
|
1641
1715
|
await this.variantHistory.persistVariant({
|
|
1642
1716
|
projectPath: historyProjectPath,
|
|
@@ -1647,6 +1721,7 @@ class AgentVariantsOrchestrator {
|
|
|
1647
1721
|
sessionPrompt,
|
|
1648
1722
|
kind: 'project-created',
|
|
1649
1723
|
sourceDir,
|
|
1724
|
+
preview,
|
|
1650
1725
|
changedFilesCount,
|
|
1651
1726
|
projectKind: 'fresh',
|
|
1652
1727
|
designMarkdown: designArtifact?.markdown,
|
|
@@ -1688,6 +1763,7 @@ class AgentVariantsOrchestrator {
|
|
|
1688
1763
|
variantId: workItemId,
|
|
1689
1764
|
status,
|
|
1690
1765
|
destinationPath: isChosen ? args.destinationPath : undefined,
|
|
1766
|
+
runnablePath: args.runnablePaths?.get(workItemId),
|
|
1691
1767
|
});
|
|
1692
1768
|
}
|
|
1693
1769
|
}
|
|
@@ -1807,8 +1883,9 @@ class AgentVariantsOrchestrator {
|
|
|
1807
1883
|
*/
|
|
1808
1884
|
preserveUnchosenVariants(args) {
|
|
1809
1885
|
const resources = this.resources.get(args.sessionId);
|
|
1886
|
+
const runnablePaths = new Map();
|
|
1810
1887
|
if (!resources)
|
|
1811
|
-
return;
|
|
1888
|
+
return runnablePaths;
|
|
1812
1889
|
const destinationParent = path_1.default.dirname(args.destinationPath);
|
|
1813
1890
|
const projectSlug = path_1.default.basename(args.destinationPath);
|
|
1814
1891
|
const historyDir = (0, createProjectArtifacts_1.createVariantsHistoryPath)(destinationParent, projectSlug);
|
|
@@ -1827,6 +1904,7 @@ class AgentVariantsOrchestrator {
|
|
|
1827
1904
|
const folderName = `${numericPrefix}-${slug}`;
|
|
1828
1905
|
if (variant.workItemId === args.chosenVariantId) {
|
|
1829
1906
|
chosenSlug = slug;
|
|
1907
|
+
runnablePaths.set(variant.workItemId, projectSlug);
|
|
1830
1908
|
manifestEntries.push({
|
|
1831
1909
|
variantId: variant.workItemId,
|
|
1832
1910
|
label,
|
|
@@ -1892,6 +1970,7 @@ class AgentVariantsOrchestrator {
|
|
|
1892
1970
|
// Update the in-memory record so teardown doesn't try to operate on
|
|
1893
1971
|
// the stale path.
|
|
1894
1972
|
record.worktreePath = newPath;
|
|
1973
|
+
runnablePaths.set(variant.workItemId, `${path_1.default.basename(historyDir)}/${folderName}`);
|
|
1895
1974
|
manifestEntries.push({
|
|
1896
1975
|
variantId: variant.workItemId,
|
|
1897
1976
|
label,
|
|
@@ -1926,6 +2005,7 @@ class AgentVariantsOrchestrator {
|
|
|
1926
2005
|
log.warn(`Writing variants history manifest failed for ${historyDir}`, err);
|
|
1927
2006
|
}
|
|
1928
2007
|
resources.vitePreservedSiblings = true;
|
|
2008
|
+
return runnablePaths;
|
|
1929
2009
|
}
|
|
1930
2010
|
/**
|
|
1931
2011
|
* Rename `sourceWorktreePath` into `destinationPath`, then replace the
|
|
@@ -2323,15 +2403,26 @@ const toActiveProjectContext = (projectContext) => {
|
|
|
2323
2403
|
* Resolve the user-facing supporting artifacts for a session.
|
|
2324
2404
|
*
|
|
2325
2405
|
* For 0→1 (`fresh`) sessions with a populated `designContext`, each slot is
|
|
2326
|
-
* turned into a `design_context` artifact
|
|
2327
|
-
*
|
|
2406
|
+
* turned into a `design_context` artifact that links to Rivet-hosted
|
|
2407
|
+
* DESIGN.md views:
|
|
2328
2408
|
* - `slug` entries resolve bundled catalog markdown via the design catalog.
|
|
2329
2409
|
* - `markdown` entries (Agent Browser / inspiration extractor output) carry
|
|
2330
|
-
* their stored markdown
|
|
2410
|
+
* their stored markdown when the linked route is requested.
|
|
2331
2411
|
* Slots whose markdown can't be resolved are skipped so the UI never renders
|
|
2332
2412
|
* a metadata-only DESIGN.md row.
|
|
2333
2413
|
*/
|
|
2334
|
-
const buildSessionArtifacts = (projectContext) => {
|
|
2414
|
+
const buildSessionArtifacts = (sessionId, projectContext) => {
|
|
2415
|
+
const artifacts = buildResolvedDesignContextArtifacts(projectContext);
|
|
2416
|
+
return artifacts.map(({ content: _content, ...artifact }) => ({
|
|
2417
|
+
...artifact,
|
|
2418
|
+
rawUrl: buildDesignContextRawUrl(sessionId, artifact.id),
|
|
2419
|
+
viewUrl: buildDesignContextViewUrl(sessionId, artifact.id),
|
|
2420
|
+
}));
|
|
2421
|
+
};
|
|
2422
|
+
const findDesignContextArtifact = (projectContext, artifactId) => {
|
|
2423
|
+
return buildResolvedDesignContextArtifacts(projectContext).find((artifact) => artifact.id === artifactId);
|
|
2424
|
+
};
|
|
2425
|
+
const buildResolvedDesignContextArtifacts = (projectContext) => {
|
|
2335
2426
|
if (projectContext.kind !== 'fresh')
|
|
2336
2427
|
return [];
|
|
2337
2428
|
const designContext = projectContext.designContext;
|
|
@@ -2350,7 +2441,9 @@ const buildSessionArtifacts = (projectContext) => {
|
|
|
2350
2441
|
id: `design_context:${slot}:${entry.slug}`,
|
|
2351
2442
|
kind: 'design_context',
|
|
2352
2443
|
label: catalogEntry?.name ?? entry.slug,
|
|
2353
|
-
...(catalogEntry?.description
|
|
2444
|
+
...(catalogEntry?.description
|
|
2445
|
+
? { summary: catalogEntry.description }
|
|
2446
|
+
: {}),
|
|
2354
2447
|
status: 'ready',
|
|
2355
2448
|
source: 'static',
|
|
2356
2449
|
contentType: 'text/markdown',
|
|
@@ -2382,8 +2475,6 @@ const buildSessionArtifacts = (projectContext) => {
|
|
|
2382
2475
|
});
|
|
2383
2476
|
};
|
|
2384
2477
|
const addDesignContextArtifact = (artifactsByContent, artifact) => {
|
|
2385
|
-
if (!artifact.content)
|
|
2386
|
-
return;
|
|
2387
2478
|
const existing = artifactsByContent.get(artifact.content);
|
|
2388
2479
|
if (existing) {
|
|
2389
2480
|
existing.usedByVariantCount += 1;
|
|
@@ -2394,6 +2485,310 @@ const addDesignContextArtifact = (artifactsByContent, artifact) => {
|
|
|
2394
2485
|
usedByVariantCount: 1,
|
|
2395
2486
|
});
|
|
2396
2487
|
};
|
|
2488
|
+
const buildDesignContextRawUrl = (sessionId, artifactId) => {
|
|
2489
|
+
return `/api/variants/${encodeURIComponent(sessionId)}/${DESIGN_CONTEXT_ROUTE_SEGMENT}/${encodeURIComponent(artifactId)}`;
|
|
2490
|
+
};
|
|
2491
|
+
const buildDesignContextViewUrl = (sessionId, artifactId) => {
|
|
2492
|
+
return `${buildDesignContextRawUrl(sessionId, artifactId)}/${DESIGN_CONTEXT_VIEW_SEGMENT}`;
|
|
2493
|
+
};
|
|
2494
|
+
const buildDesignContextViewerDocument = (artifact) => {
|
|
2495
|
+
const title = `${artifact.label} DESIGN.md`;
|
|
2496
|
+
const visualHtml = renderDesignMarkdown(artifact.content);
|
|
2497
|
+
const rawMarkdown = escapeHtml(artifact.content);
|
|
2498
|
+
return `<!doctype html>
|
|
2499
|
+
<html lang="en">
|
|
2500
|
+
<head>
|
|
2501
|
+
<meta charset="utf-8" />
|
|
2502
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2503
|
+
<title>${escapeHtml(title)}</title>
|
|
2504
|
+
<style>
|
|
2505
|
+
:root {
|
|
2506
|
+
color-scheme: light;
|
|
2507
|
+
--ink: #201b16;
|
|
2508
|
+
--muted: #75695e;
|
|
2509
|
+
--paper: #fbf7ef;
|
|
2510
|
+
--panel: #fffdf8;
|
|
2511
|
+
--rule: #eadfcf;
|
|
2512
|
+
--accent: #e45d2f;
|
|
2513
|
+
--accent-soft: #ffe0d3;
|
|
2514
|
+
--code: #2a2521;
|
|
2515
|
+
}
|
|
2516
|
+
* { box-sizing: border-box; }
|
|
2517
|
+
body {
|
|
2518
|
+
margin: 0;
|
|
2519
|
+
min-height: 100vh;
|
|
2520
|
+
background:
|
|
2521
|
+
radial-gradient(circle at top left, rgba(228, 93, 47, 0.18), transparent 36rem),
|
|
2522
|
+
linear-gradient(135deg, #fbf7ef 0%, #f2eadc 100%);
|
|
2523
|
+
color: var(--ink);
|
|
2524
|
+
font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif;
|
|
2525
|
+
}
|
|
2526
|
+
main {
|
|
2527
|
+
width: min(1440px, calc(100vw - 40px));
|
|
2528
|
+
margin: 0 auto;
|
|
2529
|
+
padding: 40px 0;
|
|
2530
|
+
}
|
|
2531
|
+
header {
|
|
2532
|
+
display: flex;
|
|
2533
|
+
align-items: center;
|
|
2534
|
+
justify-content: space-between;
|
|
2535
|
+
gap: 24px;
|
|
2536
|
+
margin-bottom: 24px;
|
|
2537
|
+
border-bottom: 1px solid var(--rule);
|
|
2538
|
+
padding-bottom: 18px;
|
|
2539
|
+
}
|
|
2540
|
+
h1 {
|
|
2541
|
+
margin: 0;
|
|
2542
|
+
max-width: 780px;
|
|
2543
|
+
font-size: clamp(2rem, 5vw, 4.5rem);
|
|
2544
|
+
letter-spacing: -0.06em;
|
|
2545
|
+
line-height: 0.92;
|
|
2546
|
+
}
|
|
2547
|
+
.mode-input {
|
|
2548
|
+
position: absolute;
|
|
2549
|
+
opacity: 0;
|
|
2550
|
+
pointer-events: none;
|
|
2551
|
+
}
|
|
2552
|
+
.mode-toggle {
|
|
2553
|
+
display: inline-flex;
|
|
2554
|
+
flex-shrink: 0;
|
|
2555
|
+
gap: 4px;
|
|
2556
|
+
border: 1px solid var(--rule);
|
|
2557
|
+
border-radius: 999px;
|
|
2558
|
+
background: rgba(255, 253, 248, 0.72);
|
|
2559
|
+
padding: 4px;
|
|
2560
|
+
box-shadow: 0 12px 30px rgba(61, 44, 26, 0.08);
|
|
2561
|
+
}
|
|
2562
|
+
.mode-toggle label {
|
|
2563
|
+
position: relative;
|
|
2564
|
+
cursor: pointer;
|
|
2565
|
+
border-radius: 999px;
|
|
2566
|
+
padding: 8px 14px;
|
|
2567
|
+
color: var(--muted);
|
|
2568
|
+
font: 600 0.74rem/1.2 ui-sans-serif, system-ui, sans-serif;
|
|
2569
|
+
letter-spacing: 0.12em;
|
|
2570
|
+
text-transform: uppercase;
|
|
2571
|
+
transition:
|
|
2572
|
+
background 160ms ease,
|
|
2573
|
+
color 160ms ease;
|
|
2574
|
+
}
|
|
2575
|
+
.mode-toggle label:has(input:checked) {
|
|
2576
|
+
background: var(--ink);
|
|
2577
|
+
color: var(--paper);
|
|
2578
|
+
}
|
|
2579
|
+
.viewer {
|
|
2580
|
+
display: block;
|
|
2581
|
+
}
|
|
2582
|
+
.viewer-panel {
|
|
2583
|
+
display: none;
|
|
2584
|
+
}
|
|
2585
|
+
main:has(#designmd-visual-mode:checked) .visual-panel,
|
|
2586
|
+
main:has(#designmd-raw-mode:checked) .raw-panel {
|
|
2587
|
+
display: block;
|
|
2588
|
+
}
|
|
2589
|
+
.panel {
|
|
2590
|
+
overflow: hidden;
|
|
2591
|
+
border: 1px solid var(--rule);
|
|
2592
|
+
border-radius: 18px;
|
|
2593
|
+
background: color-mix(in srgb, var(--panel) 92%, white);
|
|
2594
|
+
box-shadow: 0 20px 60px rgba(61, 44, 26, 0.12);
|
|
2595
|
+
}
|
|
2596
|
+
.panel-title {
|
|
2597
|
+
display: flex;
|
|
2598
|
+
align-items: center;
|
|
2599
|
+
justify-content: space-between;
|
|
2600
|
+
border-bottom: 1px solid var(--rule);
|
|
2601
|
+
padding: 12px 16px;
|
|
2602
|
+
color: var(--muted);
|
|
2603
|
+
font: 700 0.72rem/1 ui-sans-serif, system-ui, sans-serif;
|
|
2604
|
+
letter-spacing: 0.12em;
|
|
2605
|
+
text-transform: uppercase;
|
|
2606
|
+
}
|
|
2607
|
+
.visual {
|
|
2608
|
+
padding: 24px;
|
|
2609
|
+
}
|
|
2610
|
+
.visual h1,
|
|
2611
|
+
.visual h2,
|
|
2612
|
+
.visual h3 {
|
|
2613
|
+
margin: 1.3em 0 0.45em;
|
|
2614
|
+
letter-spacing: -0.04em;
|
|
2615
|
+
line-height: 1;
|
|
2616
|
+
}
|
|
2617
|
+
.visual h1:first-child,
|
|
2618
|
+
.visual h2:first-child,
|
|
2619
|
+
.visual h3:first-child {
|
|
2620
|
+
margin-top: 0;
|
|
2621
|
+
}
|
|
2622
|
+
.visual h1 { font-size: 2.25rem; }
|
|
2623
|
+
.visual h2 { font-size: 1.6rem; }
|
|
2624
|
+
.visual h3 { font-size: 1.18rem; }
|
|
2625
|
+
.visual p,
|
|
2626
|
+
.visual li,
|
|
2627
|
+
.visual blockquote {
|
|
2628
|
+
color: #3d342c;
|
|
2629
|
+
font-size: 1rem;
|
|
2630
|
+
line-height: 1.62;
|
|
2631
|
+
}
|
|
2632
|
+
.visual ul {
|
|
2633
|
+
display: grid;
|
|
2634
|
+
gap: 8px;
|
|
2635
|
+
padding-left: 1.1rem;
|
|
2636
|
+
}
|
|
2637
|
+
.visual blockquote {
|
|
2638
|
+
margin: 18px 0;
|
|
2639
|
+
border-left: 4px solid var(--accent);
|
|
2640
|
+
padding-left: 14px;
|
|
2641
|
+
color: var(--muted);
|
|
2642
|
+
}
|
|
2643
|
+
.visual code {
|
|
2644
|
+
border-radius: 6px;
|
|
2645
|
+
background: var(--accent-soft);
|
|
2646
|
+
padding: 0.12rem 0.34rem;
|
|
2647
|
+
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
2648
|
+
font-size: 0.92em;
|
|
2649
|
+
}
|
|
2650
|
+
pre {
|
|
2651
|
+
margin: 0;
|
|
2652
|
+
max-height: calc(100vh - 190px);
|
|
2653
|
+
overflow: auto;
|
|
2654
|
+
background: #181512;
|
|
2655
|
+
color: #f9ead7;
|
|
2656
|
+
padding: 20px;
|
|
2657
|
+
font: 0.78rem/1.55 ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
|
2658
|
+
white-space: pre-wrap;
|
|
2659
|
+
word-break: break-word;
|
|
2660
|
+
}
|
|
2661
|
+
@media (max-width: 900px) {
|
|
2662
|
+
main { width: min(100vw - 24px, 760px); padding: 24px 0; }
|
|
2663
|
+
header { align-items: start; flex-direction: column; }
|
|
2664
|
+
pre { max-height: 520px; }
|
|
2665
|
+
}
|
|
2666
|
+
</style>
|
|
2667
|
+
</head>
|
|
2668
|
+
<body>
|
|
2669
|
+
<main>
|
|
2670
|
+
<header>
|
|
2671
|
+
<h1>${escapeHtml(title)}</h1>
|
|
2672
|
+
<div class="mode-toggle" aria-label="DesignMD view mode">
|
|
2673
|
+
<label>
|
|
2674
|
+
<input class="mode-input" type="radio" name="designmd-view-mode" id="designmd-visual-mode" checked />
|
|
2675
|
+
<span>Visual</span>
|
|
2676
|
+
</label>
|
|
2677
|
+
<label>
|
|
2678
|
+
<input class="mode-input" type="radio" name="designmd-view-mode" id="designmd-raw-mode" />
|
|
2679
|
+
<span>Raw</span>
|
|
2680
|
+
</label>
|
|
2681
|
+
</div>
|
|
2682
|
+
</header>
|
|
2683
|
+
<section class="viewer" aria-label="DesignMD artifact">
|
|
2684
|
+
<article class="panel viewer-panel visual-panel" id="designmd-visual">
|
|
2685
|
+
<div class="panel-title"><span>Visual</span><span>Rendered DESIGN.md</span></div>
|
|
2686
|
+
<div class="visual">${visualHtml}</div>
|
|
2687
|
+
</article>
|
|
2688
|
+
<article class="panel viewer-panel raw-panel" id="designmd-raw">
|
|
2689
|
+
<div class="panel-title"><span>Raw</span><span>Markdown source</span></div>
|
|
2690
|
+
<pre>${rawMarkdown}</pre>
|
|
2691
|
+
</article>
|
|
2692
|
+
</section>
|
|
2693
|
+
</main>
|
|
2694
|
+
</body>
|
|
2695
|
+
</html>`;
|
|
2696
|
+
};
|
|
2697
|
+
const renderDesignMarkdown = (markdown) => {
|
|
2698
|
+
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
2699
|
+
const output = [];
|
|
2700
|
+
const paragraph = [];
|
|
2701
|
+
const listItems = [];
|
|
2702
|
+
const codeLines = [];
|
|
2703
|
+
let isCodeBlock = false;
|
|
2704
|
+
const flushParagraph = () => {
|
|
2705
|
+
if (paragraph.length === 0)
|
|
2706
|
+
return;
|
|
2707
|
+
output.push(`<p>${renderInlineMarkdown(paragraph.join(' '))}</p>`);
|
|
2708
|
+
paragraph.length = 0;
|
|
2709
|
+
};
|
|
2710
|
+
const flushList = () => {
|
|
2711
|
+
if (listItems.length === 0)
|
|
2712
|
+
return;
|
|
2713
|
+
output.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join('')}</ul>`);
|
|
2714
|
+
listItems.length = 0;
|
|
2715
|
+
};
|
|
2716
|
+
const flushCode = () => {
|
|
2717
|
+
if (codeLines.length === 0)
|
|
2718
|
+
return;
|
|
2719
|
+
output.push(`<pre>${escapeHtml(codeLines.join('\n'))}</pre>`);
|
|
2720
|
+
codeLines.length = 0;
|
|
2721
|
+
};
|
|
2722
|
+
lines.forEach((line) => {
|
|
2723
|
+
if (line.trim().startsWith('```')) {
|
|
2724
|
+
if (isCodeBlock) {
|
|
2725
|
+
flushCode();
|
|
2726
|
+
isCodeBlock = false;
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
flushParagraph();
|
|
2730
|
+
flushList();
|
|
2731
|
+
isCodeBlock = true;
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
if (isCodeBlock) {
|
|
2735
|
+
codeLines.push(line);
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const trimmed = line.trim();
|
|
2739
|
+
if (!trimmed) {
|
|
2740
|
+
flushParagraph();
|
|
2741
|
+
flushList();
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (/^---+$/.test(trimmed)) {
|
|
2745
|
+
flushParagraph();
|
|
2746
|
+
flushList();
|
|
2747
|
+
output.push('<hr />');
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
2751
|
+
if (headingMatch) {
|
|
2752
|
+
flushParagraph();
|
|
2753
|
+
flushList();
|
|
2754
|
+
const level = Math.min(headingMatch[1].length, 3);
|
|
2755
|
+
output.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`);
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
const listMatch = trimmed.match(/^[-*]\s+(.+)$/);
|
|
2759
|
+
if (listMatch) {
|
|
2760
|
+
flushParagraph();
|
|
2761
|
+
listItems.push(listMatch[1]);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
const quoteMatch = trimmed.match(/^>\s?(.+)$/);
|
|
2765
|
+
if (quoteMatch) {
|
|
2766
|
+
flushParagraph();
|
|
2767
|
+
flushList();
|
|
2768
|
+
output.push(`<blockquote>${renderInlineMarkdown(quoteMatch[1])}</blockquote>`);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
paragraph.push(trimmed);
|
|
2772
|
+
});
|
|
2773
|
+
flushCode();
|
|
2774
|
+
flushParagraph();
|
|
2775
|
+
flushList();
|
|
2776
|
+
return output.join('\n');
|
|
2777
|
+
};
|
|
2778
|
+
const renderInlineMarkdown = (value) => {
|
|
2779
|
+
return escapeHtml(value)
|
|
2780
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
2781
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
2782
|
+
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
2783
|
+
};
|
|
2784
|
+
const escapeHtml = (value) => {
|
|
2785
|
+
return value
|
|
2786
|
+
.replace(/&/g, '&')
|
|
2787
|
+
.replace(/</g, '<')
|
|
2788
|
+
.replace(/>/g, '>')
|
|
2789
|
+
.replace(/"/g, '"')
|
|
2790
|
+
.replace(/'/g, ''');
|
|
2791
|
+
};
|
|
2397
2792
|
/**
|
|
2398
2793
|
* Resolve a per-variant design context entry into the raw DESIGN.md markdown
|
|
2399
2794
|
* the worktree scaffolder writes, plus a small `designSource` descriptor for
|