@svadmin/surface 0.8.19 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +76 -38
  2. package/STYLING.md +33 -0
  3. package/compatibility.json +40 -8
  4. package/dist/agent-contract.d.ts +37 -0
  5. package/dist/agent-contract.js +206 -0
  6. package/dist/agent.d.ts +38 -7
  7. package/dist/agent.js +98 -59
  8. package/dist/binding.d.ts +2 -0
  9. package/dist/binding.js +7 -1
  10. package/dist/builtin-definitions.d.ts +6 -0
  11. package/dist/builtin-definitions.js +40 -0
  12. package/dist/builtin-schemas.d.ts +25 -0
  13. package/dist/builtin-schemas.js +13 -0
  14. package/dist/business-data.d.ts +16 -0
  15. package/dist/business-data.js +42 -0
  16. package/dist/business-definitions.d.ts +28 -0
  17. package/dist/business-definitions.js +53 -0
  18. package/dist/business.d.ts +3 -0
  19. package/dist/business.js +17 -0
  20. package/dist/catalog.d.ts +4 -61
  21. package/dist/catalog.js +20 -44
  22. package/dist/components/ActivityFeedWidget.svelte +34 -0
  23. package/dist/components/ActivityFeedWidget.svelte.d.ts +4 -0
  24. package/dist/components/MetricWidget.svelte +13 -8
  25. package/dist/components/MetricWidget.svelte.d.ts +1 -0
  26. package/dist/components/ResourceDetailWidget.svelte +56 -0
  27. package/dist/components/ResourceDetailWidget.svelte.d.ts +4 -0
  28. package/dist/components/ResourceFormBody.svelte +97 -0
  29. package/dist/components/ResourceFormBody.svelte.d.ts +13 -0
  30. package/dist/components/ResourceFormWidget.svelte +19 -0
  31. package/dist/components/ResourceFormWidget.svelte.d.ts +4 -0
  32. package/dist/components/ResourceTableWidget.svelte +15 -12
  33. package/dist/components/ResourceTableWidget.svelte.d.ts +1 -0
  34. package/dist/components/SurfaceEditPreview.svelte +107 -0
  35. package/dist/components/SurfaceEditPreview.svelte.d.ts +20 -0
  36. package/dist/components/SurfaceRenderer.svelte +121 -163
  37. package/dist/components/SurfaceRenderer.svelte.d.ts +5 -0
  38. package/dist/components/SurfaceWorkflowProvider.svelte +22 -0
  39. package/dist/components/SurfaceWorkflowProvider.svelte.d.ts +17 -0
  40. package/dist/components/SvarGridWidget.svelte +47 -0
  41. package/dist/components/SvarGridWidget.svelte.d.ts +4 -0
  42. package/dist/components/SvarSurfaceProvider.svelte +12 -0
  43. package/dist/components/SvarSurfaceProvider.svelte.d.ts +11 -0
  44. package/dist/design-contract.d.ts +2 -0
  45. package/dist/design-contract.js +2 -0
  46. package/dist/edits.d.ts +57 -0
  47. package/dist/edits.js +166 -0
  48. package/dist/index.d.ts +8 -2
  49. package/dist/index.js +5 -1
  50. package/dist/interactive.d.ts +6 -0
  51. package/dist/interactive.js +15 -0
  52. package/dist/localization.d.ts +11 -1
  53. package/dist/localization.js +20 -0
  54. package/dist/openui.d.ts +64 -0
  55. package/dist/openui.js +168 -0
  56. package/dist/recipes.d.ts +20 -0
  57. package/dist/recipes.js +20 -0
  58. package/dist/schema.d.ts +108 -0
  59. package/dist/schema.js +73 -0
  60. package/dist/server-sqlite.d.ts +1 -0
  61. package/dist/server-sqlite.js +2 -0
  62. package/dist/server.d.ts +4 -0
  63. package/dist/server.js +4 -0
  64. package/dist/source-cache.d.ts +30 -0
  65. package/dist/source-cache.js +135 -0
  66. package/dist/styles/editor.css +124 -0
  67. package/dist/styles/editor.d.ts +8 -0
  68. package/dist/styles/editor.js +9 -0
  69. package/dist/styles.css +43 -0
  70. package/dist/svar-catalog.d.ts +3 -0
  71. package/dist/svar-catalog.js +7 -0
  72. package/dist/svar-context.d.ts +9 -0
  73. package/dist/svar-context.js +1 -0
  74. package/dist/svar-schema.d.ts +21 -0
  75. package/dist/svar-schema.js +36 -0
  76. package/dist/svar.d.ts +4 -0
  77. package/dist/svar.js +3 -0
  78. package/dist/svelte.d.ts +4 -2
  79. package/dist/svelte.js +2 -1
  80. package/dist/types.d.ts +5 -1
  81. package/dist/validation.js +14 -108
  82. package/dist/wire.d.ts +13 -0
  83. package/dist/wire.js +30 -0
  84. package/dist/workflows/action-contracts.d.ts +7 -0
  85. package/dist/workflows/action-contracts.js +54 -0
  86. package/dist/workflows/catalog.d.ts +20 -0
  87. package/dist/workflows/catalog.js +50 -0
  88. package/dist/workflows/client.d.ts +45 -0
  89. package/dist/workflows/client.js +125 -0
  90. package/dist/workflows/context.d.ts +9 -0
  91. package/dist/workflows/context.js +5 -0
  92. package/dist/workflows/openui-guard.d.ts +19 -0
  93. package/dist/workflows/openui-guard.js +209 -0
  94. package/dist/workflows/service.d.ts +58 -0
  95. package/dist/workflows/service.js +296 -0
  96. package/dist/workflows/sqlite-store.d.ts +20 -0
  97. package/dist/workflows/sqlite-store.js +91 -0
  98. package/dist/workflows/types.d.ts +103 -0
  99. package/dist/workflows/types.js +8 -0
  100. package/dist/workflows.d.ts +5 -0
  101. package/dist/workflows.js +4 -0
  102. package/package.json +59 -8
package/dist/agent.js CHANGED
@@ -1,78 +1,117 @@
1
1
  import { Type } from '@sinclair/typebox';
2
2
  import { TypeCompiler } from '@sinclair/typebox/compiler';
3
- import { jsonPointer } from './json.js';
3
+ import { SURFACE_AGENT_LIMITS, SURFACE_AGENT_RESPONSE_SCHEMA_VERSION, SURFACE_AGENT_SCHEMA_VERSION, createSurfaceAgentResponseSchema, createSurfaceCatalogManifest, createSurfaceGenerationSpecSchema, surfaceCannotFulfillSchema, surfaceSchemaToJson, } from './agent-contract.js';
4
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({
5
+ import { parseSurfaceJson, surfaceWireError } from './wire.js';
6
+ export { SURFACE_AGENT_SCHEMA_VERSION } from './agent-contract.js';
7
+ const proposalFields = {
8
8
  schemaVersion: Type.Literal(SURFACE_AGENT_SCHEMA_VERSION),
9
9
  action: Type.Literal('propose'),
10
10
  summary: Type.Optional(Type.String({ minLength: 1, maxLength: 240 })),
11
11
  spec: Type.Unknown(),
12
+ };
13
+ const proposalSchema = Type.Object(proposalFields, { additionalProperties: false });
14
+ const responseProposalSchema = Type.Object({
15
+ ...proposalFields,
16
+ schemaVersion: Type.Literal(SURFACE_AGENT_RESPONSE_SCHEMA_VERSION),
12
17
  }, { additionalProperties: false });
13
18
  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);
19
+ const compiledResponseSchema = TypeCompiler.Compile(Type.Union([
20
+ proposalSchema, responseProposalSchema, surfaceCannotFulfillSchema,
21
+ ]));
22
+ function schemaIssues(errors) {
23
+ return [...errors].map((issue) => ({ code: 'invalid_json', path: issue.path || '', message: issue.message }));
20
24
  }
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
- */
25
+ /** 保留 v1 的函数签名、返回类型以及带说明文字的 fenced JSON 兼容行为。 */
39
26
  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')] };
27
+ const parsed = parseSurfaceJson(input, true);
28
+ if (!parsed.ok)
29
+ return parsed;
30
+ if (!compiledProposalSchema.Check(parsed.value)) {
31
+ return { ok: false, issues: schemaIssues(compiledProposalSchema.Errors(parsed.value)) };
46
32
  }
47
- if (!compiledProposalSchema.Check(candidate)) {
48
- return { ok: false, issues: proposalSchemaIssues(compiledProposalSchema.Errors(candidate)) };
33
+ const surface = validateSurfaceSpec(parsed.value.spec, catalog, policy);
34
+ if (!surface.ok)
35
+ return surface;
36
+ return { ok: true, value: { ...parsed.value, spec: surface.value } };
37
+ }
38
+ /** 结构化“无法完成”也是有效响应,但永远不是可渲染/可执行的提案。 */
39
+ export function parseSurfaceAgentResponse(input, catalog, policy) {
40
+ const parsed = parseSurfaceJson(input);
41
+ if (!parsed.ok)
42
+ return parsed;
43
+ if (!compiledResponseSchema.Check(parsed.value)) {
44
+ return { ok: false, issues: schemaIssues(compiledResponseSchema.Errors(parsed.value)) };
49
45
  }
50
- const parsed = candidate;
51
- const surface = validateSurfaceSpec(parsed.spec, catalog, policy);
46
+ const candidate = parsed.value;
47
+ if (candidate.action === 'cannot-fulfill')
48
+ return { ok: true, value: candidate };
49
+ const surface = validateSurfaceSpec(candidate.spec, catalog, policy);
52
50
  if (!surface.ok)
53
51
  return surface;
52
+ return { ok: true, value: { ...candidate, spec: surface.value } };
53
+ }
54
+ const safetyInstructions = 'Never generate or execute Svelte, HTML, CSS, JavaScript, SQL, URLs, event handlers, or mutations. Use only catalog widgets and policy-authorized resources and fields. Widget IDs and source IDs must be unique across the document. Bind item widgets to /items, and scalar widgets to /total or an explicitly readable getOne field. JSON Schema does not replace runtime policy validation or server authorization.';
55
+ function validateRequest(request) {
56
+ if (typeof request !== 'string' || request.trim().length === 0 || request.length > SURFACE_AGENT_LIMITS.maxRequestCharacters) {
57
+ throw new Error('Surface request must be non-empty and within the request character limit');
58
+ }
59
+ }
60
+ function contractContext(catalog, policy) {
61
+ const manifest = createSurfaceCatalogManifest(catalog);
62
+ const resources = Object.entries(policy.resources).map(([resource, rule]) => (`${resource}(read=${rule.readFields.join(',') || '(none)'};filter=${rule.filterFields?.join(',') || '(none)'};sort=${rule.sortFields?.join(',') || '(none)'};getOne=${rule.allowGetOne === true};maxPageSize=${rule.maxPageSize ?? 'default'})`)).join(' | ') || '(none)';
63
+ return `Catalog: ${JSON.stringify(manifest.catalogVersion)}\nComponent contracts: ${JSON.stringify(manifest)}\nResource policy: ${resources}\nPolicy JSON: ${JSON.stringify(policy)}`;
64
+ }
65
+ function boundedPrompt(prompt) {
66
+ if (prompt.length > SURFACE_AGENT_LIMITS.maxContractCharacters)
67
+ throw new Error('Surface prompt exceeds the contract character limit; select a smaller catalog');
68
+ return prompt;
69
+ }
70
+ /** 兼容旧 v1 输出,同时补齐从组件契约派生的完整参数/结构 schema。 */
71
+ export function buildSurfaceAgentPrompt(request, catalog, policy) {
72
+ validateRequest(request);
73
+ const schema = surfaceSchemaToJson(Type.Object({
74
+ ...proposalFields,
75
+ spec: createSurfaceGenerationSpecSchema(catalog, policy),
76
+ }, { additionalProperties: false }));
77
+ return boundedPrompt(`${request}\n\n[svadmin surface agent protocol]\nReturn only a human-reviewable fenced JSON proposal. ${safetyInstructions}\n${contractContext(catalog, policy)}\nOutput JSON Schema: ${JSON.stringify(schema)}\nIf the request cannot be represented safely, explain the limitation without inventing fields or capabilities. For machine-readable cannot-fulfill responses use the surface-agent/v2 message API.`);
78
+ }
79
+ /** 系统契约与用户需求分开传递;不绑定某个模型厂商或 CSS 编译器。 */
80
+ export function buildSurfaceAgentMessages(request, catalog, policy) {
81
+ validateRequest(request);
82
+ const content = boundedPrompt(`[svadmin surface agent protocol]\nReturn exactly one JSON object matching the output schema, without markdown or prose. ${safetyInstructions}\n${contractContext(catalog, policy)}\nOutput JSON Schema: ${JSON.stringify(createSurfaceAgentResponseSchema(catalog, policy))}\nIf the request cannot be represented, return action "cannot-fulfill" with a reason and a short message. Never invent capabilities or return an incomplete spec.`);
83
+ return [{ role: 'system', content }, { role: 'user', content: request }];
84
+ }
85
+ /** 有界传输缓冲,不冒充 OpenUI 增量渲染;半成品不进入运行时。 */
86
+ export function createSurfaceAgentStream(catalog, policy) {
87
+ let chunks = [];
88
+ let characters = 0;
89
+ let result;
54
90
  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,
91
+ push(chunk) {
92
+ if (result !== undefined)
93
+ return surfaceWireError('Surface stream is already closed');
94
+ if (typeof chunk !== 'string') {
95
+ result = surfaceWireError('Surface stream chunks must be text');
96
+ }
97
+ else if (characters + chunk.length > SURFACE_AGENT_LIMITS.maxInputCharacters) {
98
+ result = surfaceWireError('Surface stream is too large', 'limit_exceeded');
99
+ }
100
+ else {
101
+ characters += chunk.length;
102
+ if (chunk.length > 0)
103
+ chunks.push(chunk);
104
+ return { ok: true, characters };
105
+ }
106
+ chunks = [];
107
+ return result;
108
+ },
109
+ finish() {
110
+ if (result === undefined) {
111
+ result = parseSurfaceAgentResponse(chunks.join(''), catalog, policy);
112
+ chunks = [];
113
+ }
114
+ return result;
61
115
  },
62
116
  };
63
117
  }
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
- }
package/dist/binding.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  import type { SurfaceSourceDataState, SurfaceWidget, SurfaceWidgetDataState } from './types.js';
2
2
  export declare function resolveSurfaceWidgetData(widget: SurfaceWidget, sourceStates: Readonly<Record<string, SurfaceSourceDataState>>): SurfaceWidgetDataState;
3
+ /** Resolve one binding without subscribing the widget to unrelated source states. */
4
+ export declare function resolveSurfaceSourceData(widget: SurfaceWidget, sourceState: SurfaceSourceDataState | undefined): SurfaceWidgetDataState;
package/dist/binding.js CHANGED
@@ -37,10 +37,16 @@ function missingPointerState(sourceId) {
37
37
  };
38
38
  }
39
39
  export function resolveSurfaceWidgetData(widget, sourceStates) {
40
+ const sourceId = widget.binding?.sourceId;
41
+ return resolveSurfaceSourceData(widget, sourceId !== undefined && Object.hasOwn(sourceStates, sourceId)
42
+ ? sourceStates[sourceId]
43
+ : undefined);
44
+ }
45
+ /** Resolve one binding without subscribing the widget to unrelated source states. */
46
+ export function resolveSurfaceSourceData(widget, sourceState) {
40
47
  if (!widget.binding)
41
48
  return { status: 'unbound' };
42
49
  const sourceId = widget.binding.sourceId;
43
- const sourceState = Object.hasOwn(sourceStates, sourceId) ? sourceStates[sourceId] : undefined;
44
50
  if (!sourceState)
45
51
  return { status: 'loading', sourceId };
46
52
  if (sourceState.status !== 'ready')
@@ -0,0 +1,6 @@
1
+ import type { SurfaceCatalog } from './types.js';
2
+ export declare const DEFAULT_SURFACE_CATALOG_VERSION: "svadmin/v1";
3
+ export declare const STYLED_SURFACE_CATALOG_VERSION: "svadmin/styled-v1";
4
+ /** The same definitions are consumed by Node validation and Svelte rendering. */
5
+ export declare const defaultSurfaceDefinitions: SurfaceCatalog;
6
+ export declare const styledSurfaceDefinitions: SurfaceCatalog;
@@ -0,0 +1,40 @@
1
+ import { Value } from '@sinclair/typebox/value';
2
+ import { styledMetricPropsSchema, styledResourceTablePropsSchema, barChartPropsSchema, lineChartPropsSchema, metricPropsSchema, resourceTablePropsSchema, } from './builtin-schemas.js';
3
+ export const DEFAULT_SURFACE_CATALOG_VERSION = 'svadmin/v1';
4
+ export const STYLED_SURFACE_CATALOG_VERSION = 'svadmin/styled-v1';
5
+ /** The same definitions are consumed by Node validation and Svelte rendering. */
6
+ export const defaultSurfaceDefinitions = {
7
+ version: DEFAULT_SURFACE_CATALOG_VERSION,
8
+ widgets: [
9
+ { type: 'metric', dataKind: 'scalar',
10
+ description: 'Display a count or readable scalar. Bind list counts to /total; currency metrics require an ISO currency code.',
11
+ examples: [{ label: 'Total', format: 'number' }], propsSchema: metricPropsSchema },
12
+ { type: 'resource-table', dataKind: 'items',
13
+ description: 'Display up to eight explicitly readable resource fields. Bind to /items and select fields from the active resource policy.',
14
+ propsSchema: resourceTablePropsSchema,
15
+ getReferencedFields: (props) => Value.Decode(resourceTablePropsSchema, props).columns.map((column) => column.field) },
16
+ { type: 'bar-chart', dataKind: 'items',
17
+ description: 'Compare a readable numeric field by a readable label field. Bind to /items; aggregated data must come from an authorized backend resource.',
18
+ propsSchema: barChartPropsSchema,
19
+ getReferencedFields: (props) => { const p = Value.Decode(barChartPropsSchema, props); return [p.labelField, p.valueField]; } },
20
+ { type: 'line-chart', dataKind: 'items',
21
+ description: 'Show a trend using readable label and numeric fields. Bind to /items; request only policy-authorized sorting.',
22
+ propsSchema: lineChartPropsSchema,
23
+ getReferencedFields: (props) => { const p = Value.Decode(lineChartPropsSchema, props); return [p.labelField, p.valueField]; } },
24
+ ],
25
+ };
26
+ export const styledSurfaceDefinitions = {
27
+ version: STYLED_SURFACE_CATALOG_VERSION,
28
+ widgets: defaultSurfaceDefinitions.widgets.map((widget) => {
29
+ if (widget.type === 'metric')
30
+ return { ...widget, propsSchema: styledMetricPropsSchema,
31
+ description: 'Numeric KPI; tone and density select prebuilt semantic styles, never CSS.',
32
+ examples: [{ label: 'Pending orders', format: 'number', tone: 'warning', density: 'compact' }] };
33
+ if (widget.type === 'resource-table')
34
+ return { ...widget, propsSchema: styledResourceTablePropsSchema,
35
+ getReferencedFields: (props) => Value.Decode(styledResourceTablePropsSchema, props).columns.map((column) => column.field),
36
+ description: 'Read-only table; density is a prebuilt semantic variant.',
37
+ examples: [{ title: 'Inventory', columns: [{ field: 'name', label: 'Name' }], density: 'compact' }] };
38
+ return widget;
39
+ }),
40
+ };
@@ -36,3 +36,28 @@ export type MetricProps = Static<typeof metricPropsSchema>;
36
36
  export type ResourceTableProps = Static<typeof resourceTablePropsSchema>;
37
37
  export type BarChartProps = Static<typeof barChartPropsSchema>;
38
38
  export type LineChartProps = Static<typeof lineChartPropsSchema>;
39
+ export declare const styledMetricPropsSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
40
+ tone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
41
+ density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"compact" | "comfortable">[]>>;
42
+ label: import("@sinclair/typebox").TString;
43
+ format: import("@sinclair/typebox").TLiteral<"currency">;
44
+ currency: import("@sinclair/typebox").TString;
45
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
46
+ }>, import("@sinclair/typebox").TObject<{
47
+ tone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
48
+ density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"compact" | "comfortable">[]>>;
49
+ label: import("@sinclair/typebox").TString;
50
+ format: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"percent">]>;
51
+ currency: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNever>;
52
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
53
+ }>]>;
54
+ export declare const styledResourceTablePropsSchema: import("@sinclair/typebox").TObject<{
55
+ density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"compact" | "comfortable">[]>>;
56
+ title: import("@sinclair/typebox").TString;
57
+ emptyLabel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
58
+ columns: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
59
+ field: import("@sinclair/typebox").TString;
60
+ label: import("@sinclair/typebox").TString;
61
+ 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">]>>;
62
+ }>>;
63
+ }>;
@@ -1,3 +1,4 @@
1
+ import { surfaceDesignContract } from './design-contract.js';
1
2
  import { Type } from "@sinclair/typebox";
2
3
  const catalogFieldSchema = Type.String({
3
4
  minLength: 1,
@@ -44,3 +45,15 @@ export const lineChartPropsSchema = Type.Object({
44
45
  showDots: Type.Optional(Type.Boolean()),
45
46
  fill: Type.Optional(Type.Boolean()),
46
47
  }, { additionalProperties: false });
48
+ const styledMetricProperties = {
49
+ tone: Type.Optional(Type.Union(surfaceDesignContract.metric.tone.map((value) => Type.Literal(value)))),
50
+ density: Type.Optional(Type.Union(surfaceDesignContract.metric.density.map((value) => Type.Literal(value)))),
51
+ };
52
+ export const styledMetricPropsSchema = Type.Union([
53
+ Type.Object({ ...currencyMetric.properties, ...styledMetricProperties }, { additionalProperties: false }),
54
+ Type.Object({ ...otherMetric.properties, ...styledMetricProperties }, { additionalProperties: false }),
55
+ ]);
56
+ export const styledResourceTablePropsSchema = Type.Object({
57
+ ...resourceTablePropsSchema.properties,
58
+ density: Type.Optional(Type.Union(surfaceDesignContract.table.density.map((value) => Type.Literal(value)))),
59
+ }, { additionalProperties: false });
@@ -0,0 +1,16 @@
1
+ import type { ActivityFeedProps } from './business-definitions.js';
2
+ import type { JsonValue } from './types.js';
3
+ import { type WidgetValueResult } from './widget-data.js';
4
+ export interface SurfaceActivityItem {
5
+ id: string;
6
+ action: string;
7
+ timestamp: string;
8
+ user: {
9
+ name: string;
10
+ };
11
+ target?: string;
12
+ comment?: string;
13
+ status?: 'info' | 'success' | 'warning' | 'destructive';
14
+ }
15
+ /** 只映射选中的字段;不推断操作者、补造时间或把未知状态伪装成成功。 */
16
+ export declare function asSurfaceActivities(value: JsonValue, props: ActivityFeedProps, unknownActor: string): WidgetValueResult<SurfaceActivityItem[]>;
@@ -0,0 +1,42 @@
1
+ import { asRecordArray } from './widget-data.js';
2
+ /** 只映射选中的字段;不推断操作者、补造时间或把未知状态伪装成成功。 */
3
+ export function asSurfaceActivities(value, props, unknownActor) {
4
+ const records = asRecordArray(value);
5
+ if (!records.ok)
6
+ return records;
7
+ if (records.value.length > 100)
8
+ return { ok: false, message: 'Too many activity records' };
9
+ const items = [];
10
+ const ids = new Set();
11
+ for (const record of records.value) {
12
+ const idValue = record[props.idField];
13
+ const action = record[props.actionField];
14
+ const timestamp = record[props.timestampField];
15
+ if (!((typeof idValue === 'string' && idValue.length > 0 && idValue.length <= 128)
16
+ || (typeof idValue === 'number' && Number.isFinite(idValue)))
17
+ || typeof action !== 'string' || !action.trim() || action.length > 500
18
+ || typeof timestamp !== 'string' || !timestamp.trim() || timestamp.length > 128) {
19
+ return { ok: false, message: 'Activity identity, action or timestamp is invalid' };
20
+ }
21
+ // 数字 1 与字符串 "1" 不得造成 keyed each 身份冲突。
22
+ const id = `${typeof idValue}:${idValue}`;
23
+ if (ids.has(id))
24
+ return { ok: false, message: 'Duplicate activity identity' };
25
+ ids.add(id);
26
+ const optional = [props.actorField, props.targetField, props.commentField, props.statusField]
27
+ .map((field) => field === undefined ? undefined : record[field]);
28
+ if (optional.some((item) => item != null && (typeof item !== 'string' || item.length > 4000))) {
29
+ return { ok: false, message: 'Optional activity text is invalid' };
30
+ }
31
+ const [actor, target, comment, status] = optional;
32
+ if (status != null && !['info', 'success', 'warning', 'destructive'].includes(String(status))) {
33
+ return { ok: false, message: 'Unknown activity status' };
34
+ }
35
+ items.push({ id, action, timestamp, user: { name: typeof actor === 'string' && actor.trim() ? actor : unknownActor },
36
+ ...(typeof target === 'string' ? { target } : {}),
37
+ ...(typeof comment === 'string' ? { comment } : {}),
38
+ ...(typeof status === 'string' ? { status: status } : {}),
39
+ });
40
+ }
41
+ return { ok: true, value: items };
42
+ }
@@ -0,0 +1,28 @@
1
+ import { type Static } from '@sinclair/typebox';
2
+ import type { SurfaceCatalog } from './types.js';
3
+ export declare const resourceDetailPropsSchema: import("@sinclair/typebox").TObject<{
4
+ fields: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
5
+ field: import("@sinclair/typebox").TString;
6
+ label: import("@sinclair/typebox").TString;
7
+ format: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<string>[]>>;
8
+ }>>;
9
+ title: import("@sinclair/typebox").TString;
10
+ tone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
11
+ density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"compact" | "comfortable">[]>>;
12
+ }>;
13
+ export declare const activityFeedPropsSchema: import("@sinclair/typebox").TObject<{
14
+ idField: import("@sinclair/typebox").TString;
15
+ actionField: import("@sinclair/typebox").TString;
16
+ timestampField: import("@sinclair/typebox").TString;
17
+ actorField: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
18
+ targetField: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
19
+ commentField: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
20
+ statusField: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
21
+ title: import("@sinclair/typebox").TString;
22
+ tone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"info" | "warning" | "success" | "neutral" | "danger">[]>>;
23
+ density: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<import("@sinclair/typebox").TLiteral<"compact" | "comfortable">[]>>;
24
+ }>;
25
+ export type ResourceDetailProps = Static<typeof resourceDetailPropsSchema>;
26
+ export type ActivityFeedProps = Static<typeof activityFeedPropsSchema>;
27
+ /** 显式升级目录;旧目录、表单操作及默认权限不变。 */
28
+ export declare function createBusinessSurfaceDefinitions(base?: SurfaceCatalog): SurfaceCatalog;
@@ -0,0 +1,53 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import { styledSurfaceDefinitions } from './builtin-definitions.js';
4
+ import { surfaceDesignContract } from './design-contract.js';
5
+ const field = Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' });
6
+ const presentation = {
7
+ title: Type.String({ minLength: 1, maxLength: 80 }),
8
+ tone: Type.Optional(Type.Union(surfaceDesignContract.metric.tone.map((value) => Type.Literal(value)))),
9
+ density: Type.Optional(Type.Union(surfaceDesignContract.metric.density.map((value) => Type.Literal(value)))),
10
+ };
11
+ export const resourceDetailPropsSchema = Type.Object({
12
+ ...presentation,
13
+ fields: Type.Array(Type.Object({
14
+ field, label: Type.String({ minLength: 1, maxLength: 60 }),
15
+ format: Type.Optional(Type.Union(['text', 'number', 'date', 'boolean'].map((value) => Type.Literal(value)))),
16
+ }, { additionalProperties: false }), { minItems: 1, maxItems: 16 }),
17
+ }, { additionalProperties: false });
18
+ export const activityFeedPropsSchema = Type.Object({
19
+ ...presentation,
20
+ idField: field, actionField: field, timestampField: field,
21
+ actorField: Type.Optional(field), targetField: Type.Optional(field),
22
+ commentField: Type.Optional(field), statusField: Type.Optional(field),
23
+ }, { additionalProperties: false });
24
+ const definitions = [
25
+ {
26
+ type: 'resource-detail', dataKind: 'record', propsSchema: resourceDetailPropsSchema,
27
+ description: 'Read-only detail of one authorized record. Bind a permitted resource-one source with pointer "". Select up to sixteen distinct readable fields; never provide record values in props.',
28
+ examples: [{ title: 'Contact details', fields: [{ field: 'name', label: 'Name' }], density: 'compact' }],
29
+ getReferencedFields: (props) => {
30
+ const fields = Value.Decode(resourceDetailPropsSchema, props).fields.map((item) => item.field);
31
+ if (new Set(fields).size !== fields.length)
32
+ throw new Error('Detail fields must be distinct');
33
+ return fields;
34
+ },
35
+ },
36
+ {
37
+ type: 'activity-feed', dataKind: 'items', propsSchema: activityFeedPropsSchema,
38
+ description: 'Read-only activity timeline from an authorized resource-list /items binding. Explicitly map readable ID, action and timestamp fields; optional actor/target/comment/status fields also require read permission. No comment composer or mutations. Backend owns ordering and event authenticity; this is not proof of an audit trail.',
39
+ examples: [{ title: 'Recent activity', idField: 'id', actionField: 'action', timestampField: 'at', tone: 'info' }],
40
+ getReferencedFields: (props) => {
41
+ const p = Value.Decode(activityFeedPropsSchema, props);
42
+ return [p.idField, p.actionField, p.timestampField, p.actorField, p.targetField, p.commentField, p.statusField]
43
+ .filter((name) => typeof name === 'string');
44
+ },
45
+ },
46
+ ];
47
+ /** 显式升级目录;旧目录、表单操作及默认权限不变。 */
48
+ export function createBusinessSurfaceDefinitions(base = styledSurfaceDefinitions) {
49
+ const reserved = new Set(definitions.map((widget) => widget.type));
50
+ if (base.widgets.some((widget) => reserved.has(widget.type)))
51
+ throw new Error('Business widget type is already registered');
52
+ return { version: `${base.version}+business/v1`, widgets: [...base.widgets, ...definitions] };
53
+ }
@@ -0,0 +1,3 @@
1
+ import { type SurfaceRenderCatalog } from './catalog.js';
2
+ /** 同一份 DOM-free 定义用于模型提示、服务端校验及真实组件。 */
3
+ export declare function createBusinessSurfaceCatalog(base?: SurfaceRenderCatalog): SurfaceRenderCatalog;
@@ -0,0 +1,17 @@
1
+ import { defineSurfaceCatalog, styledSurfaceCatalog } from './catalog.js';
2
+ import { createBusinessSurfaceDefinitions } from './business-definitions.js';
3
+ import ResourceDetailWidget from './components/ResourceDetailWidget.svelte';
4
+ import ActivityFeedWidget from './components/ActivityFeedWidget.svelte';
5
+ /** 同一份 DOM-free 定义用于模型提示、服务端校验及真实组件。 */
6
+ export function createBusinessSurfaceCatalog(base = styledSurfaceCatalog) {
7
+ const definitions = createBusinessSurfaceDefinitions(base);
8
+ const components = new Map(base.widgets.map((widget) => [widget.type, widget.component]));
9
+ components.set('resource-detail', ResourceDetailWidget);
10
+ components.set('activity-feed', ActivityFeedWidget);
11
+ return defineSurfaceCatalog({ ...definitions, widgets: definitions.widgets.map((widget) => {
12
+ const component = components.get(widget.type);
13
+ if (!component)
14
+ throw new Error(`Missing renderer for ${widget.type}`);
15
+ return { ...widget, component };
16
+ }) });
17
+ }
package/dist/catalog.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Component } from 'svelte';
2
2
  import type { JsonObject, SurfaceCatalog, SurfaceWidgetDataState, SurfaceWidgetDefinition } from './types.js';
3
3
  import type { SurfaceMessages } from './localization.js';
4
- export declare const DEFAULT_SURFACE_CATALOG_VERSION: "svadmin/v1";
4
+ export { DEFAULT_SURFACE_CATALOG_VERSION, STYLED_SURFACE_CATALOG_VERSION } from './builtin-definitions.js';
5
5
  export interface SurfaceWidgetRendererProps {
6
6
  readonly widgetId: string;
7
7
  readonly props: JsonObject;
@@ -10,69 +10,12 @@ export interface SurfaceWidgetRendererProps {
10
10
  readonly messages?: SurfaceMessages;
11
11
  }
12
12
  export interface SurfaceWidgetRegistration extends SurfaceWidgetDefinition {
13
+ readonly presentation?: 'surface-appearance/v1';
13
14
  readonly component: Component<SurfaceWidgetRendererProps>;
14
15
  }
15
16
  export interface SurfaceRenderCatalog extends SurfaceCatalog {
16
17
  readonly widgets: readonly SurfaceWidgetRegistration[];
17
18
  }
18
19
  export declare function defineSurfaceCatalog<const TCatalog extends SurfaceRenderCatalog>(catalog: TCatalog): TCatalog;
19
- declare function tableFields(props: JsonObject): readonly string[];
20
- declare function barChartFields(props: JsonObject): readonly string[];
21
- declare function lineChartFields(props: JsonObject): readonly string[];
22
- export declare const defaultSurfaceCatalog: {
23
- readonly version: "svadmin/v1";
24
- readonly widgets: readonly [{
25
- readonly type: "metric";
26
- readonly dataKind: "scalar";
27
- readonly propsSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
28
- label: import("@sinclair/typebox").TString;
29
- format: import("@sinclair/typebox").TLiteral<"currency">;
30
- currency: import("@sinclair/typebox").TString;
31
- description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
32
- }>, import("@sinclair/typebox").TObject<{
33
- label: import("@sinclair/typebox").TString;
34
- format: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"number">, import("@sinclair/typebox").TLiteral<"percent">]>;
35
- currency: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNever>;
36
- description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
37
- }>]>;
38
- readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
39
- }, {
40
- readonly type: "resource-table";
41
- readonly dataKind: "items";
42
- readonly propsSchema: import("@sinclair/typebox").TObject<{
43
- title: import("@sinclair/typebox").TString;
44
- emptyLabel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
45
- columns: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
46
- field: import("@sinclair/typebox").TString;
47
- label: import("@sinclair/typebox").TString;
48
- 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">]>>;
49
- }>>;
50
- }>;
51
- readonly getReferencedFields: typeof tableFields;
52
- readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
53
- }, {
54
- readonly type: "bar-chart";
55
- readonly dataKind: "items";
56
- readonly propsSchema: import("@sinclair/typebox").TObject<{
57
- title: import("@sinclair/typebox").TString;
58
- labelField: import("@sinclair/typebox").TString;
59
- valueField: import("@sinclair/typebox").TString;
60
- showValues: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
61
- }>;
62
- readonly getReferencedFields: typeof barChartFields;
63
- readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
64
- }, {
65
- readonly type: "line-chart";
66
- readonly dataKind: "items";
67
- readonly propsSchema: import("@sinclair/typebox").TObject<{
68
- title: import("@sinclair/typebox").TString;
69
- labelField: import("@sinclair/typebox").TString;
70
- valueField: import("@sinclair/typebox").TString;
71
- showDots: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
72
- fill: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
73
- }>;
74
- readonly getReferencedFields: typeof lineChartFields;
75
- readonly component: Component<SurfaceWidgetRendererProps, {}, "">;
76
- }];
77
- };
78
- export {};
20
+ export declare const defaultSurfaceCatalog: SurfaceRenderCatalog;
21
+ export declare const styledSurfaceCatalog: SurfaceRenderCatalog;