pmx-canvas 0.7.2 → 0.7.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 +13 -0
- package/Readme.md +25 -11
- package/dist/canvas/global.css +123 -40
- package/dist/canvas/index.js +84 -84
- package/dist/json-render/index.js +55 -55
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -1
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +23 -11
- package/skills/pmx-canvas/evals/evals.json +69 -0
- package/skills/pmx-canvas/references/full-reference.md +3 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +49 -0
- package/src/client/canvas/CanvasNode.tsx +200 -195
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +25 -23
- package/src/client/canvas/FeedbackDialog.tsx +10 -8
- package/src/client/canvas/ToolRail.tsx +46 -99
- package/src/client/nodes/McpAppNode.tsx +16 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/canvas-store.ts +3 -0
- package/src/client/state/intent-bridge.ts +14 -2
- package/src/client/theme/global.css +123 -40
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +8 -1
- package/src/server/server.ts +3 -1
- package/src/shared/ax-surface-protocol.ts +3 -0
|
@@ -572,9 +572,23 @@ body,
|
|
|
572
572
|
box-shadow: 0 4px 24px var(--c-shadow);
|
|
573
573
|
display: flex;
|
|
574
574
|
flex-direction: column;
|
|
575
|
-
overflow:
|
|
575
|
+
overflow: visible;
|
|
576
576
|
transition: box-shadow 0.15s ease;
|
|
577
|
-
contain: layout style
|
|
577
|
+
contain: layout style;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* Clip card content, not the connection handles centered on its border. */
|
|
581
|
+
.node-content {
|
|
582
|
+
display: flex;
|
|
583
|
+
flex-direction: column;
|
|
584
|
+
flex: 1;
|
|
585
|
+
min-height: 0;
|
|
586
|
+
border-radius: inherit;
|
|
587
|
+
overflow: hidden;
|
|
588
|
+
contain: paint;
|
|
589
|
+
}
|
|
590
|
+
.group-node > .node-content {
|
|
591
|
+
display: contents;
|
|
578
592
|
}
|
|
579
593
|
|
|
580
594
|
.canvas-node:hover {
|
|
@@ -610,34 +624,42 @@ body,
|
|
|
610
624
|
/* ── Connection port handles (drag-to-connect) ───────────── */
|
|
611
625
|
.node-port {
|
|
612
626
|
position: absolute;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
627
|
+
/* The visible dot stays compact; its grab area stays usable at every zoom. */
|
|
628
|
+
width: calc(32px * var(--node-port-scale, 1));
|
|
629
|
+
height: calc(32px * var(--node-port-scale, 1));
|
|
630
|
+
display: grid;
|
|
631
|
+
place-items: center;
|
|
618
632
|
opacity: 0;
|
|
619
633
|
cursor: crosshair;
|
|
620
634
|
z-index: 10;
|
|
621
|
-
|
|
622
|
-
|
|
635
|
+
touch-action: none;
|
|
636
|
+
transition: opacity 0.15s;
|
|
623
637
|
pointer-events: auto;
|
|
624
638
|
}
|
|
625
|
-
.
|
|
639
|
+
.node-port::after {
|
|
640
|
+
content: '';
|
|
641
|
+
width: calc(6px * var(--node-port-scale, 1));
|
|
642
|
+
height: calc(6px * var(--node-port-scale, 1));
|
|
643
|
+
border-radius: 50%;
|
|
644
|
+
background: var(--c-accent);
|
|
645
|
+
border: calc(2px * var(--node-port-scale, 1)) solid var(--c-panel);
|
|
646
|
+
box-shadow: 0 0 0 calc(1px * var(--node-port-scale, 1)) var(--c-accent);
|
|
647
|
+
}
|
|
648
|
+
.canvas-node:hover .node-port,
|
|
649
|
+
.canvas-node.selected .node-port,
|
|
650
|
+
.canvas-node:focus-within .node-port {
|
|
626
651
|
opacity: 1;
|
|
627
|
-
transform: translate(-50%, -50%) scale(1);
|
|
628
652
|
}
|
|
629
|
-
.node-port:hover {
|
|
653
|
+
.node-port:hover::after {
|
|
630
654
|
background: var(--c-accent-hover);
|
|
631
|
-
transform: translate(-50%, -50%) scale(1.3);
|
|
632
655
|
}
|
|
633
|
-
|
|
634
|
-
.node-port-
|
|
635
|
-
.node-port-
|
|
636
|
-
.node-port-
|
|
637
|
-
.
|
|
638
|
-
.
|
|
639
|
-
.node-port-
|
|
640
|
-
.node-port-right:hover { transform: translate(50%, -50%) scale(1.3); }
|
|
656
|
+
/* Bias vertical grab areas outward so they do not steal title/footer clicks. */
|
|
657
|
+
.node-port-top { top: 0; left: 50%; transform: translate(-50%, -75%); }
|
|
658
|
+
.node-port-bottom { bottom: 0; left: 50%; transform: translate(-50%, 75%); }
|
|
659
|
+
.node-port-top::after { transform: translateY(calc(8px * var(--node-port-scale, 1))); }
|
|
660
|
+
.node-port-bottom::after { transform: translateY(calc(-8px * var(--node-port-scale, 1))); }
|
|
661
|
+
.node-port-left { left: 0; top: 50%; transform: translate(-50%, -50%); }
|
|
662
|
+
.node-port-right { right: 0; top: 50%; transform: translate(50%, -50%); }
|
|
641
663
|
|
|
642
664
|
.canvas-node .node-titlebar {
|
|
643
665
|
display: flex;
|
|
@@ -2001,6 +2023,7 @@ body,
|
|
|
2001
2023
|
color: var(--c-dim);
|
|
2002
2024
|
}
|
|
2003
2025
|
.command-bar-composer {
|
|
2026
|
+
position: relative;
|
|
2004
2027
|
display: flex;
|
|
2005
2028
|
align-items: center;
|
|
2006
2029
|
gap: 10px;
|
|
@@ -2012,24 +2035,72 @@ body,
|
|
|
2012
2035
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
|
2013
2036
|
}
|
|
2014
2037
|
.command-bar-target {
|
|
2015
|
-
appearance: none;
|
|
2016
|
-
-webkit-appearance: none;
|
|
2017
2038
|
max-width: 148px;
|
|
2018
|
-
padding: 4px 18px 4px 8px;
|
|
2019
|
-
border: 1px solid var(--c-line);
|
|
2020
|
-
border-radius: 7px;
|
|
2021
|
-
background: color-mix(in srgb, var(--c-panel) 80%, transparent)
|
|
2022
|
-
url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M1 1l3 3 3-3' fill='none' stroke='%237a8699' stroke-width='1.4'/%3E%3C/svg%3E")
|
|
2023
|
-
no-repeat right 6px center;
|
|
2024
|
-
color: var(--c-text-soft);
|
|
2025
|
-
font: 12px/1.3 var(--mono);
|
|
2026
|
-
cursor: pointer;
|
|
2027
2039
|
flex-shrink: 0;
|
|
2028
2040
|
}
|
|
2029
|
-
.
|
|
2030
|
-
|
|
2031
|
-
|
|
2041
|
+
.choice-menu {
|
|
2042
|
+
position: relative;
|
|
2043
|
+
min-width: 0;
|
|
2044
|
+
}
|
|
2045
|
+
.choice-menu-trigger {
|
|
2046
|
+
display: flex;
|
|
2047
|
+
align-items: center;
|
|
2048
|
+
justify-content: space-between;
|
|
2049
|
+
gap: 10px;
|
|
2050
|
+
width: 100%;
|
|
2051
|
+
min-height: 36px;
|
|
2052
|
+
padding: 8px 10px;
|
|
2053
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 65%, var(--c-muted));
|
|
2054
|
+
border-radius: 8px;
|
|
2055
|
+
background: var(--c-panel-overlay);
|
|
2032
2056
|
color: var(--c-text);
|
|
2057
|
+
font: 12px/1.35 var(--font);
|
|
2058
|
+
cursor: pointer;
|
|
2059
|
+
}
|
|
2060
|
+
.choice-menu-trigger > span {
|
|
2061
|
+
overflow: hidden;
|
|
2062
|
+
text-overflow: ellipsis;
|
|
2063
|
+
white-space: nowrap;
|
|
2064
|
+
}
|
|
2065
|
+
.choice-menu-trigger svg { flex-shrink: 0; }
|
|
2066
|
+
.choice-menu-trigger:hover,
|
|
2067
|
+
.choice-menu-trigger[aria-expanded='true'] { border-color: var(--c-accent); }
|
|
2068
|
+
.choice-menu-trigger:focus-visible,
|
|
2069
|
+
.choice-menu-options button:focus-visible {
|
|
2070
|
+
outline: 2px solid var(--c-accent);
|
|
2071
|
+
outline-offset: 2px;
|
|
2072
|
+
}
|
|
2073
|
+
.toolbar-menu.choice-menu-options {
|
|
2074
|
+
top: calc(100% + 6px);
|
|
2075
|
+
left: 0;
|
|
2076
|
+
right: auto;
|
|
2077
|
+
min-width: 100%;
|
|
2078
|
+
width: max-content;
|
|
2079
|
+
max-width: min(320px, calc(100vw - 64px));
|
|
2080
|
+
max-height: min(280px, 40vh);
|
|
2081
|
+
overflow-y: auto;
|
|
2082
|
+
z-index: 20;
|
|
2083
|
+
}
|
|
2084
|
+
.toolbar-menu.choice-menu-options.opens-above {
|
|
2085
|
+
top: auto;
|
|
2086
|
+
bottom: calc(100% + 6px);
|
|
2087
|
+
}
|
|
2088
|
+
.command-bar-target.choice-menu {
|
|
2089
|
+
position: static;
|
|
2090
|
+
}
|
|
2091
|
+
.command-bar-target .choice-menu-options {
|
|
2092
|
+
/* Anchor to the composer, not its inset trigger, to clear the session rail. */
|
|
2093
|
+
min-width: min(200px, 100%);
|
|
2094
|
+
max-width: 100%;
|
|
2095
|
+
}
|
|
2096
|
+
.choice-menu-options .toolbar-menu-item {
|
|
2097
|
+
min-height: 36px;
|
|
2098
|
+
white-space: normal;
|
|
2099
|
+
overflow-wrap: anywhere;
|
|
2100
|
+
}
|
|
2101
|
+
.choice-menu-options button:disabled {
|
|
2102
|
+
opacity: 0.5;
|
|
2103
|
+
cursor: not-allowed;
|
|
2033
2104
|
}
|
|
2034
2105
|
|
|
2035
2106
|
.command-bar-icon {
|
|
@@ -4498,13 +4569,24 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4498
4569
|
}
|
|
4499
4570
|
.text-prompt-cancel,
|
|
4500
4571
|
.text-prompt-confirm {
|
|
4501
|
-
|
|
4572
|
+
min-height: 36px;
|
|
4573
|
+
padding: 8px 14px;
|
|
4502
4574
|
border-radius: 7px;
|
|
4503
|
-
border: 1px solid var(--c-line);
|
|
4504
|
-
background:
|
|
4505
|
-
color: var(--c-text
|
|
4575
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 65%, var(--c-muted));
|
|
4576
|
+
background: var(--c-panel-overlay);
|
|
4577
|
+
color: var(--c-text);
|
|
4506
4578
|
cursor: pointer;
|
|
4507
4579
|
font-size: 12px;
|
|
4580
|
+
font-weight: 600;
|
|
4581
|
+
}
|
|
4582
|
+
.text-prompt-cancel:hover {
|
|
4583
|
+
background: var(--c-accent-10);
|
|
4584
|
+
border-color: var(--c-muted);
|
|
4585
|
+
}
|
|
4586
|
+
.text-prompt-cancel:focus-visible,
|
|
4587
|
+
.text-prompt-confirm:focus-visible {
|
|
4588
|
+
outline: 2px solid var(--c-accent);
|
|
4589
|
+
outline-offset: 2px;
|
|
4508
4590
|
}
|
|
4509
4591
|
.text-prompt-confirm {
|
|
4510
4592
|
background: var(--c-accent);
|
|
@@ -4524,7 +4606,8 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4524
4606
|
overflow-y: auto;
|
|
4525
4607
|
color: var(--c-text);
|
|
4526
4608
|
}
|
|
4527
|
-
.feedback-dialog label
|
|
4609
|
+
.feedback-dialog label,
|
|
4610
|
+
.feedback-field {
|
|
4528
4611
|
display: flex;
|
|
4529
4612
|
flex-direction: column;
|
|
4530
4613
|
gap: 6px;
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
* from an inline global injected by the server-side viewer route.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { createStateStore, type Spec, type StateStore } from '@json-render/core';
|
|
11
|
+
import {
|
|
12
|
+
AX_SURFACE_EMIT_SOURCE,
|
|
13
|
+
HTML_SURFACE_PUSH_SOURCE,
|
|
14
|
+
JSON_VIEWER_STATE_SOURCE,
|
|
15
|
+
} from '../../shared/ax-surface-protocol.js';
|
|
13
16
|
import { createRoot } from 'react-dom/client';
|
|
14
|
-
import { defineRegistry, JSONUIProvider, Renderer
|
|
17
|
+
import { defineRegistry, JSONUIProvider, Renderer } from '@json-render/react';
|
|
15
18
|
import { shadcnComponents } from '@json-render/shadcn';
|
|
16
19
|
import { catalog } from '../catalog';
|
|
17
20
|
import { chartComponents } from '../charts/components';
|
|
@@ -106,29 +109,10 @@ declare global {
|
|
|
106
109
|
__PMX_CANVAS_JSON_RENDER_NODE_ID__?: string;
|
|
107
110
|
__PMX_CANVAS_AX_TOKEN__?: string;
|
|
108
111
|
__PMX_CANVAS_AX_STATE__?: unknown;
|
|
112
|
+
__PMX_CANVAS_UI_STATE_TOKEN__?: string;
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
// Read-side AX bridge for json-render: keeps the spec-bound `/ax` state live as
|
|
113
|
-
// the parent canvas pushes nonce-validated `ax-update` messages, so a declarative
|
|
114
|
-
// board ({ "$state": "/ax/workItems" }) reflects the work queue in real time.
|
|
115
|
-
function AxStateSync() {
|
|
116
|
-
const [, setAx] = useStateBinding<unknown>('ax');
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
const token = window.__PMX_CANVAS_AX_TOKEN__;
|
|
119
|
-
if (!token) return undefined;
|
|
120
|
-
function onMessage(event: MessageEvent) {
|
|
121
|
-
const m = event.data as { source?: string; type?: string; token?: string; state?: unknown } | null;
|
|
122
|
-
if (!m || m.source !== HTML_SURFACE_PUSH_SOURCE || m.type !== 'ax-update' || m.token !== token) return;
|
|
123
|
-
window.__PMX_CANVAS_AX_STATE__ = m.state;
|
|
124
|
-
setAx(m.state);
|
|
125
|
-
}
|
|
126
|
-
window.addEventListener('message', onMessage);
|
|
127
|
-
return () => window.removeEventListener('message', onMessage);
|
|
128
|
-
}, [setAx]);
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
116
|
// AX interaction types a json-render spec can bind actions to. When an action
|
|
133
117
|
// named like one of these fires, we forward it to the parent canvas (which
|
|
134
118
|
// validates + submits through the capability-gated endpoint). Convention-based
|
|
@@ -188,7 +172,7 @@ function applyTheme(theme: unknown): void {
|
|
|
188
172
|
document.documentElement.style.colorScheme = theme === 'light' ? 'light' : 'dark';
|
|
189
173
|
}
|
|
190
174
|
|
|
191
|
-
function App() {
|
|
175
|
+
function App({ store }: { store: StateStore }) {
|
|
192
176
|
const spec = window.__PMX_CANVAS_JSON_RENDER_SPEC__;
|
|
193
177
|
|
|
194
178
|
if (!spec) {
|
|
@@ -208,11 +192,6 @@ function App() {
|
|
|
208
192
|
);
|
|
209
193
|
}
|
|
210
194
|
|
|
211
|
-
// Seed AX state under a reserved `/ax` key so specs can bind { "$state": "/ax/workItems" }.
|
|
212
|
-
const axState = window.__PMX_CANVAS_AX_STATE__;
|
|
213
|
-
const initialState =
|
|
214
|
-
axState !== undefined && axState !== null ? { ...(spec.state ?? {}), ax: axState } : (spec.state ?? undefined);
|
|
215
|
-
|
|
216
195
|
// Standalone "Open as site" tab (#65): fill the browser viewport instead of the
|
|
217
196
|
// in-canvas card height. The chart child flex-grows; useChartFrameHeight measures
|
|
218
197
|
// the full viewport in this mode. Embedded/expanded keep the padded min-height box.
|
|
@@ -229,13 +208,7 @@ function App() {
|
|
|
229
208
|
: { minHeight: '100vh', padding: 16, boxSizing: 'border-box' as const };
|
|
230
209
|
return (
|
|
231
210
|
<div style={containerStyle}>
|
|
232
|
-
<JSONUIProvider
|
|
233
|
-
registry={registry}
|
|
234
|
-
initialState={initialState}
|
|
235
|
-
directives={pmxCanvasDirectives}
|
|
236
|
-
handlers={buildAxHandlers()}
|
|
237
|
-
>
|
|
238
|
-
<AxStateSync />
|
|
211
|
+
<JSONUIProvider registry={registry} store={store} directives={pmxCanvasDirectives} handlers={buildAxHandlers()}>
|
|
239
212
|
<div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
|
|
240
213
|
<Renderer spec={spec} registry={registry} loading={false} />
|
|
241
214
|
</div>
|
|
@@ -248,5 +221,52 @@ function App() {
|
|
|
248
221
|
const root = document.getElementById('root');
|
|
249
222
|
if (root) {
|
|
250
223
|
syncPreferredTheme();
|
|
251
|
-
|
|
224
|
+
const stateToken = window.__PMX_CANVAS_UI_STATE_TOKEN__;
|
|
225
|
+
let store: StateStore | undefined;
|
|
226
|
+
const mount = (restored: Record<string, unknown> | null) => {
|
|
227
|
+
if (store) return;
|
|
228
|
+
const seed = restored ?? window.__PMX_CANVAS_JSON_RENDER_SPEC__?.state ?? {};
|
|
229
|
+
const ax = window.__PMX_CANVAS_AX_STATE__;
|
|
230
|
+
store = createStateStore(ax != null ? { ...seed, ax } : seed);
|
|
231
|
+
if (stateToken) {
|
|
232
|
+
// subscribe fires synchronously at the input/action write, not in a React
|
|
233
|
+
// effect that can be lost on unmount. Never mirror the server-owned /ax.
|
|
234
|
+
store.subscribe(() => {
|
|
235
|
+
const { ax: _ax, ...state } = store!.getSnapshot();
|
|
236
|
+
window.parent.postMessage(
|
|
237
|
+
{
|
|
238
|
+
source: JSON_VIEWER_STATE_SOURCE,
|
|
239
|
+
type: 'snapshot',
|
|
240
|
+
token: stateToken,
|
|
241
|
+
nodeId: window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,
|
|
242
|
+
state,
|
|
243
|
+
},
|
|
244
|
+
'*',
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
createRoot(root).render(<App store={store} />);
|
|
249
|
+
};
|
|
250
|
+
// Install before document load. The parent supplies a snapshot (or null) in
|
|
251
|
+
// onLoad; restore BEFORE mounting so watchers don't replay past user actions.
|
|
252
|
+
// Standalone viewers have no state token and render immediately.
|
|
253
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
254
|
+
if (event.source !== window.parent) return;
|
|
255
|
+
const m = event.data as { source?: string; type?: string; token?: string; state?: unknown } | null;
|
|
256
|
+
if (!m) return;
|
|
257
|
+
if (m.source === JSON_VIEWER_STATE_SOURCE && m.type === 'restore' && stateToken && m.token === stateToken) {
|
|
258
|
+
if (m.state === null || (typeof m.state === 'object' && !Array.isArray(m.state)))
|
|
259
|
+
mount(m.state as Record<string, unknown> | null);
|
|
260
|
+
}
|
|
261
|
+
if (
|
|
262
|
+
m.source === HTML_SURFACE_PUSH_SOURCE &&
|
|
263
|
+
m.type === 'ax-update' &&
|
|
264
|
+
window.__PMX_CANVAS_AX_TOKEN__ &&
|
|
265
|
+
m.token === window.__PMX_CANVAS_AX_TOKEN__
|
|
266
|
+
) {
|
|
267
|
+
window.__PMX_CANVAS_AX_STATE__ = m.state;
|
|
268
|
+
store?.set('/ax', m.state);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
if (!stateToken) mount(null);
|
|
252
272
|
}
|
|
@@ -979,6 +979,8 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
979
979
|
nodeId?: string;
|
|
980
980
|
axToken?: string;
|
|
981
981
|
axState?: unknown;
|
|
982
|
+
/** Independent nonce for tab-local runtime state across focus transitions. */
|
|
983
|
+
stateToken?: string;
|
|
982
984
|
/** Nonce for the content-height reporter so the node can grow to fit the chart. */
|
|
983
985
|
frameToken?: string;
|
|
984
986
|
/** When true, charts render at their natural (intrinsic) height instead of
|
|
@@ -995,9 +997,14 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
995
997
|
...(options.theme ? [`window.__PMX_CANVAS_JSON_RENDER_THEME__ = ${JSON.stringify(options.theme)};`] : []),
|
|
996
998
|
...(options.display ? [`window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ = ${JSON.stringify(options.display)};`] : []),
|
|
997
999
|
...(options.devtools ? ['window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ = true;'] : []),
|
|
1000
|
+
...(options.nodeId
|
|
1001
|
+
? [`window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`]
|
|
1002
|
+
: []),
|
|
1003
|
+
...(options.stateToken
|
|
1004
|
+
? [`window.__PMX_CANVAS_UI_STATE_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.stateToken))};`]
|
|
1005
|
+
: []),
|
|
998
1006
|
...(options.nodeId && options.axToken
|
|
999
1007
|
? [
|
|
1000
|
-
`window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`,
|
|
1001
1008
|
`window.__PMX_CANVAS_AX_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.axToken))};`,
|
|
1002
1009
|
// Read-side AX state: seed for initial render + bound under /ax for specs.
|
|
1003
1010
|
`window.__PMX_CANVAS_AX_STATE__ = ${JSON.stringify(options.axState ?? null).replace(/</g, '\\u003c')};`,
|
package/src/server/server.ts
CHANGED
|
@@ -1737,10 +1737,12 @@ async function handleJsonRenderView(url: URL): Promise<Response> {
|
|
|
1737
1737
|
const html = await buildJsonRenderViewerHtml({
|
|
1738
1738
|
title,
|
|
1739
1739
|
spec,
|
|
1740
|
+
nodeId,
|
|
1741
|
+
stateToken: url.searchParams.get('stateToken') ?? undefined,
|
|
1740
1742
|
...(theme ? { theme } : {}),
|
|
1741
1743
|
...(display ? { display } : {}),
|
|
1742
1744
|
...(devtoolsEnabled ? { devtools: true } : {}),
|
|
1743
|
-
...(axToken ? {
|
|
1745
|
+
...(axToken ? { axToken } : {}),
|
|
1744
1746
|
// Seed the read-side AX state (only for AX-enabled nodes) so specs can bind /ax.
|
|
1745
1747
|
...(axToken && axEnabled ? { axState: buildCanvasAxSurfaceSnapshot() } : {}),
|
|
1746
1748
|
// Content-fit: report natural height (charts render intrinsic) so the node grows.
|
|
@@ -18,6 +18,9 @@ export const AX_SURFACE_ACK_SOURCE = 'pmx-canvas-ax-ack';
|
|
|
18
18
|
/** Parent→surface pushes (theme updates, live AX state) into html/viewer iframes. */
|
|
19
19
|
export const HTML_SURFACE_PUSH_SOURCE = 'pmx-canvas-html-node';
|
|
20
20
|
|
|
21
|
+
/** Tab-local JSON viewer state handoff, independent of AX capabilities. */
|
|
22
|
+
export const JSON_VIEWER_STATE_SOURCE = 'pmx-canvas-json-state';
|
|
23
|
+
|
|
21
24
|
/** Ext-app boot beacon: the iframe's scripts executed (WebKit watchdog liveness). */
|
|
22
25
|
export const EXT_APP_BOOT_BEACON_SOURCE = 'pmx-canvas-ext-app-alive';
|
|
23
26
|
|