@svadmin/surface 0.6.2 → 0.7.1

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
@@ -78,6 +78,25 @@ import {
78
78
 
79
79
  When rendered inside `AdminApp`, the renderer resolves the configured provider for each resource. A trusted host may instead pass `dataProvider`, which is still narrowed to `getList` and `getOne`.
80
80
 
81
+ ## AI proposals
82
+
83
+ The DOM-free root entry also exposes an opt-in Agent protocol. `buildSurfaceAgentPrompt()` constrains a model to return a proposal envelope and includes the host's widget/resource/field allowlists. `parseSurfaceAgentProposal()` parses and validates the complete `SurfaceSpec` against the same catalog and policy without querying a provider:
84
+
85
+ ```ts
86
+ import {
87
+ buildSurfaceAgentPrompt,
88
+ parseSurfaceAgentProposal,
89
+ } from '@svadmin/surface';
90
+
91
+ const prompt = buildSurfaceAgentPrompt('Generate an inventory dashboard', catalog, policy);
92
+ const proposal = parseSurfaceAgentProposal(modelText, catalog, policy);
93
+ if (proposal.ok) {
94
+ // Preview proposal.value.spec, then require explicit user approval before rendering.
95
+ }
96
+ ```
97
+
98
+ The adapter is deliberately proposal-only. Persistence, revision history, audit records, and the final apply decision belong to the host application. It never executes generated code or mutation actions.
99
+
81
100
  ## Built-in catalog
82
101
 
83
102
  | Type | Binding | Purpose |
@@ -1,5 +1,5 @@
1
1
  {
2
- "surface": "0.6.x",
2
+ "surface": "0.7.x",
3
3
  "minimumSupported": {
4
4
  "@svadmin/core": "0.34.2",
5
5
  "@svadmin/ui": "0.40.6",
@@ -0,0 +1,23 @@
1
+ import type { SurfaceCatalog, SurfacePolicy, SurfaceSpec, SurfaceValidationIssue } from './types.js';
2
+ /** Wire version for model-produced, human-reviewable Surface proposals. */
3
+ export declare const SURFACE_AGENT_SCHEMA_VERSION: "surface-agent/v1";
4
+ export interface SurfaceAgentProposal {
5
+ readonly schemaVersion: typeof SURFACE_AGENT_SCHEMA_VERSION;
6
+ readonly action: 'propose';
7
+ readonly summary?: string;
8
+ readonly spec: SurfaceSpec;
9
+ }
10
+ export type SurfaceAgentValidationResult = {
11
+ readonly ok: true;
12
+ readonly value: SurfaceAgentProposal;
13
+ } | {
14
+ readonly ok: false;
15
+ readonly issues: readonly SurfaceValidationIssue[];
16
+ };
17
+ /**
18
+ * Parse and fully validate an AI-generated proposal before a host previews it.
19
+ * This function has no side effects and never queries a provider.
20
+ */
21
+ export declare function parseSurfaceAgentProposal(input: unknown, catalog: SurfaceCatalog, policy: SurfacePolicy): SurfaceAgentValidationResult;
22
+ /** Build a model instruction that keeps generation inside the Surface contract. */
23
+ export declare function buildSurfaceAgentPrompt(request: string, catalog: SurfaceCatalog, policy: SurfacePolicy): string;
package/dist/agent.js ADDED
@@ -0,0 +1,78 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { TypeCompiler } from '@sinclair/typebox/compiler';
3
+ import { jsonPointer } from './json.js';
4
+ import { validateSurfaceSpec } from './validation.js';
5
+ /** Wire version for model-produced, human-reviewable Surface proposals. */
6
+ export const SURFACE_AGENT_SCHEMA_VERSION = 'surface-agent/v1';
7
+ const proposalSchema = Type.Object({
8
+ schemaVersion: Type.Literal(SURFACE_AGENT_SCHEMA_VERSION),
9
+ action: Type.Literal('propose'),
10
+ summary: Type.Optional(Type.String({ minLength: 1, maxLength: 240 })),
11
+ spec: Type.Unknown(),
12
+ }, { additionalProperties: false });
13
+ const compiledProposalSchema = TypeCompiler.Compile(proposalSchema);
14
+ function parseCandidate(value) {
15
+ if (typeof value !== 'string')
16
+ return value;
17
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(value);
18
+ const source = fenced?.[1] ?? value.trim();
19
+ return JSON.parse(source);
20
+ }
21
+ function proposalIssue(message) {
22
+ return {
23
+ code: 'invalid_json',
24
+ path: '/',
25
+ message,
26
+ };
27
+ }
28
+ function proposalSchemaIssues(errors) {
29
+ return [...errors].map((issue) => ({
30
+ code: 'invalid_json',
31
+ path: issue.path || jsonPointer([]),
32
+ message: issue.message,
33
+ }));
34
+ }
35
+ /**
36
+ * Parse and fully validate an AI-generated proposal before a host previews it.
37
+ * This function has no side effects and never queries a provider.
38
+ */
39
+ export function parseSurfaceAgentProposal(input, catalog, policy) {
40
+ let candidate;
41
+ try {
42
+ candidate = parseCandidate(input);
43
+ }
44
+ catch {
45
+ return { ok: false, issues: [proposalIssue('Agent proposal must be valid JSON')] };
46
+ }
47
+ if (!compiledProposalSchema.Check(candidate)) {
48
+ return { ok: false, issues: proposalSchemaIssues(compiledProposalSchema.Errors(candidate)) };
49
+ }
50
+ const parsed = candidate;
51
+ const surface = validateSurfaceSpec(parsed.spec, catalog, policy);
52
+ if (!surface.ok)
53
+ return surface;
54
+ return {
55
+ ok: true,
56
+ value: {
57
+ schemaVersion: SURFACE_AGENT_SCHEMA_VERSION,
58
+ action: 'propose',
59
+ ...(parsed.summary === undefined ? {} : { summary: parsed.summary }),
60
+ spec: surface.value,
61
+ },
62
+ };
63
+ }
64
+ /** Build a model instruction that keeps generation inside the Surface contract. */
65
+ export function buildSurfaceAgentPrompt(request, catalog, policy) {
66
+ const widgetTypes = catalog.widgets.map((widget) => widget.type).join(', ') || '(none)';
67
+ const resources = Object.entries(policy.resources).map(([resource, resourcePolicy]) => {
68
+ const permissions = [
69
+ `read=${resourcePolicy.readFields.join(',') || '(none)'}`,
70
+ `filter=${resourcePolicy.filterFields?.join(',') || '(none)'}`,
71
+ `sort=${resourcePolicy.sortFields?.join(',') || '(none)'}`,
72
+ `getOne=${resourcePolicy.allowGetOne === true}`,
73
+ `maxPageSize=${resourcePolicy.maxPageSize ?? 'default'}`,
74
+ ];
75
+ return `${resource}(${permissions.join(';')})`;
76
+ }).join(' | ') || '(none)';
77
+ return `${request}\n\n[svadmin surface agent protocol]\nReturn only a human-reviewable fenced JSON proposal. Never generate or execute Svelte, HTML, CSS, JavaScript, SQL, URLs, event handlers, or mutations. The envelope must be {"schemaVersion":"${SURFACE_AGENT_SCHEMA_VERSION}","action":"propose","summary":"...","spec":{...}}. The spec must use schemaVersion "surface/v1" and catalogVersion "${catalog.version}". Allowed widget types: ${widgetTypes}. Resource policy: ${resources}. Use only catalog widgets and policy-authorized resources and fields. If the request cannot be represented safely, explain the limitation without inventing fields or capabilities.`;
78
+ }
@@ -1,20 +1,4 @@
1
- import { type Static, type TSchema } from "@sinclair/typebox";
2
- export declare function createParsedSchema<T extends TSchema>(schema: T): T & {
3
- parse: (value: unknown) => Static<T>;
4
- safeParse: (value: unknown) => {
5
- success: true;
6
- data: Static<T>;
7
- } | {
8
- success: false;
9
- error: {
10
- issues: Array<{
11
- path: string[];
12
- message: string;
13
- }>;
14
- };
15
- };
16
- Check: (value: unknown) => boolean;
17
- };
1
+ import { type Static } from "@sinclair/typebox";
18
2
  export declare const metricPropsSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
19
3
  label: import("@sinclair/typebox").TString;
20
4
  format: import("@sinclair/typebox").TLiteral<"currency">;
@@ -25,42 +9,7 @@ export declare const metricPropsSchema: import("@sinclair/typebox").TUnion<[impo
25
9
  format: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"percent">]>;
26
10
  currency: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNever>;
27
11
  description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
28
- }>]> & {
29
- parse: (value: unknown) => {
30
- description?: string | undefined;
31
- currency: string;
32
- label: string;
33
- format: "currency";
34
- } | {
35
- description?: string | undefined;
36
- currency?: undefined;
37
- label: string;
38
- format: "number" | "percent";
39
- };
40
- safeParse: (value: unknown) => {
41
- success: false;
42
- error: {
43
- issues: Array<{
44
- path: string[];
45
- message: string;
46
- }>;
47
- };
48
- } | {
49
- success: true;
50
- data: {
51
- description?: string | undefined;
52
- currency: string;
53
- label: string;
54
- format: "currency";
55
- } | {
56
- description?: string | undefined;
57
- currency?: undefined;
58
- label: string;
59
- format: "number" | "percent";
60
- };
61
- };
62
- Check: (value: unknown) => boolean;
63
- };
12
+ }>]>;
64
13
  export declare const resourceTablePropsSchema: import("@sinclair/typebox").TObject<{
65
14
  title: import("@sinclair/typebox").TString;
66
15
  emptyLabel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
@@ -69,103 +18,20 @@ export declare const resourceTablePropsSchema: import("@sinclair/typebox").TObje
69
18
  label: import("@sinclair/typebox").TString;
70
19
  format: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"text">, import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"date">, import("@sinclair/typebox").TLiteral<"boolean">]>>;
71
20
  }>>;
72
- }> & {
73
- parse: (value: unknown) => {
74
- emptyLabel?: string | undefined;
75
- title: string;
76
- columns: {
77
- format?: "number" | "boolean" | "text" | "date" | undefined;
78
- field: string;
79
- label: string;
80
- }[];
81
- };
82
- safeParse: (value: unknown) => {
83
- success: false;
84
- error: {
85
- issues: Array<{
86
- path: string[];
87
- message: string;
88
- }>;
89
- };
90
- } | {
91
- success: true;
92
- data: {
93
- emptyLabel?: string | undefined;
94
- title: string;
95
- columns: {
96
- format?: "number" | "boolean" | "text" | "date" | undefined;
97
- field: string;
98
- label: string;
99
- }[];
100
- };
101
- };
102
- Check: (value: unknown) => boolean;
103
- };
21
+ }>;
104
22
  export declare const barChartPropsSchema: import("@sinclair/typebox").TObject<{
105
23
  title: import("@sinclair/typebox").TString;
106
24
  labelField: import("@sinclair/typebox").TString;
107
25
  valueField: import("@sinclair/typebox").TString;
108
26
  showValues: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
109
- }> & {
110
- parse: (value: unknown) => {
111
- showValues?: boolean | undefined;
112
- title: string;
113
- labelField: string;
114
- valueField: string;
115
- };
116
- safeParse: (value: unknown) => {
117
- success: false;
118
- error: {
119
- issues: Array<{
120
- path: string[];
121
- message: string;
122
- }>;
123
- };
124
- } | {
125
- success: true;
126
- data: {
127
- showValues?: boolean | undefined;
128
- title: string;
129
- labelField: string;
130
- valueField: string;
131
- };
132
- };
133
- Check: (value: unknown) => boolean;
134
- };
27
+ }>;
135
28
  export declare const lineChartPropsSchema: import("@sinclair/typebox").TObject<{
136
29
  title: import("@sinclair/typebox").TString;
137
30
  labelField: import("@sinclair/typebox").TString;
138
31
  valueField: import("@sinclair/typebox").TString;
139
32
  showDots: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
140
33
  fill: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
141
- }> & {
142
- parse: (value: unknown) => {
143
- fill?: boolean | undefined;
144
- showDots?: boolean | undefined;
145
- title: string;
146
- labelField: string;
147
- valueField: string;
148
- };
149
- safeParse: (value: unknown) => {
150
- success: false;
151
- error: {
152
- issues: Array<{
153
- path: string[];
154
- message: string;
155
- }>;
156
- };
157
- } | {
158
- success: true;
159
- data: {
160
- fill?: boolean | undefined;
161
- showDots?: boolean | undefined;
162
- title: string;
163
- labelField: string;
164
- valueField: string;
165
- };
166
- };
167
- Check: (value: unknown) => boolean;
168
- };
34
+ }>;
169
35
  export type MetricProps = Static<typeof metricPropsSchema>;
170
36
  export type ResourceTableProps = Static<typeof resourceTablePropsSchema>;
171
37
  export type BarChartProps = Static<typeof barChartPropsSchema>;
@@ -1,40 +1,4 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { TypeCompiler } from "@sinclair/typebox/compiler";
3
- export function createParsedSchema(schema) {
4
- const compiled = TypeCompiler.Compile(schema);
5
- const parse = (value) => {
6
- if (compiled.Check(value))
7
- return value;
8
- const errors = [...compiled.Errors(value)];
9
- const message = errors.map((e) => (e.path ? e.path + ": " : "") + e.message).join(", ") || "Validation error";
10
- const err = new Error(message);
11
- err.issues = errors.map((e) => ({
12
- path: e.path ? (e.path.startsWith("/") ? e.path.slice(1) : e.path).split("/") : [],
13
- message: e.message,
14
- }));
15
- throw err;
16
- };
17
- const safeParse = (value) => {
18
- if (compiled.Check(value)) {
19
- return { success: true, data: value };
20
- }
21
- const errors = [...compiled.Errors(value)];
22
- return {
23
- success: false,
24
- error: {
25
- issues: errors.map((e) => ({
26
- path: e.path ? (e.path.startsWith("/") ? e.path.slice(1) : e.path).split("/") : [],
27
- message: e.message,
28
- })),
29
- },
30
- };
31
- };
32
- return Object.assign(schema, {
33
- parse,
34
- safeParse,
35
- Check: (val) => compiled.Check(val),
36
- });
37
- }
38
2
  const catalogFieldSchema = Type.String({
39
3
  minLength: 1,
40
4
  maxLength: 64,
@@ -52,8 +16,8 @@ const otherMetric = Type.Object({
52
16
  currency: Type.Optional(Type.Never()),
53
17
  description: Type.Optional(Type.String({ minLength: 1, maxLength: 160 })),
54
18
  }, { additionalProperties: false });
55
- export const metricPropsSchema = createParsedSchema(Type.Union([currencyMetric, otherMetric]));
56
- export const resourceTablePropsSchema = createParsedSchema(Type.Object({
19
+ export const metricPropsSchema = Type.Union([currencyMetric, otherMetric]);
20
+ export const resourceTablePropsSchema = Type.Object({
57
21
  title: Type.String({ minLength: 1, maxLength: 80 }),
58
22
  emptyLabel: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })),
59
23
  columns: Type.Array(Type.Object({
@@ -66,17 +30,17 @@ export const resourceTablePropsSchema = createParsedSchema(Type.Object({
66
30
  Type.Literal("boolean"),
67
31
  ])),
68
32
  }, { additionalProperties: false }), { minItems: 1, maxItems: 8 }),
69
- }, { additionalProperties: false }));
70
- export const barChartPropsSchema = createParsedSchema(Type.Object({
33
+ }, { additionalProperties: false });
34
+ export const barChartPropsSchema = Type.Object({
71
35
  title: Type.String({ minLength: 1, maxLength: 80 }),
72
36
  labelField: catalogFieldSchema,
73
37
  valueField: catalogFieldSchema,
74
38
  showValues: Type.Optional(Type.Boolean()),
75
- }, { additionalProperties: false }));
76
- export const lineChartPropsSchema = createParsedSchema(Type.Object({
39
+ }, { additionalProperties: false });
40
+ export const lineChartPropsSchema = Type.Object({
77
41
  title: Type.String({ minLength: 1, maxLength: 80 }),
78
42
  labelField: catalogFieldSchema,
79
43
  valueField: catalogFieldSchema,
80
44
  showDots: Type.Optional(Type.Boolean()),
81
45
  fill: Type.Optional(Type.Boolean()),
82
- }, { additionalProperties: false }));
46
+ }, { additionalProperties: false });
package/dist/catalog.d.ts CHANGED
@@ -31,42 +31,7 @@ export declare const defaultSurfaceCatalog: {
31
31
  format: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"percent">]>;
32
32
  currency: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNever>;
33
33
  description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
34
- }>]> & {
35
- parse: (value: unknown) => {
36
- description?: string | undefined;
37
- currency: string;
38
- label: string;
39
- format: "currency";
40
- } | {
41
- description?: string | undefined;
42
- currency?: undefined;
43
- label: string;
44
- format: "number" | "percent";
45
- };
46
- safeParse: (value: unknown) => {
47
- success: false;
48
- error: {
49
- issues: Array<{
50
- path: string[];
51
- message: string;
52
- }>;
53
- };
54
- } | {
55
- success: true;
56
- data: {
57
- description?: string | undefined;
58
- currency: string;
59
- label: string;
60
- format: "currency";
61
- } | {
62
- description?: string | undefined;
63
- currency?: undefined;
64
- label: string;
65
- format: "number" | "percent";
66
- };
67
- };
68
- Check: (value: unknown) => boolean;
69
- };
34
+ }>]>;
70
35
  readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
71
36
  }, {
72
37
  readonly type: "resource-table";
@@ -79,38 +44,7 @@ export declare const defaultSurfaceCatalog: {
79
44
  label: import("@sinclair/typebox").TString;
80
45
  format: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"text">, import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"date">, import("@sinclair/typebox").TLiteral<"boolean">]>>;
81
46
  }>>;
82
- }> & {
83
- parse: (value: unknown) => {
84
- emptyLabel?: string | undefined;
85
- title: string;
86
- columns: {
87
- format?: "number" | "boolean" | "text" | "date" | undefined;
88
- field: string;
89
- label: string;
90
- }[];
91
- };
92
- safeParse: (value: unknown) => {
93
- success: false;
94
- error: {
95
- issues: Array<{
96
- path: string[];
97
- message: string;
98
- }>;
99
- };
100
- } | {
101
- success: true;
102
- data: {
103
- emptyLabel?: string | undefined;
104
- title: string;
105
- columns: {
106
- format?: "number" | "boolean" | "text" | "date" | undefined;
107
- field: string;
108
- label: string;
109
- }[];
110
- };
111
- };
112
- Check: (value: unknown) => boolean;
113
- };
47
+ }>;
114
48
  readonly getReferencedFields: typeof tableFields;
115
49
  readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
116
50
  }, {
@@ -121,32 +55,7 @@ export declare const defaultSurfaceCatalog: {
121
55
  labelField: import("@sinclair/typebox").TString;
122
56
  valueField: import("@sinclair/typebox").TString;
123
57
  showValues: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
124
- }> & {
125
- parse: (value: unknown) => {
126
- showValues?: boolean | undefined;
127
- title: string;
128
- labelField: string;
129
- valueField: string;
130
- };
131
- safeParse: (value: unknown) => {
132
- success: false;
133
- error: {
134
- issues: Array<{
135
- path: string[];
136
- message: string;
137
- }>;
138
- };
139
- } | {
140
- success: true;
141
- data: {
142
- showValues?: boolean | undefined;
143
- title: string;
144
- labelField: string;
145
- valueField: string;
146
- };
147
- };
148
- Check: (value: unknown) => boolean;
149
- };
58
+ }>;
150
59
  readonly getReferencedFields: typeof barChartFields;
151
60
  readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
152
61
  }, {
@@ -158,34 +67,7 @@ export declare const defaultSurfaceCatalog: {
158
67
  valueField: import("@sinclair/typebox").TString;
159
68
  showDots: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
160
69
  fill: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
161
- }> & {
162
- parse: (value: unknown) => {
163
- fill?: boolean | undefined;
164
- showDots?: boolean | undefined;
165
- title: string;
166
- labelField: string;
167
- valueField: string;
168
- };
169
- safeParse: (value: unknown) => {
170
- success: false;
171
- error: {
172
- issues: Array<{
173
- path: string[];
174
- message: string;
175
- }>;
176
- };
177
- } | {
178
- success: true;
179
- data: {
180
- fill?: boolean | undefined;
181
- showDots?: boolean | undefined;
182
- title: string;
183
- labelField: string;
184
- valueField: string;
185
- };
186
- };
187
- Check: (value: unknown) => boolean;
188
- };
70
+ }>;
189
71
  readonly getReferencedFields: typeof lineChartFields;
190
72
  readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
191
73
  }];
package/dist/catalog.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Value } from '@sinclair/typebox/value';
1
2
  import BarChartWidget from './components/BarChartWidget.svelte';
2
3
  import LineChartWidget from './components/LineChartWidget.svelte';
3
4
  import MetricWidget from './components/MetricWidget.svelte';
@@ -16,14 +17,14 @@ export function defineSurfaceCatalog(catalog) {
16
17
  return catalog;
17
18
  }
18
19
  function tableFields(props) {
19
- return resourceTablePropsSchema.parse(props).columns.map((column) => column.field);
20
+ return Value.Decode(resourceTablePropsSchema, props).columns.map((column) => column.field);
20
21
  }
21
22
  function barChartFields(props) {
22
- const chartProps = barChartPropsSchema.parse(props);
23
+ const chartProps = Value.Decode(barChartPropsSchema, props);
23
24
  return [chartProps.labelField, chartProps.valueField];
24
25
  }
25
26
  function lineChartFields(props) {
26
- const chartProps = lineChartPropsSchema.parse(props);
27
+ const chartProps = Value.Decode(lineChartPropsSchema, props);
27
28
  return [chartProps.labelField, chartProps.valueField];
28
29
  }
29
30
  export const defaultSurfaceCatalog = defineSurfaceCatalog({
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
2
3
  import BarChart from '@svadmin/ui/components/charts/BarChart.svelte';
3
4
  import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
4
5
  import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
@@ -10,7 +11,7 @@
10
11
 
11
12
  let { props, data }: SurfaceWidgetRendererProps = $props();
12
13
 
13
- const chartProps = $derived(barChartPropsSchema.parse(props));
14
+ const chartProps = $derived(Value.Decode(barChartPropsSchema, props));
14
15
  const points = $derived(data.status === 'ready'
15
16
  ? asChartPoints(data.value, chartProps.labelField, chartProps.valueField)
16
17
  : null);
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
2
3
  import LineChart from '@svadmin/ui/components/charts/LineChart.svelte';
3
4
  import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
4
5
  import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
@@ -10,7 +11,7 @@
10
11
 
11
12
  let { props, data }: SurfaceWidgetRendererProps = $props();
12
13
 
13
- const chartProps = $derived(lineChartPropsSchema.parse(props));
14
+ const chartProps = $derived(Value.Decode(lineChartPropsSchema, props));
14
15
  const points = $derived(data.status === 'ready'
15
16
  ? asChartPoints(data.value, chartProps.labelField, chartProps.valueField)
16
17
  : null);
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
2
3
  import StatsCard from '@svadmin/ui/components/StatsCard.svelte';
3
4
  import { metricPropsSchema } from '../builtin-schemas.js';
4
5
  import type { SurfaceWidgetRendererProps } from '../catalog.js';
5
6
 
6
7
  let { widgetId, props, data }: SurfaceWidgetRendererProps = $props();
7
8
 
8
- const metricProps = $derived(metricPropsSchema.parse(props));
9
+ const metricProps = $derived(Value.Decode(metricPropsSchema, props));
9
10
  const formattedValue = $derived.by(() => {
10
11
  if (data.status !== 'ready') return '—';
11
12
  const value = data.value;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
2
3
  import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
3
4
  import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
4
5
  import CardTitle from '@svadmin/ui/components/ui/card/card-title.svelte';
@@ -15,7 +16,7 @@
15
16
 
16
17
  let { props, data }: SurfaceWidgetRendererProps = $props();
17
18
 
18
- const tableProps = $derived(resourceTablePropsSchema.parse(props));
19
+ const tableProps = $derived(Value.Decode(resourceTablePropsSchema, props));
19
20
  const records = $derived(data.status === 'ready' ? asRecordArray(data.value) : null);
20
21
  </script>
21
22
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION } from './types.js';
2
2
  export type { JsonObject, JsonPrimitive, JsonValue, ResourceListDataSource, ResourceListSource, ResourceOneDataSource, ResourceOneSource, SurfaceBinding, SurfaceCatalog, SurfaceCatalogDataKind, SurfaceDataError, SurfaceDataProvider, SurfaceDataSource, SurfaceFilter, SurfaceGridLayout, SurfaceGridSpan, SurfacePolicy, SurfaceResourcePolicy, SurfaceSort, SurfaceSpec, SurfaceValidationCode, SurfaceValidationIssue, SurfaceValidationResult, SurfaceWidget, SurfaceWidgetDataState, SurfaceWidgetDefinition, } from './types.js';
3
3
  export { validateSurfaceSpec } from './validation.js';
4
+ export { SURFACE_AGENT_SCHEMA_VERSION, buildSurfaceAgentPrompt, parseSurfaceAgentProposal, } from './agent.js';
5
+ export type { SurfaceAgentProposal, SurfaceAgentValidationResult, } from './agent.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION } from './types.js';
2
2
  export { validateSurfaceSpec } from './validation.js';
3
+ export { SURFACE_AGENT_SCHEMA_VERSION, buildSurfaceAgentPrompt, parseSurfaceAgentProposal, } from './agent.js';
package/dist/types.d.ts CHANGED
@@ -92,12 +92,7 @@ export type SurfaceCatalogDataKind = 'none' | 'scalar' | 'items';
92
92
  export interface SurfaceWidgetDefinition {
93
93
  readonly type: string;
94
94
  readonly dataKind: SurfaceCatalogDataKind;
95
- readonly propsSchema: TSchema | {
96
- Check?: (data: unknown) => boolean;
97
- safeParse?: (data: unknown) => {
98
- success: boolean;
99
- };
100
- };
95
+ readonly propsSchema: TSchema;
101
96
  /** 组件从绑定记录中读取的字段,由可信 Catalog 提供。 */
102
97
  readonly getReferencedFields?: (props: JsonObject) => readonly string[];
103
98
  }
@@ -234,9 +234,6 @@ function checkPropsSchema(schema, data) {
234
234
  if ("Check" in schema && typeof schema.Check === "function") {
235
235
  return schema.Check(data);
236
236
  }
237
- if ("safeParse" in schema && typeof schema.safeParse === "function") {
238
- return schema.safeParse(data).success;
239
- }
240
237
  return Value.Check(schema, data);
241
238
  }
242
239
  function widgetIssue(widget, widgetIndex, definition, sources, policy) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/surface",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Validated declarative surfaces for svadmin dashboards",
5
5
  "type": "module",
6
6
  "sideEffects": false,