pmx-canvas 0.1.29 → 0.1.30
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 +161 -0
- package/Readme.md +20 -10
- package/dist/canvas/global.css +13 -0
- package/dist/canvas/index.js +80 -163
- package/dist/canvas/surface-theme.css +142 -0
- package/dist/json-render/index.js +103 -103
- package/dist/types/client/nodes/HtmlNode.d.ts +0 -7
- package/dist/types/client/nodes/ax-node-actions.d.ts +18 -0
- package/dist/types/client/nodes/surface-url.d.ts +22 -0
- package/dist/types/client/state/attention-bridge.d.ts +3 -0
- package/dist/types/client/state/intent-bridge.d.ts +17 -0
- package/dist/types/json-render/renderer/index.d.ts +2 -0
- package/dist/types/json-render/schema.d.ts +2 -0
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +47 -0
- package/dist/types/server/ax-interaction.d.ts +210 -0
- package/dist/types/server/ax-state.d.ts +67 -1
- package/dist/types/server/canvas-db.d.ts +4 -0
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +47 -2
- package/dist/types/server/html-surface.d.ts +40 -0
- package/dist/types/server/index.d.ts +50 -2
- package/dist/types/server/mutation-history.d.ts +1 -1
- package/dist/types/server/placement.d.ts +1 -1
- package/dist/types/shared/surface.d.ts +19 -0
- package/docs/cli.md +30 -0
- package/docs/http-api.md +55 -0
- package/docs/mcp.md +40 -2
- package/docs/node-types.md +26 -0
- package/docs/plans/plan-004-pmx-ax-primitives.md +623 -394
- package/docs/sdk.md +20 -0
- package/package.json +2 -2
- package/skills/pmx-canvas/SKILL.md +107 -9
- package/src/cli/agent.ts +177 -0
- package/src/client/canvas/CanvasNode.tsx +8 -4
- package/src/client/canvas/ExpandedNodeOverlay.tsx +12 -0
- package/src/client/nodes/ContextNode.tsx +17 -0
- package/src/client/nodes/ExtAppFrame.tsx +40 -3
- package/src/client/nodes/FileNode.tsx +26 -0
- package/src/client/nodes/HtmlNode.tsx +60 -188
- package/src/client/nodes/McpAppNode.tsx +47 -2
- package/src/client/nodes/StatusNode.tsx +20 -0
- package/src/client/nodes/ax-node-actions.ts +39 -0
- package/src/client/nodes/surface-url.ts +48 -0
- package/src/client/state/attention-bridge.ts +5 -0
- package/src/client/state/intent-bridge.ts +33 -0
- package/src/client/theme/global.css +13 -0
- package/src/client/theme/surface-theme.css +142 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/json-render/schema.ts +4 -0
- package/src/json-render/server.ts +13 -0
- package/src/mcp/canvas-access.ts +195 -0
- package/src/mcp/server.ts +232 -2
- package/src/server/ax-context.ts +3 -0
- package/src/server/ax-interaction.ts +549 -0
- package/src/server/ax-state.ts +188 -2
- package/src/server/canvas-db.ts +20 -0
- package/src/server/canvas-operations.ts +11 -0
- package/src/server/canvas-serialization.ts +9 -0
- package/src/server/canvas-state.ts +177 -16
- package/src/server/html-surface.ts +170 -0
- package/src/server/index.ts +98 -0
- package/src/server/mutation-history.ts +5 -0
- package/src/server/placement.ts +5 -1
- package/src/server/server.ts +305 -0
- package/src/shared/surface.ts +38 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side builder for an `html` node's standalone surface document.
|
|
3
|
+
*
|
|
4
|
+
* This is the canonical wrapper that used to live in the client (HtmlNode's
|
|
5
|
+
* `buildSrcDoc`). It now lives on the server so a single document definition
|
|
6
|
+
* backs BOTH the in-canvas iframe and the "Open as site" tab — the iframe and
|
|
7
|
+
* the standalone tab load the exact same URL (/api/canvas/surface/:nodeId), so
|
|
8
|
+
* there is one render path and no content fork.
|
|
9
|
+
*
|
|
10
|
+
* Theming: instead of inlining a token `<style>` block, the document links the
|
|
11
|
+
* same-origin `/canvas/surface-theme.css` stylesheet and selects a palette via
|
|
12
|
+
* the `<html data-theme="...">` attribute. A sandboxed (opaque-origin) document
|
|
13
|
+
* can still load this same-origin stylesheet, and live theme switching works by
|
|
14
|
+
* toggling the attribute (the theme bridge below) — no CSS payload over
|
|
15
|
+
* postMessage required.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type SurfaceTheme = 'dark' | 'light' | 'high-contrast';
|
|
19
|
+
|
|
20
|
+
/** Path the surface document links for its theme tokens (served from dist/canvas). */
|
|
21
|
+
export const SURFACE_THEME_STYLESHEET = '/canvas/surface-theme.css';
|
|
22
|
+
|
|
23
|
+
/** CSP sandbox tokens for an `html`/`html-primitive` surface — scripts only, opaque origin. */
|
|
24
|
+
export const HTML_SURFACE_SANDBOX = 'allow-scripts';
|
|
25
|
+
|
|
26
|
+
export function normalizeSurfaceTheme(value: string | null | undefined): SurfaceTheme {
|
|
27
|
+
return value === 'light' || value === 'high-contrast' ? value : 'dark';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Restrict a caller-supplied token to a safe charset before it is embedded
|
|
32
|
+
* inside an inline `<script>` string. The token is a CSRF-style nonce minted by
|
|
33
|
+
* the client (shape `theme-<base36>-<base36>` / `presentation-<...>`), but it
|
|
34
|
+
* arrives as a query parameter, so it must never be trusted verbatim — anything
|
|
35
|
+
* outside `[A-Za-z0-9_-]` (notably `<`, `"`, backtick) could break out of the
|
|
36
|
+
* script context.
|
|
37
|
+
*/
|
|
38
|
+
function sanitizeToken(value: string | null | undefined): string {
|
|
39
|
+
if (typeof value !== 'string') return '';
|
|
40
|
+
return value.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 64);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Bridge that lets the parent canvas live-switch the surface theme by toggling
|
|
45
|
+
* the `data-theme` attribute. Validates source + type + nonce so unrelated
|
|
46
|
+
* windows cannot drive the attribute. No-op in a standalone tab (no parent
|
|
47
|
+
* posts to it), which is exactly what we want there.
|
|
48
|
+
*/
|
|
49
|
+
function buildThemeBridge(themeToken: string): string {
|
|
50
|
+
const token = JSON.stringify(themeToken);
|
|
51
|
+
return `<script data-pmx-canvas-theme-bridge>
|
|
52
|
+
const PMX_CANVAS_THEME_TOKEN = ${token};
|
|
53
|
+
window.addEventListener('message', (event) => {
|
|
54
|
+
const message = event.data;
|
|
55
|
+
if (!message || message.source !== 'pmx-canvas-html-node' || message.type !== 'theme-update' || message.token !== PMX_CANVAS_THEME_TOKEN) return;
|
|
56
|
+
if (typeof message.theme !== 'string') return;
|
|
57
|
+
document.documentElement.setAttribute('data-pmx-canvas-theme', message.theme);
|
|
58
|
+
document.documentElement.setAttribute('data-theme', message.theme);
|
|
59
|
+
});
|
|
60
|
+
</script>`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Presentation bridge (deck mode). Identical contract to the previous client
|
|
65
|
+
* version: Escape posts an exit message to the parent overlay, and the parent
|
|
66
|
+
* can forward slide keys back in. Only relevant when the surface is embedded in
|
|
67
|
+
* the in-canvas presentation overlay; harmless (inert) in a standalone tab.
|
|
68
|
+
*/
|
|
69
|
+
function buildPresentationEscapeBridge(exitToken: string): string {
|
|
70
|
+
const token = JSON.stringify(exitToken);
|
|
71
|
+
return `<script data-pmx-canvas-presentation-bridge>
|
|
72
|
+
const PMX_CANVAS_PRESENTATION_EXIT_TOKEN = ${token};
|
|
73
|
+
document.addEventListener('keydown', (event) => {
|
|
74
|
+
if (event.key === 'Escape') {
|
|
75
|
+
window.parent.postMessage({ source: 'pmx-canvas-html-node', type: 'presentation-exit', token: PMX_CANVAS_PRESENTATION_EXIT_TOKEN }, '*');
|
|
76
|
+
}
|
|
77
|
+
}, true);
|
|
78
|
+
window.addEventListener('message', (event) => {
|
|
79
|
+
const message = event.data;
|
|
80
|
+
if (!message || message.source !== 'pmx-canvas-html-node' || message.type !== 'presentation-key' || message.token !== PMX_CANVAS_PRESENTATION_EXIT_TOKEN) return;
|
|
81
|
+
if (typeof message.key !== 'string') return;
|
|
82
|
+
if (typeof window.PMX_CANVAS_PRESENTATION_HANDLE_KEY === 'function') {
|
|
83
|
+
window.PMX_CANVAS_PRESENTATION_HANDLE_KEY(message.key);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
document.dispatchEvent(new CustomEvent('pmx-presentation-key', { detail: { key: message.key }, bubbles: true, cancelable: true }));
|
|
87
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: message.key, bubbles: true, cancelable: true }));
|
|
88
|
+
});
|
|
89
|
+
</script>`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function injectIntoHead(html: string, content: string): string {
|
|
93
|
+
if (/<head[\s>]/i.test(html)) {
|
|
94
|
+
return html.replace(/<head([^>]*)>/i, `<head$1>${content}`);
|
|
95
|
+
}
|
|
96
|
+
if (/<html[\s>]/i.test(html)) {
|
|
97
|
+
return html.replace(/<html([^>]*)>/i, `<html$1><head>${content}</head>`);
|
|
98
|
+
}
|
|
99
|
+
return html;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bridge that exposes `window.PMX_AX.emit(type, payload)` to author HTML. Calls
|
|
104
|
+
* post a nonce-tagged message to the parent canvas, which validates the nonce +
|
|
105
|
+
* node id and submits the interaction through the capability-gated endpoint. Only
|
|
106
|
+
* injected when the node's AX capabilities are enabled (opt-in for `html`), and
|
|
107
|
+
* the server re-validates every interaction — so this is a convenience surface,
|
|
108
|
+
* not a trust boundary.
|
|
109
|
+
*/
|
|
110
|
+
function buildAxBridge(axToken: string, nodeId: string): string {
|
|
111
|
+
const token = JSON.stringify(axToken);
|
|
112
|
+
const node = JSON.stringify(nodeId);
|
|
113
|
+
return `<script data-pmx-canvas-ax-bridge>
|
|
114
|
+
const PMX_AX_TOKEN = ${token};
|
|
115
|
+
const PMX_AX_NODE_ID = ${node};
|
|
116
|
+
window.PMX_AX = {
|
|
117
|
+
emit(type, payload) {
|
|
118
|
+
window.parent.postMessage({
|
|
119
|
+
source: 'pmx-canvas-ax',
|
|
120
|
+
token: PMX_AX_TOKEN,
|
|
121
|
+
nodeId: PMX_AX_NODE_ID,
|
|
122
|
+
interaction: { type: String(type), payload: payload && typeof payload === 'object' ? payload : {} },
|
|
123
|
+
}, '*');
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
</script>`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface HtmlSurfaceOptions {
|
|
130
|
+
theme: SurfaceTheme;
|
|
131
|
+
/** Client nonce that authorizes parent → iframe theme-update messages. */
|
|
132
|
+
themeToken?: string;
|
|
133
|
+
presentation?: boolean;
|
|
134
|
+
presentationExitToken?: string;
|
|
135
|
+
/** Inject window.PMX_AX.emit (only when the node's AX capabilities are enabled). */
|
|
136
|
+
axBridge?: boolean;
|
|
137
|
+
/** Nonce authorizing iframe → parent AX emits; embedded in the bridge. */
|
|
138
|
+
axToken?: string;
|
|
139
|
+
/** Node id stamped on emitted interactions. */
|
|
140
|
+
nodeId?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Wrap author HTML into a complete, themed standalone document. Accepts either a
|
|
145
|
+
* full HTML document (injects into its `<head>`) or a fragment (wraps it).
|
|
146
|
+
*/
|
|
147
|
+
export function buildHtmlSurfaceDocument(userHtml: string, options: HtmlSurfaceOptions): string {
|
|
148
|
+
const themeToken = sanitizeToken(options.themeToken);
|
|
149
|
+
const link = `<link rel="stylesheet" href="${SURFACE_THEME_STYLESHEET}">`;
|
|
150
|
+
const themeBridge = buildThemeBridge(themeToken);
|
|
151
|
+
const presentationBridge = options.presentation
|
|
152
|
+
? buildPresentationEscapeBridge(sanitizeToken(options.presentationExitToken))
|
|
153
|
+
: '';
|
|
154
|
+
const axBridge = options.axBridge
|
|
155
|
+
? buildAxBridge(sanitizeToken(options.axToken), sanitizeToken(options.nodeId))
|
|
156
|
+
: '';
|
|
157
|
+
const injectedHeadContent = `${link}${themeBridge}${presentationBridge}${axBridge}`;
|
|
158
|
+
const presentationAttr = options.presentation ? ' data-pmx-presentation-mode="present"' : '';
|
|
159
|
+
const trimmed = userHtml.trim();
|
|
160
|
+
const isFullDoc = /<html[\s>]/i.test(trimmed);
|
|
161
|
+
if (isFullDoc) {
|
|
162
|
+
const withTheme = trimmed.replace(
|
|
163
|
+
/<html([^>]*)>/i,
|
|
164
|
+
`<html$1 data-pmx-canvas-theme="${options.theme}" data-theme="${options.theme}"${presentationAttr}>`,
|
|
165
|
+
);
|
|
166
|
+
return injectIntoHead(withTheme, injectedHeadContent);
|
|
167
|
+
}
|
|
168
|
+
// Fragment — wrap in a full document.
|
|
169
|
+
return `<!doctype html><html data-pmx-canvas-theme="${options.theme}" data-theme="${options.theme}"${presentationAttr}><head><meta charset="utf-8">${injectedHeadContent}</head><body>${userHtml}</body></html>`;
|
|
170
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -2,14 +2,20 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { canvasState, IMAGE_MIME_MAP } from './canvas-state.js';
|
|
3
3
|
import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from './canvas-state.js';
|
|
4
4
|
import { buildCanvasAxContext } from './ax-context.js';
|
|
5
|
+
import { applyAxInteraction, type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
|
|
5
6
|
import type {
|
|
6
7
|
PmxAxApprovalGate,
|
|
8
|
+
PmxAxCommandDescriptor,
|
|
7
9
|
PmxAxContext,
|
|
10
|
+
PmxAxElicitation,
|
|
8
11
|
PmxAxEvent,
|
|
9
12
|
PmxAxEvidence,
|
|
10
13
|
PmxAxEvidenceKind,
|
|
11
14
|
PmxAxFocusState,
|
|
12
15
|
PmxAxHostCapability,
|
|
16
|
+
PmxAxMode,
|
|
17
|
+
PmxAxModeRequest,
|
|
18
|
+
PmxAxPolicy,
|
|
13
19
|
PmxAxReviewAnchorType,
|
|
14
20
|
PmxAxReviewAnnotation,
|
|
15
21
|
PmxAxReviewKind,
|
|
@@ -523,6 +529,22 @@ export class PmxCanvas extends EventEmitter {
|
|
|
523
529
|
return ok;
|
|
524
530
|
}
|
|
525
531
|
|
|
532
|
+
/** Undelivered steering for a consumer (loop-safe; excludes consumer-originated). */
|
|
533
|
+
getPendingSteering(options?: { consumer?: string; limit?: number }): PmxAxSteeringMessage[] {
|
|
534
|
+
return canvasState.getPendingSteering(options ?? {});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Submit a node-originated AX interaction (plan-004 Phase 1). Validates the
|
|
539
|
+
* envelope + node capabilities, maps the interaction onto the matching AX
|
|
540
|
+
* operation, and emits the outcome + state SSE events.
|
|
541
|
+
*/
|
|
542
|
+
submitAxInteraction(input: AxInteractionInput, options?: { source?: PmxAxSource }): AxInteractionPublicResult {
|
|
543
|
+
const { result, events } = applyAxInteraction(canvasState, input, options?.source ?? 'sdk');
|
|
544
|
+
for (const e of events) emitPrimaryWorkbenchEvent(e.event, e.payload);
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
|
|
526
548
|
getAxTimeline(query?: AxTimelineQuery): ReturnType<typeof canvasState.getAxTimeline> {
|
|
527
549
|
return canvasState.getAxTimeline(query);
|
|
528
550
|
}
|
|
@@ -628,6 +650,75 @@ export class PmxCanvas extends EventEmitter {
|
|
|
628
650
|
return host;
|
|
629
651
|
}
|
|
630
652
|
|
|
653
|
+
listElicitations(): PmxAxElicitation[] {
|
|
654
|
+
return canvasState.getElicitations();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
requestElicitation(
|
|
658
|
+
input: { prompt: string; fields?: string[]; nodeIds?: string[] },
|
|
659
|
+
options?: { source?: PmxAxSource },
|
|
660
|
+
): PmxAxElicitation {
|
|
661
|
+
const elicitation = canvasState.requestElicitation(input, { source: options?.source ?? 'sdk' });
|
|
662
|
+
emitPrimaryWorkbenchEvent('ax-state-changed', { elicitation });
|
|
663
|
+
return elicitation;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
respondElicitation(
|
|
667
|
+
id: string,
|
|
668
|
+
response: Record<string, unknown>,
|
|
669
|
+
options?: { source?: PmxAxSource },
|
|
670
|
+
): PmxAxElicitation | null {
|
|
671
|
+
const elicitation = canvasState.respondElicitation(id, response, { source: options?.source ?? 'sdk' });
|
|
672
|
+
if (elicitation) emitPrimaryWorkbenchEvent('ax-state-changed', { elicitation });
|
|
673
|
+
return elicitation;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
listModeRequests(): PmxAxModeRequest[] {
|
|
677
|
+
return canvasState.getModeRequests();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
requestMode(
|
|
681
|
+
input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
|
|
682
|
+
options?: { source?: PmxAxSource },
|
|
683
|
+
): PmxAxModeRequest {
|
|
684
|
+
const modeRequest = canvasState.requestMode(input, { source: options?.source ?? 'sdk' });
|
|
685
|
+
emitPrimaryWorkbenchEvent('ax-state-changed', { modeRequest });
|
|
686
|
+
return modeRequest;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
resolveModeRequest(
|
|
690
|
+
id: string,
|
|
691
|
+
decision: 'approved' | 'rejected',
|
|
692
|
+
options?: { resolution?: string; source?: PmxAxSource },
|
|
693
|
+
): PmxAxModeRequest | null {
|
|
694
|
+
const modeRequest = canvasState.resolveModeRequest(id, decision, { ...(options ?? {}), source: options?.source ?? 'sdk' });
|
|
695
|
+
if (modeRequest) emitPrimaryWorkbenchEvent('ax-state-changed', { modeRequest });
|
|
696
|
+
return modeRequest;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
getCommandRegistry(): PmxAxCommandDescriptor[] {
|
|
700
|
+
return canvasState.getCommandRegistry();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
invokeCommand(name: string, args?: Record<string, unknown> | null, options?: { source?: PmxAxSource }): PmxAxEvent | null {
|
|
704
|
+
const event = canvasState.invokeCommand(name, args ?? null, { source: options?.source ?? 'sdk' });
|
|
705
|
+
if (event) emitPrimaryWorkbenchEvent('ax-event-created', { event });
|
|
706
|
+
return event;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
getPolicy(): PmxAxPolicy {
|
|
710
|
+
return canvasState.getPolicy();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
setPolicy(
|
|
714
|
+
patch: { tools?: Partial<PmxAxPolicy['tools']>; prompt?: Partial<PmxAxPolicy['prompt']> },
|
|
715
|
+
options?: { source?: PmxAxSource },
|
|
716
|
+
): PmxAxPolicy {
|
|
717
|
+
const policy = canvasState.setPolicy(patch, { source: options?.source ?? 'sdk' });
|
|
718
|
+
emitPrimaryWorkbenchEvent('ax-state-changed', { policy });
|
|
719
|
+
return policy;
|
|
720
|
+
}
|
|
721
|
+
|
|
631
722
|
fitView(options?: {
|
|
632
723
|
width?: number;
|
|
633
724
|
height?: number;
|
|
@@ -1112,13 +1203,20 @@ export { traceManager } from './trace-manager.js';
|
|
|
1112
1203
|
export type {
|
|
1113
1204
|
PmxAxApprovalGate,
|
|
1114
1205
|
PmxAxApprovalStatus,
|
|
1206
|
+
PmxAxCommandDescriptor,
|
|
1115
1207
|
PmxAxContext,
|
|
1116
1208
|
PmxAxEvent,
|
|
1209
|
+
PmxAxElicitation,
|
|
1210
|
+
PmxAxElicitationStatus,
|
|
1117
1211
|
PmxAxEventKind,
|
|
1118
1212
|
PmxAxEvidence,
|
|
1119
1213
|
PmxAxEvidenceKind,
|
|
1120
1214
|
PmxAxFocusState,
|
|
1121
1215
|
PmxAxHostCapability,
|
|
1216
|
+
PmxAxMode,
|
|
1217
|
+
PmxAxModeRequest,
|
|
1218
|
+
PmxAxModeRequestStatus,
|
|
1219
|
+
PmxAxPolicy,
|
|
1122
1220
|
PmxAxReviewAnchorType,
|
|
1123
1221
|
PmxAxReviewAnnotation,
|
|
1124
1222
|
PmxAxReviewKind,
|
|
@@ -35,6 +35,11 @@ export type MutationOp =
|
|
|
35
35
|
| 'resolveApproval'
|
|
36
36
|
| 'addReviewAnnotation'
|
|
37
37
|
| 'updateReviewAnnotation'
|
|
38
|
+
| 'requestElicitation'
|
|
39
|
+
| 'respondElicitation'
|
|
40
|
+
| 'requestMode'
|
|
41
|
+
| 'resolveModeRequest'
|
|
42
|
+
| 'setPolicy'
|
|
38
43
|
| 'batch'
|
|
39
44
|
| 'viewport'
|
|
40
45
|
| 'groupNodes'
|
package/src/server/placement.ts
CHANGED
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
|
|
8
8
|
export { findOpenCanvasPosition, type CanvasPlacementRect } from '../shared/placement.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Margin between a group frame and the children it contains. Kept generous so
|
|
11
|
+
// the auto-fit frame stays visibly larger than its children — leaving room for
|
|
12
|
+
// the node-count/type badge in the header that would otherwise sit under the
|
|
13
|
+
// top-left child.
|
|
14
|
+
export const GROUP_PAD = 56;
|
|
11
15
|
export const GROUP_TITLEBAR_HEIGHT = 32;
|
|
12
16
|
const GROUP_LAYOUT_GAP_X = 32;
|
|
13
17
|
const GROUP_LAYOUT_GAP_Y = 32;
|