Skip to content

Commit e1554b1

Browse files
fix(spec): 参考文档的联合单元格每种拼写只印一次,重复变体计入既有标记 (#6569) (#6738)
* fix(spec): 参考文档的联合单元格每种拼写只印一次,重复变体计入既有标记 (#6569) 同一个联合的多个变体如果渲染成逐字相同的字符串,现在只印一次,被折叠掉的 变体数计进 #6226 既有的 `… +N more` 标记。全语料实测 14 格改变(7 个页面), 类型文本合计 3682 → 2469 字符;字面的 `object | object | object | object` 从 9 格降到 0 格。 裁决落在 issue 正文的 B 与 C 之间:折叠(重复拼写不携带第一份之外的信息), 但自报元数(#6226:省略必须说出自己的大小),并且复用表格已有的标记词汇而不 发明第四种省略记法。对读者成立的一句话:印出的拼写数 + 计数 = 联合的元数, 无论变体是被上限藏起来的还是被折叠掉的。 三条有意的边界:判等在渲染出的字符串上(`object | object[]` 是两种拼写, 永不折叠);不限于相邻(语料里唯一分歧处正是 #6226 立案的 `App.navigation`, 分隔符排第七位,相邻规则会把第八份导航项再印一遍);共享的守卫原样适用, 两三个重复仍逐字保留,四个才是第一个划算的宽度。 #6226 的 `string | string | string | string | string` pin 因此翻转 —— 改的是 候选而不是守卫,同一条未修改的守卫对新候选给出相反结论。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o * docs(spec): 注释里的语料数字改用本次实测值,并写明与 issue 正文 11 格的差因 (#6569) 去重注释沿用了 issue 正文的「11 格 / 6 个页面」,而同一个 PR 的实测把它否掉了: 带相同 `object` 重复的是 **9 格 / 5 个页面**。11 是子串统计带进来的 —— `object \| object` 也会命中 `object \| object[]` 那两格(`ui/page.mdx` 的 `slots`、`automation/state-machine.mdx` 的 `states`),那是两种不同拼写,规则 有意不折叠。 注释是长期物,PR 正文不是;下一个读这个函数的人会把注释里的数字当实测事实。 差因一并写进注释,免得后来者 grep 出 11 之后误判「去重漏了两格」。 仅注释文本,逻辑、夹具、生成物均未改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 901eb85 commit e1554b1

10 files changed

Lines changed: 432 additions & 53 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
fix(spec): 参考文档的联合单元格每种拼写只印一次,重复的变体计入既有的 `… +N more` (#6569)
6+
7+
`content/docs/references/**` 的类型单元格里,同一个联合的多个变体如果**渲染成
8+
逐字相同的字符串**,现在只印一次,被折叠掉的变体数计进 #6226 既有的
9+
`… +N more` 标记。全语料实测:**14 格**改变(7 个页面),这些格的类型文本合计
10+
**3682 → 2469 字符**;字面的 `object | object | object | object`**9 格降到
11+
0 格**
12+
13+
## 为什么会有这种重复
14+
15+
#6374 给内联形状摘要加了深度预算(嵌套形状印 `object`)。后果之一:一个联合的
16+
多个变体如果**都是对象**,它们在摘要之下就都渲染成 `object`,单元格里于是出现
17+
连续重复。`kernel/manifest.mdx``Manifest.navigationContributions` 最刺眼 ——
18+
它印了四个 `object`,再数「`… +5 more`」个**和已印出的那四个一模一样**
19+
`object`
20+
21+
重复本身不是 #6374 造出来的新行为(`ui/app.mdx``App.navigation`#6226
22+
之前就把同一个形状印了九遍),但深度预算让它变成了语料里的常见拼写。这些页面是
23+
AI 作者的权威输入(ADR-0033),一格读起来像渲染 bug 的代价高于它的宽度。
24+
25+
## 裁决落地的形状
26+
27+
维护者的裁决在 issue 正文的 B 与 C 之间:**折叠**(重复的拼写不携带第一份之外
28+
的任何信息),但**自报元数**(#6226 的规则:省略必须说出自己的大小),并且用
29+
表格**已有**的标记词汇,而不是新发明一种 `object ×4` 之类的记号 —— #6226
30+
裁决理由恰恰是「同一张表里出现第二种省略风格,比它要修的宽度更糟」。
31+
32+
于是一句话对读者成立:**印出的拼写数 + 计数 = 联合的元数**,无论某个变体是被
33+
变体数上限藏起来的,还是因为这一格已经逐字印过它而被折叠掉的。
34+
35+
## 三条有意的边界
36+
37+
- **判等在渲染出来的字符串上**,不在 schema 结构上:`object | object[]`
38+
(`ui/page.mdx``Page.slots`)是两种拼写,永不折叠。这个渲染器判的是读者
39+
看到的东西。
40+
- **不限于相邻**。实测语料里唯一一处两种规则会分歧的地方,正是 #6226 立案的那
41+
一格:`App.navigation` 的九个变体里,八个是同一种导航项拼写、一个是
42+
`{ type: 'separator'; … }`,而分隔符排在**第七位**——`uniq` 式的相邻规则会
43+
把第八份导航项**再印一遍**,等于把缺陷留在这一格里。
44+
- **共享的「标记必须挣回自己的位置」守卫原样适用**,没有豁免:`object | object`
45+
加上标记反而**变长 3 字符**,`object | object | object` 只省 6 字符而标记占
46+
12 字符,两者都保留重复;四个重复才是第一个划算的宽度(省 15 字符)。语料里
47+
14 处带重复拼写的联合渲染全部通过守卫。
48+
49+
## 一条既有 pin 因此翻转
50+
51+
#6226 曾把 `string | string | string | string | string` 逐字钉住,理由写在测试
52+
注释里:按变体数上限印四个再加标记会**更长**,守卫拒绝。这次改的是**候选**
53+
不是守卫 —— 一种拼写加 `… +4 more` 是 18 字符对 42 字符,省 24 —— 同一条未经
54+
修改的守卫因此接受。旧 pin 给出的理由,正是新 pin 给出相反结论的理由。守卫的
55+
拒绝区间仍然有人占着,只是往下挪了一档(见上一节)。

content/docs/references/ai/conversation.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const result = CodeContentSchema.parse(data);
118118
| **context** | `{ sessionId: string; userId?: string; agentId?: string; object?: string; … }` || |
119119
| **modelId** | `string` | optional | AI model ID |
120120
| **tokenBudget** | `{ maxTokens: integer; maxPromptTokens?: integer; maxCompletionTokens?: integer; reserveTokens: integer; … }` || |
121-
| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: (object \| object \| object \| object)[]; … }[]` || |
121+
| **messages** | `{ id: string; timestamp: string; role: Enum<'system' \| 'user' \| 'assistant' \| 'function' \| 'tool'>; content: (object \| … +3 more)[]; … }[]` || |
122122
| **tokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer; budgetLimit: integer; … }` | optional | |
123123
| **totalTokens** | `{ promptTokens: integer; completionTokens: integer; totalTokens: integer }` | optional | Total tokens across all messages |
124124
| **totalCost** | `number` | optional | Total cost for this session in USD |

content/docs/references/api/protocol.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,9 @@ Get package response
948948
| **name** | `string` | optional | Item name — supplied by the metadata door; for an object-scoped container it is the object name. |
949949
| **label** | `string \| Record<string, string>` | optional | Human-readable label shown in metadata lists. |
950950
| **object** | `string` | optional | Object this container binds to — how a stack-level `views: [...]` entry says which object its views belong to; read by `getViewsByObject()` / `GET /meta/view?object=`. |
951-
| **list** | `{ name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }` | optional | |
951+
| **list** | `{ name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }` | optional | |
952952
| **form** | `{ type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }` | optional | |
953-
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) |
953+
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }>` | optional | Additional named list views (views mode — dropdown userFilters allowed, no tabs; ADR-0047) |
954954
| **formViews** | `Record<string, { type?: Enum<'simple' \| 'tabbed' \| 'wizard' \| 'split' \| 'drawer' \| 'modal'>; layout?: Enum<'vertical' \| 'horizontal' \| 'inline' \| 'grid'>; columns?: integer; title?: string; … }>` | optional | Additional named form views |
955955
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
956956
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |

content/docs/references/data/object.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ const result = ApiMethod.parse(data);
136136
| **titleFormat** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | [DEPRECATED → nameField (ADR-0079)] Render-only title template; the server cannot return or query it, and an explicit nameField now takes precedence. Migrate a single-field title to nameField, a composite to a formula field designated as nameField. |
137137
| **highlightFields** | `string[]` | optional | [ADR-0085] Ordered most-important fields; first entry wins where only one fits. Drives default columns, cards, previews, detail highlight strip. Renamed from compactLayout. |
138138
| **stageField** | `string \| false` | optional | [ADR-0085] Lifecycle stage field (linear/ordered), or false to declare the status field non-linear and suppress stage heuristics. Absent = heuristic detection allowed. |
139-
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| object \| object \| object; … }>` | optional | Built-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047) |
139+
| **listViews** | `Record<string, { name?: string; label?: string \| Record<string, string>; type?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>; data?: object \| … +3 more; … }>` | optional | Built-in named list views (segmented tabs) shipped with the object schema — "views" mode, dropdown userFilters allowed, no page-only tabs (ADR-0047) |
140140
| **searchableFields** | `string[]` | optional | Fields the `$search` query matches against (ADR-0061). Canonical default for the record picker, list quick-search and global search; views may narrow it. When unset, search auto-defaults to the name/title field plus short-text fields. |
141141
| **enable** | `{ trackHistory?: boolean; searchable?: boolean; apiEnabled?: boolean; apiMethods?: Enum<'get' \| 'list' \| 'create' \| 'update' \| 'delete' \| 'bulk'>[]; … }` | optional | Enabled system features modules |
142142
| **sharingModel** | `Enum<'private' \| 'public_read' \| 'public_read_write' \| 'controlled_by_parent'>` | optional | Org-Wide Default record visibility (OWD) for INTERNAL users. Canonical four only (legacy aliases removed, ADR-0090 D4): private (owner-only) \| public_read (everyone reads, owner writes) \| public_read_write (everyone reads+writes) \| controlled_by_parent (derived from the master record). A CUSTOM object that omits this resolves to private at runtime (ADR-0090 D1). |

content/docs/references/data/validation.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ const result = ConditionalValidationSchema.parse(data);
115115
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |
116116
| **type** | `'conditional'` || |
117117
| **when** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` || Predicate (CEL). e.g. P`record.type == 'enterprise'` |
118-
| **then** | `{ name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| … +2 more` || Validation rule to apply when condition is true |
119-
| **otherwise** | `{ name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| { name: string; label?: string; description?: string; active?: boolean; … } \| … +2 more` | optional | Validation rule to apply when condition is false |
118+
| **then** | `{ name: string; label?: string; description?: string; active?: boolean; … } \| [ConditionalValidation](#conditionalvalidation) \| … +4 more` || Validation rule to apply when condition is true |
119+
| **otherwise** | `{ name: string; label?: string; description?: string; active?: boolean; … } \| [ConditionalValidation](#conditionalvalidation) \| … +4 more` | optional | Validation rule to apply when condition is false |
120120

121121

122122
---

content/docs/references/kernel/manifest.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const result = ManifestSchema.parse(data);
4444
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
4545
| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability |
4646
| **extensions** | `Record<string, any>` | optional | Extension points and contributions |
47-
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| object \| object \| object \| … +5 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
47+
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
4848
| **loading** | `{ strategy?: Enum<'eager' \| 'lazy' \| 'parallel' \| 'deferred' \| 'on-demand'>; preload?: object; codeSplitting?: object; dynamicImport?: object; … }` | optional | Plugin loading and runtime behavior configuration |
4949
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
5050
| **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) |

content/docs/references/ui/app.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const result = ActionNavItemSchema.parse(data);
7373
| **active** | `boolean` | optional | Whether the app is enabled |
7474
| **isDefault** | `boolean` | optional | Is default app |
7575
| **hidden** | `boolean` | optional | Hide from the App Switcher; the shell surfaces hidden apps via the avatar menu instead |
76-
| **navigation** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| … +5 more)[]` | optional | Full navigation tree for the app sidebar |
76+
| **navigation** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | optional | Full navigation tree for the app sidebar |
7777
| **areas** | `{ id: string; label: string \| Record<string, string>; icon?: string; description?: string \| Record<string, string>; … }[]` | optional | Navigation areas for partitioning navigation by business domain |
7878
| **contextSelectors** | `{ id: string; label: string \| Record<string, string>; icon?: string; optionsSource: object; … }[]` | optional | App-level scope dropdowns whose value is injected into nav items as `{<id>}` template vars |
7979
| **homePageId** | `never` | optional | [REMOVED] `app.homePageId` was removed in @objectstack/spec 17.0.0 (#4667, #4709, ADR-0049). objectui's console did read it before v17 (`resolveLandingRoute`), so this key had a consumer — it was retired because the capability is better expressed on the navigation item itself than as an ID cross-reference that silently falls back when it dangles. An app's landing page IS its first navigation item (by `order`), and the root landing follows `isDefault` routing. Delete the key; to change where an app opens, reorder `navigation` so the intended entry is first, and set `isDefault` on the app that should own the root landing. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
@@ -204,7 +204,7 @@ const result = ActionNavItemSchema.parse(data);
204204
| **label** | `string \| Record<string, string>` || Area display label |
205205
| **icon** | `string` | optional | Area icon name |
206206
| **description** | `string \| Record<string, string>` | optional | Area description |
207-
| **navigation** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| … +5 more)[]` || Navigation items within this area |
207+
| **navigation** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` || Navigation items within this area |
208208

209209

210210
---
@@ -220,7 +220,7 @@ A navigation contribution: a package injecting nav items into an app it does not
220220
| **app** | `string` || Target app name to contribute navigation into (e.g. "setup") |
221221
| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level |
222222
| **priority** | `integer` | optional | Merge priority within the target group — lower applied first (matches object extender priority) |
223-
| **items** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| … +5 more)[]` || Navigation items contributed into the target app/group |
223+
| **items** | `({ id: string; label: string \| Record<string, string>; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` || Navigation items contributed into the target app/group |
224224

225225

226226
---

0 commit comments

Comments
 (0)