@svadmin/surface 0.9.0 → 0.11.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 (145) hide show
  1. package/README.md +9 -3
  2. package/STYLING.md +4 -2
  3. package/compatibility.json +18 -7
  4. package/dist/agent-contract.d.ts +1 -1
  5. package/dist/agent-contract.js +5 -2
  6. package/dist/builtin-schemas.js +1 -1
  7. package/dist/business-data.d.ts +16 -0
  8. package/dist/business-data.js +42 -0
  9. package/dist/business-definitions.d.ts +28 -0
  10. package/dist/business-definitions.js +53 -0
  11. package/dist/business.d.ts +3 -0
  12. package/dist/business.js +17 -0
  13. package/dist/components/ActivityFeedWidget.svelte +34 -0
  14. package/dist/components/ActivityFeedWidget.svelte.d.ts +4 -0
  15. package/dist/components/MetricWidget.svelte +2 -1
  16. package/dist/components/MetricWidget.svelte.d.ts +1 -0
  17. package/dist/components/ResourceDetailWidget.svelte +56 -0
  18. package/dist/components/ResourceDetailWidget.svelte.d.ts +4 -0
  19. package/dist/components/ResourceFormBody.svelte +1 -1
  20. package/dist/components/ResourceTableWidget.svelte +2 -1
  21. package/dist/components/ResourceTableWidget.svelte.d.ts +1 -0
  22. package/dist/components/SurfaceEditPreview.svelte +1 -1
  23. package/dist/components/SurfaceRenderer.svelte +2 -1
  24. package/dist/components/SurfaceRenderer.svelte.d.ts +1 -0
  25. package/dist/design-contract.d.ts +2 -0
  26. package/dist/design-contract.js +2 -0
  27. package/dist/localization.d.ts +11 -1
  28. package/dist/localization.js +20 -0
  29. package/dist/openui.js +17 -1
  30. package/dist/recipes.d.ts +20 -0
  31. package/dist/recipes.js +20 -0
  32. package/dist/styles/{editor.generated.js → editor.js} +1 -1
  33. package/dist/styles.css +41 -1260
  34. package/dist/types.d.ts +1 -1
  35. package/dist/validation.js +12 -6
  36. package/dist/workflows/catalog.js +1 -1
  37. package/package.json +15 -8
  38. package/dist/styled-system/css/conditions.js +0 -36
  39. package/dist/styled-system/css/css.d.ts +0 -22
  40. package/dist/styled-system/css/css.js +0 -45
  41. package/dist/styled-system/css/cva.d.ts +0 -6
  42. package/dist/styled-system/css/cva.js +0 -87
  43. package/dist/styled-system/css/cx.d.ts +0 -5
  44. package/dist/styled-system/css/cx.js +0 -15
  45. package/dist/styled-system/css/index.d.ts +0 -5
  46. package/dist/styled-system/css/index.js +0 -4
  47. package/dist/styled-system/css/sva.d.ts +0 -4
  48. package/dist/styled-system/css/sva.js +0 -46
  49. package/dist/styled-system/design-contract.d.ts +0 -13
  50. package/dist/styled-system/design-contract.js +0 -11
  51. package/dist/styled-system/helpers.js +0 -316
  52. package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
  53. package/dist/styled-system/patterns/aspect-ratio.js +0 -38
  54. package/dist/styled-system/patterns/bleed.d.ts +0 -21
  55. package/dist/styled-system/patterns/bleed.js +0 -24
  56. package/dist/styled-system/patterns/box.d.ts +0 -20
  57. package/dist/styled-system/patterns/box.js +0 -15
  58. package/dist/styled-system/patterns/center.d.ts +0 -20
  59. package/dist/styled-system/patterns/center.js +0 -21
  60. package/dist/styled-system/patterns/circle.d.ts +0 -20
  61. package/dist/styled-system/patterns/circle.js +0 -25
  62. package/dist/styled-system/patterns/container.d.ts +0 -20
  63. package/dist/styled-system/patterns/container.js +0 -21
  64. package/dist/styled-system/patterns/cq.d.ts +0 -21
  65. package/dist/styled-system/patterns/cq.js +0 -21
  66. package/dist/styled-system/patterns/divider.d.ts +0 -22
  67. package/dist/styled-system/patterns/divider.js +0 -25
  68. package/dist/styled-system/patterns/flex.d.ts +0 -26
  69. package/dist/styled-system/patterns/flex.js +0 -26
  70. package/dist/styled-system/patterns/float.d.ts +0 -23
  71. package/dist/styled-system/patterns/float.js +0 -52
  72. package/dist/styled-system/patterns/grid-item.d.ts +0 -25
  73. package/dist/styled-system/patterns/grid-item.js +0 -25
  74. package/dist/styled-system/patterns/grid.d.ts +0 -24
  75. package/dist/styled-system/patterns/grid.js +0 -27
  76. package/dist/styled-system/patterns/hstack.d.ts +0 -21
  77. package/dist/styled-system/patterns/hstack.js +0 -24
  78. package/dist/styled-system/patterns/index.d.ts +0 -21
  79. package/dist/styled-system/patterns/index.js +0 -20
  80. package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
  81. package/dist/styled-system/patterns/link-overlay.js +0 -24
  82. package/dist/styled-system/patterns/spacer.d.ts +0 -20
  83. package/dist/styled-system/patterns/spacer.js +0 -25
  84. package/dist/styled-system/patterns/square.d.ts +0 -20
  85. package/dist/styled-system/patterns/square.js +0 -24
  86. package/dist/styled-system/patterns/stack.d.ts +0 -23
  87. package/dist/styled-system/patterns/stack.js +0 -24
  88. package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
  89. package/dist/styled-system/patterns/visually-hidden.js +0 -18
  90. package/dist/styled-system/patterns/vstack.d.ts +0 -21
  91. package/dist/styled-system/patterns/vstack.js +0 -24
  92. package/dist/styled-system/patterns/wrap.d.ts +0 -24
  93. package/dist/styled-system/patterns/wrap.js +0 -25
  94. package/dist/styled-system/recipes/content-header.d.ts +0 -31
  95. package/dist/styled-system/recipes/content-header.js +0 -65
  96. package/dist/styled-system/recipes/content-page.d.ts +0 -34
  97. package/dist/styled-system/recipes/content-page.js +0 -43
  98. package/dist/styled-system/recipes/create-recipe.js +0 -82
  99. package/dist/styled-system/recipes/index.d.ts +0 -17
  100. package/dist/styled-system/recipes/index.js +0 -16
  101. package/dist/styled-system/recipes/metric-block.d.ts +0 -34
  102. package/dist/styled-system/recipes/metric-block.js +0 -76
  103. package/dist/styled-system/recipes/product-list.d.ts +0 -31
  104. package/dist/styled-system/recipes/product-list.js +0 -73
  105. package/dist/styled-system/recipes/product-section.d.ts +0 -31
  106. package/dist/styled-system/recipes/product-section.js +0 -49
  107. package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
  108. package/dist/styled-system/recipes/product-settings-row.js +0 -58
  109. package/dist/styled-system/recipes/product-settings.d.ts +0 -31
  110. package/dist/styled-system/recipes/product-settings.js +0 -57
  111. package/dist/styled-system/recipes/product-status.d.ts +0 -34
  112. package/dist/styled-system/recipes/product-status.js +0 -45
  113. package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
  114. package/dist/styled-system/recipes/product-toolbar.js +0 -41
  115. package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
  116. package/dist/styled-system/recipes/product-workspace.js +0 -58
  117. package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
  118. package/dist/styled-system/recipes/surface-metric.js +0 -63
  119. package/dist/styled-system/recipes/surface-table.d.ts +0 -34
  120. package/dist/styled-system/recipes/surface-table.js +0 -62
  121. package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
  122. package/dist/styled-system/recipes/ui-badge.js +0 -38
  123. package/dist/styled-system/recipes/ui-button.d.ts +0 -32
  124. package/dist/styled-system/recipes/ui-button.js +0 -43
  125. package/dist/styled-system/recipes/ui-input.d.ts +0 -31
  126. package/dist/styled-system/recipes/ui-input.js +0 -49
  127. package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
  128. package/dist/styled-system/recipes/ui-textarea.js +0 -24
  129. package/dist/styled-system/styles.css +0 -1257
  130. package/dist/styled-system/tokens/index.d.ts +0 -9
  131. package/dist/styled-system/tokens/index.js +0 -268
  132. package/dist/styled-system/tokens/tokens.d.ts +0 -36
  133. package/dist/styled-system/types/composition.d.ts +0 -227
  134. package/dist/styled-system/types/conditions.d.ts +0 -236
  135. package/dist/styled-system/types/csstype.d.ts +0 -22570
  136. package/dist/styled-system/types/index.d.ts +0 -6
  137. package/dist/styled-system/types/parts.d.ts +0 -8
  138. package/dist/styled-system/types/pattern.d.ts +0 -78
  139. package/dist/styled-system/types/prop-type.d.ts +0 -223
  140. package/dist/styled-system/types/recipe.d.ts +0 -181
  141. package/dist/styled-system/types/selectors.d.ts +0 -59
  142. package/dist/styled-system/types/static-css.d.ts +0 -56
  143. package/dist/styled-system/types/style-props.d.ts +0 -8088
  144. package/dist/styled-system/types/system-types.d.ts +0 -151
  145. /package/dist/styles/{editor.generated.d.ts → editor.d.ts} +0 -0
package/README.md CHANGED
@@ -6,11 +6,17 @@
6
6
 
7
7
  ## Install
8
8
 
9
+ The declared minimum combination is Core 0.53.0, UI 0.73.0, and Svelte
10
+ 5.56.10, matching the published UI peer requirements. `compatibility.json`
11
+ lists verification targets, not proof of a successful run. Acceptance requires
12
+ the `minimum-supported` strict install and consumer checks in `pack:check` to
13
+ pass against registry artifacts; workspace tarballs do not replace that check.
14
+
9
15
  ```bash
10
16
  bun add @svadmin/surface @svadmin/core @svadmin/ui @tanstack/svelte-query svelte @sinclair/typebox
11
17
  ```
12
18
 
13
- Import protocol types and validation from the DOM-free root entry. Import rendering code from the Svelte subpath. Hosts do not need Panda or Tailwind compiler plugins to consume precompiled component styles.
19
+ Import protocol types and validation from the DOM-free root entry. Import rendering code from the Svelte subpath. Hosts do not need CSS compiler plugins to consume the package's static component styles.
14
20
 
15
21
  ```ts
16
22
  import { validateSurfaceSpec, type SurfacePolicy, type SurfaceSpec } from '@svadmin/surface';
@@ -137,7 +143,7 @@ The Svelte `SurfaceEditPreview` component accepts a controlled revision and untr
137
143
 
138
144
  It leaves the current surface intact during streaming, offers a separate preview/back action, and revalidates at the explicit apply click. It calls `onApply(candidate)` only for a valid proposal. The host performs any persistence with server authorization and atomic revision compare-and-swap, then updates `revision` and clears `proposal`. Missing handlers, invalid proposals and pending callbacks disable application. A rejected callback reports an error without changing the controlled revision. Changing scope does not authorize an old proposal: the host must discard old proposals as described above.
139
145
 
140
- The new preview controls use Panda build-time tokens/recipes and import their compiled CSS. `@svadmin/surface/editor.css` is also available for explicit CSS collection. Only finite density/button variants are emitted; host styles can override the existing complete-color semantic CSS variables. The integrated main already uses the compiler-free native/Panda build from PR #430; this preview adds its own scoped recipes without restoring Tailwind dependencies. The separate `@svadmin/surface/styles.css` entry for `styledSurfaceCatalog` remains available; see `STYLING.md`.
146
+ Preview controls use local finite class helpers and maintained static CSS. `@svadmin/surface/editor.css` remains available for explicit CSS collection. Density/button variants and complete-color semantic CSS variable overrides are preserved. Widgets use local semantic recipes to retain compatibility with older UI peers; no UI-generated helpers are copied during build. The separate `@svadmin/surface/styles.css` entry remains available and rendering components also import it automatically; see `STYLING.md`.
141
147
 
142
148
  ## Built-in catalog
143
149
 
@@ -182,4 +188,4 @@ Limits are eight data sources, 24 widgets, 100 rows per page, eight filters, thr
182
188
 
183
189
  Actual OpenUI Lang parsing, nested interactive forms, registered business actions, server persistence, SSR/Lite rendering, arbitrary URLs, client aggregation, Canvas and iframe execution are not implemented. Aggregated metrics should come from a policy-authorized backend summary resource and bind through `resource-one`. The current UI contract remains read-only even though its definition can be edited.
184
190
 
185
- See `docs/architecture/openui-surface-phase2.md` for the original feature scope and `docs/architecture/surface-integration-provenance.md` for its integration with the current native/Panda main. 中文指南见文档站的“声明式 Surface”。
191
+ See `docs/architecture/openui-surface-phase2.md` for the original feature scope and `docs/architecture/surface-integration-provenance.md` for historical integration evidence. 中文指南见文档站的“声明式 Surface”。
package/STYLING.md CHANGED
@@ -24,8 +24,10 @@ import { styledSurfaceCatalog, STYLED_SURFACE_CATALOG_VERSION } from '@svadmin/s
24
24
 
25
25
  以上只是 widget 片段;完整 spec 仍需要 schemaVersion、catalogVersion、数据源、绑定及布局等必要字段。
26
26
 
27
- Surface 自行打包预生成的 recipe 助手与 styles.css,避免让旧 UI peer 安装必须提供新增的 JS 导出入口。构建本仓库时先构建 UI 样式,再构建 Surface;消费发布包时不需要安装 Panda 或 Tailwind。配合尚未包含新 recipe CSS 的 UI 版本时必须加载 Surface 的 styles.css。
27
+ Surface 使用本地有限 recipe helper 和静态 styles.css;design-contract 统一从 @svadmin/ui/design-contract 导入,要求 UI >=0.73.0 <0.74.0。不复制 UI 生成物,也不依赖样式编译器。渲染组件自动导入语义样式;原有 styles.css 子路径仍可显式导入。UI 基础样式仍由宿主通过 @svadmin/ui/app.css 加载。构建仅执行 svelte-package;styles:check 检查全部有限变体及静态 CSS 边界。
28
+
29
+ editor.css 与本地 styles/editor.ts 一起维护,保留 density、button variant、禁用/焦点状态和语义变量覆盖;editor.css 子路径不变。宿主升级时应同时升级 UI 与 Surface,并重新执行消费侧构建。
28
30
 
29
31
  AI 只能选择公开枚举,不能指定 class/style、任意颜色、recipe 定义或可执行代码。字段权限、只读查询、版本匹配和宿主确认要求不因样式变体而改变。
30
32
 
31
- 本次是两类 Surface 组件的 recipes 试点;其他 UI 继续使用保留的原生兼容样式。OpenUI Lang 的解析、流式渲染适配、全量 UI recipe 重写以及 DTCG 文件转换器不在本次已实现范围。不能把 CSS 编译器移除等同于以上功能已经完成。
33
+ 此次仅替换样式基础设施。OpenUI 入口、catalog/schema 校验、workflow 授权与执行、提案预览及宿主确认逻辑保持不变;去除样式编译器不代表增加新的协议能力。
@@ -1,16 +1,16 @@
1
1
  {
2
- "surface": "0.9.x",
2
+ "surface": "0.11.x",
3
3
  "minimumSupported": {
4
- "@svadmin/core": "0.34.2",
5
- "@svadmin/ui": "0.40.6",
6
- "svelte": "5.56.8"
4
+ "@svadmin/core": "0.53.0",
5
+ "@svadmin/ui": "0.73.0",
6
+ "svelte": "5.56.10"
7
7
  },
8
8
  "testedCombinations": [
9
9
  {
10
10
  "name": "minimum-supported",
11
- "core": "0.34.2",
12
- "ui": "0.40.6",
13
- "svelte": "5.56.8"
11
+ "core": "0.53.0",
12
+ "ui": "0.73.0",
13
+ "svelte": "5.56.10"
14
14
  },
15
15
  {
16
16
  "name": "workspace-packed",
@@ -38,6 +38,17 @@
38
38
  "./server/sqlite": {
39
39
  "runtime": "Node.js",
40
40
  "verification": "Current CI Node version with real node:sqlite; no browser or distributed-store support implied."
41
+ },
42
+ "./business": {
43
+ "status": "experimental",
44
+ "requiredUiCapabilities": [
45
+ "ActivityFeed.title",
46
+ "ActivityFeed.emptyLabel",
47
+ "ActivityFeed.countLabel"
48
+ ],
49
+ "minimumPublishedUi": null,
50
+ "verification": "same-checkout business + interactive packed consumer; current Surface workflow reports",
51
+ "releaseGate": "Coordinate a tested UI release and verify the peer floor before publishing this new entry."
41
52
  }
42
53
  }
43
54
  }
@@ -13,7 +13,7 @@ export interface SurfaceCatalogManifest {
13
13
  readonly catalogVersion: string;
14
14
  readonly widgets: readonly {
15
15
  readonly type: string;
16
- readonly dataKind: 'none' | 'scalar' | 'items';
16
+ readonly dataKind: 'none' | 'scalar' | 'items' | 'record';
17
17
  readonly description?: string;
18
18
  readonly propsSchema: JsonObject;
19
19
  readonly examples?: readonly JsonObject[];
@@ -100,8 +100,10 @@ export function createSurfaceCatalogManifest(catalog) {
100
100
  throw new Error(`Invalid or duplicate surface widget type "${widget.type}"`);
101
101
  }
102
102
  known.add(widget.type);
103
- if (!['none', 'scalar', 'items'].includes(widget.dataKind))
103
+ if (!['none', 'scalar', 'items', 'record'].includes(widget.dataKind))
104
104
  throw new Error('Invalid surface widget data kind');
105
+ if (widget.dataKind === 'record' && !widget.getReferencedFields)
106
+ throw new Error('Record widgets require an explicit field selector');
105
107
  const propsSchema = surfaceSchemaToJson(widget.propsSchema);
106
108
  if (!isClosedObject(propsSchema))
107
109
  throw new Error(`Widget "${widget.type}" must use a closed object props schema`);
@@ -171,7 +173,8 @@ export function createSurfaceGenerationSpecSchema(catalog, policy) {
171
173
  ...(widget.dataKind === 'none' ? {} : {
172
174
  binding: Type.Object({
173
175
  sourceId: surfaceIdSchema,
174
- pointer: widget.dataKind === 'items' ? Type.Literal('/items') : literals([...scalarPointers]),
176
+ pointer: widget.dataKind === 'items' ? Type.Literal('/items')
177
+ : widget.dataKind === 'record' ? Type.Literal('') : literals([...scalarPointers]),
175
178
  }, { additionalProperties: false }),
176
179
  }),
177
180
  }, { additionalProperties: false, ...(widget.description ? { description: widget.description } : {}) });
@@ -1,4 +1,4 @@
1
- import { surfaceDesignContract } from './styled-system/design-contract.js';
1
+ import { surfaceDesignContract } from './design-contract.js';
2
2
  import { Type } from "@sinclair/typebox";
3
3
  const catalogFieldSchema = Type.String({
4
4
  minLength: 1,
@@ -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
+ }
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import ActivityFeed from '@svadmin/ui/components/ActivityFeed.svelte';
4
+ import { activityFeedPropsSchema } from '../business-definitions.js';
5
+ import { asSurfaceActivities } from '../business-data.js';
6
+ import type { SurfaceWidgetRendererProps } from '../catalog.js';
7
+ import { surfaceMetric } from '../recipes.js';
8
+ import { formatSurfaceMessage, resolveSurfaceMessages } from '../localization.js';
9
+
10
+ let { props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
11
+ const baseProps = $derived(Object.fromEntries(Object.entries(props).filter(([key]) => key !== 'appearance')));
12
+ const p = $derived(Value.Decode(activityFeedPropsSchema, baseProps));
13
+ const appearance = $derived(typeof props['appearance'] === 'object' && props['appearance'] !== null ? props['appearance'] as { tone?: typeof p.tone; density?: typeof p.density } : undefined);
14
+ const tone = $derived(appearance?.tone ?? p.tone ?? 'neutral');
15
+ const density = $derived(appearance?.density ?? p.density ?? 'comfortable');
16
+ const classes = $derived(surfaceMetric({ tone, density }));
17
+ const text = $derived(resolveSurfaceMessages(locale, messages));
18
+ const activities = $derived(data.status === 'ready' ? asSurfaceActivities(data.value, p, text.activityUnknownActor) : null);
19
+ </script>
20
+
21
+ <section class={classes.root + ' ' + classes.card} aria-label={p.title} data-surface-business="activity-feed" data-surface-density={density}>
22
+ {#if data.status === 'loading'}
23
+ <p role="status">{text.activityLoading}</p>
24
+ {:else if data.status === 'empty'}
25
+ <p role="status">{text.activityEmpty}</p>
26
+ {:else if data.status === 'error'}
27
+ <p role="alert">{text.activityUnavailable}</p>
28
+ {:else if activities?.ok}
29
+ <ActivityFeed activities={activities.value} allowComment={false} title={p.title} emptyLabel={text.activityEmpty}
30
+ countLabel={formatSurfaceMessage(text.activityCount, { count: activities.value.length })} />
31
+ {:else}
32
+ <p role="alert">{text.activityInvalidData}</p>
33
+ {/if}
34
+ </section>
@@ -0,0 +1,4 @@
1
+ import type { SurfaceWidgetRendererProps } from '../catalog.js';
2
+ declare const ActivityFeedWidget: import("svelte").Component<SurfaceWidgetRendererProps, {}, "">;
3
+ type ActivityFeedWidget = ReturnType<typeof ActivityFeedWidget>;
4
+ export default ActivityFeedWidget;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Value } from '@sinclair/typebox/value';
3
- import { surfaceMetric } from '../styled-system/recipes/index.js';
3
+ import { surfaceMetric } from '../recipes.js';
4
+ import '../styles.css';
4
5
  import StatsCard from '@svadmin/ui/components/StatsCard.svelte';
5
6
  import { styledMetricPropsSchema as metricPropsSchema } from '../builtin-schemas.js';
6
7
  import type { SurfaceWidgetRendererProps } from '../catalog.js';
@@ -1,3 +1,4 @@
1
+ import '../styles.css';
1
2
  import type { SurfaceWidgetRendererProps } from '../catalog.js';
2
3
  declare const MetricWidget: import("svelte").Component<SurfaceWidgetRendererProps, {}, "">;
3
4
  type MetricWidget = ReturnType<typeof MetricWidget>;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import Card from '@svadmin/ui/components/ui/card/card.svelte';
4
+ import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
5
+ import CardTitle from '@svadmin/ui/components/ui/card/card-title.svelte';
6
+ import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
7
+ import { resourceDetailPropsSchema } from '../business-definitions.js';
8
+ import type { SurfaceWidgetRendererProps } from '../catalog.js';
9
+ import type { JsonObject } from '../types.js';
10
+ import { surfaceMetric } from '../recipes.js';
11
+ import { resolveSurfaceMessages } from '../localization.js';
12
+ import { displayTableValue } from '../widget-data.js';
13
+
14
+ let { props, data, locale = 'en-US', messages }: SurfaceWidgetRendererProps = $props();
15
+ const baseProps = $derived(Object.fromEntries(Object.entries(props).filter(([key]) => key !== 'appearance')));
16
+ const p = $derived(Value.Decode(resourceDetailPropsSchema, baseProps));
17
+ const appearance = $derived(typeof props['appearance'] === 'object' && props['appearance'] !== null ? props['appearance'] as { tone?: typeof p.tone; density?: typeof p.density } : undefined);
18
+ const tone = $derived(appearance?.tone ?? p.tone ?? 'neutral');
19
+ const density = $derived(appearance?.density ?? p.density ?? 'comfortable');
20
+ const classes = $derived(surfaceMetric({ tone, density }));
21
+ const text = $derived(resolveSurfaceMessages(locale, messages));
22
+ const record = $derived(data.status === 'ready' && data.value !== null && typeof data.value === 'object' && !Array.isArray(data.value)
23
+ ? data.value as JsonObject : null);
24
+ </script>
25
+
26
+ <Card class={classes.root + ' ' + classes.card} data-surface-business="resource-detail" data-surface-density={density}>
27
+ <CardHeader><CardTitle>{p.title}</CardTitle></CardHeader>
28
+ <CardContent>
29
+ {#if data.status === 'loading'}
30
+ <p role="status">{text.detailLoading}</p>
31
+ {:else if data.status === 'empty'}
32
+ <p role="status">{text.detailEmpty}</p>
33
+ {:else if data.status === 'error'}
34
+ <p role="alert">{text.detailUnavailable}</p>
35
+ {:else if record}
36
+ <dl aria-label={p.title} data-density={density}>
37
+ {#each p.fields as item, index (`${index}:${item.field}`)}
38
+ <div>
39
+ <dt>{item.label}</dt>
40
+ <dd>{displayTableValue(record[item.field], { format: item.format, locale, messages: text })}</dd>
41
+ </div>
42
+ {/each}
43
+ </dl>
44
+ {:else}
45
+ <p role="alert">{text.detailInvalidData}</p>
46
+ {/if}
47
+ </CardContent>
48
+ </Card>
49
+
50
+ <style>
51
+ dl { display: grid; gap: 1rem; margin: 0; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); }
52
+ dl > div { min-width: 0; }
53
+ dt { color: var(--muted-foreground); font-size: .8125rem; }
54
+ dd { margin: .25rem 0 0; color: var(--foreground); overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
55
+ dl[data-density='compact'] { gap: .5rem; }
56
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { SurfaceWidgetRendererProps } from '../catalog.js';
2
+ declare const ResourceDetailWidget: import("svelte").Component<SurfaceWidgetRendererProps, {}, "">;
3
+ type ResourceDetailWidget = ReturnType<typeof ResourceDetailWidget>;
4
+ export default ResourceDetailWidget;
@@ -6,7 +6,7 @@
6
6
  import type { SurfaceWorkflowHost } from '../workflows/context.js';
7
7
  import type { SurfaceActionDescriptor } from '../workflows/types.js';
8
8
  import type { JsonObject } from '../types.js';
9
- import { editorClasses, editorButtonClasses } from '../styles/editor.generated.js';
9
+ import { editorClasses, editorButtonClasses } from '../styles/editor.js';
10
10
  import '../styles/editor.css';
11
11
  let { widgetId, host, action, title, locale }: {
12
12
  widgetId: string; host: SurfaceWorkflowHost; action: SurfaceActionDescriptor; title: string; locale: string;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Value } from '@sinclair/typebox/value';
3
- import { surfaceTable } from '../styled-system/recipes/index.js';
3
+ import { surfaceTable } from '../recipes.js';
4
+ import '../styles.css';
4
5
  import CardContent from '@svadmin/ui/components/ui/card/card-content.svelte';
5
6
  import CardHeader from '@svadmin/ui/components/ui/card/card-header.svelte';
6
7
  import CardTitle from '@svadmin/ui/components/ui/card/card-title.svelte';
@@ -1,3 +1,4 @@
1
+ import '../styles.css';
1
2
  import type { SurfaceWidgetRendererProps } from '../catalog.js';
2
3
  declare const ResourceTableWidget: import("svelte").Component<SurfaceWidgetRendererProps, {}, "">;
3
4
  type ResourceTableWidget = ReturnType<typeof ResourceTableWidget>;
@@ -6,7 +6,7 @@
6
6
  import type { SurfaceRenderCatalog } from '../catalog.js';
7
7
  import type { SurfaceDataProvider, SurfacePolicy } from '../types.js';
8
8
  import SurfaceRenderer from './SurfaceRenderer.svelte';
9
- import { editorClasses, editorButtonClasses } from '../styles/editor.generated.js';
9
+ import { editorClasses, editorButtonClasses } from '../styles/editor.js';
10
10
  import '../styles/editor.css';
11
11
 
12
12
  export interface SurfaceEditPreviewProps {
@@ -16,7 +16,8 @@
16
16
  import { validateSurfaceSpec } from '../validation.js';
17
17
  import { createSurfaceSourceCache, sameSourceIdentity, snapshotSurfaceSource } from '../source-cache.js';
18
18
  import type { SurfaceSourceRequest } from '../source-cache.js';
19
- import { surfaceMetric } from '../styled-system/recipes/index.js';
19
+ import { surfaceMetric } from '../recipes.js';
20
+ import '../styles.css';
20
21
  import { withoutSurfaceAppearance } from '../workflows/catalog.js';
21
22
 
22
23
  function appearanceClasses(props: JsonObject): string {
@@ -1,6 +1,7 @@
1
1
  import type { SurfaceRenderCatalog } from '../catalog.js';
2
2
  import type { SurfaceMessages } from '../localization.js';
3
3
  import type { SurfaceDataError, SurfaceDataProvider, SurfacePolicy, SurfaceValidationIssue } from '../types.js';
4
+ import '../styles.css';
4
5
  export type SurfaceRendererError = {
5
6
  readonly type: 'validation';
6
7
  readonly issues: readonly SurfaceValidationIssue[];
@@ -0,0 +1,2 @@
1
+ export { surfaceDesignContract } from '@svadmin/ui/design-contract';
2
+ export type { SurfaceTone, SurfaceDensity } from '@svadmin/ui/design-contract';
@@ -0,0 +1,2 @@
1
+ // 唯一事实源是 UI 的纯数据入口,不加载 Svelte 或浏览器代码。
2
+ export { surfaceDesignContract } from '@svadmin/ui/design-contract';
@@ -11,8 +11,18 @@ export interface SurfaceMessages {
11
11
  readonly tableNoRecords: string;
12
12
  readonly tableInvalidData: string;
13
13
  readonly tableUnavailable: string;
14
+ readonly detailLoading?: string;
15
+ readonly detailEmpty?: string;
16
+ readonly detailUnavailable?: string;
17
+ readonly detailInvalidData?: string;
18
+ readonly activityLoading?: string;
19
+ readonly activityEmpty?: string;
20
+ readonly activityUnavailable?: string;
21
+ readonly activityInvalidData?: string;
22
+ readonly activityUnknownActor?: string;
23
+ readonly activityCount?: string;
14
24
  readonly booleanTrue: string;
15
25
  readonly booleanFalse: string;
16
26
  }
17
- export declare function resolveSurfaceMessages(locale: string, overrides?: Partial<SurfaceMessages>): SurfaceMessages;
27
+ export declare function resolveSurfaceMessages(locale: string, overrides?: Partial<SurfaceMessages>): Required<SurfaceMessages>;
18
28
  export declare function formatSurfaceMessage(template: string, params: Readonly<Record<string, string | number>>): string;
@@ -11,6 +11,16 @@ const englishMessages = {
11
11
  tableNoRecords: 'No records',
12
12
  tableInvalidData: 'Table data is invalid',
13
13
  tableUnavailable: 'Table data is unavailable',
14
+ detailLoading: 'Loading record',
15
+ detailEmpty: 'No record',
16
+ detailUnavailable: 'Record is unavailable',
17
+ detailInvalidData: 'Record data is invalid',
18
+ activityLoading: 'Loading activity',
19
+ activityEmpty: 'No recent activity',
20
+ activityUnavailable: 'Activity is unavailable',
21
+ activityInvalidData: 'Activity data is invalid',
22
+ activityUnknownActor: 'Unknown actor',
23
+ activityCount: '{count} events',
14
24
  booleanTrue: 'Yes',
15
25
  booleanFalse: 'No',
16
26
  };
@@ -27,6 +37,16 @@ const simplifiedChineseMessages = {
27
37
  tableNoRecords: '暂无记录',
28
38
  tableInvalidData: '表格数据无效',
29
39
  tableUnavailable: '表格数据不可用',
40
+ detailLoading: '正在加载记录',
41
+ detailEmpty: '暂无记录',
42
+ detailUnavailable: '记录不可用',
43
+ detailInvalidData: '记录数据无效',
44
+ activityLoading: '正在加载动态',
45
+ activityEmpty: '暂无动态',
46
+ activityUnavailable: '动态不可用',
47
+ activityInvalidData: '动态数据无效',
48
+ activityUnknownActor: '未知操作者',
49
+ activityCount: '{count} 条动态',
30
50
  booleanTrue: '是',
31
51
  booleanFalse: '否',
32
52
  };
package/dist/openui.js CHANGED
@@ -43,6 +43,21 @@ function element(value) {
43
43
  && typeof value.typeName === 'string'
44
44
  && !!value.props && typeof value.props === 'object';
45
45
  }
46
+ function surfaceDataSource(value) {
47
+ if (!value || typeof value !== 'object' || Array.isArray(value))
48
+ return false;
49
+ const source = value;
50
+ if (typeof source['id'] !== 'string' || typeof source['resource'] !== 'string')
51
+ return false;
52
+ if (source['type'] === 'resource-one') {
53
+ return typeof source['recordId'] === 'string' || typeof source['recordId'] === 'number';
54
+ }
55
+ if (source['type'] !== 'resource-list')
56
+ return false;
57
+ if (source['pageSize'] !== undefined && typeof source['pageSize'] !== 'number')
58
+ return false;
59
+ return true;
60
+ }
46
61
  export function createSurfaceOpenUIStream(options) {
47
62
  const { catalog, policy } = options;
48
63
  const schema = createSurfaceOpenUISchema(catalog);
@@ -77,7 +92,8 @@ export function createSurfaceOpenUIStream(options) {
77
92
  for (const source of sources) {
78
93
  if (source === null && !final)
79
94
  continue;
80
- if (!element(source) || source.typeName !== 'Source' || source.partial || !isJsonValue(source.props['value'])) {
95
+ if (!element(source) || source.typeName !== 'Source' || source.partial
96
+ || !isJsonValue(source.props['value']) || !surfaceDataSource(source.props['value'])) {
81
97
  throw new SurfaceOpenUIError('syntax', 'Invalid Source node');
82
98
  }
83
99
  dataSources.push(source.props['value']);
@@ -0,0 +1,20 @@
1
+ import type { SurfaceDensity, SurfaceTone } from './design-contract.js';
2
+ export declare function surfaceMetric({ tone, density, }?: {
3
+ tone?: SurfaceTone | undefined;
4
+ density?: SurfaceDensity | undefined;
5
+ }): {
6
+ root: string;
7
+ card: string;
8
+ description: string;
9
+ state: string;
10
+ };
11
+ export declare function surfaceTable({ density }?: {
12
+ density?: SurfaceDensity | undefined;
13
+ }): {
14
+ root: string;
15
+ header: string;
16
+ content: string;
17
+ head: string;
18
+ cell: string;
19
+ state: string;
20
+ };
@@ -0,0 +1,20 @@
1
+ // 有限语义变体与静态 CSS 配套;不接受模型提供的 class/style。
2
+ export function surfaceMetric({ tone = 'neutral', density = 'comfortable', } = {}) {
3
+ return {
4
+ root: 'svadmin-surface-metric__root',
5
+ card: `svadmin-surface-metric__card svadmin-surface-tone--${tone} svadmin-surface-density--${density}`,
6
+ description: 'svadmin-surface-metric__description',
7
+ state: `svadmin-surface-metric__state svadmin-surface-tone--${tone} svadmin-surface-density--${density}`,
8
+ };
9
+ }
10
+ export function surfaceTable({ density = 'comfortable' } = {}) {
11
+ const variant = `svadmin-surface-density--${density}`;
12
+ return {
13
+ root: 'svadmin-surface-table__root',
14
+ header: `svadmin-surface-table__header ${variant}`,
15
+ content: `svadmin-surface-table__content ${variant}`,
16
+ head: `svadmin-surface-table__head ${variant}`,
17
+ cell: `svadmin-surface-table__cell ${variant}`,
18
+ state: `svadmin-surface-table__state ${variant}`,
19
+ };
20
+ }