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.
Files changed (70) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/Readme.md +19 -6
  3. package/dist/canvas/global.css +35 -2
  4. package/dist/canvas/index.js +70 -69
  5. package/dist/json-render/index.js +109 -109
  6. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  7. package/dist/types/client/icons.d.ts +2 -0
  8. package/dist/types/client/state/canvas-store.d.ts +2 -0
  9. package/dist/types/client/types.d.ts +2 -1
  10. package/dist/types/json-render/charts/components.d.ts +5 -1
  11. package/dist/types/json-render/renderer/index.d.ts +1 -0
  12. package/dist/types/json-render/server.d.ts +1 -0
  13. package/dist/types/mcp/canvas-access.d.ts +3 -0
  14. package/dist/types/server/canvas-operations.d.ts +4 -0
  15. package/dist/types/server/canvas-schema.d.ts +19 -3
  16. package/dist/types/server/canvas-serialization.d.ts +1 -0
  17. package/dist/types/server/canvas-state.d.ts +8 -2
  18. package/dist/types/server/html-primitives.d.ts +34 -0
  19. package/dist/types/server/index.d.ts +19 -0
  20. package/docs/RELEASE.md +153 -0
  21. package/docs/bun-webview-integration.md +296 -0
  22. package/docs/cli.md +143 -0
  23. package/docs/evals/e2e-cli-coverage.md +61 -0
  24. package/docs/http-api.md +201 -0
  25. package/docs/mcp.md +137 -0
  26. package/docs/node-types.md +272 -0
  27. package/docs/plans/.gitkeep +0 -0
  28. package/docs/plans/plan-001-semantic-watch-mvp.md +335 -0
  29. package/docs/plans/plan-002-human-attention-layer-design-spec.md +679 -0
  30. package/docs/plans/plan-003-human-attention-layer-implementation-plan.md +572 -0
  31. package/docs/reactive-canvas-proposal.md +578 -0
  32. package/docs/release-review-0.1.0.md +38 -0
  33. package/docs/screenshot.png +0 -0
  34. package/docs/screenshots/demo-workbench-dark.png +0 -0
  35. package/docs/screenshots/demo-workbench-light.png +0 -0
  36. package/docs/screenshots/welcome-dark.png +0 -0
  37. package/docs/screenshots/welcome-light.png +0 -0
  38. package/docs/sdk.md +103 -0
  39. package/package.json +2 -1
  40. package/skills/pmx-canvas/SKILL.md +8 -0
  41. package/src/cli/agent.ts +167 -5
  42. package/src/client/App.tsx +20 -1
  43. package/src/client/canvas/AnnotationLayer.tsx +33 -12
  44. package/src/client/canvas/CanvasViewport.tsx +88 -7
  45. package/src/client/canvas/CommandPalette.tsx +1 -1
  46. package/src/client/canvas/ContextMenu.tsx +2 -2
  47. package/src/client/canvas/ExpandedNodeOverlay.tsx +7 -1
  48. package/src/client/icons.tsx +13 -0
  49. package/src/client/nodes/McpAppNode.tsx +12 -4
  50. package/src/client/state/canvas-store.ts +15 -5
  51. package/src/client/state/sse-bridge.ts +4 -3
  52. package/src/client/theme/global.css +35 -2
  53. package/src/client/types.ts +2 -1
  54. package/src/json-render/charts/components.tsx +41 -7
  55. package/src/json-render/charts/extra-components.tsx +13 -12
  56. package/src/json-render/renderer/index.tsx +1 -0
  57. package/src/json-render/server.ts +3 -1
  58. package/src/mcp/canvas-access.ts +25 -0
  59. package/src/mcp/server.ts +85 -27
  60. package/src/server/agent-context.ts +17 -0
  61. package/src/server/canvas-operations.ts +91 -38
  62. package/src/server/canvas-schema.ts +83 -3
  63. package/src/server/canvas-serialization.ts +9 -2
  64. package/src/server/canvas-state.ts +27 -9
  65. package/src/server/demo-state.json +1143 -0
  66. package/src/server/demo.ts +25 -777
  67. package/src/server/html-primitives.ts +990 -0
  68. package/src/server/index.ts +43 -2
  69. package/src/server/server.ts +140 -14
  70. package/src/server/spatial-analysis.ts +3 -3
@@ -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';
@@ -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(24, Math.max(1, body.width))
1263
- : 4;
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: 'freehand',
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
- ...(label ? { label } : {}),
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 "graph".' }, 400);
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 spec = node.data.spec;
1980
- if (!spec || typeof spec !== 'object') {
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: spec as { root: string; elements: Record<string, unknown>; state?: Record<string, unknown> },
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);