Repository navigation
Release v0.2.1 with changeset automation - #77
Conversation
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
📦 Bundle Size Report
Size Limits
|
|
✅ All checks passed!
|
|
@copilot fix 2026-01-15T08:27:39.3165536Z ##[group]Run # Check if there are any changesets |
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Fixed in commit 99cf828. Added a changeset file to satisfy the CI check. The changeset workflow expects PRs that modify packages to include a changeset file - it gets consumed when the PR is merged and the automated release workflow runs. |
📦 Bundle Size Report
Size Limits
|
|
✅ All checks passed!
|
…he array (objectui#8221) (objectstack-ai#8758) * feat(core)!: retire the legacy string `sort` clause — one spelling, the array `convertSortToQueryParams` drops the string arm (objectui#8221, director ruling, decision batch objectstack-ai#77, option B). Its signature narrows to `QuerySortEntry[]`, and a string that still reaches it at runtime is REFUSED with a `console.error` naming the array form, quoting what arrived and stating that the query carries no `$orderby` — never a silent drop. Every declaration that published a string arm narrows with it, TypeScript face and zod mirror together: `ObjectGridSchema.sort`, `ObjectMapSchema.sort`, `ObjectGanttSchema.sort`, plus the local `sort` inputs on `LineItemsPanel`, `ObjectTimeline` and `deriveRelatedLists`'s ListView reader. Docs teach the array only. Deliberately untouched, measured rather than assumed: `record:related_list`'s `'field'` / `'-field'` string is a DIFFERENT dialect, normalized by `RelatedList.normalizeSortSpec`, and never reaches this sink. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w * fix(examples,tooling): migrate the one authored string `sort` fixture and re-key the moved doc-example ledger row `examples/schema-catalog/src/schemas/plugin-view/object-view-list.json` authored `table.sort: "name asc"` — the retired clause. `pnpm check` reported it by name ("did not validate as an ObjectUI schema"), and `objectui validate` gave the reason: `table -> sort` expected array, received string. Migrated to `[{ "field": "name", "order": "asc" }]`; the warning count moved 4 -> 3, which is the firing control that the fixed file is the one that moved. `scripts/check-doc-example-types.mjs`'s ledger is keyed by FILE:LINE, and the `objectql.ts` edits shifted `ObjectFormSchema`'s example block 1604 -> 1607. Re-keyed; `scripts/__tests__/check-doc-example-types.test.ts` is green again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w --------- Co-authored-by: Claude <noreply@anthropic.com>
…objectui#8254) (objectstack-ai#9605) Fixes objectstack-ai#8254 The renderer half objectui#7928's option A requires before `ObjectViewSchema.listViews` is mirrored by reference onto the protocol's `ObjectListViewSchema` — decision batch objectstack-ai#70, 2026-09-07. The mirror is never shipped as a type-only edit, so the renderer honours the protocol's shape here and the declaration follows on that card. ## What the measurement found The protocol declares a named view's `columns` as the union `string[] | ListColumn[]`. The local `NamedListView` declares only `string[]`. Two of the three routes out of `ObjectView` end in a **name slot** whose consumers index each record by every entry — `ObjectGridSchema.fields`, and the `fields` of the node `generateViewSchema` emits — and the named-view segment reached both of them **raw**. Measured on the base tree by rendering a fixture built with `ObjectListViewSchema.parse(...)`: a named view carrying the `ListColumn[]` half arrived at both name slots as the column objects themselves, i.e. a non-empty field list naming nothing. That is verbatim the failure the header of `tableColumnFieldNames` already describes for `table.columns` — `ObjectKanban` skips its `highlightFields` fallback whenever the list is non-empty, so it renders worse than an empty one — and objectui#5269 already answered it at that boundary. `viewColumnFieldNames` answers it at this one: the presence-preserving twin of that fold, over `columnIdentity`, this repo's single converged reader for "which field does this column entry name". It accepts no spelling `columnIdentity` did not already accept, so it narrows a declared union rather than widening a tolerated set. The **union** slots on the same branches — `ObjectGridSchema.columns` and the delegated `list-view` `columns` — keep taking the authored value raw, because their declared shape holds it and narrowing there would drop a column's own `label`. One value, two slots, each given the shape it declares. Precedence does not move. An authored empty `columns` still stops the `||` chain where it stopped before — deliberately unlike the `table.columns` fold, whose fall-through to the deprecated `table.fields` alias is a precedence decision belonging to that chain alone. The only visible behaviour change is that column entries resolving to no identity now collapse to an empty list instead of reaching a name slot as nameless entries. `filter` (spec rule objects, not the local tuple dialect), `sort`, `label` and `data` needed **no** renderer change. They are pinned anyway: "already works" is the claim that rots without an instrument. Also gone: the stale `as any` on the named-view config's `label` read. It was not load-bearing — `label` is a declared member — and under the mirror `label` becomes optional, which the cast was hiding from the type checker at the one read site that has to survive its absence. Declared here rather than folded in silently. ## The pin `packages/plugin-view/src/__tests__/ObjectView.specShapedNamedView-8254.test.tsx` — 13 cases. The spec leg of every fixture is built by running `ObjectListViewSchema.parse(...)`, not by looking spec-shaped. A fixture that merely resembles the protocol pins the author's memory of it; one that has been through the protocol's own parser cannot drift from it, and the schema is strict upstream, so the parse also proves the fixture carries no local-dialect key. Both legs ride **one** fixture, side by side in the same `expect`, which is what the card asked for: asserting each leg against a hard-coded literal in two separate cases would let both drift together and still pass. ## Reverse verification — and where the prediction was wrong Reverting the two `viewColumnFieldNames(...)` call sites to their raw forwards, on the committed fix: ``` anchor occurrences BEFORE mutation: 2 (expect 2) python replaced 2 occurrence(s) anchor occurrences AFTER mutation: 0 (expect 0); raw-forward occurrences: 2 (expect 2) HEAD blob: db4c76a mutated blob: e90ba0ad823b3fb2d5590b3c1be0d71601d1525e Test Files 1 failed (1) Tests 3 failed | 10 passed (13) RESTORED: byte-identical to HEAD (db4c76a) ``` Predicted: the four `columns` cases red. **Measured: three, and the prediction was wrong about which three.** Two of those four assert a union slot the fix never touched, so they stayed green exactly as they should — they are controls, not fix cases, and the prediction had mis-sorted them; the third red came from the controls block instead, the no-identity case, which rides the fold. The miss is recorded in the test file's own header rather than re-fitted to the result. The mutation reached the code under test directly: the test imports `../ObjectView` by relative path, so no `dist/` sits between the edit and the run. Both legs proved the edit on disk by occurrence count and by `git hash-object` against the `HEAD` blob, and the restore is proved by the blob hash plus an empty `git diff HEAD`, never by an exit code. ## Gates Run at `de2276e`, exit codes captured on their own line after redirection — never after a pipe. | gate | exit | | --- | --- | | `vitest run packages/plugin-view/ packages/types/…/object-view-unmirrored-keys-7779.test.ts packages/app-shell/…/ObjectView.relayRungCensus-7559.test.ts` | 0 — 42 files, 546 tests | | `pnpm --filter @object-ui/plugin-view type-check` | 0 | | `pnpm exec eslint .` in `packages/plugin-view` | 0 — 0 errors, 362 warnings, no ratchet on warnings by design | | `check-changeset-presence` · `-fixed` · `-no-major` · `-claims` | 0 · 0 · 0 · 0 | | `check-control-bytes` · `check-new-cross-file-line-citations` | 0 · 0 | | `check-test-path-roots` · `check-vi-mock-specifiers` · `-inherit` · `-override-shape` | 0 · 0 · 0 · 0 | | `check-unreferenced-sources` · `check-spec-symbol-derivation` · `check-phantom-dependencies` | 0 · 0 · 0 | | `check-governed-queue-guard --test` (the three changed paths) | 0 — NOT GOVERNED, 3 paths against 5 surfaces, none matched | `scripts/pm/dispatch-gates.mjs` lives in the sibling `objectstack` checkout and **refuses** for objectui paths (exit 2, "a sister repo's list is hand-derived from its own package manifest and its own workflow files"), so this list was hand-derived from this repo's own `package.json` scripts and `.github/workflows/`. **Declared narrowing.** The manifest carries ninety-odd `check:*` scripts. This run is the subset whose **input** this diff can move, plus the two pins that re-derive from the edited file by AST — `object-view-unmirrored-keys-7779.test.ts` and app-shell's `ObjectView.relayRungCensus-7559.test.ts`, both of which parse `packages/plugin-view/src/ObjectView.tsx`. The rest is the whole farm and belongs to CI. `check-changeset-claims` reported five pending changesets naming the edited file; all five were read and none is falsified by this diff — they describe the non-grid fetch, the gantt branch of `generateViewSchema`, the `title` composition and the `viewTabBar` census, none of which this change touches. Type-check needed the dependency closure built first (`turbo run build --filter '@object-ui/plugin-view^...'`, exit 0); without it `tsc` reports missing workspace `.d.ts` files, which is not a finding about this diff. ## Acceptance notes **The card's `data` sub-decision is already answered, and its stated premise is dead.** The card describes an undeclared `data` reached through a cast on the named-view config. On today's tree: `data` **is** declared on `NamedListView` — by reference to the spec type, not a hand copy — the cast is **gone**, and the protocol's own `ObjectListViewSchema` declares `data` too. objectui#8980 landed all of it under the director-seat ruling of 2026-09-13. The default direction on this card ("remove the read") would now delete a read of a member both faces declare, which the file's own comment forbids by name, so the read stands unchanged and this PR decides nothing about it. Authored population carrying an inline `data` block on a named view: **zero**, measured structurally over every authored named view in `apps/`, `examples/` and `content/`, with a live grep channel as the control. **The card's member figures are stale.** "47 declared members, 6 read" was the pre-objectui#8980 reading. The instrument that re-derives both numbers is `object-view-unmirrored-keys-7779.test.ts`; read the counts there rather than from this paragraph. Of the members the renderer reads off a named view, exactly one has no counterpart in `ObjectListViewSchema`: `options`, the untyped bag. Every other one maps. **The card's line anchor does not point where it says.** Re-derived anchors are reported on the card. **The landing package is `packages/plugin-view/src/ObjectView.tsx`**, re-derived: `packages/app-shell/src/views/ObjectView.tsx` contains zero occurrences of the named-view binding this card is about. **Population count, for the conversion note the mirror will need.** Twelve authored named views across `apps/` + `examples/` + `content/`; `apps/` contributes none. Five are accepted by `ObjectListViewSchema` as written, seven are refused — six for the now-required `columns`, two for the tuple filter dialect. The refusals concentrate in the documentation examples, so objectui#7928's mirror needs a docs pass shipped with it. Counts taken on this card, on this tree; re-run the census before acting on them. **Not filed, noted only:** the `views`-prop segment beside the named-view one at both name slots still forwards raw. It declares `string[]` and is the objectui#5097 host-composition surface, so its contract has not moved and there is nothing to honour yet; when a host writes column objects there, it is that surface's card. The named-view `sort` is a second, narrower asymmetry: `ObjectListViewSchema` still admits a bare string sort clause while objectui retired that spelling under decision batch objectstack-ai#77 and refuses it in the grid path. Both are recorded for whoever lands the mirror, not carried here. objectui#7928 is `pm:blocked` on this card and is **not** closed by this pull request; it remains open, and its own half — the by-reference declaration — is still to come. --- _Generated by [Claude Code](https://claude.ai/code/session_017bzjQQsBrHdwBy17iHVVe9)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ropping it in silence (objectstack-ai#9984) Fixes objectstack-ai#9955 ## What was wrong `packages/core/src/utils/sort-query.ts` refused the retired **scalar** `sort` clause out loud, and dropped the **same clause one container deeper** in complete silence. The author's row order disappeared and the console said nothing. Re-measured at source on this branch's base, objectui `origin/main` `8a7e09fa1` — the array arm had exactly two statements per entry, `continue` and `out.push`, and no sink call anywhere between the `Array.isArray` guard and the return. ### Three arms, before and after Each row is a real run of `convertSortToQueryParams` from `packages/core/src/utils/__tests__/sort-query.test.ts` on this branch. | input | `$orderby` BEFORE | diagnostics BEFORE | `$orderby` AFTER | diagnostics AFTER | |---|---|---|---|---| | CONTROL A — scalar `'name asc'` | `undefined` | 1 (scalar message) | `undefined` | 1 (scalar message, byte-identical) | | SUBJECT — `['name asc']` | `undefined` | **0** | `undefined` | **1 (entry message)** | | CONTROL B — `[{ field: 'name', order: 'desc' }]` | `{ name: 'desc' }` | 0 | `{ name: 'desc' }` | 0 | | CONTROL C — `[{ order: 'asc' }]` | `undefined` | 0 | `undefined` | **0 — unchanged, on purpose** | | MIXED — `[{ field: 'a', order: 'asc' }, 'b desc']` | `{ a: 'asc' }` | 0 | `{ a: 'asc' }` | 1 (names `'b desc'`) | Only the SUBJECT row moves, and it moves only in the diagnostics column. ⭐ CONTROL C is the load-bearing one. `[{ order: 'asc' }]` is also an entry that contributes nothing, and its silence is **declared** in `normalizeSortEntries`' docblock: "an entry with no usable `field` is SKIPPED (it names nothing to order by)". It stays silent. That is what shows the seam was cut at `typeof entry === 'string'` and not at "this entry has no field", which would have converted a designed silence into noise on every partially-authored view. ## ⛔ The accepted input set is UNCHANGED — by one key and by one shape `sort: ['name asc']` still contributes **no ordering**. Nothing that was refused is now accepted, and nothing that was accepted is now refused. Making the array-of-strings *work* would re-admit the retired spelling one container deeper and reverse the director ruling quoted in this file's own docblock — decision batch objectstack-ai#77, 2026-09-07, objectui#8221, maintainer approved, option B: "The platform has one `sort` spelling, the array, everywhere." Loud is not accepted. Pinned, not merely asserted, by `ACCEPTED INPUT SET UNCHANGED — the string entry contributes exactly nothing`, which compares five arrays against their own string-free twins on both `convertSortToQueryParams` and `normalizeSortEntries` — and which is the one new case that stays **green under the ablation below**, because it pins the non-change rather than the change. The two docblock sentences that look contradictory are both still true, and neither was edited: - "Not a lenient alias: an unusable input (a number, an object, an array of strings) yields `undefined` rather than a guess" — governs the RETURN VALUE. Unchanged. - "A retired string is REFUSED OUT LOUD, not dropped in silence" — governs the DIAGNOSTIC. This is the one that was being violated, and the only one this branch moves. ## The dedupe key — the decision, and why the line count stays bounded **A sibling reporter, and ARM-TAGGED keys in the one existing Set.** `retiredKey(ARM, SPELLING)` returns the arm token, a newline, then the spelling, so the two key spaces are disjoint **by construction**: the arm token is one of two fixed words and neither contains a newline, so the text before the first newline names the arm no matter what an author wrote. A bare string prefix would only have been disjoint until somebody authored the prefix — the spellings come out of authored JSON. Why not reuse `reportRetiredSortSpelling` with the entry as its key: 1. **The sentence an author acts on differs.** The scalar refusal can say "this query carries no `$orderby`" because refusing the whole value refuses the whole ordering. One bad entry among good ones does not: `[{ field: 'a', order: 'asc' }, 'b desc']` still orders by `a`. Reusing the scalar text would tell an author their rows are unordered while they are ordered by everything except the key they are hunting for — worse than silence. 2. **On one shared raw key the first arm to fire silences the other**, so a page carrying both spellings would print a sentence about the wrong container. Pinned by `CONTROL A — the scalar arm is untouched, and the two messages differ`. **The bound**: ONE line per distinct spelling per arm, however many blocks inherit the same bad view. That is the property the existing dedupe was built for — this sink runs inside the query memo of every object-bound block, so a related-list derivation over an object with N lists re-enters it N times per pass. Pinned by `BOUNDED — N blocks inheriting one bad view print ONE line, per spelling`, which drives 25 renders of one bad view and asserts one line, then a second spelling and asserts two, then clears the seam and asserts three. ## The message text `console.error`, unconditional, not dev-gated — the same channel and the same severity class as the scalar sibling, for the reason its docblock already argues: this REFUSES an authored row order, so the page renders in an order the author did not ask for. Not the `warnOnUnknownActionKeys` class, which reports keys nothing was ever going to read. Asserted against `console.warn` in the same body. Both halves objectui#9031 ruled load-bearing survive in this arm too, and they are judged rather than eyeballed: the new case `objectui#9031 — the ENTRY message prescribes metadata the installed spec ACCEPTS` parses the entries back out of the **real emitted string** and feeds them to `SortItemSchema` from the INSTALLED `@objectstack/spec`, exactly as the scalar pin does. The message names `ARRAY_FORM_EXAMPLE`, states `order` is required at publish, and states that the ascending default is a runtime tolerance and not permission to omit the key. ## One decision worth a reviewer's eye: the empty-string entry `['']` stays **silent**, and that is symmetry with the scalar arm rather than an exception to the seam. `sort: ''` never reaches the loud scalar arm either — the `!sort` guard answers first, and the pre-existing case `stays silent for values that were never the retired spelling` says why in words: an empty spelling is "the author asked for nothing", and reporting it would train readers to ignore the message. Inverting that one container deeper would recreate exactly the inconsistency this card is about, in the other direction. Whitespace is NOT in the carve-out: `' '` is truthy, is loud on the scalar arm today, and is loud as an entry too. Both legs are the control in `the '' entry keeps the scalar arm's silence — symmetry, not an exception`. ## Reverse verification (ablation) — measured, on disk, from the committed state Declared direction before running: turns RED. Run through `scripts/ablation-replace.mjs`, which proves the mutation reached the disk and proves the restore: ``` anchor x1 -> x0 replace x0 -> x1 blob ea88c7b -> d046030a78eb15e235d3689f71bd65149bcac347 ok mutation landed -> Tests 7 failed | 32 passed (39) ok restored: blob == HEAD (ea88c7b) and `git diff HEAD` is empty ``` The 7 that fall are exactly the new objectui#9955 cases that assert the diagnostic. The 32 that hold include every pre-existing scalar refusal — so the sink is demonstrably not dead, and the new assertions are not vacuous. ## The plugin-grid pin this change invalidates — updated in the same branch <sub>This section was rewritten by the `domain:ui#2` seat at 2026-09-19T02:04Z, under the standing clause that a dev writes a PR body once and the seat writes later corrections. It replaces a section headed 「⚠️ ONE RED」 that described a state which lasted one round; the evidence below is the seat's own and the implementer's, each attributed in place.</sub> `packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx` had one case pinning the very silence this card removes, and that pin **anticipated this card by name**. Its own comment read: >⚠️ Deliberately NOT a new refusal diagnostic. objectui#8767 landed route C for the > retired STRING clause; widening that refusal to cover the array-of-strings is that > card's business, not this one's. This entry is dropped exactly as the shared sink drops > an unusable entry — silently. objectui#9955 is that card, and it landed the widening. The implementer stopped at the face boundary and asked rather than riding; the seat granted route **A** (verdict `5738396331`) and expanded the declared face by that one file, on three readings taken in that act: - open PR objectui#9924 modifies the same file — the module docblock, the imports, `afterEach`, and an appended section — and ⛔ does **not** touch this case's hunk; - every `errorSpy` assertion on that branch is one of the three pre-existing ones, and its appended section carries **no** `errorSpy` assertion and no array-of-strings case at all; - ⇒ the two edits are disjoint **in both directions**, so neither order breaks the other and the serialization the in-flight rule buys is buying nothing here — while the wait it costs is unbounded. ### What the update asserts `expectNoOrderBy(params)` stays **first and unchanged** — the RETURN value moved nothing, and that is the half a later reader is likeliest to get wrong. The diagnostic half becomes `toHaveBeenCalledTimes(1)` plus three assertions that **discriminate** the entry refusal from the scalar one: the message must carry the entry sentence's own opening, must **not** carry the scalar sentence's, and must quote the value that arrived. A bare `toHaveBeenCalled()`, or a match on `objectui#8221`, would be satisfied just as well by the scalar message — and the input in this case is an **array**. ### The fence held `git diff -U0` on that file yields exactly one hunk header, `@@ -188,5 +188,22 @@`. The scalar-refusal case, the imports, the `afterEach` and the module docblock — PR objectui#9924's hunks — are all outside it. `packages/core/src/utils/sort-query.ts`, its test and the changeset are byte-identical to the first commit. ### The reading Same command, same denominators, both sides: the composite affected set went from `Test Files 1 failed | 165 passed (166)` / `Tests 1 failed | 3400 passed (3401)` to `Test Files 166 passed (166)` / `Tests 3401 passed (3401)`. ⭐ The equality of the totals is the reading — it says the one failure is gone **and nothing else moved**. The whole `plugin-grid` suite is green on its own: 143 files, 1348 tests. An advisory is posted on PR objectui#9924 (`5738398717`) so its author is not surprised; it asks nothing of that seat. ## Verification Run at commit `09d61f337`, from the repo root, per the repo's canonical invocation. - `pnpm exec vitest run packages/core/` — **158 files, 3319 tests, all passed**. - `pnpm --filter @object-ui/core run type-check` — **exit 0** (both legs: `tsc --noEmit` and `tsc -p tsconfig.test.json`), after `pnpm --filter '@object-ui/core^...' build`, without which every reading is a stale-`dist` artefact (`TS6305`). - Sink-aware consumers, run by name because the module's behaviour changed even though its exported symbols did not — `deriveRelatedLists.inheritSort`, `RecordDetailView.relatedListInheritedSort-5795`, `serverSorting`, `ObjectView.sortSink`, `ObjectView.namedViewSortArity`, `gridRetiredStringSort-8767`, `ObjectCalendar.sortMembersReachTheWire-8171`, `ObjectKanban.structuredMembersReachTheirSinks-8313`, `kanban-calendar-filter-sort-8174`, `ListView` — **all passed**. - `pnpm exec eslint . --no-inline-config --format json` — the **whole repo**, not a narrowing: 5151 files linted, 95 errors and 13387 warnings, **every one of them pre-existing**; both files this branch touches report 0 errors and 0 warnings. The population and the file count are read from that JSON, not estimated. - `pnpm check:control-bytes` — OK, 8050 tracked text files scanned. - `pnpm check:new-line-citations` — 0 new citations, exit 0. - `node scripts/check-changeset-presence.mjs` — 2 published source files changed, 1 changeset declared, exit 0. The one red is the out-of-face pin described above, and only that. ## Scope ⛔ Three things this branch deliberately does not touch, each declared out of scope by the dispatch order: 1. The `field`-less OBJECT entry — its silence is declared in the docblock and is pinned as CONTROL C to keep it that way. 2. The empty array — objectui#9607, already closed as a measured no-op. Not folded in. 3. The `object-grid` wire shape — route B on objectui#8767, declined by the maintainer by name on 2026-09-10. Nowhere near this branch. ## Acceptance notes - `noted, not filed` — the dedupe Set is now keyed by `ARM + newline + SPELLING` while the sibling sink `reportRetiredFieldType` (same module family, same "retired spelling" idea) still keys on a raw value. Not a defect and not filed: that sink has one arm, so it has nothing to collide with. Successor that would meet it: whoever gives that sink a second arm. Named here so the next reader of the two does not read the asymmetry as an oversight. This PR is a **DRAFT** and stays one: landing is the claiming seat's act after review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq --- _Generated by [Claude Code](https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq)_ --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…only (objectui#10581) (objectstack-ai#10621) Fixes objectstack-ai#10581 Clause-②: no `Clause-②: no` — reference docs stop teaching a refused spelling; no code, export or accept set moves. ## What changed One row of `content/docs/api/schema-reference.md`, in the `ObjectGridSchema` property table: - before: `sort` | `string \| SortConfig[]` | "Default sort configuration." - after: `sort` | `SortConfig[]` | "Default sort configuration. The string clause (`"name desc"`) was retired in objectui#8221 and now fails validation." Docs only, under `content/` (not a workspace package, not released): no changeset. `node scripts/check-changeset-presence.mjs` agrees (verdict line below). ## The retired-row form, and where it was copied from The row copies the form the same file already uses for a live key whose value lost a member: the `CalendarViewSchema` table's `view` row, which keeps its row, narrows the Type column to what survives, and says "`"agenda"` was retired in objectui#5740 and now fails validation." The Type-column `*retired*` / `RETIRED` form (the `BaseSchema` `body` row, the `related` row) is for a retired KEY; `sort` is live, so that form does not apply. ## Premise check (objectui `origin/main` at `6c2f3c533`) - The row read `string \| SortConfig[]`, as the card said. - `@object-ui/types` declares `ObjectGridSchema.sort` as `SortConfig[]` on the TypeScript face and as `z.array(SortConfigSchema)` on the zod mirror (describe text: "array only; the legacy string clause is retired — objectui#8221"). - The grid refuses a string at runtime: the `typeof schemaSort === 'string'` arm in `ObjectGrid.tsx` ("the legacy string `sort` clause is RETIRED (objectui#8221, decision batch objectstack-ai#77) and is REFUSED here rather than lowered") sends no `$orderby`, and `convertSortToQueryParams` reports the spelling once with `console.error`. - "now fails validation" is an existing pin, not prose: `packages/types/src/__tests__/sort-string-arm-retired-8221.test.ts`, test "object-grid refuses the legacy string clause", asserts `ObjectGridSchema.safeParse` fails on `sort: 'name desc'` with an issue at path `sort`. It ran green at this head (below). ## Sweep of `content/` No other row or example in `content/` authors a string `sort` on a grid-family node. Searches run: quoted `field asc|desc` clauses; `sort:` / `"sort":` followed by a string or a string array; every `sort` table row. What they found: - the `ObjectGridSchema` example in this file already writes `"sort": [{ "field": "name", "order": "asc" }]`; - `plugin-grid.mdx`, `plugin-map.mdx`, `plugin-gantt.mdx` and `plugin-view.mdx` type or write `sort` as the array; - the only string hits are not grid-family `sort`: `report-schema.mdx` `groupBy[].sort: 'asc'` (the report grouping direction enum), and the `$orderby` strings `'createdAt asc'` in `plugin-detail.mdx` and `'createdAt desc'` in `data-objectstack.mdx` (`DataSource.find` query params). A repo-wide search for the union spelling `string | SortConfig` outside `content/` matches only changesets, a CHANGELOG and test comments that record the retirement. ## Gates (run locally at head `810b89da0`) - `pnpm docs:check-links`, exit 0: "Links are valid across 17 scan roots." - `pnpm check:doc-fences`, exit 0: "check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …" - `pnpm check:doc-example-ids`, exit 0: "414 real reference(s) all resolve in the catalog registry." - `pnpm check:doc-types`, exit 0: "Every documented component type is registered." - `node scripts/check-prompt-component-keys.mjs`, exit 0 (the same workflow as `check:doc-types`). - `node scripts/check-doc-expression-carriage.mjs`, exit 0 (Build Docs job step; report-only census). - `pnpm check:doc-example-readers`, exit 0; `pnpm check:spec-symbols`, exit 0: "nothing cites a key its spec symbol does not declare." - `node scripts/check-shell-escape-residue.mjs`, exit 0; `pnpm check:docs-route-closure`, exit 0. - `pnpm check:new-line-citations`, exit 0: "VERDICT new-cross-file-line-citations: 0 new citation(s)". - `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned 8717 tracked text file(s) …)". - `node scripts/check-changeset-presence.mjs`, exit 0: "No source or published contract of a released package changed in this range, so no changeset is owed." `check-changeset-claims`, exit 0 (report-only). - `node scripts/check-governed-queue-guard.mjs --test content/docs/api/schema-reference.md`: "NOT GOVERNED". - Tests that read this file: `node scripts/markdown-test-inputs.mjs --changed` names it, and its ledger names 18 test files (the `content/docs/**` glob plus the two that read this file by name). Those 18 plus the objectui#8221 pin ran under the shared verify lock with `pnpm exec vitest run --maxWorkers=2`: "Test Files 19 passed (19) · Tests 969 passed (969)", `VERDICT command-exit 0`. No test pins this row's text (`Default sort configuration` has no hit outside the row), so no test changed. ## Acceptance notes - No new pin was added, so there is no red-then-green pair to show. The "fails validation" claim rests on the existing objectui#8221 pin named above. - Declared narrowing, left to CI: `check:doc-snippets` / Doc Snippet Types (it needs the packages' built `dist/*.d.ts` across a near-whole-workspace build closure). Its population is fenced ts/tsx blocks, and the edited row sits outside any fence (34 fence delimiters come before it, an even count), so this diff cannot move that gate's verdict. The Build Docs site build (`turbo run build --filter=@object-ui/site`) and the full test shards are also CI's. No workspace package is touched, so no package typecheck or test is owed. - Observation only, not filed: the installed `@objectstack/spec@17.4.0` still declares `ObjectGridPropsSchema.sort` as `z.unknown()`, and it accepts `"name desc"` (measured with `safeParse`). objectstack `main` already declares it as `z.array(SortItemSchema)`, and its describe text refuses the string clause. The protocol face therefore catches up on the next spec release that objectui installs. This row documents the `@object-ui/types` face, which is the one this page covers. Dev run `https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN`, dispatched by the `domain:ui` seat 2 (claim comment 5832093380). --- _Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_ Co-authored-by: Claude <noreply@anthropic.com>
Version 0.2.1 Release - Complete ✅
Summary
Successfully prepared version 0.2.1 release for all Object UI packages using the changeset workflow.
Changes Completed
Packages Released
All 11 main packages at v0.2.1:
VSCode extension at v0.1.1 (dependency update)
Release Notes
This patch release includes infrastructure improvements:
Next Steps
Merge this PR to trigger automated npm publish via GitHub Actions.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.