pmx-canvas 0.1.18 → 0.1.20
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/CHANGELOG.md +128 -0
- package/Readme.md +19 -6
- package/dist/canvas/global.css +35 -2
- package/dist/canvas/index.js +70 -69
- package/dist/json-render/index.js +109 -109
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/icons.d.ts +2 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/types.d.ts +2 -1
- package/dist/types/json-render/charts/components.d.ts +5 -1
- package/dist/types/json-render/renderer/index.d.ts +1 -0
- package/dist/types/json-render/server.d.ts +1 -0
- package/dist/types/mcp/canvas-access.d.ts +3 -0
- package/dist/types/server/canvas-operations.d.ts +4 -0
- package/dist/types/server/canvas-schema.d.ts +19 -3
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +8 -2
- package/dist/types/server/html-primitives.d.ts +34 -0
- package/dist/types/server/index.d.ts +19 -0
- package/docs/RELEASE.md +153 -0
- package/docs/bun-webview-integration.md +296 -0
- package/docs/cli.md +143 -0
- package/docs/evals/e2e-cli-coverage.md +61 -0
- package/docs/http-api.md +201 -0
- package/docs/mcp.md +137 -0
- package/docs/node-types.md +272 -0
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/plan-001-semantic-watch-mvp.md +335 -0
- package/docs/plans/plan-002-human-attention-layer-design-spec.md +679 -0
- package/docs/plans/plan-003-human-attention-layer-implementation-plan.md +572 -0
- package/docs/reactive-canvas-proposal.md +578 -0
- package/docs/release-review-0.1.0.md +38 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/demo-workbench-dark.png +0 -0
- package/docs/screenshots/demo-workbench-light.png +0 -0
- package/docs/screenshots/welcome-dark.png +0 -0
- package/docs/screenshots/welcome-light.png +0 -0
- package/docs/sdk.md +103 -0
- package/package.json +2 -1
- package/skills/pmx-canvas/SKILL.md +8 -0
- package/src/cli/agent.ts +167 -5
- package/src/client/App.tsx +20 -1
- package/src/client/canvas/AnnotationLayer.tsx +33 -12
- package/src/client/canvas/CanvasViewport.tsx +88 -7
- package/src/client/canvas/CommandPalette.tsx +1 -1
- package/src/client/canvas/ContextMenu.tsx +2 -2
- package/src/client/canvas/ExpandedNodeOverlay.tsx +7 -1
- package/src/client/icons.tsx +13 -0
- package/src/client/nodes/McpAppNode.tsx +12 -4
- package/src/client/state/canvas-store.ts +15 -5
- package/src/client/state/sse-bridge.ts +4 -3
- package/src/client/theme/global.css +35 -2
- package/src/client/types.ts +2 -1
- package/src/json-render/charts/components.tsx +41 -7
- package/src/json-render/charts/extra-components.tsx +13 -12
- package/src/json-render/renderer/index.tsx +1 -0
- package/src/json-render/server.ts +3 -1
- package/src/mcp/canvas-access.ts +25 -0
- package/src/mcp/server.ts +85 -27
- package/src/server/agent-context.ts +17 -0
- package/src/server/canvas-operations.ts +91 -38
- package/src/server/canvas-schema.ts +83 -3
- package/src/server/canvas-serialization.ts +9 -2
- package/src/server/canvas-state.ts +27 -9
- package/src/server/demo-state.json +1143 -0
- package/src/server/demo.ts +25 -777
- package/src/server/html-primitives.ts +990 -0
- package/src/server/index.ts +43 -2
- package/src/server/server.ts +140 -14
- package/src/server/spatial-analysis.ts +3 -3
package/src/server/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './co
|
|
|
10
10
|
import {
|
|
11
11
|
addCanvasNode,
|
|
12
12
|
addCanvasEdge,
|
|
13
|
+
MARKDOWN_NODE_DEFAULT_SIZE,
|
|
13
14
|
applyCanvasNodeUpdates,
|
|
14
15
|
arrangeCanvasNodes,
|
|
15
16
|
clearCanvas,
|
|
@@ -39,6 +40,8 @@ import {
|
|
|
39
40
|
} from './canvas-operations.js';
|
|
40
41
|
import { validateCanvasLayout } from './canvas-validation.js';
|
|
41
42
|
import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
43
|
+
import { buildHtmlPrimitive, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
|
|
44
|
+
import type { HtmlPrimitiveKind } from './html-primitives.js';
|
|
42
45
|
import {
|
|
43
46
|
buildWebArtifactOnCanvas,
|
|
44
47
|
type WebArtifactBuildInput,
|
|
@@ -178,8 +181,8 @@ export class PmxCanvas extends EventEmitter {
|
|
|
178
181
|
}
|
|
179
182
|
const { id, needsCodeGraphRecompute } = addCanvasNode({
|
|
180
183
|
...input,
|
|
181
|
-
defaultWidth: 360,
|
|
182
|
-
defaultHeight: 200,
|
|
184
|
+
defaultWidth: input.type === 'markdown' ? MARKDOWN_NODE_DEFAULT_SIZE.width : 360,
|
|
185
|
+
defaultHeight: input.type === 'markdown' ? MARKDOWN_NODE_DEFAULT_SIZE.height : 200,
|
|
183
186
|
fileMode: 'path',
|
|
184
187
|
...(input.strictSize ? { strictSize: true } : {}),
|
|
185
188
|
});
|
|
@@ -666,6 +669,42 @@ export class PmxCanvas extends EventEmitter {
|
|
|
666
669
|
return id;
|
|
667
670
|
}
|
|
668
671
|
|
|
672
|
+
addHtmlPrimitive(input: {
|
|
673
|
+
kind: HtmlPrimitiveKind;
|
|
674
|
+
title?: string;
|
|
675
|
+
data?: Record<string, unknown>;
|
|
676
|
+
x?: number;
|
|
677
|
+
y?: number;
|
|
678
|
+
width?: number;
|
|
679
|
+
height?: number;
|
|
680
|
+
strictSize?: boolean;
|
|
681
|
+
}): { id: string; kind: HtmlPrimitiveKind; title: string; htmlBytes: number } {
|
|
682
|
+
const built = buildHtmlPrimitive({
|
|
683
|
+
kind: input.kind,
|
|
684
|
+
...(typeof input.title === 'string' ? { title: input.title } : {}),
|
|
685
|
+
...(input.data ? { data: input.data } : {}),
|
|
686
|
+
});
|
|
687
|
+
const { id } = addCanvasNode({
|
|
688
|
+
type: 'html',
|
|
689
|
+
title: built.title,
|
|
690
|
+
data: {
|
|
691
|
+
html: built.html,
|
|
692
|
+
htmlPrimitive: built.kind,
|
|
693
|
+
primitiveData: built.data,
|
|
694
|
+
description: built.summary,
|
|
695
|
+
},
|
|
696
|
+
...(typeof input.x === 'number' ? { x: input.x } : {}),
|
|
697
|
+
...(typeof input.y === 'number' ? { y: input.y } : {}),
|
|
698
|
+
...(typeof input.width === 'number' ? { width: input.width } : {}),
|
|
699
|
+
...(typeof input.height === 'number' ? { height: input.height } : {}),
|
|
700
|
+
...(input.strictSize ? { strictSize: true } : {}),
|
|
701
|
+
defaultWidth: built.defaultSize.width,
|
|
702
|
+
defaultHeight: built.defaultSize.height,
|
|
703
|
+
});
|
|
704
|
+
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
705
|
+
return { id, kind: built.kind, title: built.title, htmlBytes: Buffer.byteLength(built.html, 'utf-8') };
|
|
706
|
+
}
|
|
707
|
+
|
|
669
708
|
addGraphNode(input: GraphNodeInput): { id: string; url: string; spec: JsonRenderSpec } {
|
|
670
709
|
const result = createCanvasGraphNode(input);
|
|
671
710
|
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
@@ -747,6 +786,7 @@ export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo }
|
|
|
747
786
|
export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
|
|
748
787
|
export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
|
|
749
788
|
export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
789
|
+
export { buildHtmlPrimitive, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
|
|
750
790
|
export {
|
|
751
791
|
buildWebArtifactOnCanvas,
|
|
752
792
|
executeWebArtifactBuild,
|
|
@@ -771,4 +811,5 @@ export type {
|
|
|
771
811
|
WebArtifactCanvasOpenResult,
|
|
772
812
|
} from './web-artifacts.js';
|
|
773
813
|
export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
|
|
814
|
+
export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
|
|
774
815
|
export { traceManager } from './trace-manager.js';
|
package/src/server/server.ts
CHANGED
|
@@ -79,6 +79,7 @@ import { validateLocalImageFile } from './image-source.js';
|
|
|
79
79
|
import {
|
|
80
80
|
addCanvasNode,
|
|
81
81
|
addCanvasEdge,
|
|
82
|
+
MARKDOWN_NODE_DEFAULT_SIZE,
|
|
82
83
|
applyCanvasNodeUpdates,
|
|
83
84
|
buildStructuredNodeUpdate,
|
|
84
85
|
arrangeCanvasNodes,
|
|
@@ -110,6 +111,7 @@ import {
|
|
|
110
111
|
} from './canvas-operations.js';
|
|
111
112
|
import { validateCanvasLayout } from './canvas-validation.js';
|
|
112
113
|
import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
114
|
+
import { buildHtmlPrimitive, isHtmlPrimitiveKind } from './html-primitives.js';
|
|
113
115
|
import {
|
|
114
116
|
EXCALIDRAW_READ_CHECKPOINT_TOOL,
|
|
115
117
|
EXCALIDRAW_SAVE_CHECKPOINT_TOOL,
|
|
@@ -129,6 +131,7 @@ import {
|
|
|
129
131
|
WEBPAGE_NODE_DEFAULT_SIZE,
|
|
130
132
|
normalizeWebpageUrl,
|
|
131
133
|
} from './webpage-node.js';
|
|
134
|
+
import type { JsonRenderSpec } from '../json-render/server.js';
|
|
132
135
|
|
|
133
136
|
const DEFAULT_HOST = '127.0.0.1';
|
|
134
137
|
const DEFAULT_PORT = 4313;
|
|
@@ -147,6 +150,40 @@ const canvasThemeSetting = (['dark', 'light', 'high-contrast'].includes(process.
|
|
|
147
150
|
: 'dark');
|
|
148
151
|
let lastWorkbenchContextCardsEnvelope: Record<string, unknown> | null = null;
|
|
149
152
|
|
|
153
|
+
function normalizeGraphViewerSpec(
|
|
154
|
+
node: { type: string; data: Record<string, unknown> },
|
|
155
|
+
spec: JsonRenderSpec,
|
|
156
|
+
display: string | null,
|
|
157
|
+
): JsonRenderSpec {
|
|
158
|
+
if (node.type !== 'graph') return spec;
|
|
159
|
+
const graphConfig = node.data.graphConfig;
|
|
160
|
+
if (
|
|
161
|
+
display !== 'expanded' &&
|
|
162
|
+
graphConfig &&
|
|
163
|
+
typeof graphConfig === 'object' &&
|
|
164
|
+
typeof (graphConfig as Record<string, unknown>).height === 'number'
|
|
165
|
+
) {
|
|
166
|
+
return spec;
|
|
167
|
+
}
|
|
168
|
+
const chart = spec.elements.chart;
|
|
169
|
+
if (!chart || typeof chart !== 'object') return spec;
|
|
170
|
+
const chartRecord = chart as Record<string, unknown>;
|
|
171
|
+
const props = chartRecord.props;
|
|
172
|
+
if (!props || typeof props !== 'object' || typeof (props as Record<string, unknown>).height !== 'number') return spec;
|
|
173
|
+
const nextProps = { ...(props as Record<string, unknown>) };
|
|
174
|
+
delete nextProps.height;
|
|
175
|
+
return {
|
|
176
|
+
...spec,
|
|
177
|
+
elements: {
|
|
178
|
+
...spec.elements,
|
|
179
|
+
chart: {
|
|
180
|
+
...chartRecord,
|
|
181
|
+
props: nextProps,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
150
187
|
export interface PrimaryWorkbenchEventPayload {
|
|
151
188
|
[key: string]: unknown;
|
|
152
189
|
}
|
|
@@ -1238,6 +1275,15 @@ function annotationBounds(points: CanvasAnnotation['points']): CanvasAnnotation[
|
|
|
1238
1275
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1239
1276
|
}
|
|
1240
1277
|
|
|
1278
|
+
function textAnnotationBounds(point: CanvasAnnotation['points'][number], text: string, width: number): CanvasAnnotation['bounds'] {
|
|
1279
|
+
return {
|
|
1280
|
+
x: point.x,
|
|
1281
|
+
y: point.y - width,
|
|
1282
|
+
width: Math.max(width, text.length * width * 0.62),
|
|
1283
|
+
height: width * 1.2,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1241
1287
|
function parseAnnotationPoints(value: unknown): CanvasAnnotation['points'] {
|
|
1242
1288
|
if (!Array.isArray(value)) return [];
|
|
1243
1289
|
return value
|
|
@@ -1253,31 +1299,41 @@ function parseAnnotationPoints(value: unknown): CanvasAnnotation['points'] {
|
|
|
1253
1299
|
|
|
1254
1300
|
async function handleCanvasAddAnnotation(req: Request): Promise<Response> {
|
|
1255
1301
|
const body = await readJson(req);
|
|
1302
|
+
const type = body.type === 'text' ? 'text' : 'freehand';
|
|
1256
1303
|
const points = parseAnnotationPoints(body.points);
|
|
1257
|
-
if (points.length < 2) {
|
|
1258
|
-
return responseJson({ ok: false, error: 'Annotation requires at least two valid points.' }, 400);
|
|
1304
|
+
if (points.length < (type === 'text' ? 1 : 2)) {
|
|
1305
|
+
return responseJson({ ok: false, error: type === 'text' ? 'Text annotation requires a valid point.' : 'Annotation requires at least two valid points.' }, 400);
|
|
1259
1306
|
}
|
|
1260
1307
|
|
|
1308
|
+
const defaultWidth = type === 'text' ? 24 : 4;
|
|
1309
|
+
const maxWidth = type === 'text' ? 96 : 24;
|
|
1261
1310
|
const width = typeof body.width === 'number' && Number.isFinite(body.width)
|
|
1262
|
-
? Math.min(
|
|
1263
|
-
:
|
|
1311
|
+
? Math.min(maxWidth, Math.max(1, body.width))
|
|
1312
|
+
: defaultWidth;
|
|
1264
1313
|
const color = typeof body.color === 'string' && (body.color === 'currentColor' || /^#[0-9a-fA-F]{6}$/.test(body.color))
|
|
1265
1314
|
? body.color
|
|
1266
1315
|
: 'currentColor';
|
|
1267
1316
|
const label = typeof body.label === 'string' && body.label.trim().length > 0
|
|
1268
1317
|
? body.label.trim().slice(0, 160)
|
|
1269
1318
|
: undefined;
|
|
1319
|
+
const text = type === 'text' && typeof body.text === 'string' && body.text.trim().length > 0
|
|
1320
|
+
? body.text.trim().slice(0, 240)
|
|
1321
|
+
: undefined;
|
|
1322
|
+
if (type === 'text' && !text) {
|
|
1323
|
+
return responseJson({ ok: false, error: 'Text annotation requires text.' }, 400);
|
|
1324
|
+
}
|
|
1270
1325
|
const id = typeof body.id === 'string' && body.id.trim().length > 0
|
|
1271
1326
|
? body.id.trim().slice(0, 120)
|
|
1272
1327
|
: `ann-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1273
1328
|
const annotation: CanvasAnnotation = {
|
|
1274
1329
|
id,
|
|
1275
|
-
type
|
|
1330
|
+
type,
|
|
1276
1331
|
points,
|
|
1277
|
-
bounds: annotationBounds(points),
|
|
1332
|
+
bounds: type === 'text' ? textAnnotationBounds(points[0]!, text!, width) : annotationBounds(points),
|
|
1278
1333
|
color,
|
|
1279
1334
|
width,
|
|
1280
|
-
...(
|
|
1335
|
+
...(text ? { text } : {}),
|
|
1336
|
+
...(label ?? text ? { label: label ?? text } : {}),
|
|
1281
1337
|
createdAt: new Date().toISOString(),
|
|
1282
1338
|
};
|
|
1283
1339
|
|
|
@@ -1401,6 +1457,9 @@ async function handleCanvasAddNode(req: Request): Promise<Response> {
|
|
|
1401
1457
|
error: 'Node type "web-artifact" is created via POST /api/canvas/web-artifact with appTsx + title.',
|
|
1402
1458
|
}, 400);
|
|
1403
1459
|
}
|
|
1460
|
+
if (type === 'html-primitive') {
|
|
1461
|
+
return createCanvasHtmlPrimitiveNode(body);
|
|
1462
|
+
}
|
|
1404
1463
|
return responseJson({ ok: false, error: `Invalid node type: "${type}".` }, 400);
|
|
1405
1464
|
}
|
|
1406
1465
|
|
|
@@ -1408,6 +1467,10 @@ async function handleCanvasAddNode(req: Request): Promise<Response> {
|
|
|
1408
1467
|
return createCanvasWebpageNode(body);
|
|
1409
1468
|
}
|
|
1410
1469
|
|
|
1470
|
+
if (type === 'html' && (typeof body.primitive === 'string' || typeof body.kind === 'string')) {
|
|
1471
|
+
return createCanvasHtmlPrimitiveNode(body);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1411
1474
|
const extraData = body.data && typeof body.data === 'object' && !Array.isArray(body.data)
|
|
1412
1475
|
? body.data as Record<string, unknown>
|
|
1413
1476
|
: undefined;
|
|
@@ -1443,8 +1506,8 @@ async function handleCanvasAddNode(req: Request): Promise<Response> {
|
|
|
1443
1506
|
...(type === 'trace' && typeof body.error === 'string' ? { error: body.error } : {}),
|
|
1444
1507
|
...(body.strictSize === true ? { strictSize: true } : {}),
|
|
1445
1508
|
...geometry,
|
|
1446
|
-
defaultWidth: type === 'html' ? 720 : 360,
|
|
1447
|
-
defaultHeight: type === 'html' ? 640 : 200,
|
|
1509
|
+
defaultWidth: type === 'html' ? 720 : type === 'markdown' ? MARKDOWN_NODE_DEFAULT_SIZE.width : 360,
|
|
1510
|
+
defaultHeight: type === 'html' ? 640 : type === 'markdown' ? MARKDOWN_NODE_DEFAULT_SIZE.height : 200,
|
|
1448
1511
|
fileMode: 'auto',
|
|
1449
1512
|
});
|
|
1450
1513
|
} catch (error) {
|
|
@@ -1462,6 +1525,44 @@ async function handleCanvasAddNode(req: Request): Promise<Response> {
|
|
|
1462
1525
|
return responseJson(buildNodeResponse(node));
|
|
1463
1526
|
}
|
|
1464
1527
|
|
|
1528
|
+
function createCanvasHtmlPrimitiveNode(body: Record<string, unknown>): Response {
|
|
1529
|
+
const rawKind = typeof body.primitive === 'string' ? body.primitive : body.kind;
|
|
1530
|
+
if (typeof rawKind !== 'string' || !isHtmlPrimitiveKind(rawKind)) {
|
|
1531
|
+
return responseJson({ ok: false, error: `Unknown HTML primitive: ${String(rawKind)}.` }, 400);
|
|
1532
|
+
}
|
|
1533
|
+
const data = isRecord(body.data) ? body.data : {};
|
|
1534
|
+
const built = buildHtmlPrimitive({
|
|
1535
|
+
kind: rawKind,
|
|
1536
|
+
...(typeof body.title === 'string' ? { title: body.title } : {}),
|
|
1537
|
+
data,
|
|
1538
|
+
});
|
|
1539
|
+
const geometry = resolveCreateGeometry(body);
|
|
1540
|
+
const { node } = addCanvasNode({
|
|
1541
|
+
type: 'html',
|
|
1542
|
+
title: built.title,
|
|
1543
|
+
data: {
|
|
1544
|
+
html: built.html,
|
|
1545
|
+
htmlPrimitive: built.kind,
|
|
1546
|
+
primitiveData: built.data,
|
|
1547
|
+
description: built.summary,
|
|
1548
|
+
},
|
|
1549
|
+
...(body.strictSize === true ? { strictSize: true } : {}),
|
|
1550
|
+
...geometry,
|
|
1551
|
+
defaultWidth: built.defaultSize.width,
|
|
1552
|
+
defaultHeight: built.defaultSize.height,
|
|
1553
|
+
});
|
|
1554
|
+
emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
1555
|
+
return responseJson({
|
|
1556
|
+
...buildNodeResponse(node),
|
|
1557
|
+
primitive: {
|
|
1558
|
+
kind: built.kind,
|
|
1559
|
+
title: built.title,
|
|
1560
|
+
htmlBytes: Buffer.byteLength(built.html, 'utf-8'),
|
|
1561
|
+
defaultSize: built.defaultSize,
|
|
1562
|
+
},
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1465
1566
|
// ── Group operations ─────────────────────────────────────────
|
|
1466
1567
|
async function handleCanvasCreateGroup(req: Request): Promise<Response> {
|
|
1467
1568
|
const body = await readJson(req);
|
|
@@ -1799,8 +1900,8 @@ function handleCanvasDescribeSchema(): Response {
|
|
|
1799
1900
|
async function handleCanvasValidateSpec(req: Request): Promise<Response> {
|
|
1800
1901
|
const body = await readJson(req);
|
|
1801
1902
|
const rawType = typeof body.type === 'string' ? body.type.trim() : '';
|
|
1802
|
-
if (rawType !== 'json-render' && rawType !== 'graph') {
|
|
1803
|
-
return responseJson({ ok: false, error: 'Validation type must be "json-render" or "
|
|
1903
|
+
if (rawType !== 'json-render' && rawType !== 'graph' && rawType !== 'html-primitive') {
|
|
1904
|
+
return responseJson({ ok: false, error: 'Validation type must be "json-render", "graph", or "html-primitive".' }, 400);
|
|
1804
1905
|
}
|
|
1805
1906
|
|
|
1806
1907
|
try {
|
|
@@ -1815,6 +1916,23 @@ async function handleCanvasValidateSpec(req: Request): Promise<Response> {
|
|
|
1815
1916
|
}));
|
|
1816
1917
|
}
|
|
1817
1918
|
|
|
1919
|
+
if (rawType === 'html-primitive') {
|
|
1920
|
+
const kind = typeof body.kind === 'string'
|
|
1921
|
+
? body.kind
|
|
1922
|
+
: typeof body.primitive === 'string'
|
|
1923
|
+
? body.primitive
|
|
1924
|
+
: '';
|
|
1925
|
+
const data = isRecord(body.data) ? body.data : {};
|
|
1926
|
+
return responseJson(validateStructuredCanvasPayload({
|
|
1927
|
+
type: 'html-primitive',
|
|
1928
|
+
primitive: {
|
|
1929
|
+
kind,
|
|
1930
|
+
...(typeof body.title === 'string' ? { title: body.title } : {}),
|
|
1931
|
+
data,
|
|
1932
|
+
},
|
|
1933
|
+
}));
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1818
1936
|
const data = parseGraphPayloadData(body.data);
|
|
1819
1937
|
if (!data) {
|
|
1820
1938
|
return responseJson({ ok: false, error: 'Graph validation requires a data array.' }, 400);
|
|
@@ -1976,10 +2094,15 @@ async function handleJsonRenderView(url: URL): Promise<Response> {
|
|
|
1976
2094
|
return responseText('json-render node not found', 404);
|
|
1977
2095
|
}
|
|
1978
2096
|
|
|
1979
|
-
const
|
|
1980
|
-
if (!
|
|
2097
|
+
const rawSpec = node.data.spec;
|
|
2098
|
+
if (!rawSpec || typeof rawSpec !== 'object') {
|
|
1981
2099
|
return responseText('json-render spec missing', 404);
|
|
1982
2100
|
}
|
|
2101
|
+
const spec = normalizeGraphViewerSpec(
|
|
2102
|
+
{ type: node.type, data: node.data },
|
|
2103
|
+
rawSpec as { root: string; elements: Record<string, unknown>; state?: Record<string, unknown> },
|
|
2104
|
+
url.searchParams.get('display'),
|
|
2105
|
+
);
|
|
1983
2106
|
|
|
1984
2107
|
const themeValue = url.searchParams.get('theme');
|
|
1985
2108
|
const theme =
|
|
@@ -1989,8 +2112,9 @@ async function handleJsonRenderView(url: URL): Promise<Response> {
|
|
|
1989
2112
|
const title = (node.data.title as string) || node.id;
|
|
1990
2113
|
const html = await buildJsonRenderViewerHtml({
|
|
1991
2114
|
title,
|
|
1992
|
-
spec
|
|
2115
|
+
spec,
|
|
1993
2116
|
...(theme ? { theme } : {}),
|
|
2117
|
+
...(url.searchParams.get('display') === 'expanded' ? { display: 'expanded' as const } : {}),
|
|
1994
2118
|
});
|
|
1995
2119
|
return new Response(html, {
|
|
1996
2120
|
headers: {
|
|
@@ -4145,6 +4269,8 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
4145
4269
|
return responseJson(listCanvasSnapshots({
|
|
4146
4270
|
limit: parsePositiveIntegerParam(url.searchParams.get('limit')),
|
|
4147
4271
|
query: url.searchParams.get('q') ?? url.searchParams.get('query') ?? undefined,
|
|
4272
|
+
before: url.searchParams.get('before') ?? undefined,
|
|
4273
|
+
after: url.searchParams.get('after') ?? undefined,
|
|
4148
4274
|
all: url.searchParams.get('all') === 'true',
|
|
4149
4275
|
}));
|
|
4150
4276
|
}
|
|
@@ -160,7 +160,7 @@ function summarizeAnnotationForSpatialContext(
|
|
|
160
160
|
);
|
|
161
161
|
return {
|
|
162
162
|
id: annotation.id,
|
|
163
|
-
label: annotation.label ?? null,
|
|
163
|
+
label: annotation.label ?? annotation.text ?? null,
|
|
164
164
|
bounds: annotation.bounds,
|
|
165
165
|
targetNodeIds: targetNodes.map((node) => node.id),
|
|
166
166
|
targetNodeTitles,
|
|
@@ -312,7 +312,7 @@ export function searchNodes(
|
|
|
312
312
|
|
|
313
313
|
for (const node of nodes) {
|
|
314
314
|
const title = ((node.data.title as string) ?? '').toLowerCase();
|
|
315
|
-
const content = ((node.data.content as string) ?? (node.data.fileContent as string) ?? '').toLowerCase();
|
|
315
|
+
const content = ((node.data.content as string) ?? (node.data.description as string) ?? (node.data.fileContent as string) ?? '').toLowerCase();
|
|
316
316
|
const path = ((node.data.path as string) ?? '').toLowerCase();
|
|
317
317
|
const description = ((node.data.description as string) ?? '').toLowerCase();
|
|
318
318
|
const url = ((node.data.url as string) ?? '').toLowerCase();
|
|
@@ -331,7 +331,7 @@ export function searchNodes(
|
|
|
331
331
|
|
|
332
332
|
// Extract a snippet around the first match in content
|
|
333
333
|
let snippet = '';
|
|
334
|
-
const fullContent = (node.data.content as string) ?? (node.data.fileContent as string) ?? '';
|
|
334
|
+
const fullContent = (node.data.content as string) ?? (node.data.description as string) ?? (node.data.fileContent as string) ?? '';
|
|
335
335
|
const matchIdx = fullContent.toLowerCase().indexOf(terms[0]);
|
|
336
336
|
if (matchIdx >= 0) {
|
|
337
337
|
const start = Math.max(0, matchIdx - 40);
|