Skip to content

Commit 2a61116

Browse files
os-zhuangclaude
andauthored
fix(spec): gen:docs 内联形状里的长枚举按测得阈值省略,并印出被隐藏的成员数 (#5340) (#6211)
* fix(spec): elide an over-wide enum inside a docs inline shape summary, with a count of what was hidden (#5340) `formatType()` capped how many KEYS an inline object summary prints (`INLINE_KEY_LIMIT = 4`) but never capped how wide one key's TYPE could be, so a long enum reached through a summary printed every member into a single table cell. Filed on `BulkActionDef.params` (~900 chars); the corpus measurement found worse — the 261-member error-code vocabulary inlined into `error` shapes on 80 rows across 13 `api/*.mdx` pages, at 6242 characters in one cell. An `Enum<…>` body rendered below a summary's `{ … }` is now cut to `INLINE_ENUM_WIDTH_LIMIT` (80 characters) and the count of what was cut is printed in its place: `Enum<'text' | 'textarea' | … +42 more>`. The count is the safety property — a silent prefix would leave the page looking complete while it was not, which is a worse defect than a wide cell for pages that are the authoritative input for AI authors (ADR-0033). Only a SECOND copy is cut. `ctx.inShapeSummary` is set once, where a summary renders a child, and inherited downward; a schema's own row, a union variant on its own row (`Enum<…> | string`), a top-level `Record<string, Enum<…>>` and an array of a top-level enum all still print every member. For 457 of the 805 in-shape occurrences that is where the elided copy's full list still lives, on the same page. The 80 is measured, not chosen: across 216 pages / 8541 type cells / 1768 enum occurrences the in-shape population is bimodal and its per-character density collapses at 80 (3.6/char over (64,80], 1.6 over (80,100], 0.5 over (100,200]). An elision must also pay for its own marker, so a body a member or two over budget is left whole and the limit is not a cliff at exactly 81. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5 * chore(docs): regenerate reference pages for the inline-enum elision (#5340) `pnpm --filter @objectstack/spec gen:schema && gen:docs` output only — no hand edits (`content/docs/references/` is AUTO-GEN). 42 pages, 144 rows, one direction: every changed row is shorter (462,140 characters removed in total, largest single row -6266) and every one carries a `… +N more` marker — no row was silently truncated and none grew. Cells over 900 characters 76 → 4, over 200 characters 246 → 145, p99 cell width 643 → 247, p95 unchanged at 145. The widest cell left is `api/contract.mdx` `code` at 6092 — the error-code vocabulary on its OWN row, deliberately untouched because it is the only full copy of that list on the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 939f579 commit 2a61116

45 files changed

Lines changed: 635 additions & 146 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
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): the reference generator elides an over-wide enum inside an inline shape summary, and says how many members it hid (#5340)
6+
7+
`formatType()` capped how many **keys** an inline object summary prints
8+
(`INLINE_KEY_LIMIT = 4`) but never capped how wide a single key's **type** could
9+
be. One long enum reached through a summary therefore printed every member into
10+
one table cell. The issue was filed on `BulkActionDef.params` at ~900
11+
characters; measuring the whole corpus found that is not close to the worst —
12+
the 261-member error-code vocabulary is inlined into the `error` shape of 80
13+
rows across 13 `api/*.mdx` pages, at **6242 characters in a single cell**.
14+
15+
An `Enum` body rendered below a summary's `{ … }` is now cut to 80 characters
16+
and the count of what was cut is printed in its place:
17+
18+
```
19+
type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>
20+
```
21+
22+
The count is the safety property, not decoration. A silent prefix would leave
23+
the page looking complete while it was not — a reader cannot tell a 7-member
24+
vocabulary from the first 7 of 49 — and these pages are the authoritative input
25+
for AI authors (ADR-0033), so a page that lies by omission is a worse defect
26+
than a wide cell.
27+
28+
**Nothing that owns its vocabulary is elided.** The cut applies only below an
29+
inline shape summary, which is by construction a *second* copy: a schema's own
30+
row (`BulkActionParam.type`, `ErrorResponse.code`), a union variant on its own
31+
row (`Enum< … > | string`, the `PageComponent.type` shape), a top-level
32+
`Record< string, Enum< … > >` and an array of a top-level enum all still print
33+
every member. For 457 of the corpus's 805 in-shape occurrences the elided
34+
copy's full list is still on the same page for that reason; for the remaining
35+
348 the count carries it, and the JSON Schema under `json-schema/` remains the
36+
authority it always was.
37+
38+
**The 80 is measured, not chosen.** Across 216 pages / 8541 type cells / 1768
39+
`Enum` occurrences, the 805 in-shape ones are bimodal and their density per
40+
character collapses at 80 (3.6 occurrences/char over `(64,80]`, 1.6 over
41+
`(80,100]`, 0.5 over `(100,200]`). Below it sit the ordinary short vocabularies
42+
a reader wants spelled out; above it sit listings. A tighter budget buys almost
43+
nothing and costs real information — budget 24 would elide 79% of them to save
44+
4% more characters — and a fixed member cap is worse at every setting.
45+
46+
An elision must also **pay for its own marker**: a body only a member or two
47+
over budget gives back less than `… +N more` costs to print, so it is left
48+
whole. That is why 31 in-shape enums between 81 and 107 characters are
49+
unchanged, and why the limit is not a cliff at exactly 81.
50+
51+
42 reference pages change, 144 rows, all in one direction: every changed row is
52+
shorter (462,140 characters removed in total, largest single row -6266), and
53+
every one carries a `… +N more` marker — no row is silently truncated and none
54+
grew. Cells over 900 characters go from 76 to 4, over 200 from 246 to 145;
55+
the p95 cell width is unchanged at 145, i.e. ordinary cells do not move.

content/docs/references/ai/knowledge-source.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const result = FileKnowledgeSourceSchema.parse(data);
8888
| **adapterConfig** | `Record<string, any>` | optional | |
8989
| **source** | `{ kind: 'object'; object: string; contentFields: string[]; metadataFields?: string[]; … } \| { kind: 'file'; prefix: string; mimeTypes?: string[] } \| { kind: 'http'; urls: string[]; userAgent?: string }` || |
9090
| **embedding** | `{ provider: Enum<'openai' \| 'cohere' \| 'azure_openai' \| 'huggingface' \| 'local' \| 'custom'>; model: string; dimensions: integer; endpoint?: string; … }` | optional | |
91-
| **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| 'opensearch' \| 'elasticsearch' \| 'custom'>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | |
91+
| **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| … +3 more>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | |
9292
| **refresh** | `{ onRecordChange?: boolean; cron?: string }` | optional | |
9393
| **aiExposed** | `boolean` | optional | |
9494

content/docs/references/ai/solution-blueprint.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const result = BlueprintAppSchema.parse(data);
102102
| **name** | `string` || Object machine name (snake_case) |
103103
| **label** | `string` | optional | Human-readable singular label |
104104
| **description** | `string` | optional | What this object represents |
105-
| **fields** | `{ name: string; label?: 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'>; required?: boolean; … }[]` || Fields to create on the object |
105+
| **fields** | `{ name: string; label?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required?: boolean; … }[]` || Fields to create on the object |
106106
| **nameField** | `string` | optional | The record title field — which field holds the human-readable name shown on cards, lookup chips, breadcrumbs and search (ADR-0079). Set it to the object's text label field (e.g. "product_name"). For a numbered entity (invoice/ticket), set it to a formula field that composes number + name (e.g. "`{order_no}` · `{customer}`"). Omitting it lets the platform auto-pick a text field, but declaring it is strongly preferred. |
107107

108108

@@ -174,7 +174,7 @@ const result = BlueprintAppSchema.parse(data);
174174
| **summary** | `string` | optional | One-line description of the proposed solution |
175175
| **assumptions** | `string[]` || Design assumptions made from the underspecified goal |
176176
| **questions** | `string[]` | optional | At most 1-2 structure-deciding questions to confirm before building |
177-
| **objects** | `{ name: string; label?: string; description?: string; fields: { name: string; label?: 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'>; required?: boolean; … }[]; … }[]` || Objects (tables) to create |
177+
| **objects** | `{ name: string; label?: string; description?: string; fields: { name: string; label?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required?: boolean; … }[]; … }[]` || Objects (tables) to create |
178178
| **views** | `{ object: string; name: string; label?: string; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'>; … }[]` | optional | Views to create |
179179
| **dashboards** | `{ name: string; label?: string; widgets?: { id: string; title?: string; object?: string; chart?: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'>; … }[] }[]` | optional | Dashboards to create |
180180
| **app** | `{ name: string; label?: string; icon?: string; nav?: { type: Enum<'object' \| 'dashboard'>; target: string; label?: string; icon?: string }[] }` | optional | The navigation shell (app) that surfaces the created objects/dashboards to end users |
@@ -192,7 +192,7 @@ const result = BlueprintAppSchema.parse(data);
192192
| **summary** | `string` || One-line description of the proposed solution |
193193
| **assumptions** | `string[]` || Design assumptions made from the underspecified goal |
194194
| **questions** | `string[] \| null` || At most 1-2 structure-deciding questions to confirm before building, or null |
195-
| **objects** | `{ name: string; label: string \| null; description: string \| null; fields: { name: string; label: string \| null; 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'>; required: boolean \| null; … }[] }[]` || Objects (tables) to create |
195+
| **objects** | `{ name: string; label: string \| null; description: string \| null; fields: { name: string; label: string \| null; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required: boolean \| null; … }[] }[]` || Objects (tables) to create |
196196
| **views** | `{ object: string; name: string; label: string \| null; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'> \| null; … }[] \| null` || Views to create, or null |
197197
| **dashboards** | `{ name: string; label: string \| null; widgets: { id: string; title: string \| null; object: string \| null; chart: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'> \| null; … }[] \| null }[] \| null` || Dashboards to create, or null |
198198
| **app** | `{ name: string; label: string \| null; icon: string \| null; nav: { type: Enum<'object' \| 'dashboard'>; target: string; label: string \| null; icon: string \| null }[] \| null } \| null` || The navigation shell (app) that surfaces the created objects/dashboards, or null |

0 commit comments

Comments
 (0)