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
55 changes: 55 additions & 0 deletions .changeset/docs-gen-inline-enum-width.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
"@objectstack/spec": patch
---

fix(spec): the reference generator elides an over-wide enum inside an inline shape summary, and says how many members it hid (#5340)

`formatType()` capped how many **keys** an inline object summary prints
(`INLINE_KEY_LIMIT = 4`) but never capped how wide a single key's **type** could
be. One long enum reached through a summary therefore printed every member into
one table cell. The issue was filed on `BulkActionDef.params` at ~900
characters; measuring the whole corpus found that is not close to the worst —
the 261-member error-code vocabulary is inlined into the `error` shape of 80
rows across 13 `api/*.mdx` pages, at **6242 characters in a single cell**.

An `Enum` body rendered below a summary's `{ … }` is now cut to 80 characters
and the count of what was cut is printed in its place:

```
type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>
```

The count is the safety property, not decoration. A silent prefix would leave
the page looking complete while it was not — a reader cannot tell a 7-member
vocabulary from the first 7 of 49 — and these pages are the authoritative input
for AI authors (ADR-0033), so a page that lies by omission is a worse defect
than a wide cell.

**Nothing that owns its vocabulary is elided.** The cut applies only below an
inline shape summary, which is by construction a *second* copy: a schema's own
row (`BulkActionParam.type`, `ErrorResponse.code`), a union variant on its own
row (`Enum< … > | string`, the `PageComponent.type` shape), a top-level
`Record< string, Enum< … > >` and an array of a top-level enum all still print
every member. For 457 of the corpus's 805 in-shape occurrences the elided
copy's full list is still on the same page for that reason; for the remaining
348 the count carries it, and the JSON Schema under `json-schema/` remains the
authority it always was.

**The 80 is measured, not chosen.** Across 216 pages / 8541 type cells / 1768
`Enum` occurrences, the 805 in-shape ones are bimodal and their density per
character collapses at 80 (3.6 occurrences/char over `(64,80]`, 1.6 over
`(80,100]`, 0.5 over `(100,200]`). Below it sit the ordinary short vocabularies
a reader wants spelled out; above it sit listings. A tighter budget buys almost
nothing and costs real information — budget 24 would elide 79% of them to save
4% more characters — and a fixed member cap is worse at every setting.

An elision must also **pay for its own marker**: a body only a member or two
over budget gives back less than `… +N more` costs to print, so it is left
whole. That is why 31 in-shape enums between 81 and 107 characters are
unchanged, and why the limit is not a cliff at exactly 81.

42 reference pages change, 144 rows, all in 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 is silently truncated and none
grew. Cells over 900 characters go from 76 to 4, over 200 from 246 to 145;
the p95 cell width is unchanged at 145, i.e. ordinary cells do not move.
2 changes: 1 addition & 1 deletion content/docs/references/ai/knowledge-source.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const result = FileKnowledgeSourceSchema.parse(data);
| **adapterConfig** | `Record<string, any>` | optional | |
| **source** | `{ kind: 'object'; object: string; contentFields: string[]; metadataFields?: string[]; … } \| { kind: 'file'; prefix: string; mimeTypes?: string[] } \| { kind: 'http'; urls: string[]; userAgent?: string }` | ✅ | |
| **embedding** | `{ provider: Enum<'openai' \| 'cohere' \| 'azure_openai' \| 'huggingface' \| 'local' \| 'custom'>; model: string; dimensions: integer; endpoint?: string; … }` | optional | |
| **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| 'opensearch' \| 'elasticsearch' \| 'custom'>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | |
| **vectorStore** | `{ provider: Enum<'pgvector' \| 'chroma' \| 'qdrant' \| 'pinecone' \| 'weaviate' \| 'milvus' \| 'redis' \| … +3 more>; collection: string; endpoint?: string; secretRef?: string; … }` | optional | |
| **refresh** | `{ onRecordChange?: boolean; cron?: string }` | optional | |
| **aiExposed** | `boolean` | optional | |

Expand Down
6 changes: 3 additions & 3 deletions content/docs/references/ai/solution-blueprint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const result = BlueprintAppSchema.parse(data);
| **name** | `string` | ✅ | Object machine name (snake_case) |
| **label** | `string` | optional | Human-readable singular label |
| **description** | `string` | optional | What this object represents |
| **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 |
| **fields** | `{ name: string; label?: string; type: Enum<'text' \| 'textarea' \| 'email' \| 'url' \| 'phone' \| 'password' \| 'secret' \| … +42 more>; required?: boolean; … }[]` | ✅ | Fields to create on the object |
| **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. |


Expand Down Expand Up @@ -174,7 +174,7 @@ const result = BlueprintAppSchema.parse(data);
| **summary** | `string` | optional | One-line description of the proposed solution |
| **assumptions** | `string[]` | ✅ | Design assumptions made from the underspecified goal |
| **questions** | `string[]` | optional | At most 1-2 structure-deciding questions to confirm before building |
| **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 |
| **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 |
| **views** | `{ object: string; name: string; label?: string; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'>; … }[]` | optional | Views to create |
| **dashboards** | `{ name: string; label?: string; widgets?: { id: string; title?: string; object?: string; chart?: Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'table'>; … }[] }[]` | optional | Dashboards to create |
| **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 |
Expand All @@ -192,7 +192,7 @@ const result = BlueprintAppSchema.parse(data);
| **summary** | `string` | ✅ | One-line description of the proposed solution |
| **assumptions** | `string[]` | ✅ | Design assumptions made from the underspecified goal |
| **questions** | `string[] \| null` | ✅ | At most 1-2 structure-deciding questions to confirm before building, or null |
| **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 |
| **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 |
| **views** | `{ object: string; name: string; label: string \| null; type: Enum<'list' \| 'form' \| 'kanban' \| 'calendar' \| 'gallery' \| 'gantt'> \| null; … }[] \| null` | ✅ | Views to create, or null |
| **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 |
| **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 |
Expand Down
Loading
Loading