pmx-canvas 0.1.0
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 +38 -0
- package/LICENSE +21 -0
- package/Readme.md +865 -0
- package/dist/canvas/global.css +3173 -0
- package/dist/canvas/index.js +183 -0
- package/dist/json-render/index.css +2 -0
- package/dist/json-render/index.js +389 -0
- package/dist/types/cli/agent.d.ts +13 -0
- package/dist/types/cli/index.d.ts +2 -0
- package/dist/types/cli/watch.d.ts +5 -0
- package/dist/types/client/App.d.ts +1 -0
- package/dist/types/client/canvas/AttentionHistory.d.ts +1 -0
- package/dist/types/client/canvas/AttentionToast.d.ts +1 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +8 -0
- package/dist/types/client/canvas/CanvasViewport.d.ts +8 -0
- package/dist/types/client/canvas/CommandPalette.d.ts +4 -0
- package/dist/types/client/canvas/ContextMenu.d.ts +24 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +1 -0
- package/dist/types/client/canvas/ContextPinHud.d.ts +1 -0
- package/dist/types/client/canvas/DockedNode.d.ts +4 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
- package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -0
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +1 -0
- package/dist/types/client/canvas/Minimap.d.ts +23 -0
- package/dist/types/client/canvas/SelectionBar.d.ts +1 -0
- package/dist/types/client/canvas/ShortcutOverlay.d.ts +3 -0
- package/dist/types/client/canvas/SnapshotPanel.d.ts +7 -0
- package/dist/types/client/canvas/snap-guides.d.ts +23 -0
- package/dist/types/client/canvas/use-node-drag.d.ts +15 -0
- package/dist/types/client/canvas/use-node-resize.d.ts +15 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +16 -0
- package/dist/types/client/ext-app/bridge.d.ts +161 -0
- package/dist/types/client/icons.d.ts +70 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/nodes/ContextNode.d.ts +34 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +18 -0
- package/dist/types/client/nodes/FileNode.d.ts +5 -0
- package/dist/types/client/nodes/GroupNode.d.ts +6 -0
- package/dist/types/client/nodes/ImageNode.d.ts +10 -0
- package/dist/types/client/nodes/InlineFormatBar.d.ts +7 -0
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +14 -0
- package/dist/types/client/nodes/LedgerNode.d.ts +4 -0
- package/dist/types/client/nodes/MarkdownNode.d.ts +6 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -0
- package/dist/types/client/nodes/MdFormatBar.d.ts +8 -0
- package/dist/types/client/nodes/PromptNode.d.ts +5 -0
- package/dist/types/client/nodes/ResponseNode.d.ts +5 -0
- package/dist/types/client/nodes/StatusNode.d.ts +4 -0
- package/dist/types/client/nodes/StatusSummary.d.ts +4 -0
- package/dist/types/client/nodes/TraceNode.d.ts +4 -0
- package/dist/types/client/nodes/WebpageNode.d.ts +5 -0
- package/dist/types/client/nodes/image-warnings.d.ts +6 -0
- package/dist/types/client/nodes/inline-editor-commands.d.ts +11 -0
- package/dist/types/client/nodes/md-format.d.ts +25 -0
- package/dist/types/client/state/attention-bridge.d.ts +3 -0
- package/dist/types/client/state/attention-store.d.ts +25 -0
- package/dist/types/client/state/canvas-store.d.ts +74 -0
- package/dist/types/client/state/intent-bridge.d.ts +158 -0
- package/dist/types/client/state/sse-bridge.d.ts +5 -0
- package/dist/types/client/theme/tokens.d.ts +27 -0
- package/dist/types/client/types.d.ts +40 -0
- package/dist/types/client/utils/ext-app-tool-result.d.ts +1 -0
- package/dist/types/client/utils/placement.d.ts +1 -0
- package/dist/types/client/utils/platform.d.ts +2 -0
- package/dist/types/json-render/catalog.d.ts +815 -0
- package/dist/types/json-render/charts/components.d.ts +54 -0
- package/dist/types/json-render/charts/definitions.d.ts +103 -0
- package/dist/types/json-render/charts/extra-components.d.ts +58 -0
- package/dist/types/json-render/charts/extra-definitions.d.ts +181 -0
- package/dist/types/json-render/renderer/index.d.ts +16 -0
- package/dist/types/json-render/schema.d.ts +46 -0
- package/dist/types/json-render/server.d.ts +55 -0
- package/dist/types/mcp/server.d.ts +22 -0
- package/dist/types/server/agent-context.d.ts +21 -0
- package/dist/types/server/artifact-paths.d.ts +3 -0
- package/dist/types/server/canvas-operations.d.ts +154 -0
- package/dist/types/server/canvas-provenance.d.ts +13 -0
- package/dist/types/server/canvas-schema.d.ts +49 -0
- package/dist/types/server/canvas-serialization.d.ts +25 -0
- package/dist/types/server/canvas-state.d.ts +174 -0
- package/dist/types/server/canvas-validation.d.ts +33 -0
- package/dist/types/server/chart-template.d.ts +29 -0
- package/dist/types/server/code-graph.d.ts +67 -0
- package/dist/types/server/context-cards.d.ts +24 -0
- package/dist/types/server/diagram-presets.d.ts +28 -0
- package/dist/types/server/ext-app-call-registry.d.ts +16 -0
- package/dist/types/server/ext-app-tool-result.d.ts +1 -0
- package/dist/types/server/file-watcher.d.ts +16 -0
- package/dist/types/server/index.d.ts +243 -0
- package/dist/types/server/mcp-app-candidate.d.ts +25 -0
- package/dist/types/server/mcp-app-host.d.ts +65 -0
- package/dist/types/server/mcp-app-runtime.d.ts +47 -0
- package/dist/types/server/mutation-history.d.ts +105 -0
- package/dist/types/server/placement.d.ts +37 -0
- package/dist/types/server/server.d.ts +103 -0
- package/dist/types/server/spatial-analysis.d.ts +87 -0
- package/dist/types/server/trace-manager.d.ts +48 -0
- package/dist/types/server/web-artifacts.d.ts +50 -0
- package/dist/types/server/webpage-node.d.ts +25 -0
- package/dist/types/shared/auto-arrange.d.ts +29 -0
- package/dist/types/shared/ext-app-tool-result.d.ts +9 -0
- package/dist/types/shared/placement.d.ts +26 -0
- package/dist/types/shared/semantic-attention.d.ts +97 -0
- package/package.json +109 -0
- package/skills/data-analysis/SKILL.md +324 -0
- package/skills/doc-coauthoring/SKILL.md +375 -0
- package/skills/frontend-design/SKILL.md +45 -0
- package/skills/json-render-codegen/SKILL.md +112 -0
- package/skills/json-render-core/SKILL.md +265 -0
- package/skills/json-render-ink/SKILL.md +273 -0
- package/skills/json-render-mcp/SKILL.md +132 -0
- package/skills/json-render-react/SKILL.md +264 -0
- package/skills/json-render-shadcn/SKILL.md +159 -0
- package/skills/playwright-cli/SKILL.md +67 -0
- package/skills/pmx-canvas/SKILL.md +668 -0
- package/skills/pmx-canvas/evals/evals.json +186 -0
- package/skills/pmx-canvas-testing/SKILL.md +78 -0
- package/skills/published-consumer-e2e/SKILL.md +43 -0
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +241 -0
- package/skills/web-artifacts-builder/SKILL.md +80 -0
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +167 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +425 -0
- package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/src/cli/agent.ts +2144 -0
- package/src/cli/index.ts +622 -0
- package/src/cli/watch.ts +88 -0
- package/src/client/App.tsx +507 -0
- package/src/client/canvas/AttentionHistory.tsx +81 -0
- package/src/client/canvas/AttentionToast.tsx +19 -0
- package/src/client/canvas/CanvasNode.tsx +363 -0
- package/src/client/canvas/CanvasViewport.tsx +590 -0
- package/src/client/canvas/CommandPalette.tsx +302 -0
- package/src/client/canvas/ContextMenu.tsx +601 -0
- package/src/client/canvas/ContextPinBar.tsx +25 -0
- package/src/client/canvas/ContextPinHud.tsx +22 -0
- package/src/client/canvas/DockedNode.tsx +66 -0
- package/src/client/canvas/EdgeLayer.tsx +280 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +260 -0
- package/src/client/canvas/FocusFieldLayer.tsx +107 -0
- package/src/client/canvas/Minimap.tsx +301 -0
- package/src/client/canvas/SelectionBar.tsx +69 -0
- package/src/client/canvas/ShortcutOverlay.tsx +69 -0
- package/src/client/canvas/SnapshotPanel.tsx +236 -0
- package/src/client/canvas/snap-guides.ts +170 -0
- package/src/client/canvas/use-node-drag.ts +51 -0
- package/src/client/canvas/use-node-resize.ts +59 -0
- package/src/client/canvas/use-pan-zoom.ts +191 -0
- package/src/client/ext-app/bridge.ts +542 -0
- package/src/client/icons.tsx +424 -0
- package/src/client/index.tsx +7 -0
- package/src/client/nodes/ContextNode.tsx +412 -0
- package/src/client/nodes/ExtAppFrame.tsx +509 -0
- package/src/client/nodes/FileNode.tsx +256 -0
- package/src/client/nodes/GroupNode.tsx +39 -0
- package/src/client/nodes/ImageNode.tsx +160 -0
- package/src/client/nodes/InlineFormatBar.tsx +169 -0
- package/src/client/nodes/InlineMarkdownEditor.tsx +123 -0
- package/src/client/nodes/LedgerNode.tsx +37 -0
- package/src/client/nodes/MarkdownNode.tsx +359 -0
- package/src/client/nodes/McpAppNode.tsx +85 -0
- package/src/client/nodes/MdFormatBar.tsx +109 -0
- package/src/client/nodes/PromptNode.tsx +597 -0
- package/src/client/nodes/ResponseNode.tsx +153 -0
- package/src/client/nodes/StatusNode.tsx +84 -0
- package/src/client/nodes/StatusSummary.tsx +38 -0
- package/src/client/nodes/TraceNode.tsx +120 -0
- package/src/client/nodes/WebpageNode.tsx +288 -0
- package/src/client/nodes/image-warnings.ts +95 -0
- package/src/client/nodes/inline-editor-commands.ts +37 -0
- package/src/client/nodes/md-format.ts +206 -0
- package/src/client/state/attention-bridge.ts +328 -0
- package/src/client/state/attention-store.ts +73 -0
- package/src/client/state/canvas-store.ts +631 -0
- package/src/client/state/intent-bridge.ts +315 -0
- package/src/client/state/sse-bridge.ts +965 -0
- package/src/client/theme/global.css +3173 -0
- package/src/client/theme/tokens.ts +72 -0
- package/src/client/types-shims.d.ts +5 -0
- package/src/client/types.ts +81 -0
- package/src/client/utils/ext-app-tool-result.ts +4 -0
- package/src/client/utils/placement.ts +4 -0
- package/src/client/utils/platform.ts +2 -0
- package/src/json-render/catalog.ts +256 -0
- package/src/json-render/charts/components.tsx +198 -0
- package/src/json-render/charts/definitions.ts +81 -0
- package/src/json-render/charts/extra-components.tsx +267 -0
- package/src/json-render/charts/extra-definitions.ts +145 -0
- package/src/json-render/renderer/index.css +174 -0
- package/src/json-render/renderer/index.tsx +86 -0
- package/src/json-render/schema.ts +62 -0
- package/src/json-render/server.ts +597 -0
- package/src/mcp/server.ts +1377 -0
- package/src/server/agent-context.ts +242 -0
- package/src/server/artifact-paths.ts +17 -0
- package/src/server/canvas-operations.ts +1279 -0
- package/src/server/canvas-provenance.ts +243 -0
- package/src/server/canvas-schema.ts +432 -0
- package/src/server/canvas-serialization.ts +95 -0
- package/src/server/canvas-state.ts +1134 -0
- package/src/server/canvas-validation.ts +114 -0
- package/src/server/chart-template.ts +449 -0
- package/src/server/code-graph.ts +370 -0
- package/src/server/context-cards.ts +31 -0
- package/src/server/diagram-presets.ts +71 -0
- package/src/server/ext-app-call-registry.ts +77 -0
- package/src/server/ext-app-tool-result.ts +4 -0
- package/src/server/file-watcher.ts +121 -0
- package/src/server/index.ts +647 -0
- package/src/server/mcp-app-candidate.ts +174 -0
- package/src/server/mcp-app-host.ts +814 -0
- package/src/server/mcp-app-runtime.ts +459 -0
- package/src/server/mutation-history.ts +350 -0
- package/src/server/placement.ts +125 -0
- package/src/server/server.ts +3846 -0
- package/src/server/spatial-analysis.ts +356 -0
- package/src/server/trace-manager.ts +333 -0
- package/src/server/web-artifacts/scripts/bundle-artifact.sh +167 -0
- package/src/server/web-artifacts/scripts/init-artifact.sh +426 -0
- package/src/server/web-artifacts/scripts/shadcn-components.tar.gz +0 -0
- package/src/server/web-artifacts.ts +442 -0
- package/src/server/webpage-node.ts +328 -0
- package/src/shared/auto-arrange.ts +439 -0
- package/src/shared/ext-app-tool-result.ts +76 -0
- package/src/shared/placement.ts +81 -0
- package/src/shared/semantic-attention.ts +598 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Canvas design tokens — reads from CSS custom properties for theme support. */
|
|
2
|
+
|
|
3
|
+
export interface CanvasTokens {
|
|
4
|
+
bg: string;
|
|
5
|
+
panel: string;
|
|
6
|
+
panelSoft: string;
|
|
7
|
+
line: string;
|
|
8
|
+
text: string;
|
|
9
|
+
textSoft: string;
|
|
10
|
+
muted: string;
|
|
11
|
+
dim: string;
|
|
12
|
+
accent: string;
|
|
13
|
+
ok: string;
|
|
14
|
+
warn: string;
|
|
15
|
+
warnAlt: string;
|
|
16
|
+
danger: string;
|
|
17
|
+
purple: string;
|
|
18
|
+
thinking: string;
|
|
19
|
+
subagent: string;
|
|
20
|
+
font: string;
|
|
21
|
+
mono: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let cached: CanvasTokens | null = null;
|
|
25
|
+
|
|
26
|
+
function read(prop: string): string {
|
|
27
|
+
return getComputedStyle(document.documentElement).getPropertyValue(prop).trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Return current canvas tokens (reads computed CSS vars). */
|
|
31
|
+
export function getCanvasTokens(): CanvasTokens {
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
cached = {
|
|
34
|
+
bg: read('--c-bg'),
|
|
35
|
+
panel: read('--c-panel'),
|
|
36
|
+
panelSoft: read('--c-panel-soft'),
|
|
37
|
+
line: read('--c-line'),
|
|
38
|
+
text: read('--c-text'),
|
|
39
|
+
textSoft: read('--c-text-soft'),
|
|
40
|
+
muted: read('--c-muted'),
|
|
41
|
+
dim: read('--c-dim'),
|
|
42
|
+
accent: read('--c-accent'),
|
|
43
|
+
ok: read('--c-ok'),
|
|
44
|
+
warn: read('--c-warn'),
|
|
45
|
+
warnAlt: read('--c-warn-alt'),
|
|
46
|
+
danger: read('--c-danger'),
|
|
47
|
+
purple: read('--c-purple'),
|
|
48
|
+
thinking: read('--c-thinking'),
|
|
49
|
+
subagent: read('--c-subagent'),
|
|
50
|
+
font: read('--font'),
|
|
51
|
+
mono: read('--mono'),
|
|
52
|
+
};
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Invalidate the cached tokens (call after theme switch). */
|
|
57
|
+
export function invalidateTokenCache(): void {
|
|
58
|
+
cached = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Agent execution phase → CSS color mapping. Shared by StatusNode and StatusSummary. */
|
|
62
|
+
export const PHASE_COLORS: Record<string, string> = {
|
|
63
|
+
idle: 'var(--c-muted)',
|
|
64
|
+
running: 'var(--c-accent)',
|
|
65
|
+
planning: 'var(--c-thinking)',
|
|
66
|
+
thinking: 'var(--c-thinking)',
|
|
67
|
+
drafting: 'var(--c-accent)',
|
|
68
|
+
tooling: 'var(--c-accent)',
|
|
69
|
+
review: 'var(--c-ok)',
|
|
70
|
+
'waiting-approval': 'var(--c-warn)',
|
|
71
|
+
waiting: 'var(--c-warn-alt)',
|
|
72
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export interface ViewportState {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
scale: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CanvasNodeState {
|
|
8
|
+
id: string;
|
|
9
|
+
type:
|
|
10
|
+
| 'markdown'
|
|
11
|
+
| 'mcp-app'
|
|
12
|
+
| 'webpage'
|
|
13
|
+
| 'json-render'
|
|
14
|
+
| 'graph'
|
|
15
|
+
| 'prompt'
|
|
16
|
+
| 'response'
|
|
17
|
+
| 'status'
|
|
18
|
+
| 'context'
|
|
19
|
+
| 'ledger'
|
|
20
|
+
| 'trace'
|
|
21
|
+
| 'file'
|
|
22
|
+
| 'image'
|
|
23
|
+
| 'group';
|
|
24
|
+
position: { x: number; y: number };
|
|
25
|
+
size: { width: number; height: number };
|
|
26
|
+
zIndex: number;
|
|
27
|
+
collapsed: boolean;
|
|
28
|
+
pinned: boolean;
|
|
29
|
+
dockPosition: 'left' | 'right' | null;
|
|
30
|
+
data: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CanvasEdge {
|
|
34
|
+
id: string;
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
type: 'relation' | 'depends-on' | 'flow' | 'references';
|
|
38
|
+
label?: string;
|
|
39
|
+
style?: 'solid' | 'dashed' | 'dotted';
|
|
40
|
+
animated?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
|
|
44
|
+
|
|
45
|
+
// ── Shared constants for node type display ──────────────────
|
|
46
|
+
|
|
47
|
+
export const TYPE_LABELS: Record<CanvasNodeState['type'], string> = {
|
|
48
|
+
markdown: 'MD',
|
|
49
|
+
'mcp-app': 'APP',
|
|
50
|
+
webpage: 'WEB',
|
|
51
|
+
'json-render': 'UI',
|
|
52
|
+
graph: 'GRAPH',
|
|
53
|
+
prompt: 'ASK',
|
|
54
|
+
response: 'ANS',
|
|
55
|
+
status: 'STATUS',
|
|
56
|
+
context: 'CONTEXT',
|
|
57
|
+
ledger: 'LOG',
|
|
58
|
+
trace: 'TRACE',
|
|
59
|
+
file: 'FILE',
|
|
60
|
+
image: 'IMG',
|
|
61
|
+
group: 'GROUP',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Node types that support the full-viewport expand/focus overlay. */
|
|
65
|
+
export const EXPANDABLE_TYPES = new Set<CanvasNodeState['type']>([
|
|
66
|
+
'markdown',
|
|
67
|
+
'mcp-app',
|
|
68
|
+
'webpage',
|
|
69
|
+
'json-render',
|
|
70
|
+
'graph',
|
|
71
|
+
'context',
|
|
72
|
+
'ledger',
|
|
73
|
+
'file',
|
|
74
|
+
'image',
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
export interface CanvasLayout {
|
|
78
|
+
viewport: ViewportState;
|
|
79
|
+
nodes: CanvasNodeState[];
|
|
80
|
+
edges: CanvasEdge[];
|
|
81
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* json-render catalog definition for PMX Canvas.
|
|
3
|
+
*
|
|
4
|
+
* Uses the shadcn component set from @json-render/shadcn/catalog plus local
|
|
5
|
+
* chart components. The catalog validates specs before they are stored in
|
|
6
|
+
* canvas node state or rendered in the browser viewer.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { defineCatalog } from '@json-render/core';
|
|
10
|
+
import { schema } from './schema.js';
|
|
11
|
+
import { shadcnComponentDefinitions } from '@json-render/shadcn/catalog';
|
|
12
|
+
import { chartComponentDefinitions } from './charts/definitions';
|
|
13
|
+
import { extraChartComponentDefinitions } from './charts/extra-definitions';
|
|
14
|
+
|
|
15
|
+
export const allComponentDefinitions = {
|
|
16
|
+
...shadcnComponentDefinitions,
|
|
17
|
+
...chartComponentDefinitions,
|
|
18
|
+
...extraChartComponentDefinitions,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const catalog = defineCatalog(schema as never, {
|
|
22
|
+
components: allComponentDefinitions,
|
|
23
|
+
} as never);
|
|
24
|
+
|
|
25
|
+
export interface JsonRenderIssue {
|
|
26
|
+
path?: PropertyKey[];
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface JsonRenderPropDescriptor {
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
required: boolean;
|
|
34
|
+
nullable: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface JsonRenderComponentDescriptor {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
slots: string[];
|
|
41
|
+
example: unknown;
|
|
42
|
+
props: JsonRenderPropDescriptor[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface JsonRenderValidationResult {
|
|
46
|
+
success: boolean;
|
|
47
|
+
data?: unknown;
|
|
48
|
+
error?: {
|
|
49
|
+
issues?: JsonRenderIssue[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ZodishSchema {
|
|
54
|
+
shape?: Record<string, unknown>;
|
|
55
|
+
isOptional?: () => boolean;
|
|
56
|
+
isNullable?: () => boolean;
|
|
57
|
+
safeParse(value: unknown): {
|
|
58
|
+
success: boolean;
|
|
59
|
+
error?: {
|
|
60
|
+
issues?: JsonRenderIssue[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
66
|
+
return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasSafeParse(value: unknown): value is ZodishSchema {
|
|
70
|
+
return typeof value === 'object' && value !== null && 'safeParse' in value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function schemaTypeName(value: unknown): string {
|
|
74
|
+
const record = asRecord(value);
|
|
75
|
+
const def = asRecord(record?._def) ?? asRecord(record?.def);
|
|
76
|
+
const typeName = def?.typeName ?? def?.type;
|
|
77
|
+
return typeof typeName === 'string' ? typeName : '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isOptionalSchema(value: unknown): boolean {
|
|
81
|
+
if (typeof (value as ZodishSchema | null)?.isOptional === 'function') {
|
|
82
|
+
return (value as ZodishSchema).isOptional?.() === true;
|
|
83
|
+
}
|
|
84
|
+
const typeName = schemaTypeName(value);
|
|
85
|
+
return typeName === 'ZodOptional' || typeName === 'optional';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isNullableSchema(value: unknown): boolean {
|
|
89
|
+
if (typeof (value as ZodishSchema | null)?.isNullable === 'function') {
|
|
90
|
+
return (value as ZodishSchema).isNullable?.() === true;
|
|
91
|
+
}
|
|
92
|
+
const typeName = schemaTypeName(value);
|
|
93
|
+
return typeName === 'ZodNullable' || typeName === 'nullable';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function unwrapSchema(value: unknown): {
|
|
97
|
+
schema: unknown;
|
|
98
|
+
required: boolean;
|
|
99
|
+
nullable: boolean;
|
|
100
|
+
} {
|
|
101
|
+
let current = value;
|
|
102
|
+
let required = true;
|
|
103
|
+
let nullable = false;
|
|
104
|
+
|
|
105
|
+
while (current) {
|
|
106
|
+
if (isOptionalSchema(current)) {
|
|
107
|
+
required = false;
|
|
108
|
+
}
|
|
109
|
+
if (isNullableSchema(current)) {
|
|
110
|
+
nullable = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const record = asRecord(current);
|
|
114
|
+
const def = asRecord(record?._def) ?? asRecord(record?.def);
|
|
115
|
+
const inner =
|
|
116
|
+
def?.innerType ??
|
|
117
|
+
def?.schema ??
|
|
118
|
+
def?.type ??
|
|
119
|
+
def?.out ??
|
|
120
|
+
def?.in;
|
|
121
|
+
|
|
122
|
+
if (!inner || inner === current || (!isOptionalSchema(current) && !isNullableSchema(current))) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
current = inner;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { schema: current, required, nullable };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function schemaTypeLabel(value: unknown): string {
|
|
132
|
+
const { schema, nullable } = unwrapSchema(value);
|
|
133
|
+
const record = asRecord(schema);
|
|
134
|
+
const def = asRecord(record?._def) ?? asRecord(record?.def);
|
|
135
|
+
const typeName = schemaTypeName(schema);
|
|
136
|
+
|
|
137
|
+
let label = 'unknown';
|
|
138
|
+
if (typeName === 'ZodString' || typeName === 'string') {
|
|
139
|
+
label = 'string';
|
|
140
|
+
} else if (typeName === 'ZodNumber' || typeName === 'number') {
|
|
141
|
+
label = 'number';
|
|
142
|
+
} else if (typeName === 'ZodBoolean' || typeName === 'boolean') {
|
|
143
|
+
label = 'boolean';
|
|
144
|
+
} else if (typeName === 'ZodArray' || typeName === 'array') {
|
|
145
|
+
const element = def?.type ?? def?.element ?? def?.schema;
|
|
146
|
+
label = `${schemaTypeLabel(element)}[]`;
|
|
147
|
+
} else if (typeName === 'ZodObject' || typeName === 'object') {
|
|
148
|
+
label = 'object';
|
|
149
|
+
} else if (typeName === 'ZodRecord' || typeName === 'record') {
|
|
150
|
+
label = 'record';
|
|
151
|
+
} else if (typeName === 'ZodEnum' || typeName === 'enum') {
|
|
152
|
+
const rawValues = Array.isArray(def?.values)
|
|
153
|
+
? def.values
|
|
154
|
+
: Array.isArray(def?.entries)
|
|
155
|
+
? def.entries
|
|
156
|
+
: Array.isArray(def?.options)
|
|
157
|
+
? def.options
|
|
158
|
+
: [];
|
|
159
|
+
label = rawValues.length > 0
|
|
160
|
+
? rawValues.map((entry) => JSON.stringify(entry)).join(' | ')
|
|
161
|
+
: 'enum';
|
|
162
|
+
} else if (typeName === 'ZodLiteral' || typeName === 'literal') {
|
|
163
|
+
label = JSON.stringify(def?.value ?? def?.literal ?? 'literal');
|
|
164
|
+
} else if (typeName === 'ZodAny' || typeName === 'any') {
|
|
165
|
+
label = 'any';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return nullable ? `${label} | null` : label;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function describePropsSchema(propsSchema: ZodishSchema): JsonRenderPropDescriptor[] {
|
|
172
|
+
const shape = propsSchema.shape;
|
|
173
|
+
if (!shape || typeof shape !== 'object') return [];
|
|
174
|
+
|
|
175
|
+
return Object.entries(shape)
|
|
176
|
+
.map(([name, schema]) => {
|
|
177
|
+
const unwrapped = unwrapSchema(schema);
|
|
178
|
+
return {
|
|
179
|
+
name,
|
|
180
|
+
type: schemaTypeLabel(unwrapped.schema),
|
|
181
|
+
required: unwrapped.required && !unwrapped.nullable,
|
|
182
|
+
nullable: unwrapped.nullable,
|
|
183
|
+
};
|
|
184
|
+
})
|
|
185
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function describeJsonRenderCatalog(): JsonRenderComponentDescriptor[] {
|
|
189
|
+
return Object.entries(allComponentDefinitions)
|
|
190
|
+
.map(([type, definition]) => ({
|
|
191
|
+
type,
|
|
192
|
+
description: definition.description ?? '',
|
|
193
|
+
slots: 'slots' in definition && Array.isArray(definition.slots) ? [...definition.slots] : [],
|
|
194
|
+
example: 'example' in definition ? definition.example : undefined,
|
|
195
|
+
props: hasSafeParse(definition.props) ? describePropsSchema(definition.props) : [],
|
|
196
|
+
}))
|
|
197
|
+
.sort((a, b) => a.type.localeCompare(b.type));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function normalizePropsForSchema(
|
|
201
|
+
propsSchema: ZodishSchema,
|
|
202
|
+
rawProps: Record<string, unknown>,
|
|
203
|
+
): Record<string, unknown> {
|
|
204
|
+
const shape = propsSchema.shape;
|
|
205
|
+
if (!shape || typeof shape !== 'object') return rawProps;
|
|
206
|
+
|
|
207
|
+
const normalizedProps = { ...rawProps };
|
|
208
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
209
|
+
if (!(key in normalizedProps)) {
|
|
210
|
+
if (isNullableSchema(fieldSchema)) {
|
|
211
|
+
normalizedProps[key] = null;
|
|
212
|
+
} else if (isOptionalSchema(fieldSchema)) {
|
|
213
|
+
normalizedProps[key] = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return normalizedProps;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function validateShadcnElementProps(spec: unknown): JsonRenderValidationResult {
|
|
221
|
+
const specRecord = asRecord(spec);
|
|
222
|
+
const elements = asRecord(specRecord?.elements);
|
|
223
|
+
if (!elements) {
|
|
224
|
+
return { success: true, data: spec };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const issues: JsonRenderIssue[] = [];
|
|
228
|
+
for (const [elementKey, rawElement] of Object.entries(elements)) {
|
|
229
|
+
const element = asRecord(rawElement);
|
|
230
|
+
if (!element || typeof element.type !== 'string') continue;
|
|
231
|
+
|
|
232
|
+
const definition = allComponentDefinitions[element.type as keyof typeof allComponentDefinitions];
|
|
233
|
+
if (!definition || !hasSafeParse(definition.props)) continue;
|
|
234
|
+
|
|
235
|
+
const parsed = definition.props.safeParse(
|
|
236
|
+
normalizePropsForSchema(definition.props, asRecord(element.props) ?? {}),
|
|
237
|
+
);
|
|
238
|
+
if (parsed.success) continue;
|
|
239
|
+
|
|
240
|
+
for (const issue of parsed.error?.issues ?? []) {
|
|
241
|
+
const issuePath = Array.isArray(issue.path)
|
|
242
|
+
? issue.path.map((segment) => (typeof segment === 'symbol' ? String(segment) : segment))
|
|
243
|
+
: [];
|
|
244
|
+
issues.push({
|
|
245
|
+
path: ['elements', elementKey, 'props', ...issuePath],
|
|
246
|
+
message: issue.message ?? 'invalid value',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (issues.length > 0) {
|
|
252
|
+
return { success: false, error: { issues } };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { success: true, data: spec };
|
|
256
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chart component implementations for json-render.
|
|
5
|
+
*
|
|
6
|
+
* Built on Recharts, following the same pattern as the Vercel json-render
|
|
7
|
+
* chat example. Each component receives BaseComponentProps<T> and renders
|
|
8
|
+
* a responsive chart inside a styled container.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react';
|
|
12
|
+
import type { BaseComponentProps } from '@json-render/react';
|
|
13
|
+
import {
|
|
14
|
+
BarChart as RechartsBarChart,
|
|
15
|
+
Bar,
|
|
16
|
+
CartesianGrid,
|
|
17
|
+
Cell,
|
|
18
|
+
Legend,
|
|
19
|
+
Line,
|
|
20
|
+
LineChart as RechartsLineChart,
|
|
21
|
+
Pie,
|
|
22
|
+
PieChart as RechartsPieChart,
|
|
23
|
+
ResponsiveContainer,
|
|
24
|
+
Tooltip,
|
|
25
|
+
XAxis,
|
|
26
|
+
YAxis,
|
|
27
|
+
} from 'recharts';
|
|
28
|
+
|
|
29
|
+
// Fallbacks mirror the light-theme palette in renderer/index.css so the
|
|
30
|
+
// chart still reads as on-brand if the Tailwind bundle fails to load.
|
|
31
|
+
export const CHART_COLORS = [
|
|
32
|
+
'var(--chart-1, #1A7ABF)',
|
|
33
|
+
'var(--chart-2, #1a9f55)',
|
|
34
|
+
'var(--chart-3, #c89b2a)',
|
|
35
|
+
'var(--chart-4, #7c4dff)',
|
|
36
|
+
'var(--chart-5, #d32f2f)',
|
|
37
|
+
'var(--chart-6, #00838F)',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export type AggregateMode = 'sum' | 'count' | 'avg';
|
|
41
|
+
|
|
42
|
+
const AGGREGATE_FNS: Record<AggregateMode, (values: number[]) => number> = {
|
|
43
|
+
sum: (v) => v.reduce((a, b) => a + b, 0),
|
|
44
|
+
count: (v) => v.length,
|
|
45
|
+
avg: (v) => v.reduce((a, b) => a + b, 0) / v.length,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function processChartData(
|
|
49
|
+
data: Record<string, unknown>[],
|
|
50
|
+
xKey: string,
|
|
51
|
+
yKey: string,
|
|
52
|
+
aggregate: AggregateMode | null | undefined,
|
|
53
|
+
): Record<string, unknown>[] {
|
|
54
|
+
if (!aggregate) return data;
|
|
55
|
+
|
|
56
|
+
const groups = new Map<string, number[]>();
|
|
57
|
+
for (const row of data) {
|
|
58
|
+
const key = String(row[xKey] ?? '');
|
|
59
|
+
const val = Number(row[yKey] ?? 0);
|
|
60
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
61
|
+
groups.get(key)!.push(isNaN(val) ? 0 : val);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result: Record<string, unknown>[] = [];
|
|
65
|
+
for (const [key, values] of groups) {
|
|
66
|
+
result.push({ [xKey]: key, [yKey]: AGGREGATE_FNS[aggregate](values) });
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CartesianChartProps {
|
|
72
|
+
title?: string | null;
|
|
73
|
+
data: Record<string, unknown>[];
|
|
74
|
+
xKey: string;
|
|
75
|
+
yKey: string;
|
|
76
|
+
aggregate?: AggregateMode | null;
|
|
77
|
+
color?: string | null;
|
|
78
|
+
height?: number | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface PieChartProps {
|
|
82
|
+
title?: string | null;
|
|
83
|
+
data: Record<string, unknown>[];
|
|
84
|
+
nameKey: string;
|
|
85
|
+
valueKey: string;
|
|
86
|
+
height?: number | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const axisStyle = {
|
|
90
|
+
fontSize: 12,
|
|
91
|
+
fill: 'var(--muted-foreground, #666)',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const tooltipStyle = {
|
|
95
|
+
backgroundColor: 'var(--popover, #fff)',
|
|
96
|
+
border: '1px solid var(--border, #e5e5e5)',
|
|
97
|
+
borderRadius: 'var(--radius, 0.5rem)',
|
|
98
|
+
color: 'var(--popover-foreground, #111)',
|
|
99
|
+
fontSize: 13,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Shared wrapper for cartesian charts (Line + Bar). */
|
|
103
|
+
export function CartesianChart({
|
|
104
|
+
props,
|
|
105
|
+
children,
|
|
106
|
+
}: {
|
|
107
|
+
props: CartesianChartProps;
|
|
108
|
+
children: (data: Record<string, unknown>[]) => ReactNode;
|
|
109
|
+
}) {
|
|
110
|
+
const chartData = processChartData(props.data ?? [], props.xKey, props.yKey, props.aggregate);
|
|
111
|
+
const h = props.height ?? 300;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className="pmx-chart">
|
|
115
|
+
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
116
|
+
<ResponsiveContainer width="100%" height={h}>
|
|
117
|
+
{children(chartData)}
|
|
118
|
+
</ResponsiveContainer>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ChartLineChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
124
|
+
const stroke = props.color ?? CHART_COLORS[0];
|
|
125
|
+
return (
|
|
126
|
+
<CartesianChart props={props}>
|
|
127
|
+
{(data) => (
|
|
128
|
+
<RechartsLineChart data={data}>
|
|
129
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
130
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
131
|
+
<YAxis tick={axisStyle} />
|
|
132
|
+
<Tooltip contentStyle={tooltipStyle} />
|
|
133
|
+
<Line
|
|
134
|
+
type="monotone"
|
|
135
|
+
dataKey={props.yKey}
|
|
136
|
+
stroke={stroke}
|
|
137
|
+
strokeWidth={2}
|
|
138
|
+
dot={{ r: 4, fill: stroke }}
|
|
139
|
+
activeDot={{ r: 6 }}
|
|
140
|
+
/>
|
|
141
|
+
</RechartsLineChart>
|
|
142
|
+
)}
|
|
143
|
+
</CartesianChart>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ChartBarChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
148
|
+
const fill = props.color ?? CHART_COLORS[0];
|
|
149
|
+
return (
|
|
150
|
+
<CartesianChart props={props}>
|
|
151
|
+
{(data) => (
|
|
152
|
+
<RechartsBarChart data={data}>
|
|
153
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
154
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
155
|
+
<YAxis tick={axisStyle} />
|
|
156
|
+
<Tooltip contentStyle={tooltipStyle} cursor={false} />
|
|
157
|
+
<Bar dataKey={props.yKey} fill={fill} radius={[4, 4, 0, 0]} />
|
|
158
|
+
</RechartsBarChart>
|
|
159
|
+
)}
|
|
160
|
+
</CartesianChart>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ChartPieChart({ props }: BaseComponentProps<PieChartProps>) {
|
|
165
|
+
const data = props.data ?? [];
|
|
166
|
+
const h = props.height ?? 300;
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className="pmx-chart">
|
|
170
|
+
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
171
|
+
<ResponsiveContainer width="100%" height={h}>
|
|
172
|
+
<RechartsPieChart>
|
|
173
|
+
<Tooltip contentStyle={tooltipStyle} />
|
|
174
|
+
<Legend />
|
|
175
|
+
<Pie
|
|
176
|
+
data={data}
|
|
177
|
+
dataKey={props.valueKey}
|
|
178
|
+
nameKey={props.nameKey}
|
|
179
|
+
cx="50%"
|
|
180
|
+
cy="50%"
|
|
181
|
+
outerRadius="80%"
|
|
182
|
+
label
|
|
183
|
+
>
|
|
184
|
+
{data.map((_, i) => (
|
|
185
|
+
<Cell key={i} fill={CHART_COLORS[i % CHART_COLORS.length]} />
|
|
186
|
+
))}
|
|
187
|
+
</Pie>
|
|
188
|
+
</RechartsPieChart>
|
|
189
|
+
</ResponsiveContainer>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const chartComponents = {
|
|
195
|
+
LineChart: ChartLineChart,
|
|
196
|
+
BarChart: ChartBarChart,
|
|
197
|
+
PieChart: ChartPieChart,
|
|
198
|
+
};
|