pmx-canvas 0.1.19 → 0.1.21

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 (65) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/Readme.md +19 -6
  3. package/dist/canvas/global.css +123 -2
  4. package/dist/canvas/index.js +103 -68
  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/nodes/HtmlNode.d.ts +12 -1
  9. package/dist/types/client/state/canvas-store.d.ts +2 -0
  10. package/dist/types/client/types.d.ts +3 -2
  11. package/dist/types/json-render/charts/components.d.ts +5 -1
  12. package/dist/types/json-render/renderer/index.d.ts +1 -0
  13. package/dist/types/json-render/server.d.ts +1 -0
  14. package/dist/types/mcp/canvas-access.d.ts +3 -0
  15. package/dist/types/server/canvas-operations.d.ts +4 -0
  16. package/dist/types/server/canvas-schema.d.ts +19 -3
  17. package/dist/types/server/canvas-serialization.d.ts +1 -0
  18. package/dist/types/server/canvas-state.d.ts +6 -2
  19. package/dist/types/server/html-node-summary.d.ts +2 -0
  20. package/dist/types/server/html-primitives.d.ts +42 -0
  21. package/dist/types/server/index.d.ts +26 -0
  22. package/docs/cli.md +4 -1
  23. package/docs/http-api.md +11 -1
  24. package/docs/mcp.md +10 -4
  25. package/docs/node-types.md +54 -4
  26. package/docs/screenshot.png +0 -0
  27. package/docs/sdk.md +12 -0
  28. package/package.json +1 -1
  29. package/skills/pmx-canvas/SKILL.md +17 -3
  30. package/skills/pmx-canvas/references/html-primitives.md +132 -0
  31. package/src/cli/agent.ts +159 -5
  32. package/src/cli/index.ts +1 -1
  33. package/src/client/App.tsx +21 -2
  34. package/src/client/canvas/AnnotationLayer.tsx +33 -12
  35. package/src/client/canvas/CanvasViewport.tsx +88 -7
  36. package/src/client/canvas/CommandPalette.tsx +2 -2
  37. package/src/client/canvas/ContextMenu.tsx +2 -2
  38. package/src/client/canvas/ExpandedNodeOverlay.tsx +112 -3
  39. package/src/client/canvas/auto-fit.ts +5 -1
  40. package/src/client/icons.tsx +13 -0
  41. package/src/client/nodes/HtmlNode.tsx +125 -13
  42. package/src/client/nodes/McpAppNode.tsx +12 -4
  43. package/src/client/state/canvas-store.ts +15 -5
  44. package/src/client/state/sse-bridge.ts +5 -4
  45. package/src/client/theme/global.css +123 -2
  46. package/src/client/types.ts +2 -1
  47. package/src/json-render/charts/components.tsx +41 -7
  48. package/src/json-render/charts/extra-components.tsx +13 -12
  49. package/src/json-render/renderer/index.tsx +1 -0
  50. package/src/json-render/server.ts +3 -1
  51. package/src/mcp/canvas-access.ts +54 -1
  52. package/src/mcp/server.ts +98 -28
  53. package/src/server/agent-context.ts +39 -0
  54. package/src/server/canvas-operations.ts +99 -38
  55. package/src/server/canvas-provenance.ts +8 -6
  56. package/src/server/canvas-schema.ts +94 -3
  57. package/src/server/canvas-serialization.ts +16 -4
  58. package/src/server/canvas-state.ts +9 -4
  59. package/src/server/demo-state.json +1143 -0
  60. package/src/server/demo.ts +25 -777
  61. package/src/server/html-node-summary.ts +141 -0
  62. package/src/server/html-primitives.ts +1300 -0
  63. package/src/server/index.ts +63 -3
  64. package/src/server/server.ts +154 -17
  65. package/src/server/spatial-analysis.ts +5 -3
@@ -0,0 +1,141 @@
1
+ const HTML_CONTENT_SUMMARY_MAX_LENGTH = 900;
2
+ const HTML_AGENT_SUMMARY_MAX_LENGTH = 1200;
3
+ const HTML_REFERENCE_LIMIT = 12;
4
+
5
+ function pickString(value: unknown): string | null {
6
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
7
+ }
8
+
9
+ function strings(value: unknown): string[] {
10
+ return Array.isArray(value)
11
+ ? value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
12
+ : [];
13
+ }
14
+
15
+ function truncateText(value: string, maxLength: number): string {
16
+ if (value.length <= maxLength) return value;
17
+ return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
18
+ }
19
+
20
+ function normalizeWhitespace(value: string): string {
21
+ return value.replace(/\s+/g, ' ').trim();
22
+ }
23
+
24
+ function decodeHtmlEntities(value: string): string {
25
+ const named: Record<string, string> = {
26
+ amp: '&',
27
+ gt: '>',
28
+ lt: '<',
29
+ nbsp: ' ',
30
+ quot: '"',
31
+ apos: "'",
32
+ };
33
+ return value.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, entity) => {
34
+ const lower = entity.toLowerCase();
35
+ if (lower.startsWith('#x')) {
36
+ const codePoint = Number.parseInt(lower.slice(2), 16);
37
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
38
+ }
39
+ if (lower.startsWith('#')) {
40
+ const codePoint = Number.parseInt(lower.slice(1), 10);
41
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
42
+ }
43
+ return named[lower] ?? match;
44
+ });
45
+ }
46
+
47
+ export function summarizeHtmlText(html: string): string | null {
48
+ const withoutNoise = html
49
+ .replace(/<!--[\s\S]*?-->/g, ' ')
50
+ .replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
51
+ .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
52
+ .replace(/<noscript\b[\s\S]*?<\/noscript>/gi, ' ');
53
+ const text = withoutNoise
54
+ .replace(/<(?:h[1-6]|p|li|br|div|section|article|header|footer|main|aside|summary|figcaption|blockquote|tr|td|th)\b[^>]*>/gi, '\n')
55
+ .replace(/<[^>]+>/g, ' ');
56
+ const normalized = normalizeWhitespace(decodeHtmlEntities(text));
57
+ return normalized.length > 0 ? truncateText(normalized, HTML_CONTENT_SUMMARY_MAX_LENGTH) : null;
58
+ }
59
+
60
+ function uniqueLimited(values: string[]): string[] {
61
+ const seen = new Set<string>();
62
+ const unique: string[] = [];
63
+ for (const value of values) {
64
+ const trimmed = value.trim();
65
+ if (!trimmed || seen.has(trimmed)) continue;
66
+ seen.add(trimmed);
67
+ unique.push(trimmed);
68
+ if (unique.length >= HTML_REFERENCE_LIMIT) break;
69
+ }
70
+ return unique;
71
+ }
72
+
73
+ function extractHtmlNodeIds(html: string): string[] {
74
+ const ids: string[] = [];
75
+ for (const match of html.matchAll(/\b(?:node|graph|json-render|web-artifact|mcp-app|group)-[a-z0-9-]+\b/gi)) {
76
+ ids.push(match[0]);
77
+ }
78
+ return uniqueLimited(ids);
79
+ }
80
+
81
+ function extractHtmlUrls(html: string): string[] {
82
+ const urls: string[] = [];
83
+ for (const match of html.matchAll(/\b(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
84
+ const url = match[1]?.trim();
85
+ if (!url) continue;
86
+ if (/^(?:https?:)?\/\//i.test(url) || url.startsWith('/') || url.startsWith('ui://')) {
87
+ urls.push(url);
88
+ }
89
+ }
90
+ return uniqueLimited(urls);
91
+ }
92
+
93
+ function joinSummaryParts(parts: string[]): string | null {
94
+ const summary = parts
95
+ .map((part) => part.trim())
96
+ .filter(Boolean)
97
+ .filter((part, index, all) => all.findIndex((candidate) => candidate === part) === index)
98
+ .join('\n');
99
+ return summary ? truncateText(summary, HTML_AGENT_SUMMARY_MAX_LENGTH) : null;
100
+ }
101
+
102
+ export function normalizeHtmlNodeSemanticData<T extends Record<string, unknown>>(data: T): T {
103
+ const {
104
+ agentSummary: _agentSummary,
105
+ contentSummary: _contentSummary,
106
+ embeddedNodeIds: _embeddedNodeIds,
107
+ embeddedUrls: _embeddedUrls,
108
+ embeddedNodeId: _embeddedNodeId,
109
+ embeddedGraphId: _embeddedGraphId,
110
+ sourceNodeId: _sourceNodeId,
111
+ ...base
112
+ } = data;
113
+
114
+ const html = pickString(base.html);
115
+ const explicitSummary = pickString(base.summary) ?? pickString(base.description);
116
+ const primitive = pickString(base.htmlPrimitive);
117
+ const contentSummary = html ? summarizeHtmlText(html) : null;
118
+ const explicitNodeIds = [
119
+ ...strings(data.embeddedNodeIds),
120
+ pickString(data.embeddedNodeId),
121
+ pickString(data.embeddedGraphId),
122
+ pickString(data.sourceNodeId),
123
+ ].filter((value): value is string => value !== null);
124
+ const embeddedNodeIds = uniqueLimited([...explicitNodeIds, ...(html ? extractHtmlNodeIds(html) : [])]);
125
+ const embeddedUrls = uniqueLimited([...strings(data.embeddedUrls), ...(html ? extractHtmlUrls(html) : [])]);
126
+ const agentSummary = pickString(data.agentSummary) ?? joinSummaryParts([
127
+ primitive ? `HTML primitive: ${primitive}` : '',
128
+ explicitSummary ?? '',
129
+ contentSummary ?? '',
130
+ embeddedNodeIds.length > 0 ? `Embedded canvas nodes: ${embeddedNodeIds.join(', ')}` : '',
131
+ embeddedUrls.length > 0 ? `Embedded URLs: ${embeddedUrls.join(', ')}` : '',
132
+ ]);
133
+
134
+ return {
135
+ ...base,
136
+ ...(contentSummary ? { contentSummary } : {}),
137
+ ...(agentSummary ? { agentSummary } : {}),
138
+ ...(embeddedNodeIds.length > 0 ? { embeddedNodeIds } : {}),
139
+ ...(embeddedUrls.length > 0 ? { embeddedUrls } : {}),
140
+ } as T;
141
+ }