Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/gen-docs-passthrough-declared-shape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@objectstack/spec": patch
---

fix(spec): 参考页不再把「已声明键 + passthrough」的对象塌缩成 `Record` (#4912)

`gen:docs` 的类型渲染器先判 `additionalProperties`、后判 `properties`,而 JSON Schema 把
`.passthrough()` / `.catchall()` 对象**同时**表达为这两者 —— 于是每个「有形状、又开放」的
对象在参考页上都被渲染成一个光秃秃的 `Record< string, any >`,**已声明的键被整个抹掉**。
`BulkActionParam.options` 是立案时的样本:它的 `label` / `value` 是**必填**的,页面却显示
「无形状」。PR #4909 当时是在该键的 `.describe()` 散文里手工补偿的,那是逐点补偿,不是修复。

声明键与开放性是**两个独立的事实**,现在分别呈现:

- 之前:`Record< string, any >[]`
- 之后:`({ label: string; value: string | number | boolean } & Record< string, any >)[]`

数组元素上的括号是必需的 —— `A & B[]` 在 TypeScript 里是 `A & (B[])`,不加括号等于声明了
另一种类型。已声明键超过四个时仍然省略,`…`(还有更多**已声明**键)与
`& Record< string, any >`(还接受**未声明**键)是两件不同的事,单元格两者都印。

本次重生成影响 6 张参考页共 12 个单元格,全部是恢复被抹掉的声明键,没有任何一页丢失形状:
`ui/bulk-action`(`params`、`options`)、`ui/view`(`gantt`、`tree`,ListView 与
ObjectListView 各一份)、`ui/dashboard`(widget `options`)、`api/protocol`(三处 AI
`messages`)、`system/auth-config`(`socialProviders`)、`kernel/startup-orchestrator`
(`plugin`)。

渲染逻辑从 `build-docs.ts` 抽到 `scripts/lib/format-type.ts` 并配了单测:此前要断言它的输出
只能跑完整个生成器再 grep `.mdx`,这正是该塌缩能在整个 #4001 战役期间无人察觉的原因。
6 changes: 3 additions & 3 deletions content/docs/references/api/protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const result = AiAgentCapabilitiesSchema.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **messages** | `Record<string, any>[]` | ✅ | Conversation messages (at least one) |
| **messages** | `({ role: Enum<'system' \| 'user' \| 'assistant' \| 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]` | ✅ | Conversation messages (at least one) |
| **context** | `Record<string, any>` | optional | Agent context (app, object, record, …) |
| **options** | `Record<string, any>` | optional | Request options (model, temperature, …) |

Expand Down Expand Up @@ -87,7 +87,7 @@ const result = AiAgentCapabilitiesSchema.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **messages** | `Record<string, any>[]` | ✅ | Conversation messages (at least one) |
| **messages** | `({ role: Enum<'system' \| 'user' \| 'assistant' \| 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]` | ✅ | Conversation messages (at least one) |
| **system** | `string` | optional | System prompt, prepended as a system message |
| **model** | `string` | optional | Model id override |
| **temperature** | `number` | optional | Sampling temperature |
Expand Down Expand Up @@ -137,7 +137,7 @@ const result = AiAgentCapabilitiesSchema.parse(data);
| **title** | `string` | optional | Title / summary |
| **agentId** | `string` | optional | Agent this conversation is bound to |
| **userId** | `string` | optional | Owning user |
| **messages** | `Record<string, any>[]` | ✅ | Message history |
| **messages** | `({ role: Enum<'system' \| 'user' \| 'assistant' \| 'tool'>; content?: any; parts?: any[] } & Record<string, any>)[]` | ✅ | Message history |
| **createdAt** | `string` | ✅ | Creation timestamp (ISO 8601) |
| **updatedAt** | `string` | ✅ | Last update timestamp (ISO 8601) |
| **metadata** | `Record<string, any>` | optional | Conversation metadata |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/kernel/startup-orchestrator.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const result = HealthStatusSchema.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **plugin** | `Record<string, any>` | ✅ | Plugin metadata |
| **plugin** | `{ name: string; version?: string } & Record<string, any>` | ✅ | Plugin metadata |
| **success** | `boolean` | ✅ | Whether the plugin started successfully |
| **duration** | `number` | ✅ | Time taken to start the plugin in milliseconds |
| **error** | `{ name: string; message: string; stack?: string; code?: string }` | optional | Serializable error representation if startup failed |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/system/auth-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Advanced / low-level Better-Auth options
| **plugins** | `{ organization: boolean; twoFactor: boolean; passkeys: boolean; passwordRejectBreached: boolean; … }` | optional | |
| **session** | `{ expiresIn: number; updateAge: number }` | optional | |
| **trustedOrigins** | `string[]` | optional | Trusted origins for CSRF protection. Supports wildcards (e.g. "https://*.example.com"). The baseUrl origin is always trusted implicitly. |
| **socialProviders** | `Record<string, Record<string, any>>` | optional | Social/OAuth provider map forwarded to better-auth socialProviders. Keys are provider ids (google, github, apple, …). |
| **socialProviders** | `Record<string, { clientId: string; clientSecret: string; enabled: boolean; scope?: string[] } & Record<string, any>>` | optional | Social/OAuth provider map forwarded to better-auth socialProviders. Keys are provider ids (google, github, apple, …). |
| **oidcProviders** | `{ providerId: string; name?: string; discoveryUrl?: string; issuer?: string; … }[]` | optional | List of OIDC/OAuth2 providers for enterprise SSO. Product or enterprise packages can pass this directly or contribute it through auth:configure. |
| **emailAndPassword** | `{ enabled: boolean; disableSignUp?: boolean; requireEmailVerification?: boolean; minPasswordLength?: number; … }` | optional | Email and password authentication options forwarded to better-auth |
| **emailVerification** | `{ sendOnSignUp?: boolean; sendOnSignIn?: boolean; autoSignInAfterVerification?: boolean; expiresIn?: number }` | optional | Email verification options forwarded to better-auth |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/ui/bulk-action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const result = BulkActionDefSchema.parse(data);
| **operation** | `Enum<'update' \| 'delete' \| 'custom'>` | ✅ | What the executor does: 'update'/'delete' are data-plane mass mutations; 'custom' dispatches an object action (see `execution`). |
| **execution** | `Enum<'perRecord' \| 'aggregate'>` | optional | For `operation: 'custom'` — 'aggregate' dispatches the named action ONCE for the whole selection, carrying every id in `params._selectedIds` (objectui#3139). Required on a custom def: the per-record form is declared as `bulkActions: ['<name>']` instead. |
| **patch** | `Record<string, any>` | optional | For `operation: 'update'` — static field values applied to every selected record, merged UNDER the user-supplied params so a fixed value can be declared without exposing it in the dialog. |
| **params** | `Record<string, any>[]` | optional | Inputs collected once before the run. Omit to skip the params step and go straight to confirm. |
| **params** | `({ name: string; label?: string; help?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| 'markdown' \| 'html' \| 'richtext' \| 'number' \| 'currency' \| 'percent' \| 'date' \| 'datetime' \| 'time' \| 'boolean' \| 'toggle' \| 'select' \| 'multiselect' \| 'radio' \| 'checkboxes' \| 'lookup' \| 'master_detail' \| 'tree' \| 'user' \| 'image' \| 'file' \| 'avatar' \| 'video' \| 'audio' \| 'formula' \| 'summary' \| 'autonumber' \| 'composite' \| 'repeater' \| 'record' \| 'location' \| 'address' \| 'code' \| 'json' \| 'color' \| 'rating' \| 'slider' \| 'signature' \| 'qrcode' \| 'progress' \| 'tags' \| 'vector'>; … } & Record<string, any>)[]` | optional | Inputs collected once before the run. Omit to skip the params step and go straight to confirm. |
| **confirmText** | `string` | optional | Confirmation text shown above the affected-record summary. |
| **confirmLabel** | `string` | optional | Custom Confirm button label (default: "Run"). |
| **visible** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Eligibility predicate (CEL), same shape as `action.visible`. Evaluated once PER SELECTED RECORD with that record bound: the button is offered when at least one passes, the run covers only those, and the rest are reported as skipped. A record-free predicate (`features.x`, `current_user.y`) therefore behaves as a plain button-level gate. Fail-closed — a predicate that faults excludes the record. |
Expand Down Expand Up @@ -95,7 +95,7 @@ const result = BulkActionDefSchema.parse(data);
| **type** | `Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| 'markdown' \| 'html' \| 'richtext' \| 'number' \| 'currency' \| 'percent' \| 'date' \| 'datetime' \| 'time' \| 'boolean' \| 'toggle' \| 'select' \| 'multiselect' \| 'radio' \| 'checkboxes' \| 'lookup' \| 'master_detail' \| 'tree' \| 'user' \| 'image' \| 'file' \| 'avatar' \| 'video' \| 'audio' \| 'formula' \| 'summary' \| 'autonumber' \| 'composite' \| 'repeater' \| 'record' \| 'location' \| 'address' \| 'code' \| 'json' \| 'color' \| 'rating' \| 'slider' \| 'signature' \| 'qrcode' \| 'progress' \| 'tags' \| 'vector'>` | ✅ | Field widget to render, from the standard field-type vocabulary (text/number/select/lookup/date/…). |
| **required** | `boolean` | optional | Blocks the Confirm button until a value is present. |
| **default** | `any` | optional | Value applied when the dialog opens. (An ActionParam spells this `defaultValue`.) |
| **options** | `Record<string, any>[]` | optional | Static options for select-style widgets. Each entry is `{ label, value }` plus any extra widget config — the entry is open (`.passthrough()`) because the renderer forwards unknown option keys to the field widget, which reads `color` / `icon` / `disabled` / `visibleWhen` beyond the declared pair. |
| **options** | `({ label: string; value: string \| number \| boolean } & Record<string, any>)[]` | optional | Static options for select-style widgets. Each entry is `{ label, value }` plus any extra widget config — the entry is open (`.passthrough()`) because the renderer forwards unknown option keys to the field widget, which reads `color` / `icon` / `disabled` / `visibleWhen` beyond the declared pair. |
| **object** | `string` | optional | Target object for a `lookup` widget. (An ActionParam spells this `reference`.) |
| **labelField** | `string` | optional | Related-object field used as the option label for a `lookup` widget (defaults to name/full_name/email/id). |
| **multiple** | `boolean` | optional | Allow picking multiple values — the param value becomes an array and is written to the patch as-is. |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/ui/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Dashboard header action
| **dimensions** | `string[]` | optional | Dimension names — X/group/split |
| **values** | `string[]` | ✅ | Measure names — Y (at least one) |
| **layout** | `{ x: number; y: number; w: number; h: number }` | optional | Grid layout position (auto-flowed when omitted) |
| **options** | `Record<string, any>` | optional | Widget specific configuration |
| **options** | `{ dateGranularity?: Enum<'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; sortBy?: string; sortOrder?: Enum<'asc' \| 'desc'>; limit?: integer; … } & Record<string, any>` | optional | Widget specific configuration |
| **filterBindings** | `Record<string, string \| 'false'>` | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |
| **suppressWarnings** | `string[]` | optional | Build diagnostic rule ids suppressed on this widget |
| **responsive** | `any` | optional | [REMOVED] `dashboard.widgets[].responsive` was removed in @objectstack/spec 17.0.0 (#4876, ADR-0049 D2) — no renderer ever read it, so per-widget breakpoint overrides were never applied: the value parsed, validated, and then did nothing. The dashboard grid reflows by its own layout rules (`columns` + `gap` on the dashboard, the `layout` box on each widget). Delete the key. The shared `ResponsiveConfig` shape is NOT gone — it stays live on `page.components[].responsive`, which objectui `useResponsiveConfig` really does read; move the layout there if you need breakpoint behaviour today. Run `os migrate meta --from 16` to rewrite it automatically. |
Expand Down
8 changes: 4 additions & 4 deletions content/docs/references/ui/view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,11 @@ List chart view configuration
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
| **gantt** | `Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
| **chart** | `{ chartType?: Enum<'bar' \| 'line' \| 'pie' \| 'area' \| 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }` | optional | List chart view configuration |
| **tree** | `Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer } & Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **description** | `string` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
| **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting |
Expand Down Expand Up @@ -473,11 +473,11 @@ List chart view configuration
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
| **gantt** | `Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
| **chart** | `{ chartType?: Enum<'bar' \| 'line' \| 'pie' \| 'area' \| 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }` | optional | List chart view configuration |
| **tree** | `Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer } & Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **description** | `string` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
| **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting |
Expand Down
Loading
Loading