pmx-canvas 0.3.0 → 0.3.2
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 +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +194 -80
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +41 -15
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +122 -1028
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -23,7 +23,9 @@ export const allComponentDefinitions = {
|
|
|
23
23
|
Badge: {
|
|
24
24
|
...badgeDefinition,
|
|
25
25
|
props: badgeDefinition.props.extend({
|
|
26
|
-
variant: z
|
|
26
|
+
variant: z
|
|
27
|
+
.enum(['default', 'secondary', 'destructive', 'outline', 'success', 'info', 'warning', 'error', 'danger'])
|
|
28
|
+
.nullable(),
|
|
27
29
|
}),
|
|
28
30
|
},
|
|
29
31
|
Button: {
|
|
@@ -40,9 +42,12 @@ export const allComponentDefinitions = {
|
|
|
40
42
|
...tufteChartComponentDefinitions,
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
export const catalog = defineCatalog(
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
export const catalog = defineCatalog(
|
|
46
|
+
schema as never,
|
|
47
|
+
{
|
|
48
|
+
components: allComponentDefinitions,
|
|
49
|
+
} as never,
|
|
50
|
+
);
|
|
46
51
|
|
|
47
52
|
export interface JsonRenderIssue {
|
|
48
53
|
path?: PropertyKey[];
|
|
@@ -134,12 +139,7 @@ function unwrapSchema(value: unknown): {
|
|
|
134
139
|
|
|
135
140
|
const record = asRecord(current);
|
|
136
141
|
const def = asRecord(record?._def) ?? asRecord(record?.def);
|
|
137
|
-
const inner =
|
|
138
|
-
def?.innerType ??
|
|
139
|
-
def?.schema ??
|
|
140
|
-
def?.type ??
|
|
141
|
-
def?.out ??
|
|
142
|
-
def?.in;
|
|
142
|
+
const inner = def?.innerType ?? def?.schema ?? def?.type ?? def?.out ?? def?.in;
|
|
143
143
|
|
|
144
144
|
if (!inner || inner === current || (!isOptionalSchema(current) && !isNullableSchema(current))) {
|
|
145
145
|
break;
|
|
@@ -178,9 +178,7 @@ function schemaTypeLabel(value: unknown): string {
|
|
|
178
178
|
: Array.isArray(def?.options)
|
|
179
179
|
? def.options
|
|
180
180
|
: [];
|
|
181
|
-
label = rawValues.length > 0
|
|
182
|
-
? rawValues.map((entry) => JSON.stringify(entry)).join(' | ')
|
|
183
|
-
: 'enum';
|
|
181
|
+
label = rawValues.length > 0 ? rawValues.map((entry) => JSON.stringify(entry)).join(' | ') : 'enum';
|
|
184
182
|
} else if (typeName === 'ZodLiteral' || typeName === 'literal') {
|
|
185
183
|
label = JSON.stringify(def?.value ?? def?.literal ?? 'literal');
|
|
186
184
|
} else if (typeName === 'ZodAny' || typeName === 'any') {
|
|
@@ -255,9 +253,7 @@ export function validateShadcnElementProps(spec: unknown): JsonRenderValidationR
|
|
|
255
253
|
if (!definition || !hasSafeParse(definition.props)) continue;
|
|
256
254
|
|
|
257
255
|
const elementProps = asRecord(element.props) ?? {};
|
|
258
|
-
const parsed = definition.props.safeParse(
|
|
259
|
-
normalizePropsForSchema(definition.props, elementProps),
|
|
260
|
-
);
|
|
256
|
+
const parsed = definition.props.safeParse(normalizePropsForSchema(definition.props, elementProps));
|
|
261
257
|
if (parsed.success) continue;
|
|
262
258
|
|
|
263
259
|
for (const issue of parsed.error?.issues ?? []) {
|
|
@@ -120,8 +120,9 @@ export function useChartFrameHeight(explicitHeight: number | null | undefined, f
|
|
|
120
120
|
|
|
121
121
|
// Standalone "Open as site" tab (#65): fill the full browser viewport — there is no
|
|
122
122
|
// card chrome below the chart, so drop the ~44px reserve and use a larger floor.
|
|
123
|
-
const isSite =
|
|
124
|
-
|
|
123
|
+
const isSite =
|
|
124
|
+
typeof window !== 'undefined' &&
|
|
125
|
+
(window as { __PMX_CANVAS_JSON_RENDER_DISPLAY__?: string }).__PMX_CANVAS_JSON_RENDER_DISPLAY__ === 'site';
|
|
125
126
|
|
|
126
127
|
useEffect(() => {
|
|
127
128
|
const frame = frameRef.current;
|
|
@@ -169,16 +170,21 @@ export function useChartFrameHeight(explicitHeight: number | null | undefined, f
|
|
|
169
170
|
// the node/viewport height. That makes the document's scrollHeight stable so the
|
|
170
171
|
// node can grow to it once and converge (no fill-down feedback loop). When NOT in
|
|
171
172
|
// content-fit (strictSize / user-resized nodes), it fills the frame down as before.
|
|
172
|
-
const fitContent =
|
|
173
|
-
|
|
173
|
+
const fitContent =
|
|
174
|
+
typeof window !== 'undefined' &&
|
|
175
|
+
(window as { __PMX_CANVAS_FIT_CONTENT__?: boolean }).__PMX_CANVAS_FIT_CONTENT__ === true;
|
|
174
176
|
// Site mode (#65): fill the viewport (autoHeight), ignoring an explicit/configured
|
|
175
177
|
// chart height that would otherwise cap it to a shallow card. Content-fit is off in
|
|
176
178
|
// site mode (the server skips it), so site never takes the intrinsic-height branch.
|
|
177
179
|
const height = isSite
|
|
178
180
|
? autoHeight
|
|
179
181
|
: fitContent
|
|
180
|
-
?
|
|
181
|
-
|
|
182
|
+
? typeof explicitHeight === 'number'
|
|
183
|
+
? explicitHeight
|
|
184
|
+
: fallbackHeight
|
|
185
|
+
: typeof explicitHeight === 'number'
|
|
186
|
+
? Math.min(explicitHeight, autoHeight)
|
|
187
|
+
: autoHeight;
|
|
182
188
|
return {
|
|
183
189
|
frameRef,
|
|
184
190
|
height,
|
|
@@ -251,11 +257,7 @@ function ChartLineChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
|
251
257
|
* 'max'/'min' pick the tallest/shortest yKey value; a number is used as-is
|
|
252
258
|
* (clamped to range); null/out-of-range means no bar is emphasized.
|
|
253
259
|
*/
|
|
254
|
-
function resolveHighlightIndex(
|
|
255
|
-
data: Record<string, unknown>[],
|
|
256
|
-
yKey: string,
|
|
257
|
-
highlight: BarHighlight,
|
|
258
|
-
): number {
|
|
260
|
+
function resolveHighlightIndex(data: Record<string, unknown>[], yKey: string, highlight: BarHighlight): number {
|
|
259
261
|
if (highlight === null || data.length === 0) return -1;
|
|
260
262
|
if (typeof highlight === 'number') {
|
|
261
263
|
return highlight >= 0 && highlight < data.length ? highlight : -1;
|
|
@@ -299,9 +301,7 @@ function barCellFill(
|
|
|
299
301
|
case 'series':
|
|
300
302
|
default:
|
|
301
303
|
// Tufte-safe emphasis: one accent bar, the rest a muted version of it.
|
|
302
|
-
return index === highlightIndex
|
|
303
|
-
? accent
|
|
304
|
-
: `color-mix(in oklch, ${accent} 32%, transparent)`;
|
|
304
|
+
return index === highlightIndex ? accent : `color-mix(in oklch, ${accent} 32%, transparent)`;
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
@@ -317,8 +317,7 @@ function ChartBarChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
|
317
317
|
min: values.length ? Math.min(...values) : 0,
|
|
318
318
|
max: values.length ? Math.max(...values) : 0,
|
|
319
319
|
};
|
|
320
|
-
const highlightIndex =
|
|
321
|
-
mode === 'series' ? resolveHighlightIndex(data, props.yKey, highlight) : -1;
|
|
320
|
+
const highlightIndex = mode === 'series' ? resolveHighlightIndex(data, props.yKey, highlight) : -1;
|
|
322
321
|
return (
|
|
323
322
|
<RechartsBarChart data={data} margin={chartMargin}>
|
|
324
323
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
@@ -327,10 +326,7 @@ function ChartBarChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
|
327
326
|
<Tooltip contentStyle={tooltipStyle} cursor={false} />
|
|
328
327
|
<Bar dataKey={props.yKey} radius={[4, 4, 0, 0]}>
|
|
329
328
|
{data.map((_, i) => (
|
|
330
|
-
<Cell
|
|
331
|
-
key={i}
|
|
332
|
-
fill={barCellFill(mode, accent, i, values[i], range, highlightIndex)}
|
|
333
|
-
/>
|
|
329
|
+
<Cell key={i} fill={barCellFill(mode, accent, i, values[i], range, highlightIndex)} />
|
|
334
330
|
))}
|
|
335
331
|
</Bar>
|
|
336
332
|
</RechartsBarChart>
|
|
@@ -136,16 +136,7 @@ function ChartRadarChart({ props }: BaseComponentProps<RadarChartProps>) {
|
|
|
136
136
|
{props.showLegend !== false && <Legend wrapperStyle={legendMargin} />}
|
|
137
137
|
{metrics.map((metric, i) => {
|
|
138
138
|
const color = CHART_COLORS[i % CHART_COLORS.length];
|
|
139
|
-
return
|
|
140
|
-
<Radar
|
|
141
|
-
key={metric}
|
|
142
|
-
name={metric}
|
|
143
|
-
dataKey={metric}
|
|
144
|
-
stroke={color}
|
|
145
|
-
fill={color}
|
|
146
|
-
fillOpacity={0.25}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
139
|
+
return <Radar key={metric} name={metric} dataKey={metric} stroke={color} fill={color} fillOpacity={0.25} />;
|
|
149
140
|
})}
|
|
150
141
|
</RechartsRadarChart>
|
|
151
142
|
</ResponsiveContainer>
|
|
@@ -167,7 +158,7 @@ function ChartStackedBarChart({ props }: BaseComponentProps<StackedBarChartProps
|
|
|
167
158
|
const series = (props.series ?? []).filter((s) => typeof s === 'string' && s.length > 0);
|
|
168
159
|
const chartData = props.aggregate
|
|
169
160
|
? mergeAggregated(props.data ?? [], props.xKey, series, props.aggregate)
|
|
170
|
-
: props.data ?? [];
|
|
161
|
+
: (props.data ?? []);
|
|
171
162
|
const { frameRef, height } = useChartFrameHeight(props.height, 300);
|
|
172
163
|
|
|
173
164
|
return (
|
|
@@ -212,11 +203,12 @@ function mergeAggregated(
|
|
|
212
203
|
bucket[s].push(Number.isNaN(n) ? 0 : n);
|
|
213
204
|
}
|
|
214
205
|
}
|
|
215
|
-
const reducer =
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
206
|
+
const reducer =
|
|
207
|
+
aggregate === 'count'
|
|
208
|
+
? (vs: number[]) => vs.length
|
|
209
|
+
: aggregate === 'avg'
|
|
210
|
+
? (vs: number[]) => vs.reduce((a, b) => a + b, 0) / vs.length
|
|
211
|
+
: (vs: number[]) => vs.reduce((a, b) => a + b, 0);
|
|
220
212
|
return Array.from(grouped.entries()).map(([key, buckets]) => {
|
|
221
213
|
const out: Record<string, unknown> = { [xKey]: key };
|
|
222
214
|
for (const s of series) out[s] = reducer(buckets[s] ?? []);
|
|
@@ -130,8 +130,7 @@ export const extraChartComponentDefinitions = {
|
|
|
130
130
|
height: z.number().nullable(),
|
|
131
131
|
showLegend: z.boolean().optional(),
|
|
132
132
|
}),
|
|
133
|
-
description:
|
|
134
|
-
'Combined bar + line chart for paired metrics (e.g. counts + a derived rate) on the same axis.',
|
|
133
|
+
description: 'Combined bar + line chart for paired metrics (e.g. counts + a derived rate) on the same axis.',
|
|
135
134
|
example: {
|
|
136
135
|
title: 'Visits and conversion',
|
|
137
136
|
data: [
|
|
@@ -100,9 +100,7 @@ function ChartSparkline({ props }: BaseComponentProps<SparklineProps>) {
|
|
|
100
100
|
role="img"
|
|
101
101
|
aria-label={props.title ?? 'sparkline'}
|
|
102
102
|
>
|
|
103
|
-
{props.fill && areaPath &&
|
|
104
|
-
<path d={areaPath} fill={stroke} fillOpacity={0.12} stroke="none" />
|
|
105
|
-
)}
|
|
103
|
+
{props.fill && areaPath && <path d={areaPath} fill={stroke} fillOpacity={0.12} stroke="none" />}
|
|
106
104
|
{points.length > 1 && (
|
|
107
105
|
<path d={linePath} fill="none" stroke={stroke} strokeWidth={1.5} vectorEffect="non-scaling-stroke" />
|
|
108
106
|
)}
|
|
@@ -166,7 +164,13 @@ function ChartDotPlot({ props }: BaseComponentProps<DotPlotProps>) {
|
|
|
166
164
|
return (
|
|
167
165
|
<div ref={frameRef} className="pmx-chart pmx-chart--dot-plot" style={{ height }}>
|
|
168
166
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
169
|
-
<svg
|
|
167
|
+
<svg
|
|
168
|
+
className="pmx-chart__dot-plot-svg"
|
|
169
|
+
width="100%"
|
|
170
|
+
height={rows.length * rowH}
|
|
171
|
+
role="img"
|
|
172
|
+
aria-label={props.title ?? 'dot plot'}
|
|
173
|
+
>
|
|
170
174
|
{rows.map((row, i) => {
|
|
171
175
|
const cy = i * rowH + rowH / 2;
|
|
172
176
|
// Reference rule runs from the axis origin to the dot, so the line's
|
|
@@ -245,12 +249,17 @@ function ChartBulletChart({ props }: BaseComponentProps<BulletChartProps>) {
|
|
|
245
249
|
return (
|
|
246
250
|
<div ref={frameRef} className="pmx-chart pmx-chart--bullet" style={{ height }}>
|
|
247
251
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
248
|
-
<svg
|
|
252
|
+
<svg
|
|
253
|
+
className="pmx-chart__bullet-svg"
|
|
254
|
+
width="100%"
|
|
255
|
+
height={rows.length * rowH}
|
|
256
|
+
role="img"
|
|
257
|
+
aria-label={props.title ?? 'bullet chart'}
|
|
258
|
+
>
|
|
249
259
|
{rows.map((row, i) => {
|
|
250
260
|
const top = i * rowH;
|
|
251
261
|
const cy = top + rowH / 2;
|
|
252
|
-
const domainMax =
|
|
253
|
-
Math.max(row.value, row.target ?? 0, ...(row.ranges ?? [0])) || 1;
|
|
262
|
+
const domainMax = Math.max(row.value, row.target ?? 0, ...(row.ranges ?? [0])) || 1;
|
|
254
263
|
const ranges = row.ranges ?? [];
|
|
255
264
|
const left = labelW + padX;
|
|
256
265
|
const rightInset = padX;
|
|
@@ -261,9 +270,11 @@ function ChartBulletChart({ props }: BaseComponentProps<BulletChartProps>) {
|
|
|
261
270
|
const barH = Math.min(20, rowH * 0.5);
|
|
262
271
|
const measureH = barH * 0.4;
|
|
263
272
|
// Qualitative bands: lightest (worst) to darkest (best) grayscale.
|
|
264
|
-
const bandShades = [
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
const bandShades = [
|
|
274
|
+
'color-mix(in oklch, var(--muted) 35%, transparent)',
|
|
275
|
+
'color-mix(in oklch, var(--muted) 60%, transparent)',
|
|
276
|
+
'color-mix(in oklch, var(--muted) 90%, transparent)',
|
|
277
|
+
];
|
|
267
278
|
return (
|
|
268
279
|
<g key={`${row.label}-${i}`}>
|
|
269
280
|
<text x={labelW} y={cy} textAnchor="end" dominantBaseline="central" fontSize={12} fill={INK}>
|
|
@@ -285,7 +296,14 @@ function ChartBulletChart({ props }: BaseComponentProps<BulletChartProps>) {
|
|
|
285
296
|
{/* Per-row scale ticks at each band boundary so the reader does not
|
|
286
297
|
compare bar lengths across rows that may be independently scaled. */}
|
|
287
298
|
{ranges.map((hi, idx) => (
|
|
288
|
-
<text
|
|
299
|
+
<text
|
|
300
|
+
key={`tick-${idx}`}
|
|
301
|
+
x={xAt(hi)}
|
|
302
|
+
y={cy + barH / 2 + 10}
|
|
303
|
+
textAnchor="middle"
|
|
304
|
+
fontSize={9}
|
|
305
|
+
fill={MUTED}
|
|
306
|
+
>
|
|
289
307
|
{hi}
|
|
290
308
|
</text>
|
|
291
309
|
))}
|
|
@@ -345,7 +363,13 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
|
|
|
345
363
|
return (
|
|
346
364
|
<div ref={frameRef} className="pmx-chart pmx-chart--slopegraph" style={{ height }}>
|
|
347
365
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
348
|
-
<svg
|
|
366
|
+
<svg
|
|
367
|
+
className="pmx-chart__slopegraph-svg"
|
|
368
|
+
width="100%"
|
|
369
|
+
height={plotH}
|
|
370
|
+
role="img"
|
|
371
|
+
aria-label={props.title ?? 'slopegraph'}
|
|
372
|
+
>
|
|
349
373
|
<text x={leftX} y={14} textAnchor="end" fontSize={12} fontWeight={600} fill={MUTED}>
|
|
350
374
|
{props.beforeLabel ?? props.beforeKey}
|
|
351
375
|
</text>
|
|
@@ -363,14 +387,7 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
|
|
|
363
387
|
const lineColor = props.colorByDirection ? (rose ? stroke : MUTED) : stroke;
|
|
364
388
|
return (
|
|
365
389
|
<g key={`${row.label}-${i}`}>
|
|
366
|
-
<line
|
|
367
|
-
x1={leftX}
|
|
368
|
-
y1={y1}
|
|
369
|
-
x2={rightX}
|
|
370
|
-
y2={y2}
|
|
371
|
-
stroke={lineColor}
|
|
372
|
-
strokeWidth={1.5}
|
|
373
|
-
/>
|
|
390
|
+
<line x1={leftX} y1={y1} x2={rightX} y2={y2} stroke={lineColor} strokeWidth={1.5} />
|
|
374
391
|
<circle cx={leftX} cy={y1} r={2.5} fill={lineColor} />
|
|
375
392
|
<circle cx={rightX} cy={y2} r={2.5} fill={lineColor} />
|
|
376
393
|
<text x={leftX - 8} y={y1} textAnchor="end" dominantBaseline="central" fontSize={11} fill={INK}>
|
|
@@ -25,7 +25,13 @@ export const tufteChartComponentDefinitions = {
|
|
|
25
25
|
'Word-sized sparkline: a single trend line with no axes, grid, or labels. Optional end dot, min/max markers, light area fill, and an inline last value. The canonical Tufte primitive for showing a trajectory in minimal space.',
|
|
26
26
|
example: {
|
|
27
27
|
title: 'Latency p95',
|
|
28
|
-
data: [
|
|
28
|
+
data: [
|
|
29
|
+
{ t: 0, ms: 120 },
|
|
30
|
+
{ t: 1, ms: 138 },
|
|
31
|
+
{ t: 2, ms: 117 },
|
|
32
|
+
{ t: 3, ms: 152 },
|
|
33
|
+
{ t: 4, ms: 109 },
|
|
34
|
+
],
|
|
29
35
|
valueKey: 'ms',
|
|
30
36
|
color: null,
|
|
31
37
|
fill: true,
|
|
@@ -107,7 +113,7 @@ export const tufteChartComponentDefinitions = {
|
|
|
107
113
|
height: z.number().nullable(),
|
|
108
114
|
}),
|
|
109
115
|
description:
|
|
110
|
-
|
|
116
|
+
'Tufte slopegraph: two value columns (before/after) with a connecting line per category. Lines use one neutral ink by default; set colorByDirection to accent rising lines and mute falling ones. Ideal for paired change across many items.',
|
|
111
117
|
example: {
|
|
112
118
|
title: 'Coverage before/after refactor',
|
|
113
119
|
data: [
|
|
@@ -19,7 +19,16 @@ import { tufteChartComponents } from '../charts/tufte-components';
|
|
|
19
19
|
import { pmxCanvasDirectives } from '../directives';
|
|
20
20
|
import { JsonRenderDevtools } from '@json-render/devtools-react';
|
|
21
21
|
|
|
22
|
-
type BadgeVariant =
|
|
22
|
+
type BadgeVariant =
|
|
23
|
+
| 'default'
|
|
24
|
+
| 'secondary'
|
|
25
|
+
| 'destructive'
|
|
26
|
+
| 'outline'
|
|
27
|
+
| 'success'
|
|
28
|
+
| 'info'
|
|
29
|
+
| 'warning'
|
|
30
|
+
| 'error'
|
|
31
|
+
| 'danger';
|
|
23
32
|
type BadgeProps = {
|
|
24
33
|
text: string;
|
|
25
34
|
variant?: BadgeVariant | null;
|
|
@@ -30,11 +39,7 @@ function Badge({ props }: { props: BadgeProps }) {
|
|
|
30
39
|
const variant = props.variant;
|
|
31
40
|
const resolvedVariant = variant ?? 'default';
|
|
32
41
|
return (
|
|
33
|
-
<span
|
|
34
|
-
data-slot="badge"
|
|
35
|
-
data-variant={resolvedVariant}
|
|
36
|
-
className={`pmx-badge pmx-badge--${resolvedVariant}`}
|
|
37
|
-
>
|
|
42
|
+
<span data-slot="badge" data-variant={resolvedVariant} className={`pmx-badge pmx-badge--${resolvedVariant}`}>
|
|
38
43
|
{props.text}
|
|
39
44
|
</span>
|
|
40
45
|
);
|
|
@@ -111,9 +116,17 @@ function AxStateSync() {
|
|
|
111
116
|
// validates + submits through the capability-gated endpoint). Convention-based
|
|
112
117
|
// opt-in: spec authors name the action handler after the AX interaction type.
|
|
113
118
|
const AX_INTERACTION_HANDLER_NAMES = [
|
|
114
|
-
'ax.event.record',
|
|
115
|
-
'ax.
|
|
116
|
-
'ax.
|
|
119
|
+
'ax.event.record',
|
|
120
|
+
'ax.steer',
|
|
121
|
+
'ax.work.create',
|
|
122
|
+
'ax.work.update',
|
|
123
|
+
'ax.evidence.add',
|
|
124
|
+
'ax.approval.request',
|
|
125
|
+
'ax.review.add',
|
|
126
|
+
'ax.focus.set',
|
|
127
|
+
'ax.elicitation.request',
|
|
128
|
+
'ax.mode.request',
|
|
129
|
+
'ax.command.invoke',
|
|
117
130
|
] as const;
|
|
118
131
|
|
|
119
132
|
function buildAxHandlers(): Record<string, (params: Record<string, unknown>) => void> {
|
|
@@ -126,12 +139,15 @@ function buildAxHandlers(): Record<string, (params: Record<string, unknown>) =>
|
|
|
126
139
|
// is no JS surface for a Promise-style ack here, so we don't stamp a correlationId.
|
|
127
140
|
for (const type of AX_INTERACTION_HANDLER_NAMES) {
|
|
128
141
|
handlers[type] = (params: Record<string, unknown>) => {
|
|
129
|
-
window.parent.postMessage(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
window.parent.postMessage(
|
|
143
|
+
{
|
|
144
|
+
source: 'pmx-canvas-ax',
|
|
145
|
+
token,
|
|
146
|
+
nodeId,
|
|
147
|
+
interaction: { type, payload: params && typeof params === 'object' ? params : {} },
|
|
148
|
+
},
|
|
149
|
+
'*',
|
|
150
|
+
);
|
|
135
151
|
};
|
|
136
152
|
}
|
|
137
153
|
return handlers;
|
|
@@ -176,16 +192,22 @@ function App() {
|
|
|
176
192
|
|
|
177
193
|
// Seed AX state under a reserved `/ax` key so specs can bind { "$state": "/ax/workItems" }.
|
|
178
194
|
const axState = window.__PMX_CANVAS_AX_STATE__;
|
|
179
|
-
const initialState =
|
|
180
|
-
? { ...(spec.state ?? {}), ax: axState }
|
|
181
|
-
: spec.state ?? undefined;
|
|
195
|
+
const initialState =
|
|
196
|
+
axState !== undefined && axState !== null ? { ...(spec.state ?? {}), ax: axState } : (spec.state ?? undefined);
|
|
182
197
|
|
|
183
198
|
// Standalone "Open as site" tab (#65): fill the browser viewport instead of the
|
|
184
199
|
// in-canvas card height. The chart child flex-grows; useChartFrameHeight measures
|
|
185
200
|
// the full viewport in this mode. Embedded/expanded keep the padded min-height box.
|
|
186
201
|
const isSite = window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ === 'site';
|
|
187
202
|
const containerStyle = isSite
|
|
188
|
-
? {
|
|
203
|
+
? {
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column' as const,
|
|
206
|
+
height: '100dvh',
|
|
207
|
+
minHeight: '100dvh',
|
|
208
|
+
padding: 0,
|
|
209
|
+
boxSizing: 'border-box' as const,
|
|
210
|
+
}
|
|
189
211
|
: { minHeight: '100vh', padding: 16, boxSizing: 'border-box' as const };
|
|
190
212
|
return (
|
|
191
213
|
<div style={containerStyle}>
|
|
@@ -199,9 +221,7 @@ function App() {
|
|
|
199
221
|
<div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
|
|
200
222
|
<Renderer spec={spec} registry={registry} loading={false} />
|
|
201
223
|
</div>
|
|
202
|
-
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ?
|
|
203
|
-
<JsonRenderDevtools position="right" />
|
|
204
|
-
) : null}
|
|
224
|
+
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? <JsonRenderDevtools position="right" /> : null}
|
|
205
225
|
</JSONUIProvider>
|
|
206
226
|
</div>
|
|
207
227
|
);
|
|
@@ -34,11 +34,13 @@ export const schema = defineSchema(
|
|
|
34
34
|
builtInActions: [
|
|
35
35
|
{
|
|
36
36
|
name: 'setState',
|
|
37
|
-
description:
|
|
37
|
+
description:
|
|
38
|
+
'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
name: 'pushState',
|
|
41
|
-
description:
|
|
42
|
+
description:
|
|
43
|
+
'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
46
|
name: 'removeState',
|
|
@@ -46,7 +48,8 @@ export const schema = defineSchema(
|
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
50
|
name: 'validateForm',
|
|
49
|
-
description:
|
|
51
|
+
description:
|
|
52
|
+
'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
|
|
50
53
|
},
|
|
51
54
|
],
|
|
52
55
|
defaultRules: [
|
|
@@ -212,9 +212,7 @@ async function ensureJsonRenderBundle(): Promise<void> {
|
|
|
212
212
|
// Avoid live source-vs-dist rebuild checks here because Bun's bundler can stall on
|
|
213
213
|
// the @json-render/shadcn dependency graph during request-time viewer generation.
|
|
214
214
|
const needsBuild =
|
|
215
|
-
!existsSync(jsPath) ||
|
|
216
|
-
!existsSync(cssPath) ||
|
|
217
|
-
process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
|
|
215
|
+
!existsSync(jsPath) || !existsSync(cssPath) || process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
|
|
218
216
|
|
|
219
217
|
if (needsBuild) {
|
|
220
218
|
await rebuildJsonRenderBundle();
|
|
@@ -273,7 +271,7 @@ function normalizeItemArray(value: unknown): unknown {
|
|
|
273
271
|
if (hasString(item)) return { label: item, value: item };
|
|
274
272
|
const record = asRecord(item);
|
|
275
273
|
const label = hasString(record?.label) ? record.label : hasString(record?.text) ? record.text : null;
|
|
276
|
-
const resolvedValue = hasString(record?.value) ? record.value : label ?? `option-${index + 1}
|
|
274
|
+
const resolvedValue = hasString(record?.value) ? record.value : (label ?? `option-${index + 1}`);
|
|
277
275
|
return {
|
|
278
276
|
label: label ?? resolvedValue,
|
|
279
277
|
value: resolvedValue,
|
|
@@ -283,11 +281,7 @@ function normalizeItemArray(value: unknown): unknown {
|
|
|
283
281
|
|
|
284
282
|
function normalizeStringMatrix(value: unknown): unknown {
|
|
285
283
|
if (!Array.isArray(value)) return value;
|
|
286
|
-
return value.map((row) => (
|
|
287
|
-
Array.isArray(row)
|
|
288
|
-
? row.map((cell) => String(cell ?? ''))
|
|
289
|
-
: [String(row ?? '')]
|
|
290
|
-
));
|
|
284
|
+
return value.map((row) => (Array.isArray(row) ? row.map((cell) => String(cell ?? '')) : [String(row ?? '')]));
|
|
291
285
|
}
|
|
292
286
|
|
|
293
287
|
function normalizeButtonVariant(value: unknown): unknown {
|
|
@@ -347,12 +341,7 @@ function normalizeElementProps(
|
|
|
347
341
|
const props = (stripNullishDeep(rawProps) as Record<string, unknown> | undefined) ?? {};
|
|
348
342
|
|
|
349
343
|
for (const key of COERCIBLE_STRING_PROPS) {
|
|
350
|
-
if (
|
|
351
|
-
key in props &&
|
|
352
|
-
typeof props[key] !== 'string' &&
|
|
353
|
-
props[key] !== undefined &&
|
|
354
|
-
!isDynamicPropValue(props[key])
|
|
355
|
-
) {
|
|
344
|
+
if (key in props && typeof props[key] !== 'string' && props[key] !== undefined && !isDynamicPropValue(props[key])) {
|
|
356
345
|
props[key] = String(props[key]);
|
|
357
346
|
}
|
|
358
347
|
}
|
|
@@ -516,7 +505,9 @@ export function inferJsonRenderNodeTitle(spec: JsonRenderSpec, fallback = 'json-
|
|
|
516
505
|
export function normalizeAndValidateJsonRenderSpec(spec: unknown): JsonRenderSpec {
|
|
517
506
|
const specRecord = asRecord(normalizeJsonRenderInput(spec));
|
|
518
507
|
if (!specRecord || typeof specRecord.root !== 'string' || !asRecord(specRecord.elements)) {
|
|
519
|
-
throw new Error(
|
|
508
|
+
throw new Error(
|
|
509
|
+
'Missing root and elements in spec. Pass a complete {root, elements} document, or a single bare component object with a type field.',
|
|
510
|
+
);
|
|
520
511
|
}
|
|
521
512
|
|
|
522
513
|
// Reject an unrecognized $-keyed expression object in any element prop BEFORE
|
|
@@ -576,10 +567,7 @@ function collectDataKeys(data: Array<Record<string, unknown>>): Set<string> {
|
|
|
576
567
|
* as "value") without an explicit valueKey, instead of failing the data-key
|
|
577
568
|
* check on a reasonable guess.
|
|
578
569
|
*/
|
|
579
|
-
function firstPresentDataKey(
|
|
580
|
-
data: Array<Record<string, unknown>>,
|
|
581
|
-
candidates: string[],
|
|
582
|
-
): string | undefined {
|
|
570
|
+
function firstPresentDataKey(data: Array<Record<string, unknown>>, candidates: string[]): string | undefined {
|
|
583
571
|
const keys = collectDataKeys(data);
|
|
584
572
|
return candidates.find((key) => keys.has(key));
|
|
585
573
|
}
|
|
@@ -653,7 +641,9 @@ function assertGraphDataKeys(
|
|
|
653
641
|
const missing = [...required].filter((key) => !available.has(key));
|
|
654
642
|
if (missing.length === 0) return;
|
|
655
643
|
const availableList = [...available].sort().join(', ') || '(none)';
|
|
656
|
-
throw new Error(
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Graph data key mismatch for ${chartType}: missing ${missing.join(', ')}. Available keys: ${availableList}.`,
|
|
646
|
+
);
|
|
657
647
|
}
|
|
658
648
|
|
|
659
649
|
export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
@@ -685,11 +675,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
685
675
|
}
|
|
686
676
|
case 'RadarChart': {
|
|
687
677
|
const axisKey = input.axisKey ?? input.xKey ?? 'axis';
|
|
688
|
-
const metrics = input.metrics?.length
|
|
689
|
-
? input.metrics
|
|
690
|
-
: inferKeysFromData(input.data, [axisKey]);
|
|
678
|
+
const metrics = input.metrics?.length ? input.metrics : inferKeysFromData(input.data, [axisKey]);
|
|
691
679
|
if (metrics.length === 0) {
|
|
692
|
-
throw new Error(
|
|
680
|
+
throw new Error(
|
|
681
|
+
'RadarChart requires at least one metric key (provide `metrics` or include numeric columns in `data`).',
|
|
682
|
+
);
|
|
693
683
|
}
|
|
694
684
|
chartProps.axisKey = axisKey;
|
|
695
685
|
chartProps.metrics = metrics;
|
|
@@ -698,11 +688,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
698
688
|
}
|
|
699
689
|
case 'StackedBarChart': {
|
|
700
690
|
const xKey = input.xKey ?? 'label';
|
|
701
|
-
const series = input.series?.length
|
|
702
|
-
? input.series
|
|
703
|
-
: inferKeysFromData(input.data, [xKey]);
|
|
691
|
+
const series = input.series?.length ? input.series : inferKeysFromData(input.data, [xKey]);
|
|
704
692
|
if (series.length === 0) {
|
|
705
|
-
throw new Error(
|
|
693
|
+
throw new Error(
|
|
694
|
+
'StackedBarChart requires at least one series key (provide `series` or include numeric columns in `data`).',
|
|
695
|
+
);
|
|
706
696
|
}
|
|
707
697
|
chartProps.xKey = xKey;
|
|
708
698
|
chartProps.series = series;
|
|
@@ -713,9 +703,10 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
|
|
|
713
703
|
case 'ComposedChart': {
|
|
714
704
|
chartProps.xKey = input.xKey ?? 'label';
|
|
715
705
|
chartProps.barKey = input.barKey ?? input.yKey ?? 'value';
|
|
716
|
-
chartProps.lineKey =
|
|
717
|
-
|
|
718
|
-
??
|
|
706
|
+
chartProps.lineKey =
|
|
707
|
+
input.lineKey ??
|
|
708
|
+
inferKeysFromData(input.data, [chartProps.xKey as string, chartProps.barKey as string])[0] ??
|
|
709
|
+
'rate';
|
|
719
710
|
chartProps.barColor = input.barColor ?? null;
|
|
720
711
|
chartProps.lineColor = input.lineColor ?? null;
|
|
721
712
|
chartProps.showLegend = input.showLegend !== false;
|
|
@@ -952,7 +943,8 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
952
943
|
* node grows to it. Off for strictSize / user-resized nodes (they fill-down). */
|
|
953
944
|
fitContent?: boolean;
|
|
954
945
|
}): Promise<string> {
|
|
955
|
-
const sanitizeAxValue = (v?: string): string =>
|
|
946
|
+
const sanitizeAxValue = (v?: string): string =>
|
|
947
|
+
typeof v === 'string' ? v.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) : '';
|
|
956
948
|
try {
|
|
957
949
|
await ensureJsonRenderBundle();
|
|
958
950
|
const dir = bundleDir();
|
|
@@ -967,12 +959,14 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
967
959
|
...(options.theme ? [`window.__PMX_CANVAS_JSON_RENDER_THEME__ = ${JSON.stringify(options.theme)};`] : []),
|
|
968
960
|
...(options.display ? [`window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ = ${JSON.stringify(options.display)};`] : []),
|
|
969
961
|
...(options.devtools ? ['window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ = true;'] : []),
|
|
970
|
-
...(options.nodeId && options.axToken
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
962
|
+
...(options.nodeId && options.axToken
|
|
963
|
+
? [
|
|
964
|
+
`window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`,
|
|
965
|
+
`window.__PMX_CANVAS_AX_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.axToken))};`,
|
|
966
|
+
// Read-side AX state: seed for initial render + bound under /ax for specs.
|
|
967
|
+
`window.__PMX_CANVAS_AX_STATE__ = ${JSON.stringify(options.axState ?? null).replace(/</g, '\\u003c')};`,
|
|
968
|
+
]
|
|
969
|
+
: []),
|
|
976
970
|
...(options.fitContent ? ['window.__PMX_CANVAS_FIT_CONTENT__ = true;'] : []),
|
|
977
971
|
jsBundle,
|
|
978
972
|
].join('\n');
|