pmx-canvas 0.1.26 → 0.1.28
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/.github/extensions/pmx-canvas/extension.mjs +191 -0
- package/CHANGELOG.md +110 -0
- package/Readme.md +74 -27
- package/dist/canvas/index.js +82 -82
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +944 -164
- package/dist/types/json-render/catalog.d.ts +195 -20
- package/dist/types/json-render/charts/components.d.ts +17 -0
- package/dist/types/json-render/charts/definitions.d.ts +13 -1
- package/dist/types/json-render/charts/tufte-components.d.ts +65 -0
- package/dist/types/json-render/charts/tufte-definitions.d.ts +164 -0
- package/dist/types/json-render/directives.d.ts +33 -0
- package/dist/types/json-render/renderer/index.d.ts +1 -0
- package/dist/types/json-render/server.d.ts +32 -1
- package/dist/types/mcp/canvas-access.d.ts +62 -0
- package/dist/types/server/ax-state.d.ts +170 -0
- package/dist/types/server/canvas-db.d.ts +17 -1
- package/dist/types/server/canvas-operations.d.ts +53 -0
- package/dist/types/server/canvas-schema.d.ts +5 -1
- package/dist/types/server/canvas-state.d.ts +95 -4
- package/dist/types/server/index.d.ts +120 -3
- package/dist/types/server/mutation-history.d.ts +1 -1
- package/docs/cli.md +42 -0
- package/docs/http-api.md +64 -0
- package/docs/mcp.md +23 -5
- package/docs/node-types.md +1 -1
- package/docs/screenshots/codex-app.png +0 -0
- package/docs/screenshots/github-copilot-app.png +0 -0
- package/docs/sdk.md +23 -5
- package/package.json +10 -7
- package/skills/control-session-orchestrator/SKILL.md +359 -0
- package/skills/control-session-orchestrator/evals/evals.json +75 -0
- package/skills/data-analysis/SKILL.md +6 -0
- package/skills/pmx-canvas/SKILL.md +50 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +6 -0
- package/skills/tufte-viz/SKILL.md +157 -0
- package/skills/tufte-viz/references/analytical-design.md +217 -0
- package/skills/tufte-viz/references/tufte-principles.md +147 -0
- package/src/cli/agent.ts +302 -3
- package/src/cli/index.ts +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +48 -1
- package/src/client/nodes/McpAppNode.tsx +6 -2
- package/src/json-render/catalog.ts +22 -1
- package/src/json-render/charts/components.tsx +127 -15
- package/src/json-render/charts/definitions.ts +19 -2
- package/src/json-render/charts/extra-components.tsx +5 -4
- package/src/json-render/charts/tufte-components.tsx +395 -0
- package/src/json-render/charts/tufte-definitions.ts +128 -0
- package/src/json-render/directives.ts +64 -0
- package/src/json-render/renderer/index.css +107 -1
- package/src/json-render/renderer/index.tsx +33 -0
- package/src/json-render/server.ts +275 -5
- package/src/mcp/canvas-access.ts +264 -1
- package/src/mcp/server.ts +498 -9
- package/src/server/ax-context.ts +8 -3
- package/src/server/ax-state.ts +447 -0
- package/src/server/canvas-db.ts +184 -1
- package/src/server/canvas-operations.ts +123 -2
- package/src/server/canvas-schema.ts +27 -3
- package/src/server/canvas-state.ts +349 -2
- package/src/server/index.ts +259 -7
- package/src/server/mutation-history.ts +6 -0
- package/src/server/server.ts +442 -5
- package/src/server/web-artifacts.ts +31 -5
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Definitions for the Tufte primitive chart components in ./tufte-components.tsx.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from ./definitions.ts and ./extra-definitions.ts so the
|
|
5
|
+
* original chart catalogs stay untouched and the merge in ./catalog.ts is the
|
|
6
|
+
* only contact surface.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
export const tufteChartComponentDefinitions = {
|
|
12
|
+
Sparkline: {
|
|
13
|
+
props: z.object({
|
|
14
|
+
title: z.string().nullable(),
|
|
15
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
16
|
+
valueKey: z.string(),
|
|
17
|
+
color: z.string().nullable(),
|
|
18
|
+
fill: z.boolean().nullable(),
|
|
19
|
+
showEndDot: z.boolean().nullable(),
|
|
20
|
+
showMinMax: z.boolean().nullable(),
|
|
21
|
+
showValue: z.boolean().nullable(),
|
|
22
|
+
height: z.number().nullable(),
|
|
23
|
+
}),
|
|
24
|
+
description:
|
|
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
|
+
example: {
|
|
27
|
+
title: 'Latency p95',
|
|
28
|
+
data: [{ t: 0, ms: 120 }, { t: 1, ms: 138 }, { t: 2, ms: 117 }, { t: 3, ms: 152 }, { t: 4, ms: 109 }],
|
|
29
|
+
valueKey: 'ms',
|
|
30
|
+
color: null,
|
|
31
|
+
fill: true,
|
|
32
|
+
showEndDot: true,
|
|
33
|
+
showMinMax: false,
|
|
34
|
+
showValue: true,
|
|
35
|
+
height: null,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
DotPlot: {
|
|
40
|
+
props: z.object({
|
|
41
|
+
title: z.string().nullable(),
|
|
42
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
43
|
+
labelKey: z.string(),
|
|
44
|
+
valueKey: z.string(),
|
|
45
|
+
color: z.string().nullable(),
|
|
46
|
+
sort: z.enum(['asc', 'desc', 'none']).nullable(),
|
|
47
|
+
height: z.number().nullable(),
|
|
48
|
+
}),
|
|
49
|
+
description:
|
|
50
|
+
'Cleveland dot plot: categorical labels down the Y axis, one dot per category positioned by value on X. Higher data-ink ratio than a bar chart for ranked comparison. Sorts descending by default.',
|
|
51
|
+
example: {
|
|
52
|
+
title: 'Build time by package',
|
|
53
|
+
data: [
|
|
54
|
+
{ pkg: 'core', seconds: 42 },
|
|
55
|
+
{ pkg: 'client', seconds: 31 },
|
|
56
|
+
{ pkg: 'mcp', seconds: 18 },
|
|
57
|
+
{ pkg: 'cli', seconds: 9 },
|
|
58
|
+
],
|
|
59
|
+
labelKey: 'pkg',
|
|
60
|
+
valueKey: 'seconds',
|
|
61
|
+
color: null,
|
|
62
|
+
sort: 'desc',
|
|
63
|
+
height: null,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
BulletChart: {
|
|
68
|
+
props: z.object({
|
|
69
|
+
title: z.string().nullable(),
|
|
70
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
71
|
+
labelKey: z.string().nullable(),
|
|
72
|
+
valueKey: z.string(),
|
|
73
|
+
targetKey: z.string().nullable(),
|
|
74
|
+
rangesKey: z.string().nullable(),
|
|
75
|
+
color: z.string().nullable(),
|
|
76
|
+
height: z.number().nullable(),
|
|
77
|
+
}),
|
|
78
|
+
description:
|
|
79
|
+
"Stephen Few's bullet graph: a measure bar against grayscale qualitative bands with a target tick and per-row scale ticks. Compact KPI-vs-target display. Provide per-row `ranges` (ascending band thresholds) and `target`.",
|
|
80
|
+
example: {
|
|
81
|
+
title: 'Quarterly KPIs vs target',
|
|
82
|
+
data: [
|
|
83
|
+
{ label: 'Revenue', value: 84, target: 90, ranges: [50, 75, 100] },
|
|
84
|
+
{ label: 'NPS', value: 67, target: 60, ranges: [40, 60, 80] },
|
|
85
|
+
{ label: 'Uptime', value: 99, target: 99.9, ranges: [95, 99, 100] },
|
|
86
|
+
],
|
|
87
|
+
labelKey: 'label',
|
|
88
|
+
valueKey: 'value',
|
|
89
|
+
targetKey: 'target',
|
|
90
|
+
rangesKey: 'ranges',
|
|
91
|
+
color: null,
|
|
92
|
+
height: null,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
Slopegraph: {
|
|
97
|
+
props: z.object({
|
|
98
|
+
title: z.string().nullable(),
|
|
99
|
+
data: z.array(z.record(z.string(), z.unknown())),
|
|
100
|
+
labelKey: z.string(),
|
|
101
|
+
beforeKey: z.string(),
|
|
102
|
+
afterKey: z.string(),
|
|
103
|
+
beforeLabel: z.string().nullable(),
|
|
104
|
+
afterLabel: z.string().nullable(),
|
|
105
|
+
color: z.string().nullable(),
|
|
106
|
+
colorByDirection: z.boolean().nullable(),
|
|
107
|
+
height: z.number().nullable(),
|
|
108
|
+
}),
|
|
109
|
+
description:
|
|
110
|
+
"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
|
+
example: {
|
|
112
|
+
title: 'Coverage before/after refactor',
|
|
113
|
+
data: [
|
|
114
|
+
{ module: 'auth', before: 62, after: 81 },
|
|
115
|
+
{ module: 'canvas', before: 74, after: 78 },
|
|
116
|
+
{ module: 'mcp', before: 55, after: 49 },
|
|
117
|
+
],
|
|
118
|
+
labelKey: 'module',
|
|
119
|
+
beforeKey: 'before',
|
|
120
|
+
afterKey: 'after',
|
|
121
|
+
beforeLabel: 'Before',
|
|
122
|
+
afterLabel: 'After',
|
|
123
|
+
color: null,
|
|
124
|
+
colorByDirection: null,
|
|
125
|
+
height: null,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
} as const;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom json-render directives available in PMX Canvas specs.
|
|
3
|
+
*
|
|
4
|
+
* Directives let agent specs declare formatting/derivation ($format, $math,
|
|
5
|
+
* $concat, $count, $truncate, $pluralize, $join) instead of pre-formatting
|
|
6
|
+
* strings. Registered into the iframe renderer via <JSONUIProvider directives>.
|
|
7
|
+
*/
|
|
8
|
+
import type { DirectiveDefinition } from '@json-render/core';
|
|
9
|
+
import { standardDirectives } from '@json-render/directives';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Directives enabled in the PMX Canvas viewer. `standardDirectives` covers the
|
|
13
|
+
* seven stateless directives. The $t i18n directive is intentionally omitted —
|
|
14
|
+
* it is a factory requiring locale config and PMX Canvas has no locale source.
|
|
15
|
+
*/
|
|
16
|
+
export const pmxCanvasDirectives: DirectiveDefinition[] = [...standardDirectives];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The $-prefixed keys the renderer can actually resolve: core built-in bindings
|
|
20
|
+
* plus the names of the directives registered above. Anything else (e.g. a
|
|
21
|
+
* hallucinated `$path`) is NOT a valid dynamic expression — to read a value from
|
|
22
|
+
* state by path the correct binding is `$state`.
|
|
23
|
+
*/
|
|
24
|
+
const KNOWN_DYNAMIC_KEYS = new Set<string>([
|
|
25
|
+
'$state',
|
|
26
|
+
'$item',
|
|
27
|
+
'$index',
|
|
28
|
+
'$bindState',
|
|
29
|
+
'$bindItem',
|
|
30
|
+
'$cond',
|
|
31
|
+
'$computed',
|
|
32
|
+
'$template',
|
|
33
|
+
...pmxCanvasDirectives.map((directive) => directive.name),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True when a prop value is a render-time dynamic expression — a directive
|
|
38
|
+
* (`$format`/`$math`/…) or an existing binding (`$state`/`$item`/`$bindItem`/
|
|
39
|
+
* `$cond`/`$template`/`$computed`). These objects are resolved inside the
|
|
40
|
+
* renderer, so the server-side validators must leave them untouched instead of
|
|
41
|
+
* string-coercing them to `"[object Object]"` or rejecting them as the wrong
|
|
42
|
+
* primitive type. An object whose only `$`-key is unrecognized (e.g. `$path`)
|
|
43
|
+
* is NOT dynamic — the renderer has no directive to resolve it.
|
|
44
|
+
*/
|
|
45
|
+
export function isDynamicPropValue(value: unknown): boolean {
|
|
46
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
47
|
+
return Object.keys(value as Record<string, unknown>).some((key) => KNOWN_DYNAMIC_KEYS.has(key));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns the offending key when a prop value looks like a dynamic expression
|
|
52
|
+
* (it has `$`-prefixed keys) but none of them is a recognized binding or
|
|
53
|
+
* registered directive — e.g. `{ "$path": "…" }`. Such objects pass a naive
|
|
54
|
+
* "has a $-key" check yet render as `"[object Object]"` because the renderer has
|
|
55
|
+
* no directive to resolve them. Returns null for plain values and for genuinely
|
|
56
|
+
* dynamic expressions.
|
|
57
|
+
*/
|
|
58
|
+
export function findUnknownDirectiveKey(value: unknown): string | null {
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;
|
|
60
|
+
const dollarKeys = Object.keys(value as Record<string, unknown>).filter((key) => key.startsWith('$'));
|
|
61
|
+
if (dollarKeys.length === 0) return null;
|
|
62
|
+
if (dollarKeys.some((key) => KNOWN_DYNAMIC_KEYS.has(key))) return null;
|
|
63
|
+
return dollarKeys[0] ?? null;
|
|
64
|
+
}
|
|
@@ -204,12 +204,76 @@ button {
|
|
|
204
204
|
color: var(--destructive);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
.pmx-button {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
gap: 0.5rem;
|
|
212
|
+
height: 2.25rem;
|
|
213
|
+
padding: 0.5rem 1rem;
|
|
214
|
+
border: 1px solid transparent;
|
|
215
|
+
border-radius: var(--radius);
|
|
216
|
+
font-size: 0.875rem;
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
line-height: 1.25rem;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.pmx-button:disabled {
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
opacity: 0.5;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.pmx-button--primary {
|
|
230
|
+
background: var(--primary);
|
|
231
|
+
color: var(--primary-foreground);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.pmx-button--secondary {
|
|
235
|
+
background: var(--secondary);
|
|
236
|
+
color: var(--secondary-foreground);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.pmx-button--destructive,
|
|
240
|
+
.pmx-button--danger {
|
|
241
|
+
background: var(--destructive);
|
|
242
|
+
color: var(--destructive-foreground);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.pmx-button--success {
|
|
246
|
+
background: var(--chart-2);
|
|
247
|
+
color: var(--primary-foreground);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.pmx-button--outline {
|
|
251
|
+
border-color: var(--border);
|
|
252
|
+
background: transparent;
|
|
253
|
+
color: var(--foreground);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.pmx-button--ghost {
|
|
257
|
+
background: transparent;
|
|
258
|
+
color: var(--foreground);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.pmx-button--ghost:hover,
|
|
262
|
+
.pmx-button--outline:hover {
|
|
263
|
+
background: color-mix(in oklch, var(--foreground) 8%, transparent);
|
|
264
|
+
}
|
|
265
|
+
|
|
207
266
|
/* -- Chart components -- */
|
|
208
267
|
|
|
209
268
|
.pmx-chart {
|
|
210
269
|
width: 100%;
|
|
211
270
|
min-width: 280px;
|
|
212
|
-
overflow-x:
|
|
271
|
+
/* overflow:visible (not overflow-x:auto) so the chart is NOT its own scroll
|
|
272
|
+
container. overflow-x:auto makes overflow-y compute to auto too (CSS quirk),
|
|
273
|
+
which — once a chart fills the viewport — added a second nested scrollbar on
|
|
274
|
+
top of the iframe document's. Let the single iframe document handle any
|
|
275
|
+
overflow instead. */
|
|
276
|
+
overflow: visible;
|
|
213
277
|
padding: 0.5rem 0;
|
|
214
278
|
}
|
|
215
279
|
|
|
@@ -225,6 +289,48 @@ button {
|
|
|
225
289
|
min-width: 360px;
|
|
226
290
|
}
|
|
227
291
|
|
|
292
|
+
.pmx-chart--dot-plot,
|
|
293
|
+
.pmx-chart--bullet,
|
|
294
|
+
.pmx-chart--slopegraph {
|
|
295
|
+
min-width: 320px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.pmx-chart--sparkline {
|
|
299
|
+
min-width: 160px;
|
|
300
|
+
padding: 0.25rem 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.pmx-chart__sparkline-row {
|
|
304
|
+
display: flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 0.5rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.pmx-chart__sparkline-svg {
|
|
310
|
+
display: block;
|
|
311
|
+
width: 100%;
|
|
312
|
+
height: 36px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.pmx-chart__sparkline-value {
|
|
316
|
+
font-size: 0.8125rem;
|
|
317
|
+
font-weight: 600;
|
|
318
|
+
font-variant-numeric: tabular-nums;
|
|
319
|
+
white-space: nowrap;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.pmx-chart__dot-plot-svg,
|
|
323
|
+
.pmx-chart__bullet-svg,
|
|
324
|
+
.pmx-chart__slopegraph-svg {
|
|
325
|
+
display: block;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.pmx-chart--dot-plot text,
|
|
329
|
+
.pmx-chart--bullet text,
|
|
330
|
+
.pmx-chart--slopegraph text {
|
|
331
|
+
font-variant-numeric: tabular-nums;
|
|
332
|
+
}
|
|
333
|
+
|
|
228
334
|
.pmx-chart__title {
|
|
229
335
|
font-size: 0.875rem;
|
|
230
336
|
font-weight: 600;
|
|
@@ -14,6 +14,9 @@ import { shadcnComponents } from '@json-render/shadcn';
|
|
|
14
14
|
import { catalog } from '../catalog';
|
|
15
15
|
import { chartComponents } from '../charts/components';
|
|
16
16
|
import { extraChartComponents } from '../charts/extra-components';
|
|
17
|
+
import { tufteChartComponents } from '../charts/tufte-components';
|
|
18
|
+
import { pmxCanvasDirectives } from '../directives';
|
|
19
|
+
import { JsonRenderDevtools } from '@json-render/devtools-react';
|
|
17
20
|
|
|
18
21
|
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'info' | 'warning' | 'error' | 'danger';
|
|
19
22
|
type BadgeProps = {
|
|
@@ -36,12 +39,37 @@ function Badge({ props }: { props: BadgeProps }) {
|
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'danger' | 'outline' | 'ghost' | 'success';
|
|
43
|
+
type ButtonProps = {
|
|
44
|
+
label: string;
|
|
45
|
+
variant?: ButtonVariant | null;
|
|
46
|
+
disabled?: boolean | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function Button({ props, emit }: { props: ButtonProps; emit: (event: string) => void }) {
|
|
50
|
+
const resolvedVariant = props.variant ?? 'primary';
|
|
51
|
+
return (
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
data-slot="button"
|
|
55
|
+
data-variant={resolvedVariant}
|
|
56
|
+
className={`pmx-button pmx-button--${resolvedVariant}`}
|
|
57
|
+
disabled={props.disabled ?? false}
|
|
58
|
+
onClick={() => emit('press')}
|
|
59
|
+
>
|
|
60
|
+
{props.label}
|
|
61
|
+
</button>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
const { registry } = defineRegistry(catalog as never, {
|
|
40
66
|
components: {
|
|
41
67
|
...shadcnComponents,
|
|
42
68
|
Badge,
|
|
69
|
+
Button,
|
|
43
70
|
...chartComponents,
|
|
44
71
|
...extraChartComponents,
|
|
72
|
+
...tufteChartComponents,
|
|
45
73
|
} as never,
|
|
46
74
|
});
|
|
47
75
|
|
|
@@ -50,6 +78,7 @@ declare global {
|
|
|
50
78
|
__PMX_CANVAS_JSON_RENDER_SPEC__?: Spec & { state?: Record<string, unknown> };
|
|
51
79
|
__PMX_CANVAS_JSON_RENDER_THEME__?: string;
|
|
52
80
|
__PMX_CANVAS_JSON_RENDER_DISPLAY__?: string;
|
|
81
|
+
__PMX_CANVAS_JSON_RENDER_DEVTOOLS__?: boolean;
|
|
53
82
|
}
|
|
54
83
|
}
|
|
55
84
|
|
|
@@ -95,8 +124,12 @@ function App() {
|
|
|
95
124
|
<JSONUIProvider
|
|
96
125
|
registry={registry}
|
|
97
126
|
initialState={spec.state ?? undefined}
|
|
127
|
+
directives={pmxCanvasDirectives}
|
|
98
128
|
>
|
|
99
129
|
<Renderer spec={spec} registry={registry} loading={false} />
|
|
130
|
+
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? (
|
|
131
|
+
<JsonRenderDevtools position="right" />
|
|
132
|
+
) : null}
|
|
100
133
|
</JSONUIProvider>
|
|
101
134
|
</div>
|
|
102
135
|
);
|