@hachej/boring-generated-pane 0.1.60 → 0.1.65

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/README.md CHANGED
@@ -24,8 +24,8 @@ This plugin wraps Vercel Labs `json-render` so agents can write safe JSON pane s
24
24
 
25
25
  - Agents generate JSON specs.
26
26
  - Plugins define allowed components and prop schemas.
27
- - Unknown components/props are rejected by the json-render catalog.
28
- - Actions must be mapped by the host/plugin; no generated JavaScript is executed.
27
+ - Unknown components/props are rejected by the generated-pane vocabulary/catalog.
28
+ - Actions are out of scope for generated JSON specs; no generated JavaScript is executed.
29
29
 
30
30
  ## Eval
31
31
 
@@ -39,4 +39,4 @@ The eval checks that the agent writes a `panes/*.pane.json` file and the runner
39
39
 
40
40
  ## Extending
41
41
 
42
- Use `defineGeneratedPaneProfile` to add app/plugin-specific components, then render with `GeneratedPaneRenderer` or a profile-specific pane.
42
+ Use `defineGeneratedPaneVocabulary` in shared code plus `defineGeneratedPaneProfile` in front code to add app/plugin-specific components, then render with `GeneratedPaneRenderer` or a profile-specific pane.
@@ -0,0 +1,149 @@
1
+ // src/shared/index.ts
2
+ import { z } from "zod";
3
+ var GENERATED_PANE_DIAGNOSTIC_CODES = {
4
+ invalidRoot: "generated-pane.invalid_root",
5
+ invalidElements: "generated-pane.invalid_elements",
6
+ missingElement: "generated-pane.missing_element",
7
+ elementCycle: "generated-pane.element_cycle",
8
+ unknownComponent: "generated-pane.unknown_component",
9
+ invalidProps: "generated-pane.invalid_props",
10
+ unsupportedProfile: "generated-pane.unsupported_profile"
11
+ };
12
+ function defineGeneratedPaneVocabulary(vocabulary) {
13
+ return vocabulary;
14
+ }
15
+ var baseGeneratedPaneVocabulary = defineGeneratedPaneVocabulary({
16
+ id: "base",
17
+ label: "Generated Pane",
18
+ components: {
19
+ Card: {
20
+ description: "A bordered content card with an optional title and description.",
21
+ slots: ["default"],
22
+ props: z.object({ title: z.string().optional(), description: z.string().optional() })
23
+ },
24
+ Stack: {
25
+ description: "Vertical stack for grouping child elements.",
26
+ slots: ["default"],
27
+ props: z.object({ gap: z.enum(["sm", "md", "lg"]).optional() })
28
+ },
29
+ Grid: {
30
+ description: "Responsive grid layout for child elements.",
31
+ slots: ["default"],
32
+ props: z.object({ columns: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional() })
33
+ },
34
+ Text: {
35
+ description: "Plain text block.",
36
+ props: z.object({ text: z.string(), tone: z.enum(["default", "muted"]).optional() })
37
+ },
38
+ Badge: {
39
+ description: "Small status badge.",
40
+ props: z.object({ label: z.string(), variant: z.enum(["default", "secondary", "outline"]).optional() })
41
+ },
42
+ Alert: {
43
+ description: "Notice block for warnings, status, or context.",
44
+ props: z.object({ title: z.string(), description: z.string().optional() })
45
+ }
46
+ }
47
+ });
48
+ function isRecord(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+ function isStringArray(value) {
52
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
53
+ }
54
+ function diagnostic(code, message, options = {}) {
55
+ const { severity = "error", ...rest } = options;
56
+ return { severity, code, message, ...rest };
57
+ }
58
+ function schemaPath(path) {
59
+ return path.length ? path.map(String).join(".") : void 0;
60
+ }
61
+ function validateGeneratedPaneSpec(value, vocabulary = baseGeneratedPaneVocabulary) {
62
+ const diagnostics = [];
63
+ if (!isRecord(value)) {
64
+ return { spec: null, diagnostics: [diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane spec must be an object")] };
65
+ }
66
+ if (value.kind !== "boring.generated-pane") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, 'generated pane spec kind must be "boring.generated-pane"', { path: "kind" }));
67
+ if (value.version !== 1) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane spec version must be 1", { path: "version" }));
68
+ if (value.profile !== void 0 && typeof value.profile !== "string") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane profile must be a string", { path: "profile" }));
69
+ if (value.title !== void 0 && typeof value.title !== "string") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane title must be a string", { path: "title" }));
70
+ if (value.description !== void 0 && typeof value.description !== "string") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane description must be a string", { path: "description" }));
71
+ if (typeof value.root !== "string" || value.root.length === 0) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane root must be a string", { path: "root" }));
72
+ if (value.queries !== void 0 && !isRecord(value.queries)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, "generated pane queries must be an object", { path: "queries" }));
73
+ if (!isRecord(value.elements)) {
74
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, "generated pane elements must be an object", { path: "elements" }));
75
+ } else {
76
+ for (const [id, element] of Object.entries(value.elements)) {
77
+ if (!isRecord(element)) {
78
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id} must be an object`, { elementId: id, path: `elements.${id}` }));
79
+ continue;
80
+ }
81
+ if (typeof element.type !== "string" || element.type.length === 0) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id} must include type`, { elementId: id, path: `elements.${id}.type` }));
82
+ if (element.props !== void 0 && !isRecord(element.props)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id}.props must be an object`, { elementId: id, path: `elements.${id}.props` }));
83
+ if (element.children !== void 0 && !isStringArray(element.children)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id}.children must be an array of ids`, { elementId: id, path: `elements.${id}.children` }));
84
+ }
85
+ }
86
+ if (isRecord(value.elements) && typeof value.root === "string") validateGraph(value.root, value.elements, diagnostics);
87
+ const fatal = diagnostics.some((item) => item.code === GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.missingElement || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.elementCycle);
88
+ if (fatal) return { spec: null, diagnostics };
89
+ const spec = value;
90
+ const declaredProfile = spec.profile ?? "base";
91
+ if (declaredProfile !== vocabulary.id) {
92
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.unsupportedProfile, `generated pane profile ${declaredProfile} is not supported by active vocabulary ${vocabulary.id}`, { path: "profile" }));
93
+ }
94
+ for (const [id, element] of Object.entries(spec.elements)) {
95
+ const entry = vocabulary.components[element.type];
96
+ if (!entry) {
97
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.unknownComponent, `component ${id} has unsupported type ${element.type}`, { elementId: id, path: `elements.${id}.type` }));
98
+ continue;
99
+ }
100
+ const parsed = entry.props.safeParse(element.props ?? {});
101
+ if (!parsed.success) {
102
+ for (const issue of parsed.error.issues) {
103
+ const propPath = schemaPath(issue.path);
104
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidProps, `component ${id}.props${propPath ? `.${propPath}` : ""}: ${issue.message}`, { elementId: id, path: propPath ? `elements.${id}.props.${propPath}` : `elements.${id}.props` }));
105
+ }
106
+ }
107
+ }
108
+ for (const check of vocabulary.diagnostics ?? []) diagnostics.push(...check(spec));
109
+ return { spec, diagnostics };
110
+ }
111
+ function parseGeneratedPaneSpec(value, vocabulary = baseGeneratedPaneVocabulary) {
112
+ const result = validateGeneratedPaneSpec(value, vocabulary);
113
+ return {
114
+ spec: result.diagnostics.some((item) => item.severity === "error") ? null : result.spec,
115
+ errors: result.diagnostics.filter((item) => item.severity === "error").map((item) => item.message)
116
+ };
117
+ }
118
+ function validateGraph(root, elements, diagnostics) {
119
+ const visiting = /* @__PURE__ */ new Set();
120
+ const visited = /* @__PURE__ */ new Set();
121
+ const visit = (id, path) => {
122
+ if (visiting.has(id)) {
123
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.elementCycle, `generated pane element cycle: ${[...path, id].join(" -> ")}`, { elementId: id, path: `elements.${id}` }));
124
+ return;
125
+ }
126
+ if (visited.has(id)) return;
127
+ const element = elements[id];
128
+ if (!isRecord(element)) {
129
+ diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.missingElement, `element ${id} is referenced but not defined`, { elementId: id, path: `elements.${id}` }));
130
+ return;
131
+ }
132
+ visiting.add(id);
133
+ if (isStringArray(element.children)) {
134
+ for (const child of element.children) visit(child, [...path, id]);
135
+ }
136
+ visiting.delete(id);
137
+ visited.add(id);
138
+ };
139
+ visit(root, []);
140
+ }
141
+
142
+ export {
143
+ GENERATED_PANE_DIAGNOSTIC_CODES,
144
+ defineGeneratedPaneVocabulary,
145
+ baseGeneratedPaneVocabulary,
146
+ validateGeneratedPaneSpec,
147
+ parseGeneratedPaneSpec
148
+ };
149
+ //# sourceMappingURL=chunk-PELWQS23.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/index.ts"],"sourcesContent":["import { z } from \"zod\"\n\nexport interface GeneratedPaneElementSpec {\n type: string\n props?: Record<string, unknown>\n children?: string[]\n}\n\nexport interface GeneratedPaneSpec {\n kind: \"boring.generated-pane\"\n version: 1\n profile?: string\n title?: string\n description?: string\n root: string\n elements: Record<string, GeneratedPaneElementSpec>\n queries?: Record<string, unknown>\n}\n\nexport type GeneratedPaneDiagnosticSeverity = \"error\" | \"warning\" | \"info\"\n\nexport const GENERATED_PANE_DIAGNOSTIC_CODES = {\n invalidRoot: \"generated-pane.invalid_root\",\n invalidElements: \"generated-pane.invalid_elements\",\n missingElement: \"generated-pane.missing_element\",\n elementCycle: \"generated-pane.element_cycle\",\n unknownComponent: \"generated-pane.unknown_component\",\n invalidProps: \"generated-pane.invalid_props\",\n unsupportedProfile: \"generated-pane.unsupported_profile\",\n} as const\n\nexport type GeneratedPaneDiagnosticCode = typeof GENERATED_PANE_DIAGNOSTIC_CODES[keyof typeof GENERATED_PANE_DIAGNOSTIC_CODES]\n\nexport interface GeneratedPaneDiagnostic {\n severity: GeneratedPaneDiagnosticSeverity\n code: GeneratedPaneDiagnosticCode\n message: string\n elementId?: string\n path?: string\n}\n\nexport interface GeneratedPaneComponentVocabularyEntry {\n description: string\n props: z.ZodTypeAny\n slots?: string[]\n}\n\nexport interface GeneratedPaneVocabulary {\n id: string\n label: string\n components: Record<string, GeneratedPaneComponentVocabularyEntry>\n diagnostics?: Array<(spec: GeneratedPaneSpec) => GeneratedPaneDiagnostic[]>\n}\n\nexport interface GeneratedPaneValidationResult {\n spec: GeneratedPaneSpec | null\n errors: string[]\n}\n\nexport interface GeneratedPaneDiagnosticsResult {\n spec: GeneratedPaneSpec | null\n diagnostics: GeneratedPaneDiagnostic[]\n}\n\nexport function defineGeneratedPaneVocabulary(vocabulary: GeneratedPaneVocabulary): GeneratedPaneVocabulary {\n return vocabulary\n}\n\nexport const baseGeneratedPaneVocabulary = defineGeneratedPaneVocabulary({\n id: \"base\",\n label: \"Generated Pane\",\n components: {\n Card: {\n description: \"A bordered content card with an optional title and description.\",\n slots: [\"default\"],\n props: z.object({ title: z.string().optional(), description: z.string().optional() }),\n },\n Stack: {\n description: \"Vertical stack for grouping child elements.\",\n slots: [\"default\"],\n props: z.object({ gap: z.enum([\"sm\", \"md\", \"lg\"]).optional() }),\n },\n Grid: {\n description: \"Responsive grid layout for child elements.\",\n slots: [\"default\"],\n props: z.object({ columns: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional() }),\n },\n Text: {\n description: \"Plain text block.\",\n props: z.object({ text: z.string(), tone: z.enum([\"default\", \"muted\"]).optional() }),\n },\n Badge: {\n description: \"Small status badge.\",\n props: z.object({ label: z.string(), variant: z.enum([\"default\", \"secondary\", \"outline\"]).optional() }),\n },\n Alert: {\n description: \"Notice block for warnings, status, or context.\",\n props: z.object({ title: z.string(), description: z.string().optional() }),\n },\n },\n})\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\")\n}\n\nfunction diagnostic(code: GeneratedPaneDiagnosticCode, message: string, options: Omit<GeneratedPaneDiagnostic, \"severity\" | \"code\" | \"message\"> & { severity?: GeneratedPaneDiagnosticSeverity } = {}): GeneratedPaneDiagnostic {\n const { severity = \"error\", ...rest } = options\n return { severity, code, message, ...rest }\n}\n\nfunction schemaPath(path: PropertyKey[]): string | undefined {\n return path.length ? path.map(String).join(\".\") : undefined\n}\n\nexport function validateGeneratedPaneSpec(value: unknown, vocabulary: GeneratedPaneVocabulary = baseGeneratedPaneVocabulary): GeneratedPaneDiagnosticsResult {\n const diagnostics: GeneratedPaneDiagnostic[] = []\n if (!isRecord(value)) {\n return { spec: null, diagnostics: [diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane spec must be an object\")] }\n }\n\n if (value.kind !== \"boring.generated-pane\") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, 'generated pane spec kind must be \"boring.generated-pane\"', { path: \"kind\" }))\n if (value.version !== 1) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane spec version must be 1\", { path: \"version\" }))\n if (value.profile !== undefined && typeof value.profile !== \"string\") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane profile must be a string\", { path: \"profile\" }))\n if (value.title !== undefined && typeof value.title !== \"string\") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane title must be a string\", { path: \"title\" }))\n if (value.description !== undefined && typeof value.description !== \"string\") diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane description must be a string\", { path: \"description\" }))\n if (typeof value.root !== \"string\" || value.root.length === 0) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane root must be a string\", { path: \"root\" }))\n if (value.queries !== undefined && !isRecord(value.queries)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot, \"generated pane queries must be an object\", { path: \"queries\" }))\n\n if (!isRecord(value.elements)) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, \"generated pane elements must be an object\", { path: \"elements\" }))\n } else {\n for (const [id, element] of Object.entries(value.elements)) {\n if (!isRecord(element)) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id} must be an object`, { elementId: id, path: `elements.${id}` }))\n continue\n }\n if (typeof element.type !== \"string\" || element.type.length === 0) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id} must include type`, { elementId: id, path: `elements.${id}.type` }))\n if (element.props !== undefined && !isRecord(element.props)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id}.props must be an object`, { elementId: id, path: `elements.${id}.props` }))\n if (element.children !== undefined && !isStringArray(element.children)) diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements, `element ${id}.children must be an array of ids`, { elementId: id, path: `elements.${id}.children` }))\n }\n }\n\n if (isRecord(value.elements) && typeof value.root === \"string\") validateGraph(value.root, value.elements, diagnostics)\n\n const fatal = diagnostics.some((item) => item.code === GENERATED_PANE_DIAGNOSTIC_CODES.invalidRoot || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.invalidElements || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.missingElement || item.code === GENERATED_PANE_DIAGNOSTIC_CODES.elementCycle)\n if (fatal) return { spec: null, diagnostics }\n\n const spec = value as unknown as GeneratedPaneSpec\n const declaredProfile = spec.profile ?? \"base\"\n if (declaredProfile !== vocabulary.id) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.unsupportedProfile, `generated pane profile ${declaredProfile} is not supported by active vocabulary ${vocabulary.id}`, { path: \"profile\" }))\n }\n\n for (const [id, element] of Object.entries(spec.elements)) {\n const entry = vocabulary.components[element.type]\n if (!entry) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.unknownComponent, `component ${id} has unsupported type ${element.type}`, { elementId: id, path: `elements.${id}.type` }))\n continue\n }\n const parsed = entry.props.safeParse(element.props ?? {})\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n const propPath = schemaPath(issue.path)\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.invalidProps, `component ${id}.props${propPath ? `.${propPath}` : \"\"}: ${issue.message}`, { elementId: id, path: propPath ? `elements.${id}.props.${propPath}` : `elements.${id}.props` }))\n }\n }\n }\n\n for (const check of vocabulary.diagnostics ?? []) diagnostics.push(...check(spec))\n return { spec, diagnostics }\n}\n\nexport function parseGeneratedPaneSpec(value: unknown, vocabulary: GeneratedPaneVocabulary = baseGeneratedPaneVocabulary): GeneratedPaneValidationResult {\n const result = validateGeneratedPaneSpec(value, vocabulary)\n return {\n spec: result.diagnostics.some((item) => item.severity === \"error\") ? null : result.spec,\n errors: result.diagnostics.filter((item) => item.severity === \"error\").map((item) => item.message),\n }\n}\n\nfunction validateGraph(root: string, elements: Record<string, unknown>, diagnostics: GeneratedPaneDiagnostic[]): void {\n const visiting = new Set<string>()\n const visited = new Set<string>()\n const visit = (id: string, path: string[]): void => {\n if (visiting.has(id)) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.elementCycle, `generated pane element cycle: ${[...path, id].join(\" -> \")}`, { elementId: id, path: `elements.${id}` }))\n return\n }\n if (visited.has(id)) return\n const element = elements[id]\n if (!isRecord(element)) {\n diagnostics.push(diagnostic(GENERATED_PANE_DIAGNOSTIC_CODES.missingElement, `element ${id} is referenced but not defined`, { elementId: id, path: `elements.${id}` }))\n return\n }\n visiting.add(id)\n if (isStringArray(element.children)) {\n for (const child of element.children) visit(child, [...path, id])\n }\n visiting.delete(id)\n visited.add(id)\n }\n visit(root, [])\n}\n"],"mappings":"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","names":[]}
@@ -1,33 +1,25 @@
1
1
  import * as _hachej_boring_workspace_plugin from '@hachej/boring-workspace/plugin';
2
2
  import { PaneProps, BoringFrontSurfaceResolverRegistration } from '@hachej/boring-workspace/plugin';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
4
- import { GeneratedPaneSpec } from '../shared/index.js';
5
- export { GeneratedPaneElementSpec, GeneratedPaneValidationResult, parseGeneratedPaneSpec } from '../shared/index.js';
6
- import * as _json_render_core from '@json-render/core';
3
+ import * as react from 'react';
7
4
  import { ReactNode } from 'react';
8
- import { z } from 'zod';
5
+ import { GeneratedPaneComponentVocabularyEntry, GeneratedPaneVocabulary, GeneratedPaneSpec } from '../shared/index.js';
6
+ export { GENERATED_PANE_DIAGNOSTIC_CODES, GeneratedPaneDiagnostic, GeneratedPaneDiagnosticCode, GeneratedPaneDiagnosticsResult, GeneratedPaneElementSpec, GeneratedPaneValidationResult, baseGeneratedPaneVocabulary, defineGeneratedPaneVocabulary, parseGeneratedPaneSpec, validateGeneratedPaneSpec } from '../shared/index.js';
7
+ import * as zod from 'zod';
8
+ import * as zod_v4_core from 'zod/v4/core';
9
+ import * as _json_render_core from '@json-render/core';
9
10
 
10
- type GeneratedPaneActionHandler = (params: Record<string, unknown>) => void | Promise<void>;
11
- interface GeneratedPaneComponentDefinition {
12
- props: z.ZodObject<Record<string, z.ZodType>>;
13
- slots?: string[];
14
- description: string;
15
- component: (props: {
16
- props: Record<string, unknown>;
17
- children?: ReactNode;
18
- emit: (event: string) => void;
19
- }) => ReactNode;
11
+ interface GeneratedPaneComponentProps {
12
+ props: Record<string, unknown>;
13
+ children?: ReactNode;
20
14
  }
21
- interface GeneratedPaneActionDefinition {
22
- description: string;
23
- params?: z.ZodObject<Record<string, z.ZodType>>;
24
- handler?: GeneratedPaneActionHandler;
15
+ interface GeneratedPaneComponentBinding {
16
+ component: (props: GeneratedPaneComponentProps) => ReactNode;
17
+ }
18
+ interface GeneratedPaneComponentDefinition extends GeneratedPaneComponentVocabularyEntry, GeneratedPaneComponentBinding {
25
19
  }
26
20
  interface GeneratedPaneProfile {
27
- id: string;
28
- label: string;
29
- components: Record<string, GeneratedPaneComponentDefinition>;
30
- actions?: Record<string, GeneratedPaneActionDefinition>;
21
+ vocabulary: GeneratedPaneVocabulary;
22
+ components: Record<string, GeneratedPaneComponentBinding>;
31
23
  }
32
24
  declare function defineGeneratedPaneProfile(profile: GeneratedPaneProfile): GeneratedPaneProfile;
33
25
  declare const baseGeneratedPaneProfile: GeneratedPaneProfile;
@@ -57,22 +49,17 @@ declare function createGeneratedPaneCatalog(profile: GeneratedPaneProfile): _jso
57
49
  }, {
58
50
  components: {
59
51
  [k: string]: {
60
- props: z.ZodObject<Record<string, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>, z.core.$strip>;
52
+ props: zod.ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>;
61
53
  slots: string[];
62
54
  description: string;
63
55
  };
64
56
  };
65
- actions: {
66
- [k: string]: {
67
- params: z.ZodObject<Record<string, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>, z.core.$strip> | undefined;
68
- description: string;
69
- };
70
- };
57
+ actions: {};
71
58
  }>;
72
59
  declare function GeneratedPaneRenderer({ spec, profile }: {
73
60
  spec: GeneratedPaneSpec;
74
61
  profile?: GeneratedPaneProfile;
75
- }): react_jsx_runtime.JSX.Element;
62
+ }): react.JSX.Element;
76
63
 
77
64
  interface GeneratedPanePaneParams {
78
65
  path?: string;
@@ -80,7 +67,7 @@ interface GeneratedPanePaneParams {
80
67
  }
81
68
  declare function GeneratedPanePane({ params, profile }: PaneProps<GeneratedPanePaneParams> & {
82
69
  profile?: GeneratedPaneProfile;
83
- }): react_jsx_runtime.JSX.Element;
70
+ }): react.JSX.Element;
84
71
 
85
72
  interface GeneratedPaneExplorerConfig {
86
73
  title?: string;
@@ -92,12 +79,12 @@ interface GeneratedPaneExplorerConfig {
92
79
  }
93
80
  declare function createGeneratedPaneExplorerPane(config?: GeneratedPaneExplorerConfig): (props: PaneProps<{
94
81
  searchQuery?: string;
95
- }>) => react_jsx_runtime.JSX.Element;
82
+ }>) => react.JSX.Element;
96
83
  declare function GeneratedPaneExplorerPane({ params, containerApi, config }: PaneProps<{
97
84
  searchQuery?: string;
98
85
  }> & {
99
86
  config?: GeneratedPaneExplorerConfig;
100
- }): react_jsx_runtime.JSX.Element;
87
+ }): react.JSX.Element;
101
88
 
102
89
  declare const GENERATED_PANE_PANEL_ID = "generated-pane.panel";
103
90
  declare const GENERATED_PANE_EXPLORER_LEFT_TAB_ID = "generated-pane.explorer";
@@ -107,4 +94,4 @@ declare const generatedPaneSurfaceResolver: BoringFrontSurfaceResolverRegistrati
107
94
 
108
95
  declare const generatedPanePlugin: _hachej_boring_workspace_plugin.BoringFrontFactoryWithId;
109
96
 
110
- export { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID, type GeneratedPaneActionDefinition, type GeneratedPaneActionHandler, type GeneratedPaneComponentDefinition, type GeneratedPaneExplorerConfig, GeneratedPaneExplorerPane, GeneratedPanePane, type GeneratedPanePaneParams, type GeneratedPaneProfile, GeneratedPaneRenderer, GeneratedPaneSpec, baseGeneratedPaneProfile, createGeneratedPaneCatalog, createGeneratedPaneExplorerPane, generatedPanePlugin as default, defineGeneratedPaneProfile, generatedPanePlugin, generatedPaneSurfaceResolver, isGeneratedPanePath, mergeGeneratedPaneProfiles };
97
+ export { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID, type GeneratedPaneComponentBinding, type GeneratedPaneComponentDefinition, type GeneratedPaneComponentProps, type GeneratedPaneExplorerConfig, GeneratedPaneExplorerPane, GeneratedPanePane, type GeneratedPanePaneParams, type GeneratedPaneProfile, GeneratedPaneRenderer, GeneratedPaneSpec, GeneratedPaneVocabulary, baseGeneratedPaneProfile, createGeneratedPaneCatalog, createGeneratedPaneExplorerPane, generatedPanePlugin as default, defineGeneratedPaneProfile, generatedPanePlugin, generatedPaneSurfaceResolver, isGeneratedPanePath, mergeGeneratedPaneProfiles };
@@ -1,6 +1,10 @@
1
1
  import {
2
- parseGeneratedPaneSpec
3
- } from "../chunk-4IMMGGBQ.js";
2
+ GENERATED_PANE_DIAGNOSTIC_CODES,
3
+ baseGeneratedPaneVocabulary,
4
+ defineGeneratedPaneVocabulary,
5
+ parseGeneratedPaneSpec,
6
+ validateGeneratedPaneSpec
7
+ } from "../chunk-PELWQS23.js";
4
8
 
5
9
  // src/front/index.ts
6
10
  import { PanelTop as PanelTop2 } from "lucide-react";
@@ -12,23 +16,25 @@ import { EmptyState, Toolbar, ToolbarGroup, Badge as Badge2 } from "@hachej/bori
12
16
  import { useApiBaseUrl, useWorkspaceRequestId } from "@hachej/boring-workspace";
13
17
 
14
18
  // src/front/catalog.tsx
19
+ import { useMemo } from "react";
15
20
  import { defineCatalog } from "@json-render/core";
16
21
  import { defineRegistry, JSONUIProvider, Renderer } from "@json-render/react";
17
22
  import { schema } from "@json-render/react/schema";
18
- import { z } from "zod";
19
- import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from "@hachej/boring-ui-kit";
23
+ import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from "@hachej/boring-ui-kit";
20
24
  import { jsx, jsxs } from "react/jsx-runtime";
21
25
  function defineGeneratedPaneProfile(profile) {
26
+ const componentKeys = new Set(Object.keys(profile.components));
27
+ const vocabularyKeys = new Set(Object.keys(profile.vocabulary.components));
28
+ const bindingWithoutVocabulary = [...componentKeys].filter((key) => !vocabularyKeys.has(key));
29
+ const vocabularyWithoutBinding = [...vocabularyKeys].filter((key) => !componentKeys.has(key));
30
+ if (bindingWithoutVocabulary.length > 0) throw new Error(`generated pane profile ${profile.vocabulary.id} has render bindings without vocabulary entries: ${bindingWithoutVocabulary.join(", ")}`);
31
+ if (vocabularyWithoutBinding.length > 0) throw new Error(`generated pane profile ${profile.vocabulary.id} has vocabulary entries without render bindings: ${vocabularyWithoutBinding.join(", ")}`);
22
32
  return profile;
23
33
  }
24
34
  var baseGeneratedPaneProfile = defineGeneratedPaneProfile({
25
- id: "base",
26
- label: "Generated Pane",
35
+ vocabulary: baseGeneratedPaneVocabulary,
27
36
  components: {
28
37
  Card: {
29
- description: "A bordered content card with an optional title and description.",
30
- slots: ["default"],
31
- props: z.object({ title: z.string().optional(), description: z.string().optional() }),
32
38
  component: ({ props, children }) => /* @__PURE__ */ jsxs(Card, { children: [
33
39
  props.title || props.description ? /* @__PURE__ */ jsxs(CardHeader, { children: [
34
40
  typeof props.title === "string" ? /* @__PURE__ */ jsx(CardTitle, { children: props.title }) : null,
@@ -38,76 +44,70 @@ var baseGeneratedPaneProfile = defineGeneratedPaneProfile({
38
44
  ] })
39
45
  },
40
46
  Stack: {
41
- description: "Vertical stack for grouping child elements.",
42
- slots: ["default"],
43
- props: z.object({ gap: z.enum(["sm", "md", "lg"]).optional() }),
44
47
  component: ({ props, children }) => /* @__PURE__ */ jsx("div", { className: props.gap === "sm" ? "space-y-2" : props.gap === "lg" ? "space-y-6" : "space-y-4", children })
45
48
  },
46
49
  Grid: {
47
- description: "Responsive grid layout for child elements.",
48
- slots: ["default"],
49
- props: z.object({ columns: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional() }),
50
50
  component: ({ props, children }) => {
51
51
  const columns = props.columns === 3 ? "lg:grid-cols-3" : props.columns === 4 ? "lg:grid-cols-4" : props.columns === 1 ? "grid-cols-1" : "lg:grid-cols-2";
52
52
  return /* @__PURE__ */ jsx("div", { className: `grid gap-4 ${columns}`, children });
53
53
  }
54
54
  },
55
55
  Text: {
56
- description: "Plain text block.",
57
- props: z.object({ text: z.string(), tone: z.enum(["default", "muted"]).optional() }),
58
56
  component: ({ props }) => /* @__PURE__ */ jsx("p", { className: props.tone === "muted" ? "text-sm text-muted-foreground" : "text-sm text-foreground", children: String(props.text) })
59
57
  },
60
58
  Badge: {
61
- description: "Small status badge.",
62
- props: z.object({ label: z.string(), variant: z.enum(["default", "secondary", "outline"]).optional() }),
63
59
  component: ({ props }) => /* @__PURE__ */ jsx(Badge, { variant: props.variant ?? "secondary", children: String(props.label) })
64
60
  },
65
61
  Alert: {
66
- description: "Notice block for warnings, status, or context.",
67
- props: z.object({ title: z.string(), description: z.string().optional() }),
68
62
  component: ({ props }) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-muted/40 p-3 text-sm", children: [
69
63
  /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: String(props.title) }),
70
64
  typeof props.description === "string" ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-muted-foreground", children: props.description }) : null
71
65
  ] })
72
- },
73
- Button: {
74
- description: "Button that emits its press event to a configured action.",
75
- props: z.object({ label: z.string() }),
76
- component: ({ props, emit }) => /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => emit("press"), children: String(props.label) })
77
66
  }
78
67
  }
79
68
  });
80
69
  function mergeGeneratedPaneProfiles(...profiles) {
81
70
  const [first, ...rest] = profiles;
82
- return {
83
- id: first?.id ?? "generated-pane",
84
- label: first?.label ?? "Generated Pane",
85
- components: Object.assign({}, ...profiles.map((profile) => profile.components)),
86
- actions: Object.assign({}, ...profiles.map((profile) => profile.actions ?? {}))
87
- };
71
+ const mergedVocabularyComponents = Object.assign({}, ...profiles.map((profile) => profile.vocabulary.components));
72
+ const mergedBindings = Object.assign({}, ...profiles.map((profile) => profile.components));
73
+ const finalProfile = rest.at(-1) ?? first;
74
+ return defineGeneratedPaneProfile({
75
+ vocabulary: {
76
+ id: finalProfile?.vocabulary.id ?? "base",
77
+ label: finalProfile?.vocabulary.label ?? "Generated Pane",
78
+ components: Object.fromEntries(Object.entries(mergedVocabularyComponents).filter(([key]) => key in mergedBindings)),
79
+ diagnostics: profiles.flatMap((profile) => profile.vocabulary.diagnostics ?? [])
80
+ },
81
+ components: Object.fromEntries(Object.entries(mergedBindings).filter(([key]) => key in mergedVocabularyComponents))
82
+ });
88
83
  }
89
84
  function createGeneratedPaneCatalog(profile) {
90
85
  return defineCatalog(schema, {
91
- components: Object.fromEntries(Object.entries(profile.components).map(([name, definition]) => [name, {
86
+ components: Object.fromEntries(Object.entries(profile.vocabulary.components).map(([name, definition]) => [name, {
92
87
  props: definition.props,
93
88
  slots: definition.slots ?? [],
94
89
  description: definition.description
95
90
  }])),
96
- actions: Object.fromEntries(Object.entries(profile.actions ?? {}).map(([name, definition]) => [name, {
97
- params: definition.params,
98
- description: definition.description
99
- }]))
91
+ actions: {}
100
92
  });
101
93
  }
102
94
  function GeneratedPaneRenderer({ spec, profile }) {
103
- const activeProfile = profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile;
104
- const catalog = createGeneratedPaneCatalog(activeProfile);
105
- const normalizedElements = Object.fromEntries(Object.entries(spec.elements).map(([id, element]) => [id, {
95
+ const activeProfile = useMemo(() => profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile, [profile]);
96
+ const catalog = useMemo(() => createGeneratedPaneCatalog(activeProfile), [activeProfile]);
97
+ const normalizedElements = useMemo(() => Object.fromEntries(Object.entries(spec.elements).map(([id, element]) => [id, {
106
98
  ...element,
107
99
  props: element.props ?? {},
108
100
  children: element.children ?? [],
109
101
  visible: "visible" in element ? element.visible : true
110
- }]));
102
+ }])), [spec.elements]);
103
+ const diagnostics = useMemo(() => validateGeneratedPaneSpec(spec, activeProfile.vocabulary).diagnostics, [activeProfile.vocabulary, spec]);
104
+ const errors = diagnostics.filter((item) => item.severity === "error");
105
+ if (errors.length > 0) {
106
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive", children: [
107
+ "Invalid generated pane spec: ",
108
+ errors.slice(0, 3).map((issue) => issue.message).join(" \u2022 ")
109
+ ] });
110
+ }
111
111
  const validation = catalog.validate({ root: spec.root, elements: normalizedElements });
112
112
  if (!validation.success) {
113
113
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive", children: [
@@ -119,7 +119,7 @@ function GeneratedPaneRenderer({ spec, profile }) {
119
119
  return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive", children: "Invalid generated pane spec" });
120
120
  }
121
121
  const { registry } = defineRegistry(catalog, {
122
- components: Object.fromEntries(Object.entries(activeProfile.components).map(([name, definition]) => [name, ({ props, children, emit }) => definition.component({ props, children, emit })])),
122
+ components: Object.fromEntries(Object.entries(activeProfile.components).map(([name, definition]) => [name, ({ props, children }) => definition.component({ props: props && typeof props === "object" && !Array.isArray(props) ? props : {}, children })])),
123
123
  actions: {}
124
124
  });
125
125
  return /* @__PURE__ */ jsx(JSONUIProvider, { registry, handlers: {}, children: /* @__PURE__ */ jsx(Renderer, { spec: validation.data, registry }) });
@@ -153,7 +153,8 @@ function GeneratedPanePane({ params, profile }) {
153
153
  }, [apiBaseUrl, params?.path, params?.spec, workspaceId]);
154
154
  if (loadedFile.loading) return /* @__PURE__ */ jsx2(PaneState, { title: "Loading generated pane", description: params?.path });
155
155
  if (loadedFile.error) return /* @__PURE__ */ jsx2(PaneState, { title: "Could not load generated pane", description: loadedFile.error });
156
- const parsed = parseGeneratedPaneSpec(params?.spec ?? loadedFile.spec);
156
+ const activeProfile = profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile;
157
+ const parsed = parseGeneratedPaneSpec(params?.spec ?? loadedFile.spec, activeProfile.vocabulary);
157
158
  if (!parsed.spec) return /* @__PURE__ */ jsx2(PaneState, { title: "Invalid generated pane spec", description: parsed.errors.slice(0, 5).join(" \u2022 ") });
158
159
  return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground", children: [
159
160
  /* @__PURE__ */ jsx2(Toolbar, { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs2(ToolbarGroup, { children: [
@@ -172,7 +173,7 @@ function PaneState({ title, description }) {
172
173
  }
173
174
 
174
175
  // src/front/GeneratedPaneExplorerPane.tsx
175
- import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
176
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
176
177
  import { FileJson2, PanelTop, RefreshCw } from "lucide-react";
177
178
  import { Badge as Badge3, EmptyState as EmptyState2, IconButton } from "@hachej/boring-ui-kit";
178
179
  import { useApiBaseUrl as useApiBaseUrl2, useWorkspaceRequestId as useWorkspaceRequestId2 } from "@hachej/boring-workspace";
@@ -230,7 +231,7 @@ function GeneratedPaneExplorerPane({ params, containerApi, config = {} }) {
230
231
  });
231
232
  return () => controller.abort();
232
233
  }, [apiBaseUrl, patterns.join("\n"), refreshKey, workspaceId]);
233
- const paths = useMemo(
234
+ const paths = useMemo2(
234
235
  () => state.paths.filter((path) => matchesQuery(path, params?.searchQuery)),
235
236
  [params?.searchQuery, state.paths]
236
237
  );
@@ -361,20 +362,24 @@ var generatedPanePlugin = definePlugin({
361
362
  });
362
363
  var front_default = generatedPanePlugin;
363
364
  export {
365
+ GENERATED_PANE_DIAGNOSTIC_CODES,
364
366
  GENERATED_PANE_EXPLORER_LEFT_TAB_ID,
365
367
  GENERATED_PANE_PANEL_ID,
366
368
  GeneratedPaneExplorerPane,
367
369
  GeneratedPanePane,
368
370
  GeneratedPaneRenderer,
369
371
  baseGeneratedPaneProfile,
372
+ baseGeneratedPaneVocabulary,
370
373
  createGeneratedPaneCatalog,
371
374
  createGeneratedPaneExplorerPane,
372
375
  front_default as default,
373
376
  defineGeneratedPaneProfile,
377
+ defineGeneratedPaneVocabulary,
374
378
  generatedPanePlugin,
375
379
  generatedPaneSurfaceResolver,
376
380
  isGeneratedPanePath,
377
381
  mergeGeneratedPaneProfiles,
378
- parseGeneratedPaneSpec
382
+ parseGeneratedPaneSpec,
383
+ validateGeneratedPaneSpec
379
384
  };
380
385
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/front/index.ts","../../src/front/GeneratedPanePane.tsx","../../src/front/catalog.tsx","../../src/front/GeneratedPaneExplorerPane.tsx","../../src/front/constants.ts","../../src/front/surfaceResolver.ts"],"sourcesContent":["import { PanelTop } from \"lucide-react\"\nimport { definePlugin } from \"@hachej/boring-workspace/plugin\"\nimport { GeneratedPanePane } from \"./GeneratedPanePane\"\nimport { createGeneratedPaneExplorerPane, GeneratedPaneExplorerPane } from \"./GeneratedPaneExplorerPane\"\nimport { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID } from \"./constants\"\nimport { generatedPaneSurfaceResolver } from \"./surfaceResolver\"\n\nexport { GeneratedPanePane }\nexport type { GeneratedPanePaneParams } from \"./GeneratedPanePane\"\nexport { GeneratedPaneExplorerPane, createGeneratedPaneExplorerPane }\nexport type { GeneratedPaneExplorerConfig } from \"./GeneratedPaneExplorerPane\"\nexport {\n GeneratedPaneRenderer,\n baseGeneratedPaneProfile,\n createGeneratedPaneCatalog,\n defineGeneratedPaneProfile,\n mergeGeneratedPaneProfiles,\n} from \"./catalog\"\nexport type {\n GeneratedPaneActionDefinition,\n GeneratedPaneActionHandler,\n GeneratedPaneComponentDefinition,\n GeneratedPaneProfile,\n} from \"./catalog\"\nexport type { GeneratedPaneElementSpec, GeneratedPaneSpec, GeneratedPaneValidationResult } from \"../shared\"\nexport { parseGeneratedPaneSpec } from \"../shared\"\nexport { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID } from \"./constants\"\nexport { generatedPaneSurfaceResolver, isGeneratedPanePath } from \"./surfaceResolver\"\n\nexport const generatedPanePlugin = definePlugin({\n id: \"generated-pane\",\n label: \"Generated Pane\",\n panels: [\n {\n id: GENERATED_PANE_PANEL_ID,\n label: \"Generated Pane\",\n icon: PanelTop,\n component: GeneratedPanePane,\n supportsFullPage: true,\n },\n ],\n leftTabs: [\n {\n id: GENERATED_PANE_EXPLORER_LEFT_TAB_ID,\n title: \"Panes\",\n panelId: GENERATED_PANE_EXPLORER_LEFT_TAB_ID,\n icon: PanelTop,\n component: createGeneratedPaneExplorerPane({\n title: \"Panes\",\n patterns: [\"**/*.pane.json\"],\n panelId: GENERATED_PANE_PANEL_ID,\n itemLabel: \"Pane\",\n emptyDescription: \"Create panes/*.pane.json files to list generated panes here.\",\n }),\n chromeless: true,\n },\n ],\n surfaceResolvers: [generatedPaneSurfaceResolver],\n commands: [\n {\n id: \"generated-pane.open\",\n title: \"Open Generated Pane\",\n panelId: GENERATED_PANE_PANEL_ID,\n keywords: [\"json-render\", \"generated\", \"custom pane\", \"pane\"],\n },\n ],\n})\n\nexport default generatedPanePlugin\n","import { useEffect, useState } from \"react\"\nimport { EmptyState, Toolbar, ToolbarGroup, Badge } from \"@hachej/boring-ui-kit\"\nimport { useApiBaseUrl, useWorkspaceRequestId } from \"@hachej/boring-workspace\"\nimport type { PaneProps } from \"@hachej/boring-workspace/plugin\"\nimport { parseGeneratedPaneSpec, type GeneratedPaneSpec } from \"../shared\"\nimport { GeneratedPaneRenderer, type GeneratedPaneProfile } from \"./catalog\"\n\nexport interface GeneratedPanePaneParams {\n path?: string\n spec?: GeneratedPaneSpec\n}\n\ninterface LoadedPaneFile {\n spec: unknown\n error?: string\n loading: boolean\n}\n\nexport function GeneratedPanePane({ params, profile }: PaneProps<GeneratedPanePaneParams> & { profile?: GeneratedPaneProfile }) {\n const apiBaseUrl = useApiBaseUrl()\n const workspaceId = useWorkspaceRequestId()\n const [loadedFile, setLoadedFile] = useState<LoadedPaneFile>({ spec: null, loading: false })\n\n useEffect(() => {\n if (!params?.path || params.spec) {\n setLoadedFile({ spec: null, loading: false })\n return\n }\n const controller = new AbortController()\n setLoadedFile({ spec: null, loading: true })\n void fetch(`${apiBaseUrl}/api/v1/files/raw?path=${encodeURIComponent(params.path)}`, {\n signal: controller.signal,\n credentials: \"include\",\n headers: workspaceId ? { \"x-boring-workspace-id\": workspaceId } : {},\n })\n .then(async (response) => {\n if (!response.ok) throw new Error(`Failed to load ${params.path}: HTTP ${response.status}`)\n return await response.text()\n })\n .then((text) => setLoadedFile({ spec: JSON.parse(text), loading: false }))\n .catch((error) => {\n if (controller.signal.aborted) return\n setLoadedFile({ spec: null, loading: false, error: error instanceof Error ? error.message : String(error) })\n })\n return () => controller.abort()\n }, [apiBaseUrl, params?.path, params?.spec, workspaceId])\n\n if (loadedFile.loading) return <PaneState title=\"Loading generated pane\" description={params?.path} />\n if (loadedFile.error) return <PaneState title=\"Could not load generated pane\" description={loadedFile.error} />\n\n const parsed = parseGeneratedPaneSpec(params?.spec ?? loadedFile.spec)\n if (!parsed.spec) return <PaneState title=\"Invalid generated pane spec\" description={parsed.errors.slice(0, 5).join(\" • \")} />\n\n return (\n <div className=\"flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground\">\n <Toolbar className=\"border-b border-border px-3 py-2\">\n <ToolbarGroup>\n <Badge variant=\"secondary\">Generated Pane</Badge>\n {parsed.spec.profile ? <Badge variant=\"outline\">{parsed.spec.profile}</Badge> : null}\n </ToolbarGroup>\n </Toolbar>\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto p-4\">\n {parsed.spec.title ? <h1 className=\"mb-1 text-2xl font-semibold tracking-tight\">{parsed.spec.title}</h1> : null}\n {parsed.spec.description ? <p className=\"mb-4 text-sm text-muted-foreground\">{parsed.spec.description}</p> : null}\n <GeneratedPaneRenderer spec={parsed.spec} profile={profile} />\n </div>\n </div>\n )\n}\n\nfunction PaneState({ title, description }: { title: string; description?: string }) {\n return (\n <div className=\"flex h-full min-h-0 min-w-0 items-center justify-center bg-background p-6 text-foreground\">\n <EmptyState title={title} description={description} />\n </div>\n )\n}\n","import type { ReactNode } from \"react\"\nimport { defineCatalog } from \"@json-render/core\"\nimport { defineRegistry, JSONUIProvider, Renderer } from \"@json-render/react\"\nimport { schema } from \"@json-render/react/schema\"\nimport { z } from \"zod\"\nimport { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@hachej/boring-ui-kit\"\nimport type { GeneratedPaneSpec } from \"../shared\"\n\nexport type GeneratedPaneActionHandler = (params: Record<string, unknown>) => void | Promise<void>\n\nexport interface GeneratedPaneComponentDefinition {\n props: z.ZodObject<Record<string, z.ZodType>>\n slots?: string[]\n description: string\n component: (props: { props: Record<string, unknown>; children?: ReactNode; emit: (event: string) => void }) => ReactNode\n}\n\nexport interface GeneratedPaneActionDefinition {\n description: string\n params?: z.ZodObject<Record<string, z.ZodType>>\n handler?: GeneratedPaneActionHandler\n}\n\nexport interface GeneratedPaneProfile {\n id: string\n label: string\n components: Record<string, GeneratedPaneComponentDefinition>\n actions?: Record<string, GeneratedPaneActionDefinition>\n}\n\nexport function defineGeneratedPaneProfile(profile: GeneratedPaneProfile): GeneratedPaneProfile {\n return profile\n}\n\nexport const baseGeneratedPaneProfile = defineGeneratedPaneProfile({\n id: \"base\",\n label: \"Generated Pane\",\n components: {\n Card: {\n description: \"A bordered content card with an optional title and description.\",\n slots: [\"default\"],\n props: z.object({ title: z.string().optional(), description: z.string().optional() }),\n component: ({ props, children }) => (\n <Card>\n {props.title || props.description ? (\n <CardHeader>\n {typeof props.title === \"string\" ? <CardTitle>{props.title}</CardTitle> : null}\n {typeof props.description === \"string\" ? <CardDescription>{props.description}</CardDescription> : null}\n </CardHeader>\n ) : null}\n <CardContent>{children}</CardContent>\n </Card>\n ),\n },\n Stack: {\n description: \"Vertical stack for grouping child elements.\",\n slots: [\"default\"],\n props: z.object({ gap: z.enum([\"sm\", \"md\", \"lg\"]).optional() }),\n component: ({ props, children }) => <div className={props.gap === \"sm\" ? \"space-y-2\" : props.gap === \"lg\" ? \"space-y-6\" : \"space-y-4\"}>{children}</div>,\n },\n Grid: {\n description: \"Responsive grid layout for child elements.\",\n slots: [\"default\"],\n props: z.object({ columns: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]).optional() }),\n component: ({ props, children }) => {\n const columns = props.columns === 3 ? \"lg:grid-cols-3\" : props.columns === 4 ? \"lg:grid-cols-4\" : props.columns === 1 ? \"grid-cols-1\" : \"lg:grid-cols-2\"\n return <div className={`grid gap-4 ${columns}`}>{children}</div>\n },\n },\n Text: {\n description: \"Plain text block.\",\n props: z.object({ text: z.string(), tone: z.enum([\"default\", \"muted\"]).optional() }),\n component: ({ props }) => <p className={props.tone === \"muted\" ? \"text-sm text-muted-foreground\" : \"text-sm text-foreground\"}>{String(props.text)}</p>,\n },\n Badge: {\n description: \"Small status badge.\",\n props: z.object({ label: z.string(), variant: z.enum([\"default\", \"secondary\", \"outline\"]).optional() }),\n component: ({ props }) => <Badge variant={(props.variant as \"default\" | \"secondary\" | \"outline\" | undefined) ?? \"secondary\"}>{String(props.label)}</Badge>,\n },\n Alert: {\n description: \"Notice block for warnings, status, or context.\",\n props: z.object({ title: z.string(), description: z.string().optional() }),\n component: ({ props }) => (\n <div className=\"rounded-lg border border-border bg-muted/40 p-3 text-sm\">\n <div className=\"font-medium text-foreground\">{String(props.title)}</div>\n {typeof props.description === \"string\" ? <div className=\"mt-1 text-muted-foreground\">{props.description}</div> : null}\n </div>\n ),\n },\n Button: {\n description: \"Button that emits its press event to a configured action.\",\n props: z.object({ label: z.string() }),\n component: ({ props, emit }) => <Button size=\"sm\" onClick={() => emit(\"press\")}>{String(props.label)}</Button>,\n },\n },\n})\n\nexport function mergeGeneratedPaneProfiles(...profiles: GeneratedPaneProfile[]): GeneratedPaneProfile {\n const [first, ...rest] = profiles\n return {\n id: first?.id ?? \"generated-pane\",\n label: first?.label ?? \"Generated Pane\",\n components: Object.assign({}, ...profiles.map((profile) => profile.components)),\n actions: Object.assign({}, ...profiles.map((profile) => profile.actions ?? {})),\n }\n}\n\nexport function createGeneratedPaneCatalog(profile: GeneratedPaneProfile) {\n return defineCatalog(schema, {\n components: Object.fromEntries(Object.entries(profile.components).map(([name, definition]) => [name, {\n props: definition.props,\n slots: definition.slots ?? [],\n description: definition.description,\n }])),\n actions: Object.fromEntries(Object.entries(profile.actions ?? {}).map(([name, definition]) => [name, {\n params: definition.params,\n description: definition.description,\n }])),\n })\n}\n\nexport function GeneratedPaneRenderer({ spec, profile }: { spec: GeneratedPaneSpec; profile?: GeneratedPaneProfile }) {\n const activeProfile = profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile\n const catalog = createGeneratedPaneCatalog(activeProfile)\n const normalizedElements = Object.fromEntries(Object.entries(spec.elements).map(([id, element]) => [id, {\n ...element,\n props: element.props ?? {},\n children: element.children ?? [],\n visible: \"visible\" in element ? element.visible : true,\n }]))\n const validation = catalog.validate({ root: spec.root, elements: normalizedElements })\n if (!validation.success) {\n return <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">Invalid generated pane spec: {validation.error?.issues.slice(0, 3).map((issue) => issue.message).join(\" • \")}</div>\n }\n if (!validation.data) {\n return <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">Invalid generated pane spec</div>\n }\n const { registry } = defineRegistry(catalog, {\n components: Object.fromEntries(Object.entries(activeProfile.components).map(([name, definition]) => [name, ({ props, children, emit }: { props: Record<string, unknown>; children?: ReactNode; emit: (event: string) => void }) => definition.component({ props, children, emit })])),\n actions: {},\n })\n return (\n <JSONUIProvider registry={registry} handlers={{}}>\n <Renderer spec={validation.data as never} registry={registry} />\n </JSONUIProvider>\n )\n}\n","import { useEffect, useMemo, useState } from \"react\"\nimport { FileJson2, PanelTop, RefreshCw } from \"lucide-react\"\nimport { Badge, EmptyState, IconButton } from \"@hachej/boring-ui-kit\"\nimport { useApiBaseUrl, useWorkspaceRequestId } from \"@hachej/boring-workspace\"\nimport type { PaneProps } from \"@hachej/boring-workspace/plugin\"\nimport { GENERATED_PANE_PANEL_ID } from \"./constants\"\n\nexport interface GeneratedPaneExplorerConfig {\n title?: string\n patterns?: string[]\n panelId?: string\n itemLabel?: string\n emptyTitle?: string\n emptyDescription?: string\n}\n\ninterface PaneSearchState {\n loading: boolean\n error?: string\n paths: string[]\n}\n\ninterface PaneSearchResponse {\n results?: string[]\n}\n\nconst DEFAULT_PATTERNS = [\"**/*.pane.json\"]\n\nfunction titleFromPath(path: string): string {\n const file = path.split(\"/\").pop() ?? path\n return file\n .replace(/\\.(pane|dashboard)\\.json$/i, \"\")\n .replace(/[-_]+/g, \" \")\n}\n\nfunction matchesQuery(path: string, query: string | undefined): boolean {\n const value = query?.trim().toLowerCase()\n if (!value) return true\n return path.toLowerCase().includes(value) || titleFromPath(path).toLowerCase().includes(value)\n}\n\nfunction dedupeAndSort(values: string[]): string[] {\n return [...new Set(values)].sort((a, b) => a.localeCompare(b))\n}\n\nexport function createGeneratedPaneExplorerPane(config: GeneratedPaneExplorerConfig = {}) {\n return function GeneratedPaneExplorerPaneWithConfig(props: PaneProps<{ searchQuery?: string }>) {\n return <GeneratedPaneExplorerPane {...props} config={config} />\n }\n}\n\nexport function GeneratedPaneExplorerPane({ params, containerApi, config = {} }: PaneProps<{ searchQuery?: string }> & { config?: GeneratedPaneExplorerConfig }) {\n const apiBaseUrl = useApiBaseUrl()\n const workspaceId = useWorkspaceRequestId()\n const patterns = config.patterns?.length ? config.patterns : DEFAULT_PATTERNS\n const panelId = config.panelId ?? GENERATED_PANE_PANEL_ID\n const title = config.title ?? \"Panes\"\n const itemLabel = config.itemLabel ?? \"Generated pane\"\n const [state, setState] = useState<PaneSearchState>({ loading: true, paths: [] })\n const [refreshKey, setRefreshKey] = useState(0)\n\n useEffect(() => {\n const controller = new AbortController()\n setState((prev) => ({ ...prev, loading: true, error: undefined }))\n void Promise.all(patterns.map(async (pattern) => {\n const response = await fetch(`${apiBaseUrl}/api/v1/files/search?q=${encodeURIComponent(pattern)}&limit=500`, {\n signal: controller.signal,\n credentials: \"include\",\n headers: workspaceId ? { \"x-boring-workspace-id\": workspaceId } : {},\n })\n if (!response.ok) throw new Error(`Pane search failed for ${pattern} with HTTP ${response.status}`)\n const body = await response.json() as PaneSearchResponse\n return body.results ?? []\n }))\n .then((groups) => {\n setState({ loading: false, paths: dedupeAndSort(groups.flat()) })\n })\n .catch((error) => {\n if (controller.signal.aborted) return\n setState({ loading: false, paths: [], error: error instanceof Error ? error.message : String(error) })\n })\n return () => controller.abort()\n }, [apiBaseUrl, patterns.join(\"\\n\"), refreshKey, workspaceId])\n\n const paths = useMemo(\n () => state.paths.filter((path) => matchesQuery(path, params?.searchQuery)),\n [params?.searchQuery, state.paths],\n )\n\n const openPane = (path: string) => {\n containerApi.addPanel({\n id: `${panelId}:${path}`,\n component: panelId,\n title: titleFromPath(path),\n params: { path },\n })\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col text-sm\">\n <div className=\"flex items-center justify-between border-b border-border/60 px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <PanelTop className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"truncate font-medium\">{title}</span>\n <Badge variant=\"secondary\">{state.paths.length}</Badge>\n </div>\n <IconButton\n type=\"button\"\n aria-label={`Refresh ${title.toLowerCase()}`}\n variant=\"ghost\"\n size=\"icon-xs\"\n onClick={() => setRefreshKey((value) => value + 1)}\n disabled={state.loading}\n >\n <RefreshCw className={`h-3.5 w-3.5 ${state.loading ? \"animate-spin\" : \"\"}`} />\n </IconButton>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {state.error ? (\n <EmptyState title={`Could not list ${title.toLowerCase()}`} description={state.error} />\n ) : state.loading ? (\n <div className=\"px-2 py-3 text-xs text-muted-foreground\">Scanning {title.toLowerCase()}…</div>\n ) : paths.length === 0 ? (\n <EmptyState\n title={config.emptyTitle ?? `No ${title.toLowerCase()} found`}\n description={config.emptyDescription ?? `Create ${patterns.join(\" or \")} files to list them here.`}\n />\n ) : (\n <div className=\"space-y-1\">\n {paths.map((path) => (\n <button\n key={path}\n type=\"button\"\n onClick={() => openPane(path)}\n className=\"flex w-full min-w-0 items-start gap-2 rounded-lg px-2 py-2 text-left hover:bg-background/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n >\n <FileJson2 className=\"mt-0.5 h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-[13px] font-medium text-foreground\">{titleFromPath(path)}</span>\n <span className=\"block truncate text-[11px] text-muted-foreground\">{path}</span>\n </span>\n <Badge variant=\"outline\" className=\"mt-0.5 shrink-0 text-[10px]\">{itemLabel}</Badge>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )\n}\n","export const GENERATED_PANE_PANEL_ID = \"generated-pane.panel\"\nexport const GENERATED_PANE_EXPLORER_LEFT_TAB_ID = \"generated-pane.explorer\"\n","import {\n WORKSPACE_OPEN_PATH_SURFACE_KIND,\n type BoringFrontSurfaceResolverRegistration,\n} from \"@hachej/boring-workspace/plugin\"\nimport { GENERATED_PANE_PANEL_ID } from \"./constants\"\n\nexport function isGeneratedPanePath(path: string): boolean {\n return /(^|\\/)panes\\/[^/].*\\.pane\\.json$/i.test(path) || /\\.pane\\.json$/i.test(path)\n}\n\nfunction titleFromPath(path: string): string {\n const file = path.split(\"/\").pop() ?? path\n return file.replace(/\\.pane\\.json$/i, \"\").replace(/[-_]+/g, \" \")\n}\n\nexport const generatedPaneSurfaceResolver: BoringFrontSurfaceResolverRegistration = {\n id: \"generated-pane.open-path\",\n kind: WORKSPACE_OPEN_PATH_SURFACE_KIND,\n source: \"app\",\n resolve: (request) => {\n if (request.kind !== WORKSPACE_OPEN_PATH_SURFACE_KIND) return null\n const target = String(request.target ?? \"\")\n if (!isGeneratedPanePath(target)) return null\n return {\n component: GENERATED_PANE_PANEL_ID,\n title: titleFromPath(target),\n params: { path: target },\n score: 110,\n }\n },\n}\n"],"mappings":"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","names":["PanelTop","Badge","jsx","jsxs","Badge","useEffect","useState","Badge","EmptyState","useApiBaseUrl","useWorkspaceRequestId","jsx","jsxs","useApiBaseUrl","useWorkspaceRequestId","useState","useEffect","Badge","EmptyState","titleFromPath","PanelTop"]}
1
+ {"version":3,"sources":["../../src/front/index.ts","../../src/front/GeneratedPanePane.tsx","../../src/front/catalog.tsx","../../src/front/GeneratedPaneExplorerPane.tsx","../../src/front/constants.ts","../../src/front/surfaceResolver.ts"],"sourcesContent":["import { PanelTop } from \"lucide-react\"\nimport { definePlugin } from \"@hachej/boring-workspace/plugin\"\nimport { GeneratedPanePane } from \"./GeneratedPanePane\"\nimport { createGeneratedPaneExplorerPane, GeneratedPaneExplorerPane } from \"./GeneratedPaneExplorerPane\"\nimport { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID } from \"./constants\"\nimport { generatedPaneSurfaceResolver } from \"./surfaceResolver\"\n\nexport { GeneratedPanePane }\nexport type { GeneratedPanePaneParams } from \"./GeneratedPanePane\"\nexport { GeneratedPaneExplorerPane, createGeneratedPaneExplorerPane }\nexport type { GeneratedPaneExplorerConfig } from \"./GeneratedPaneExplorerPane\"\nexport {\n GeneratedPaneRenderer,\n baseGeneratedPaneProfile,\n createGeneratedPaneCatalog,\n defineGeneratedPaneProfile,\n mergeGeneratedPaneProfiles,\n} from \"./catalog\"\nexport type {\n GeneratedPaneComponentBinding,\n GeneratedPaneComponentDefinition,\n GeneratedPaneComponentProps,\n GeneratedPaneProfile,\n} from \"./catalog\"\nexport type {\n GeneratedPaneDiagnostic,\n GeneratedPaneDiagnosticCode,\n GeneratedPaneDiagnosticsResult,\n GeneratedPaneElementSpec,\n GeneratedPaneSpec,\n GeneratedPaneValidationResult,\n GeneratedPaneVocabulary,\n} from \"../shared\"\nexport { GENERATED_PANE_DIAGNOSTIC_CODES, baseGeneratedPaneVocabulary, defineGeneratedPaneVocabulary, parseGeneratedPaneSpec, validateGeneratedPaneSpec } from \"../shared\"\nexport { GENERATED_PANE_EXPLORER_LEFT_TAB_ID, GENERATED_PANE_PANEL_ID } from \"./constants\"\nexport { generatedPaneSurfaceResolver, isGeneratedPanePath } from \"./surfaceResolver\"\n\nexport const generatedPanePlugin = definePlugin({\n id: \"generated-pane\",\n label: \"Generated Pane\",\n panels: [\n {\n id: GENERATED_PANE_PANEL_ID,\n label: \"Generated Pane\",\n icon: PanelTop,\n component: GeneratedPanePane,\n supportsFullPage: true,\n },\n ],\n leftTabs: [\n {\n id: GENERATED_PANE_EXPLORER_LEFT_TAB_ID,\n title: \"Panes\",\n panelId: GENERATED_PANE_EXPLORER_LEFT_TAB_ID,\n icon: PanelTop,\n component: createGeneratedPaneExplorerPane({\n title: \"Panes\",\n patterns: [\"**/*.pane.json\"],\n panelId: GENERATED_PANE_PANEL_ID,\n itemLabel: \"Pane\",\n emptyDescription: \"Create panes/*.pane.json files to list generated panes here.\",\n }),\n chromeless: true,\n },\n ],\n surfaceResolvers: [generatedPaneSurfaceResolver],\n commands: [\n {\n id: \"generated-pane.open\",\n title: \"Open Generated Pane\",\n panelId: GENERATED_PANE_PANEL_ID,\n keywords: [\"json-render\", \"generated\", \"custom pane\", \"pane\"],\n },\n ],\n})\n\nexport default generatedPanePlugin\n","import { useEffect, useState } from \"react\"\nimport { EmptyState, Toolbar, ToolbarGroup, Badge } from \"@hachej/boring-ui-kit\"\nimport { useApiBaseUrl, useWorkspaceRequestId } from \"@hachej/boring-workspace\"\nimport type { PaneProps } from \"@hachej/boring-workspace/plugin\"\nimport { parseGeneratedPaneSpec, type GeneratedPaneSpec } from \"../shared\"\nimport { GeneratedPaneRenderer, baseGeneratedPaneProfile, mergeGeneratedPaneProfiles, type GeneratedPaneProfile } from \"./catalog\"\n\nexport interface GeneratedPanePaneParams {\n path?: string\n spec?: GeneratedPaneSpec\n}\n\ninterface LoadedPaneFile {\n spec: unknown\n error?: string\n loading: boolean\n}\n\nexport function GeneratedPanePane({ params, profile }: PaneProps<GeneratedPanePaneParams> & { profile?: GeneratedPaneProfile }) {\n const apiBaseUrl = useApiBaseUrl()\n const workspaceId = useWorkspaceRequestId()\n const [loadedFile, setLoadedFile] = useState<LoadedPaneFile>({ spec: null, loading: false })\n\n useEffect(() => {\n if (!params?.path || params.spec) {\n setLoadedFile({ spec: null, loading: false })\n return\n }\n const controller = new AbortController()\n setLoadedFile({ spec: null, loading: true })\n void fetch(`${apiBaseUrl}/api/v1/files/raw?path=${encodeURIComponent(params.path)}`, {\n signal: controller.signal,\n credentials: \"include\",\n headers: workspaceId ? { \"x-boring-workspace-id\": workspaceId } : {},\n })\n .then(async (response) => {\n if (!response.ok) throw new Error(`Failed to load ${params.path}: HTTP ${response.status}`)\n return await response.text()\n })\n .then((text) => setLoadedFile({ spec: JSON.parse(text), loading: false }))\n .catch((error) => {\n if (controller.signal.aborted) return\n setLoadedFile({ spec: null, loading: false, error: error instanceof Error ? error.message : String(error) })\n })\n return () => controller.abort()\n }, [apiBaseUrl, params?.path, params?.spec, workspaceId])\n\n if (loadedFile.loading) return <PaneState title=\"Loading generated pane\" description={params?.path} />\n if (loadedFile.error) return <PaneState title=\"Could not load generated pane\" description={loadedFile.error} />\n\n const activeProfile = profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile\n const parsed = parseGeneratedPaneSpec(params?.spec ?? loadedFile.spec, activeProfile.vocabulary)\n if (!parsed.spec) return <PaneState title=\"Invalid generated pane spec\" description={parsed.errors.slice(0, 5).join(\" • \")} />\n\n return (\n <div className=\"flex h-full min-h-0 min-w-0 flex-col bg-background text-foreground\">\n <Toolbar className=\"border-b border-border px-3 py-2\">\n <ToolbarGroup>\n <Badge variant=\"secondary\">Generated Pane</Badge>\n {parsed.spec.profile ? <Badge variant=\"outline\">{parsed.spec.profile}</Badge> : null}\n </ToolbarGroup>\n </Toolbar>\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto p-4\">\n {parsed.spec.title ? <h1 className=\"mb-1 text-2xl font-semibold tracking-tight\">{parsed.spec.title}</h1> : null}\n {parsed.spec.description ? <p className=\"mb-4 text-sm text-muted-foreground\">{parsed.spec.description}</p> : null}\n <GeneratedPaneRenderer spec={parsed.spec} profile={profile} />\n </div>\n </div>\n )\n}\n\nfunction PaneState({ title, description }: { title: string; description?: string }) {\n return (\n <div className=\"flex h-full min-h-0 min-w-0 items-center justify-center bg-background p-6 text-foreground\">\n <EmptyState title={title} description={description} />\n </div>\n )\n}\n","import { useMemo, type ReactNode } from \"react\"\nimport { defineCatalog } from \"@json-render/core\"\nimport { defineRegistry, JSONUIProvider, Renderer } from \"@json-render/react\"\nimport { schema } from \"@json-render/react/schema\"\nimport { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@hachej/boring-ui-kit\"\nimport {\n baseGeneratedPaneVocabulary,\n validateGeneratedPaneSpec,\n type GeneratedPaneComponentVocabularyEntry,\n type GeneratedPaneSpec,\n type GeneratedPaneVocabulary,\n} from \"../shared\"\n\nexport interface GeneratedPaneComponentProps {\n props: Record<string, unknown>\n children?: ReactNode\n}\n\nexport interface GeneratedPaneComponentBinding {\n component: (props: GeneratedPaneComponentProps) => ReactNode\n}\n\nexport interface GeneratedPaneComponentDefinition extends GeneratedPaneComponentVocabularyEntry, GeneratedPaneComponentBinding {}\n\nexport interface GeneratedPaneProfile {\n vocabulary: GeneratedPaneVocabulary\n components: Record<string, GeneratedPaneComponentBinding>\n}\n\nexport function defineGeneratedPaneProfile(profile: GeneratedPaneProfile): GeneratedPaneProfile {\n const componentKeys = new Set(Object.keys(profile.components))\n const vocabularyKeys = new Set(Object.keys(profile.vocabulary.components))\n const bindingWithoutVocabulary = [...componentKeys].filter((key) => !vocabularyKeys.has(key))\n const vocabularyWithoutBinding = [...vocabularyKeys].filter((key) => !componentKeys.has(key))\n if (bindingWithoutVocabulary.length > 0) throw new Error(`generated pane profile ${profile.vocabulary.id} has render bindings without vocabulary entries: ${bindingWithoutVocabulary.join(\", \")}`)\n if (vocabularyWithoutBinding.length > 0) throw new Error(`generated pane profile ${profile.vocabulary.id} has vocabulary entries without render bindings: ${vocabularyWithoutBinding.join(\", \")}`)\n return profile\n}\n\nexport function defineLegacyGeneratedPaneProfile(profile: { id: string; label: string; components: Record<string, GeneratedPaneComponentDefinition> }): GeneratedPaneProfile {\n return defineGeneratedPaneProfile({\n vocabulary: {\n id: profile.id,\n label: profile.label,\n components: Object.fromEntries(Object.entries(profile.components).map(([key, value]) => [key, { description: value.description, props: value.props, slots: value.slots }])),\n },\n components: Object.fromEntries(Object.entries(profile.components).map(([key, value]) => [key, { component: value.component }])),\n })\n}\n\nexport const baseGeneratedPaneProfile = defineGeneratedPaneProfile({\n vocabulary: baseGeneratedPaneVocabulary,\n components: {\n Card: {\n component: ({ props, children }) => (\n <Card>\n {props.title || props.description ? (\n <CardHeader>\n {typeof props.title === \"string\" ? <CardTitle>{props.title}</CardTitle> : null}\n {typeof props.description === \"string\" ? <CardDescription>{props.description}</CardDescription> : null}\n </CardHeader>\n ) : null}\n <CardContent>{children}</CardContent>\n </Card>\n ),\n },\n Stack: {\n component: ({ props, children }) => <div className={props.gap === \"sm\" ? \"space-y-2\" : props.gap === \"lg\" ? \"space-y-6\" : \"space-y-4\"}>{children}</div>,\n },\n Grid: {\n component: ({ props, children }) => {\n const columns = props.columns === 3 ? \"lg:grid-cols-3\" : props.columns === 4 ? \"lg:grid-cols-4\" : props.columns === 1 ? \"grid-cols-1\" : \"lg:grid-cols-2\"\n return <div className={`grid gap-4 ${columns}`}>{children}</div>\n },\n },\n Text: {\n component: ({ props }) => <p className={props.tone === \"muted\" ? \"text-sm text-muted-foreground\" : \"text-sm text-foreground\"}>{String(props.text)}</p>,\n },\n Badge: {\n component: ({ props }) => <Badge variant={(props.variant as \"default\" | \"secondary\" | \"outline\" | undefined) ?? \"secondary\"}>{String(props.label)}</Badge>,\n },\n Alert: {\n component: ({ props }) => (\n <div className=\"rounded-lg border border-border bg-muted/40 p-3 text-sm\">\n <div className=\"font-medium text-foreground\">{String(props.title)}</div>\n {typeof props.description === \"string\" ? <div className=\"mt-1 text-muted-foreground\">{props.description}</div> : null}\n </div>\n ),\n },\n },\n})\n\nexport function mergeGeneratedPaneProfiles(...profiles: GeneratedPaneProfile[]): GeneratedPaneProfile {\n const [first, ...rest] = profiles\n const mergedVocabularyComponents: Record<string, GeneratedPaneComponentVocabularyEntry> = Object.assign({}, ...profiles.map((profile) => profile.vocabulary.components))\n const mergedBindings: Record<string, GeneratedPaneComponentBinding> = Object.assign({}, ...profiles.map((profile) => profile.components))\n const finalProfile = rest.at(-1) ?? first\n return defineGeneratedPaneProfile({\n vocabulary: {\n id: finalProfile?.vocabulary.id ?? \"base\",\n label: finalProfile?.vocabulary.label ?? \"Generated Pane\",\n components: Object.fromEntries(Object.entries(mergedVocabularyComponents).filter(([key]) => key in mergedBindings)),\n diagnostics: profiles.flatMap((profile) => profile.vocabulary.diagnostics ?? []),\n },\n components: Object.fromEntries(Object.entries(mergedBindings).filter(([key]) => key in mergedVocabularyComponents)),\n })\n}\n\nexport function createGeneratedPaneCatalog(profile: GeneratedPaneProfile) {\n return defineCatalog(schema, {\n components: Object.fromEntries(Object.entries(profile.vocabulary.components).map(([name, definition]) => [name, {\n props: definition.props,\n slots: definition.slots ?? [],\n description: definition.description,\n }])),\n actions: {},\n })\n}\n\nexport function GeneratedPaneRenderer({ spec, profile }: { spec: GeneratedPaneSpec; profile?: GeneratedPaneProfile }) {\n const activeProfile = useMemo(() => profile ? mergeGeneratedPaneProfiles(baseGeneratedPaneProfile, profile) : baseGeneratedPaneProfile, [profile])\n const catalog = useMemo(() => createGeneratedPaneCatalog(activeProfile), [activeProfile])\n const normalizedElements = useMemo(() => Object.fromEntries(Object.entries(spec.elements).map(([id, element]) => [id, {\n ...element,\n props: element.props ?? {},\n children: element.children ?? [],\n visible: \"visible\" in element ? element.visible : true,\n }])), [spec.elements])\n const diagnostics = useMemo(() => validateGeneratedPaneSpec(spec, activeProfile.vocabulary).diagnostics, [activeProfile.vocabulary, spec])\n const errors = diagnostics.filter((item) => item.severity === \"error\")\n if (errors.length > 0) {\n return <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">Invalid generated pane spec: {errors.slice(0, 3).map((issue) => issue.message).join(\" • \")}</div>\n }\n const validation = catalog.validate({ root: spec.root, elements: normalizedElements })\n if (!validation.success) {\n return <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">Invalid generated pane spec: {validation.error?.issues.slice(0, 3).map((issue) => issue.message).join(\" • \")}</div>\n }\n if (!validation.data) {\n return <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive\">Invalid generated pane spec</div>\n }\n const { registry } = defineRegistry(catalog, {\n components: Object.fromEntries(Object.entries(activeProfile.components).map(([name, definition]) => [name, ({ props, children }: { props: unknown; children?: ReactNode }) => definition.component({ props: props && typeof props === \"object\" && !Array.isArray(props) ? props as Record<string, unknown> : {}, children })])) as never,\n actions: {},\n })\n return (\n <JSONUIProvider registry={registry} handlers={{}}>\n <Renderer spec={validation.data as never} registry={registry} />\n </JSONUIProvider>\n )\n}\n","import { useEffect, useMemo, useState } from \"react\"\nimport { FileJson2, PanelTop, RefreshCw } from \"lucide-react\"\nimport { Badge, EmptyState, IconButton } from \"@hachej/boring-ui-kit\"\nimport { useApiBaseUrl, useWorkspaceRequestId } from \"@hachej/boring-workspace\"\nimport type { PaneProps } from \"@hachej/boring-workspace/plugin\"\nimport { GENERATED_PANE_PANEL_ID } from \"./constants\"\n\nexport interface GeneratedPaneExplorerConfig {\n title?: string\n patterns?: string[]\n panelId?: string\n itemLabel?: string\n emptyTitle?: string\n emptyDescription?: string\n}\n\ninterface PaneSearchState {\n loading: boolean\n error?: string\n paths: string[]\n}\n\ninterface PaneSearchResponse {\n results?: string[]\n}\n\nconst DEFAULT_PATTERNS = [\"**/*.pane.json\"]\n\nfunction titleFromPath(path: string): string {\n const file = path.split(\"/\").pop() ?? path\n return file\n .replace(/\\.(pane|dashboard)\\.json$/i, \"\")\n .replace(/[-_]+/g, \" \")\n}\n\nfunction matchesQuery(path: string, query: string | undefined): boolean {\n const value = query?.trim().toLowerCase()\n if (!value) return true\n return path.toLowerCase().includes(value) || titleFromPath(path).toLowerCase().includes(value)\n}\n\nfunction dedupeAndSort(values: string[]): string[] {\n return [...new Set(values)].sort((a, b) => a.localeCompare(b))\n}\n\nexport function createGeneratedPaneExplorerPane(config: GeneratedPaneExplorerConfig = {}) {\n return function GeneratedPaneExplorerPaneWithConfig(props: PaneProps<{ searchQuery?: string }>) {\n return <GeneratedPaneExplorerPane {...props} config={config} />\n }\n}\n\nexport function GeneratedPaneExplorerPane({ params, containerApi, config = {} }: PaneProps<{ searchQuery?: string }> & { config?: GeneratedPaneExplorerConfig }) {\n const apiBaseUrl = useApiBaseUrl()\n const workspaceId = useWorkspaceRequestId()\n const patterns = config.patterns?.length ? config.patterns : DEFAULT_PATTERNS\n const panelId = config.panelId ?? GENERATED_PANE_PANEL_ID\n const title = config.title ?? \"Panes\"\n const itemLabel = config.itemLabel ?? \"Generated pane\"\n const [state, setState] = useState<PaneSearchState>({ loading: true, paths: [] })\n const [refreshKey, setRefreshKey] = useState(0)\n\n useEffect(() => {\n const controller = new AbortController()\n setState((prev) => ({ ...prev, loading: true, error: undefined }))\n void Promise.all(patterns.map(async (pattern) => {\n const response = await fetch(`${apiBaseUrl}/api/v1/files/search?q=${encodeURIComponent(pattern)}&limit=500`, {\n signal: controller.signal,\n credentials: \"include\",\n headers: workspaceId ? { \"x-boring-workspace-id\": workspaceId } : {},\n })\n if (!response.ok) throw new Error(`Pane search failed for ${pattern} with HTTP ${response.status}`)\n const body = await response.json() as PaneSearchResponse\n return body.results ?? []\n }))\n .then((groups) => {\n setState({ loading: false, paths: dedupeAndSort(groups.flat()) })\n })\n .catch((error) => {\n if (controller.signal.aborted) return\n setState({ loading: false, paths: [], error: error instanceof Error ? error.message : String(error) })\n })\n return () => controller.abort()\n }, [apiBaseUrl, patterns.join(\"\\n\"), refreshKey, workspaceId])\n\n const paths = useMemo(\n () => state.paths.filter((path) => matchesQuery(path, params?.searchQuery)),\n [params?.searchQuery, state.paths],\n )\n\n const openPane = (path: string) => {\n containerApi.addPanel({\n id: `${panelId}:${path}`,\n component: panelId,\n title: titleFromPath(path),\n params: { path },\n })\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col text-sm\">\n <div className=\"flex items-center justify-between border-b border-border/60 px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <PanelTop className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"truncate font-medium\">{title}</span>\n <Badge variant=\"secondary\">{state.paths.length}</Badge>\n </div>\n <IconButton\n type=\"button\"\n aria-label={`Refresh ${title.toLowerCase()}`}\n variant=\"ghost\"\n size=\"icon-xs\"\n onClick={() => setRefreshKey((value) => value + 1)}\n disabled={state.loading}\n >\n <RefreshCw className={`h-3.5 w-3.5 ${state.loading ? \"animate-spin\" : \"\"}`} />\n </IconButton>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {state.error ? (\n <EmptyState title={`Could not list ${title.toLowerCase()}`} description={state.error} />\n ) : state.loading ? (\n <div className=\"px-2 py-3 text-xs text-muted-foreground\">Scanning {title.toLowerCase()}…</div>\n ) : paths.length === 0 ? (\n <EmptyState\n title={config.emptyTitle ?? `No ${title.toLowerCase()} found`}\n description={config.emptyDescription ?? `Create ${patterns.join(\" or \")} files to list them here.`}\n />\n ) : (\n <div className=\"space-y-1\">\n {paths.map((path) => (\n <button\n key={path}\n type=\"button\"\n onClick={() => openPane(path)}\n className=\"flex w-full min-w-0 items-start gap-2 rounded-lg px-2 py-2 text-left hover:bg-background/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n >\n <FileJson2 className=\"mt-0.5 h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-[13px] font-medium text-foreground\">{titleFromPath(path)}</span>\n <span className=\"block truncate text-[11px] text-muted-foreground\">{path}</span>\n </span>\n <Badge variant=\"outline\" className=\"mt-0.5 shrink-0 text-[10px]\">{itemLabel}</Badge>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n )\n}\n","export const GENERATED_PANE_PANEL_ID = \"generated-pane.panel\"\nexport const GENERATED_PANE_EXPLORER_LEFT_TAB_ID = \"generated-pane.explorer\"\n","import {\n WORKSPACE_OPEN_PATH_SURFACE_KIND,\n type BoringFrontSurfaceResolverRegistration,\n} from \"@hachej/boring-workspace/plugin\"\nimport { GENERATED_PANE_PANEL_ID } from \"./constants\"\n\nexport function isGeneratedPanePath(path: string): boolean {\n return /(^|\\/)panes\\/[^/].*\\.pane\\.json$/i.test(path) || /\\.pane\\.json$/i.test(path)\n}\n\nfunction titleFromPath(path: string): string {\n const file = path.split(\"/\").pop() ?? path\n return file.replace(/\\.pane\\.json$/i, \"\").replace(/[-_]+/g, \" \")\n}\n\nexport const generatedPaneSurfaceResolver: BoringFrontSurfaceResolverRegistration = {\n id: \"generated-pane.open-path\",\n kind: WORKSPACE_OPEN_PATH_SURFACE_KIND,\n source: \"app\",\n resolve: (request) => {\n if (request.kind !== WORKSPACE_OPEN_PATH_SURFACE_KIND) return null\n const target = String(request.target ?? \"\")\n if (!isGeneratedPanePath(target)) return null\n return {\n component: GENERATED_PANE_PANEL_ID,\n title: titleFromPath(target),\n params: { path: target },\n score: 110,\n }\n },\n}\n"],"mappings":"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","names":["PanelTop","Badge","jsx","jsxs","Badge","useEffect","useMemo","useState","Badge","EmptyState","useApiBaseUrl","useWorkspaceRequestId","jsx","jsxs","useApiBaseUrl","useWorkspaceRequestId","useState","useEffect","useMemo","Badge","EmptyState","titleFromPath","PanelTop"]}
@@ -0,0 +1,16 @@
1
+ import { WorkspaceServerPlugin } from '@hachej/boring-workspace/server';
2
+ import { GeneratedPaneDiagnostic } from '../shared/index.js';
3
+ import 'zod';
4
+
5
+ declare const GENERATED_PANE_VALIDATE_OP = "generated-pane.v1.validate";
6
+ interface GeneratedPaneValidateInput {
7
+ spec?: unknown;
8
+ }
9
+ interface GeneratedPaneValidateOutput {
10
+ ok: boolean;
11
+ diagnostics: GeneratedPaneDiagnostic[];
12
+ }
13
+ declare function createGeneratedPaneServerPlugin(): WorkspaceServerPlugin;
14
+ declare function defaultGeneratedPaneServerPlugin(): WorkspaceServerPlugin;
15
+
16
+ export { GENERATED_PANE_VALIDATE_OP, type GeneratedPaneValidateInput, type GeneratedPaneValidateOutput, createGeneratedPaneServerPlugin, defaultGeneratedPaneServerPlugin as default };
@@ -0,0 +1,53 @@
1
+ import {
2
+ validateGeneratedPaneSpec
3
+ } from "../chunk-PELWQS23.js";
4
+
5
+ // src/server/index.ts
6
+ import { z } from "zod";
7
+ import { defineServerPlugin, defineTrustedDomainBridgeHandler } from "@hachej/boring-workspace/server";
8
+ var GENERATED_PANE_VALIDATE_OP = "generated-pane.v1.validate";
9
+ function contribution(entry) {
10
+ return {
11
+ definition: entry.definition,
12
+ handler: entry.handler
13
+ };
14
+ }
15
+ function assertValidateInput(input) {
16
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("generated-pane validation input must be an object");
17
+ if (!("spec" in input)) throw new Error("generated-pane validation requires spec");
18
+ }
19
+ function createGeneratedPaneServerPlugin() {
20
+ const validateGeneratedPane = defineTrustedDomainBridgeHandler({
21
+ op: GENERATED_PANE_VALIDATE_OP,
22
+ version: 1,
23
+ owner: "generated-pane",
24
+ callerClassesAllowed: ["browser", "runtime", "server"],
25
+ requiredCapabilities: ["generated-pane:validate"],
26
+ inputSchema: z.object({ spec: z.unknown() }),
27
+ outputSchema: { type: "object" },
28
+ maxInputBytes: 2 * 1024 * 1024,
29
+ maxOutputBytes: 512 * 1024,
30
+ timeoutMs: 1e4,
31
+ idempotencyPolicy: "none",
32
+ handler: async ({ input }) => {
33
+ assertValidateInput(input);
34
+ const result = validateGeneratedPaneSpec(input.spec);
35
+ return { ok: result.diagnostics.every((item) => item.severity !== "error"), diagnostics: result.diagnostics };
36
+ }
37
+ });
38
+ return defineServerPlugin({
39
+ id: "generated-pane",
40
+ label: "Generated Pane",
41
+ workspaceBridgeHandlers: [contribution(validateGeneratedPane)],
42
+ systemPrompt: "Use generated-pane.v1.validate through WorkspaceBridge for base generated pane specs. Profile-specific panes must use their owning plugin validate op."
43
+ });
44
+ }
45
+ function defaultGeneratedPaneServerPlugin() {
46
+ return createGeneratedPaneServerPlugin();
47
+ }
48
+ export {
49
+ GENERATED_PANE_VALIDATE_OP,
50
+ createGeneratedPaneServerPlugin,
51
+ defaultGeneratedPaneServerPlugin as default
52
+ };
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["import { z } from \"zod\"\nimport { defineServerPlugin, defineTrustedDomainBridgeHandler, type WorkspaceBridgeHandler, type WorkspaceBridgeHandlerContribution, type WorkspaceServerPlugin } from \"@hachej/boring-workspace/server\"\nimport { validateGeneratedPaneSpec, type GeneratedPaneDiagnostic } from \"../shared\"\n\nexport const GENERATED_PANE_VALIDATE_OP = \"generated-pane.v1.validate\"\n\nexport interface GeneratedPaneValidateInput {\n spec?: unknown\n}\n\nexport interface GeneratedPaneValidateOutput {\n ok: boolean\n diagnostics: GeneratedPaneDiagnostic[]\n}\n\nfunction contribution<TInput, TOutput>(entry: ReturnType<typeof defineTrustedDomainBridgeHandler<TInput, TOutput>>): WorkspaceBridgeHandlerContribution {\n return {\n definition: entry.definition,\n handler: entry.handler as WorkspaceBridgeHandler,\n }\n}\n\nfunction assertValidateInput(input: unknown): asserts input is GeneratedPaneValidateInput {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) throw new Error(\"generated-pane validation input must be an object\")\n if (!(\"spec\" in input)) throw new Error(\"generated-pane validation requires spec\")\n}\n\nexport function createGeneratedPaneServerPlugin(): WorkspaceServerPlugin {\n const validateGeneratedPane = defineTrustedDomainBridgeHandler<GeneratedPaneValidateInput, GeneratedPaneValidateOutput>({\n op: GENERATED_PANE_VALIDATE_OP,\n version: 1,\n owner: \"generated-pane\",\n callerClassesAllowed: [\"browser\", \"runtime\", \"server\"],\n requiredCapabilities: [\"generated-pane:validate\"],\n inputSchema: z.object({ spec: z.unknown() }),\n outputSchema: { type: \"object\" },\n maxInputBytes: 2 * 1024 * 1024,\n maxOutputBytes: 512 * 1024,\n timeoutMs: 10_000,\n idempotencyPolicy: \"none\",\n handler: async ({ input }) => {\n assertValidateInput(input)\n const result = validateGeneratedPaneSpec(input.spec)\n return { ok: result.diagnostics.every((item) => item.severity !== \"error\"), diagnostics: result.diagnostics }\n },\n })\n\n return defineServerPlugin({\n id: \"generated-pane\",\n label: \"Generated Pane\",\n workspaceBridgeHandlers: [contribution(validateGeneratedPane)],\n systemPrompt: \"Use generated-pane.v1.validate through WorkspaceBridge for base generated pane specs. Profile-specific panes must use their owning plugin validate op.\",\n })\n}\n\nexport default function defaultGeneratedPaneServerPlugin(): WorkspaceServerPlugin {\n return createGeneratedPaneServerPlugin()\n}\n"],"mappings":";;;;;AAAA,SAAS,SAAS;AAClB,SAAS,oBAAoB,wCAA0I;AAGhK,IAAM,6BAA6B;AAW1C,SAAS,aAA8B,OAAiH;AACtJ,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM;AAAA,EACjB;AACF;AAEA,SAAS,oBAAoB,OAA6D;AACxF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,mDAAmD;AACpI,MAAI,EAAE,UAAU,OAAQ,OAAM,IAAI,MAAM,yCAAyC;AACnF;AAEO,SAAS,kCAAyD;AACvE,QAAM,wBAAwB,iCAA0F;AAAA,IACtH,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,OAAO;AAAA,IACP,sBAAsB,CAAC,WAAW,WAAW,QAAQ;AAAA,IACrD,sBAAsB,CAAC,yBAAyB;AAAA,IAChD,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAAA,IAC3C,cAAc,EAAE,MAAM,SAAS;AAAA,IAC/B,eAAe,IAAI,OAAO;AAAA,IAC1B,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,IACX,mBAAmB;AAAA,IACnB,SAAS,OAAO,EAAE,MAAM,MAAM;AAC5B,0BAAoB,KAAK;AACzB,YAAM,SAAS,0BAA0B,MAAM,IAAI;AACnD,aAAO,EAAE,IAAI,OAAO,YAAY,MAAM,CAAC,SAAS,KAAK,aAAa,OAAO,GAAG,aAAa,OAAO,YAAY;AAAA,IAC9G;AAAA,EACF,CAAC;AAED,SAAO,mBAAmB;AAAA,IACxB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,yBAAyB,CAAC,aAAa,qBAAqB,CAAC;AAAA,IAC7D,cAAc;AAAA,EAChB,CAAC;AACH;AAEe,SAAR,mCAA2E;AAChF,SAAO,gCAAgC;AACzC;","names":[]}
@@ -1,3 +1,5 @@
1
+ import { z } from 'zod';
2
+
1
3
  interface GeneratedPaneElementSpec {
2
4
  type: string;
3
5
  props?: Record<string, unknown>;
@@ -13,10 +15,46 @@ interface GeneratedPaneSpec {
13
15
  elements: Record<string, GeneratedPaneElementSpec>;
14
16
  queries?: Record<string, unknown>;
15
17
  }
18
+ type GeneratedPaneDiagnosticSeverity = "error" | "warning" | "info";
19
+ declare const GENERATED_PANE_DIAGNOSTIC_CODES: {
20
+ readonly invalidRoot: "generated-pane.invalid_root";
21
+ readonly invalidElements: "generated-pane.invalid_elements";
22
+ readonly missingElement: "generated-pane.missing_element";
23
+ readonly elementCycle: "generated-pane.element_cycle";
24
+ readonly unknownComponent: "generated-pane.unknown_component";
25
+ readonly invalidProps: "generated-pane.invalid_props";
26
+ readonly unsupportedProfile: "generated-pane.unsupported_profile";
27
+ };
28
+ type GeneratedPaneDiagnosticCode = typeof GENERATED_PANE_DIAGNOSTIC_CODES[keyof typeof GENERATED_PANE_DIAGNOSTIC_CODES];
29
+ interface GeneratedPaneDiagnostic {
30
+ severity: GeneratedPaneDiagnosticSeverity;
31
+ code: GeneratedPaneDiagnosticCode;
32
+ message: string;
33
+ elementId?: string;
34
+ path?: string;
35
+ }
36
+ interface GeneratedPaneComponentVocabularyEntry {
37
+ description: string;
38
+ props: z.ZodTypeAny;
39
+ slots?: string[];
40
+ }
41
+ interface GeneratedPaneVocabulary {
42
+ id: string;
43
+ label: string;
44
+ components: Record<string, GeneratedPaneComponentVocabularyEntry>;
45
+ diagnostics?: Array<(spec: GeneratedPaneSpec) => GeneratedPaneDiagnostic[]>;
46
+ }
16
47
  interface GeneratedPaneValidationResult {
17
48
  spec: GeneratedPaneSpec | null;
18
49
  errors: string[];
19
50
  }
20
- declare function parseGeneratedPaneSpec(value: unknown): GeneratedPaneValidationResult;
51
+ interface GeneratedPaneDiagnosticsResult {
52
+ spec: GeneratedPaneSpec | null;
53
+ diagnostics: GeneratedPaneDiagnostic[];
54
+ }
55
+ declare function defineGeneratedPaneVocabulary(vocabulary: GeneratedPaneVocabulary): GeneratedPaneVocabulary;
56
+ declare const baseGeneratedPaneVocabulary: GeneratedPaneVocabulary;
57
+ declare function validateGeneratedPaneSpec(value: unknown, vocabulary?: GeneratedPaneVocabulary): GeneratedPaneDiagnosticsResult;
58
+ declare function parseGeneratedPaneSpec(value: unknown, vocabulary?: GeneratedPaneVocabulary): GeneratedPaneValidationResult;
21
59
 
22
- export { type GeneratedPaneElementSpec, type GeneratedPaneSpec, type GeneratedPaneValidationResult, parseGeneratedPaneSpec };
60
+ export { GENERATED_PANE_DIAGNOSTIC_CODES, type GeneratedPaneComponentVocabularyEntry, type GeneratedPaneDiagnostic, type GeneratedPaneDiagnosticCode, type GeneratedPaneDiagnosticSeverity, type GeneratedPaneDiagnosticsResult, type GeneratedPaneElementSpec, type GeneratedPaneSpec, type GeneratedPaneValidationResult, type GeneratedPaneVocabulary, baseGeneratedPaneVocabulary, defineGeneratedPaneVocabulary, parseGeneratedPaneSpec, validateGeneratedPaneSpec };
@@ -1,7 +1,15 @@
1
1
  import {
2
- parseGeneratedPaneSpec
3
- } from "../chunk-4IMMGGBQ.js";
2
+ GENERATED_PANE_DIAGNOSTIC_CODES,
3
+ baseGeneratedPaneVocabulary,
4
+ defineGeneratedPaneVocabulary,
5
+ parseGeneratedPaneSpec,
6
+ validateGeneratedPaneSpec
7
+ } from "../chunk-PELWQS23.js";
4
8
  export {
5
- parseGeneratedPaneSpec
9
+ GENERATED_PANE_DIAGNOSTIC_CODES,
10
+ baseGeneratedPaneVocabulary,
11
+ defineGeneratedPaneVocabulary,
12
+ parseGeneratedPaneSpec,
13
+ validateGeneratedPaneSpec
6
14
  };
7
15
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@hachej/boring-generated-pane",
3
- "version": "0.1.60",
3
+ "version": "0.1.65",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "MIT",
7
- "description": "Generated pane runtime for Boring workspace using json-render catalogs and safe WorkspaceBridge-backed actions.",
7
+ "description": "Generated pane runtime for Boring workspace using json-render catalogs and safe profile vocabularies.",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/hachej/boring-ui"
@@ -14,7 +14,7 @@
14
14
  "id": "generated-pane",
15
15
  "label": "Generated Pane",
16
16
  "front": "dist/front/index.js",
17
- "server": false
17
+ "server": "dist/server/index.js"
18
18
  },
19
19
  "pi": {
20
20
  "skills": [
@@ -39,6 +39,11 @@
39
39
  "types": "./dist/front/index.d.ts",
40
40
  "import": "./dist/front/index.js"
41
41
  },
42
+ "./server": {
43
+ "types": "./dist/server/index.d.ts",
44
+ "import": "./dist/server/index.js",
45
+ "default": "./dist/server/index.js"
46
+ },
42
47
  "./shared": {
43
48
  "types": "./dist/shared/index.d.ts",
44
49
  "import": "./dist/shared/index.js"
@@ -49,26 +54,26 @@
49
54
  "peerDependencies": {
50
55
  "react": "^18.0.0 || ^19.0.0",
51
56
  "react-dom": "^18.0.0 || ^19.0.0",
52
- "@hachej/boring-workspace": "0.1.60"
57
+ "@hachej/boring-workspace": "0.1.65"
53
58
  },
54
59
  "dependencies": {
55
60
  "@json-render/core": "^0.19.0",
56
61
  "@json-render/react": "^0.19.0",
57
- "lucide-react": "^1.8.0",
62
+ "lucide-react": "^1.21.0",
58
63
  "zod": "^4.3.6",
59
- "@hachej/boring-ui-kit": "0.1.60"
64
+ "@hachej/boring-ui-kit": "0.1.65"
60
65
  },
61
66
  "devDependencies": {
62
- "tsx": "^4.21.0",
63
- "@types/react": "^19.0.0",
67
+ "tsx": "^4.22.4",
68
+ "@types/react": "^19.2.17",
64
69
  "@types/react-dom": "^19.0.0",
65
- "react": "^19.0.0",
66
- "react-dom": "^19.0.0",
70
+ "react": "^19.2.7",
71
+ "react-dom": "^19.2.7",
67
72
  "tsup": "^8.4.0",
68
- "typescript": "~5.9.3",
69
- "vitest": "^3.2.6",
70
- "@hachej/boring-agent": "0.1.60",
71
- "@hachej/boring-workspace": "0.1.60"
73
+ "typescript": "~6.0.3",
74
+ "vitest": "^4.1.9",
75
+ "@hachej/boring-agent": "0.1.65",
76
+ "@hachej/boring-workspace": "0.1.65"
72
77
  },
73
78
  "scripts": {
74
79
  "build": "tsup",
@@ -26,9 +26,9 @@ Required root shape:
26
26
 
27
27
  Rules:
28
28
 
29
- - Do not generate React, JavaScript, functions, or inline event handlers.
29
+ - Do not generate React, JavaScript, functions, actions, or inline event handlers.
30
30
  - Use only components documented by the active profile/catalog.
31
31
  - Use only props documented for each component.
32
32
  - Keep element ids stable and descriptive.
33
33
  - Prefer a simple tree: one root layout, then cards/sections/widgets.
34
- - Actions must reference catalog-approved actions only.
34
+ - For base `*.pane.json` specs, validate with WorkspaceBridge op `generated-pane.v1.validate` using `{ "spec": <json> }` when available. Profile-specific specs must use their owning plugin validate op.
@@ -1,62 +0,0 @@
1
- // src/shared/index.ts
2
- function isRecord(value) {
3
- return typeof value === "object" && value !== null && !Array.isArray(value);
4
- }
5
- function isStringArray(value) {
6
- return Array.isArray(value) && value.every((item) => typeof item === "string");
7
- }
8
- function parseGeneratedPaneSpec(value) {
9
- const errors = [];
10
- if (!isRecord(value)) return { spec: null, errors: ["generated pane spec must be an object"] };
11
- if (value.kind !== "boring.generated-pane") errors.push('generated pane spec kind must be "boring.generated-pane"');
12
- if (value.version !== 1) errors.push("generated pane spec version must be 1");
13
- if (value.profile !== void 0 && typeof value.profile !== "string") errors.push("generated pane profile must be a string");
14
- if (value.title !== void 0 && typeof value.title !== "string") errors.push("generated pane title must be a string");
15
- if (value.description !== void 0 && typeof value.description !== "string") errors.push("generated pane description must be a string");
16
- if (typeof value.root !== "string" || value.root.length === 0) errors.push("generated pane root must be a string");
17
- if (!isRecord(value.elements)) {
18
- errors.push("generated pane elements must be an object");
19
- } else {
20
- for (const [id, element] of Object.entries(value.elements)) {
21
- if (!isRecord(element)) {
22
- errors.push(`element ${id} must be an object`);
23
- continue;
24
- }
25
- if (typeof element.type !== "string" || element.type.length === 0) errors.push(`element ${id} must include type`);
26
- if (element.props !== void 0 && !isRecord(element.props)) errors.push(`element ${id}.props must be an object`);
27
- if (element.children !== void 0 && !isStringArray(element.children)) errors.push(`element ${id}.children must be an array of ids`);
28
- }
29
- }
30
- if (isRecord(value.elements) && typeof value.root === "string") validateAcyclic(value.root, value.elements, errors);
31
- if (value.queries !== void 0 && !isRecord(value.queries)) errors.push("generated pane queries must be an object");
32
- if (errors.length > 0) return { spec: null, errors };
33
- return { spec: value, errors: [] };
34
- }
35
- function validateAcyclic(root, elements, errors) {
36
- const visiting = /* @__PURE__ */ new Set();
37
- const visited = /* @__PURE__ */ new Set();
38
- const visit = (id, path) => {
39
- if (visiting.has(id)) {
40
- errors.push(`generated pane element cycle: ${[...path, id].join(" -> ")}`);
41
- return;
42
- }
43
- if (visited.has(id)) return;
44
- const element = elements[id];
45
- if (!isRecord(element)) {
46
- errors.push(`element ${id} is referenced but not defined`);
47
- return;
48
- }
49
- visiting.add(id);
50
- if (isStringArray(element.children)) {
51
- for (const child of element.children) visit(child, [...path, id]);
52
- }
53
- visiting.delete(id);
54
- visited.add(id);
55
- };
56
- visit(root, []);
57
- }
58
-
59
- export {
60
- parseGeneratedPaneSpec
61
- };
62
- //# sourceMappingURL=chunk-4IMMGGBQ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/index.ts"],"sourcesContent":["export interface GeneratedPaneElementSpec {\n type: string\n props?: Record<string, unknown>\n children?: string[]\n}\n\nexport interface GeneratedPaneSpec {\n kind: \"boring.generated-pane\"\n version: 1\n profile?: string\n title?: string\n description?: string\n root: string\n elements: Record<string, GeneratedPaneElementSpec>\n queries?: Record<string, unknown>\n}\n\nexport interface GeneratedPaneValidationResult {\n spec: GeneratedPaneSpec | null\n errors: string[]\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\")\n}\n\nexport function parseGeneratedPaneSpec(value: unknown): GeneratedPaneValidationResult {\n const errors: string[] = []\n if (!isRecord(value)) return { spec: null, errors: [\"generated pane spec must be an object\"] }\n if (value.kind !== \"boring.generated-pane\") errors.push('generated pane spec kind must be \"boring.generated-pane\"')\n if (value.version !== 1) errors.push(\"generated pane spec version must be 1\")\n if (value.profile !== undefined && typeof value.profile !== \"string\") errors.push(\"generated pane profile must be a string\")\n if (value.title !== undefined && typeof value.title !== \"string\") errors.push(\"generated pane title must be a string\")\n if (value.description !== undefined && typeof value.description !== \"string\") errors.push(\"generated pane description must be a string\")\n if (typeof value.root !== \"string\" || value.root.length === 0) errors.push(\"generated pane root must be a string\")\n if (!isRecord(value.elements)) {\n errors.push(\"generated pane elements must be an object\")\n } else {\n for (const [id, element] of Object.entries(value.elements)) {\n if (!isRecord(element)) {\n errors.push(`element ${id} must be an object`)\n continue\n }\n if (typeof element.type !== \"string\" || element.type.length === 0) errors.push(`element ${id} must include type`)\n if (element.props !== undefined && !isRecord(element.props)) errors.push(`element ${id}.props must be an object`)\n if (element.children !== undefined && !isStringArray(element.children)) errors.push(`element ${id}.children must be an array of ids`)\n }\n }\n if (isRecord(value.elements) && typeof value.root === \"string\") validateAcyclic(value.root, value.elements, errors)\n if (value.queries !== undefined && !isRecord(value.queries)) errors.push(\"generated pane queries must be an object\")\n if (errors.length > 0) return { spec: null, errors }\n return { spec: value as unknown as GeneratedPaneSpec, errors: [] }\n}\n\nfunction validateAcyclic(root: string, elements: Record<string, unknown>, errors: string[]): void {\n const visiting = new Set<string>()\n const visited = new Set<string>()\n const visit = (id: string, path: string[]): void => {\n if (visiting.has(id)) {\n errors.push(`generated pane element cycle: ${[...path, id].join(\" -> \")}`)\n return\n }\n if (visited.has(id)) return\n const element = elements[id]\n if (!isRecord(element)) {\n errors.push(`element ${id} is referenced but not defined`)\n return\n }\n visiting.add(id)\n if (isStringArray(element.children)) {\n for (const child of element.children) visit(child, [...path, id])\n }\n visiting.delete(id)\n visited.add(id)\n }\n visit(root, [])\n}\n"],"mappings":";AAsBA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,cAAc,OAAmC;AACxD,SAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,SAAS,OAAO,SAAS,QAAQ;AAC/E;AAEO,SAAS,uBAAuB,OAA+C;AACpF,QAAM,SAAmB,CAAC;AAC1B,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO,EAAE,MAAM,MAAM,QAAQ,CAAC,uCAAuC,EAAE;AAC7F,MAAI,MAAM,SAAS,wBAAyB,QAAO,KAAK,0DAA0D;AAClH,MAAI,MAAM,YAAY,EAAG,QAAO,KAAK,uCAAuC;AAC5E,MAAI,MAAM,YAAY,UAAa,OAAO,MAAM,YAAY,SAAU,QAAO,KAAK,yCAAyC;AAC3H,MAAI,MAAM,UAAU,UAAa,OAAO,MAAM,UAAU,SAAU,QAAO,KAAK,uCAAuC;AACrH,MAAI,MAAM,gBAAgB,UAAa,OAAO,MAAM,gBAAgB,SAAU,QAAO,KAAK,6CAA6C;AACvI,MAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,WAAW,EAAG,QAAO,KAAK,sCAAsC;AACjH,MAAI,CAAC,SAAS,MAAM,QAAQ,GAAG;AAC7B,WAAO,KAAK,2CAA2C;AAAA,EACzD,OAAO;AACL,eAAW,CAAC,IAAI,OAAO,KAAK,OAAO,QAAQ,MAAM,QAAQ,GAAG;AAC1D,UAAI,CAAC,SAAS,OAAO,GAAG;AACtB,eAAO,KAAK,WAAW,EAAE,oBAAoB;AAC7C;AAAA,MACF;AACA,UAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,WAAW,EAAG,QAAO,KAAK,WAAW,EAAE,oBAAoB;AAChH,UAAI,QAAQ,UAAU,UAAa,CAAC,SAAS,QAAQ,KAAK,EAAG,QAAO,KAAK,WAAW,EAAE,0BAA0B;AAChH,UAAI,QAAQ,aAAa,UAAa,CAAC,cAAc,QAAQ,QAAQ,EAAG,QAAO,KAAK,WAAW,EAAE,mCAAmC;AAAA,IACtI;AAAA,EACF;AACA,MAAI,SAAS,MAAM,QAAQ,KAAK,OAAO,MAAM,SAAS,SAAU,iBAAgB,MAAM,MAAM,MAAM,UAAU,MAAM;AAClH,MAAI,MAAM,YAAY,UAAa,CAAC,SAAS,MAAM,OAAO,EAAG,QAAO,KAAK,0CAA0C;AACnH,MAAI,OAAO,SAAS,EAAG,QAAO,EAAE,MAAM,MAAM,OAAO;AACnD,SAAO,EAAE,MAAM,OAAuC,QAAQ,CAAC,EAAE;AACnE;AAEA,SAAS,gBAAgB,MAAc,UAAmC,QAAwB;AAChG,QAAM,WAAW,oBAAI,IAAY;AACjC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAQ,CAAC,IAAY,SAAyB;AAClD,QAAI,SAAS,IAAI,EAAE,GAAG;AACpB,aAAO,KAAK,iCAAiC,CAAC,GAAG,MAAM,EAAE,EAAE,KAAK,MAAM,CAAC,EAAE;AACzE;AAAA,IACF;AACA,QAAI,QAAQ,IAAI,EAAE,EAAG;AACrB,UAAM,UAAU,SAAS,EAAE;AAC3B,QAAI,CAAC,SAAS,OAAO,GAAG;AACtB,aAAO,KAAK,WAAW,EAAE,gCAAgC;AACzD;AAAA,IACF;AACA,aAAS,IAAI,EAAE;AACf,QAAI,cAAc,QAAQ,QAAQ,GAAG;AACnC,iBAAW,SAAS,QAAQ,SAAU,OAAM,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;AAAA,IAClE;AACA,aAAS,OAAO,EAAE;AAClB,YAAQ,IAAI,EAAE;AAAA,EAChB;AACA,QAAM,MAAM,CAAC,CAAC;AAChB;","names":[]}