pmx-canvas 0.6.2 → 0.6.3
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 +19 -0
- package/Readme.md +13 -1
- package/dist/canvas/index.js +50 -50
- package/dist/json-render/index.js +57 -57
- package/dist/types/client/canvas/auto-fit.d.ts +3 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +3 -3
- package/dist/types/client/state/human-store.d.ts +1 -0
- package/dist/types/json-render/catalog.d.ts +20 -20
- package/dist/types/json-render/schema.d.ts +40 -0
- package/dist/types/mcp/skills.d.ts +13 -0
- package/dist/types/server/bundled-skills.d.ts +32 -4
- package/docs/http-api.md +5 -1
- package/docs/mcp.md +35 -1
- package/docs/product-context-vision-2026-09.md +16 -5
- package/docs/product-vision-2026-09.md +4 -2
- package/package.json +11 -11
- package/skills/json-render-mcp/SKILL.md +118 -2
- package/skills/pmx-canvas/SKILL.md +34 -11
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +5 -5
- package/src/client/canvas/auto-fit.ts +20 -1
- package/src/client/nodes/use-iframe-content-height.ts +44 -24
- package/src/client/state/human-store.ts +6 -6
- package/src/json-render/catalog.ts +6 -0
- package/src/json-render/renderer/index.tsx +17 -0
- package/src/json-render/schema.ts +4 -0
- package/src/mcp/server.ts +9 -4
- package/src/mcp/skills.ts +47 -0
- package/src/server/bundled-skills.ts +92 -53
- package/src/server/operations/ops/nodes.ts +43 -11
- package/src/shared/content-height-reporter.ts +4 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* This module must never import server.ts or index.ts.
|
|
10
10
|
*/
|
|
11
|
+
import { resolve } from 'node:path';
|
|
11
12
|
import { z } from 'zod';
|
|
12
13
|
import { canvasState, type CanvasAnnotation, type CanvasLayout, type CanvasNodeState } from '../../canvas-state.js';
|
|
13
14
|
import {
|
|
@@ -1145,7 +1146,7 @@ const nodeUpdateOperation = defineOperation<z.infer<typeof nodeUpdateSchema>, Re
|
|
|
1145
1146
|
},
|
|
1146
1147
|
},
|
|
1147
1148
|
handler: async (input, ctx) => {
|
|
1148
|
-
|
|
1149
|
+
let body: Record<string, unknown> = input;
|
|
1149
1150
|
const id = input.id;
|
|
1150
1151
|
const existing = canvasState.getNode(id);
|
|
1151
1152
|
if (!existing) throw new OperationError(`Node "${id}" not found.`, 404);
|
|
@@ -1162,7 +1163,48 @@ const nodeUpdateOperation = defineOperation<z.infer<typeof nodeUpdateSchema>, Re
|
|
|
1162
1163
|
const result = await refreshCanvasWebpageNode(id, { ...(url ? { url } : {}) });
|
|
1163
1164
|
return result as unknown as Record<string, unknown>;
|
|
1164
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
let nextFilePath: string | null = null;
|
|
1168
|
+
let nextFileContent: ReturnType<typeof readFileNodeContent> | null = null;
|
|
1169
|
+
if (existing.type === 'file') {
|
|
1170
|
+
const nestedData = isRecord(body.data) ? body.data : undefined;
|
|
1171
|
+
const hasTopLevelPath = body.path !== undefined;
|
|
1172
|
+
const hasNestedPath = nestedData?.path !== undefined;
|
|
1173
|
+
if (hasTopLevelPath || hasNestedPath) {
|
|
1174
|
+
const normalizePath = (value: unknown): string => {
|
|
1175
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
1176
|
+
throw new OperationError('File node "path" must be a non-empty string.');
|
|
1177
|
+
}
|
|
1178
|
+
return resolve(canvasState.getWorkspaceRoot(), value.trim());
|
|
1179
|
+
};
|
|
1180
|
+
const normalizedTopLevelPath = hasTopLevelPath ? normalizePath(body.path) : undefined;
|
|
1181
|
+
const normalizedNestedPath = hasNestedPath ? normalizePath(nestedData?.path) : undefined;
|
|
1182
|
+
body = {
|
|
1183
|
+
...body,
|
|
1184
|
+
...(normalizedTopLevelPath ? { path: normalizedTopLevelPath } : {}),
|
|
1185
|
+
...(nestedData
|
|
1186
|
+
? { data: { ...nestedData, ...(normalizedNestedPath ? { path: normalizedNestedPath } : {}) } }
|
|
1187
|
+
: {}),
|
|
1188
|
+
};
|
|
1189
|
+
nextFilePath = normalizedNestedPath ?? normalizedTopLevelPath ?? null;
|
|
1190
|
+
nextFileContent = nextFilePath ? readFileNodeContent(nextFilePath) : null;
|
|
1191
|
+
if (!nextFileContent || nextFileContent.kind === 'unavailable') {
|
|
1192
|
+
throw new OperationError(`File node target "${nextFilePath}" is missing or unreadable.`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1165
1197
|
const { patch, groupChildIds } = buildNodePatch(existing, body);
|
|
1198
|
+
const prevPath = existing.type === 'file' && typeof existing.data.path === 'string' ? existing.data.path : null;
|
|
1199
|
+
if (nextFilePath && nextFileContent && patch.data) {
|
|
1200
|
+
const data = { ...patch.data };
|
|
1201
|
+
const prevBase = prevPath?.split(/[\\/]/).pop();
|
|
1202
|
+
if (typeof body.title !== 'string' && (!data.title || data.title === prevBase)) {
|
|
1203
|
+
data.title = nextFilePath.split(/[\\/]/).pop();
|
|
1204
|
+
}
|
|
1205
|
+
applyFileContentToNodeData(data, nextFileContent);
|
|
1206
|
+
patch.data = data;
|
|
1207
|
+
}
|
|
1166
1208
|
canvasState.updateNode(id, patch);
|
|
1167
1209
|
if (groupChildIds !== undefined && !setGroupChildrenFromApi(id, groupChildIds)) {
|
|
1168
1210
|
throw new OperationError(`Group "${id}" not found.`, 404);
|
|
@@ -1171,20 +1213,10 @@ const nodeUpdateOperation = defineOperation<z.infer<typeof nodeUpdateSchema>, Re
|
|
|
1171
1213
|
// watcher IN PLACE — before this, pointing a file node elsewhere meant
|
|
1172
1214
|
// recreating it, which lost its edges, pins, and position.
|
|
1173
1215
|
if (existing.type === 'file') {
|
|
1174
|
-
const prevPath = typeof existing.data.path === 'string' ? existing.data.path : null;
|
|
1175
1216
|
const repointed = canvasState.getNode(id);
|
|
1176
1217
|
const nextPath = repointed && typeof repointed.data.path === 'string' ? repointed.data.path : null;
|
|
1177
1218
|
if (nextPath && nextPath !== prevPath) {
|
|
1178
1219
|
if (prevPath) unwatchFileForNode(id, prevPath);
|
|
1179
|
-
const data = { ...repointed!.data };
|
|
1180
|
-
// The title follows the file unless the caller pinned one in this
|
|
1181
|
-
// patch or had renamed the node away from the old filename.
|
|
1182
|
-
const prevBase = prevPath?.split(/[\\/]/).pop();
|
|
1183
|
-
if (typeof body.title !== 'string' && (!data.title || data.title === prevBase)) {
|
|
1184
|
-
data.title = nextPath.split(/[\\/]/).pop();
|
|
1185
|
-
}
|
|
1186
|
-
applyFileContentToNodeData(data, readFileNodeContent(nextPath));
|
|
1187
|
-
canvasState.updateNode(id, { data });
|
|
1188
1220
|
watchFileForNode(id, nextPath);
|
|
1189
1221
|
scheduleCodeGraphRecompute(() => {
|
|
1190
1222
|
ctx.emit('canvas-layout-update', { layout: canvasState.getLayout() });
|
|
@@ -26,8 +26,10 @@ export function contentHeightReporterSource(frameToken: string): string {
|
|
|
26
26
|
`function m(){var d=document.documentElement;return Math.max(d?d.scrollHeight:0,document.body?document.body.scrollHeight:0);}` +
|
|
27
27
|
`function r(){var h=m();if(Math.abs(h-last)<=4)return;last=h;window.parent.postMessage({source:'pmx-canvas-frame',type:'content-height',token:T,height:h},'*');}` +
|
|
28
28
|
`function s(){if(timer)return;timer=setTimeout(function(){timer=null;r();},100);}` +
|
|
29
|
-
`if(document.readyState!=='loading')s();window.addEventListener('load',s);` +
|
|
30
|
-
`try{new ResizeObserver(s).observe(document.documentElement);}catch(e){}
|
|
29
|
+
`if(document.readyState!=='loading')s();window.addEventListener('load',s,true);` +
|
|
30
|
+
`try{new ResizeObserver(s).observe(document.documentElement);}catch(e){}` +
|
|
31
|
+
`new MutationObserver(s).observe(document.documentElement,{subtree:true,childList:true,characterData:true,attributes:true});` +
|
|
32
|
+
`if(document.fonts)document.fonts.ready.then(s);setTimeout(s,60);})();`
|
|
31
33
|
);
|
|
32
34
|
}
|
|
33
35
|
|