Repository navigation
feat(core)!: retire the bare-string globalFilters[].options lift (objectui#4356) - #10930
Conversation
…bjectui#4356)
`normalizeFilterOptions` no longer lifts a non-object `options` member to a
`{ value, label }` pair: the member yields no option, a shorthand-only filter
resolves with no `options`, and a mixed array keeps only its object members.
The Phase 1 deprecation warning (PR #4601), whose text promised the lift, goes
with the arm; a dev-mode, once-per-filter warning names the dropped members
and the rewrite instead (ADR-0078 section 4), so `resetDashboardFilterWarnings`
keeps its memo and stays exported.
Rulings: objectstack#7917 「7917 ②」 (2026-08-12) and 「objectstack#7917
不考虑现有数据」 (2026-09-02). The docs callout and the `GlobalFilterSchema`
docblock in @object-ui/types stop describing the lift. Changeset: core minor.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
The at-tier contract review of this head is in progress. |
Contract reviewServed-tier: Inputs: card #4356 (body and all 16 comments, the ① Derived judgmentsRight.
Wrong.
None of W1–W3 is code, a test assertion, a type or a changeset shape; each is a prose edit. They are what stands between this head and PASS. ② Semver level
③ Boundary flags
Check-runs on the head (43 runs at the reading above): 39 Remedy for the FAIL, bounded: (a) rewrite the two README comment lines to the "not accepted / no longer lifted / a bare-string member yields no option / rewrite as Implemented-by: VERDICT: FAIL |
…the README and three test docblocks (objectui#4356) Rework round 1 after contract review 5866198610 on bc4ea97 — prose only, no assertion, type, changeset level or Clause-② moves: - packages/plugin-dashboard/README.md (ships in `files`): the globalFilters example's options comment no longer says a stored bare string "is still lifted at runtime, and warns"; it says not accepted, no longer lifted, a bare member yields no option, a dev-mode warning names it, rewrite X as { value: X, label: X }. - packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts, packages/types/src/__tests__/report-chart-query-spec-parity.test.ts (its header docblock only) and packages/plugin-dashboard/src/__tests__/ DashboardRenderer.filters.test.tsx: docblocks that stated the lift now state the retirement. - packages/core/src/utils/__tests__/dashboard-filters.test.ts: the stale Phase 1 "DEPRECATED and says so" header above the RETIRED one is removed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…he retired mixed-array lift (objectui#4356)
Patch round 2 (W5, claim addendum 5866480991), comment only: the '(e) leaves
plain-string labels and options exactly as authored' case said the bare-string
shorthand "is deprecated and now warns" and that "the mixed-array lift has its
own pin" named `names ONLY the bare members of a MIXED array`, asserting a
lifted `{ value: 'lost', label: 'lost' }`. The shorthand is retired — a bare
member yields no option — and the pin is now `keeps ONLY the object members of
a MIXED array, and names only the dropped ones`. No assertion moves.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…o claude/issue-4356-retire-shorthand-lift Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…nce this PR made false (objectui#4356) W4 (claim addendum 5866346457), authorised by the maintainer's 2026-09-29 ruling 「Allow the appended note (Recommended)」 (card comment 5883360525): a dated note in the PR #10891 form is appended to .changeset/7759-dashboard-header-global-filters-spec.md, whose pending paragraph "The runtime is unchanged too: @object-ui/core still converts a STORED bare-string option into a pair when it reads the document, and logs a deprecation warning" would publish in the same release as this PR's "no longer lifted" entry. Append only: frontmatter byte-identical, 16 insertions, 0 deletions, no existing line edited. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs: card #4356 (body and all 25 comments — the rulings ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged.
Teaching sweep, re-derived read-only on the head — W1–W5 of record
② Semver level
③ Boundary flagsEvery
Check-runs on the head (polled until none was in progress, then re-read right before this record was written): 43 runs — 40 Implemented-by: VERDICT: PASS |
… bare apiMethods-card number (objectui#10803, batch 7) (objectstack-ai#10962) Part of objectstack-ai#10803 Clause-②: no Dispatched implementation of the `domain:ui` seat objectstack-ai#1 claim (comment `5867587761`) on objectui#10803, batch 7, session `https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. Citations only: no sentence's claim moves, and every edited pending changeset's frontmatter is byte-identical. The only runtime text that moves is two console warnings, one in `@object-ui/app-shell` and one in `@object-ui/plugin-detail`, which lose their dead pointer and nothing else (amendment `5860244997`, Q1 = A; `patch` changeset). No test pins any changed text: the literal-anchor sweep below finds no specific anchor, so no test file is edited. This batch carries release `5866922219`'s two lists: - the **30 `objectstack#N` citations that answer 404**, in the card's two classes (pending changeset prose and non-test `packages/*/src`); - the **28 bare `objectstack-ai#3391` lines in 10 files** that mean objectstack's apiMethods whitelist card, which batch 6 fixed at 2 other sites. ## Why `Part of`, not a closing line Both lists read **0** after this batch (**Census**). The brief's rule was a closing line if the card's lists all read 0. They do, but reading the sentences found **10 more lines in the same two classes that cite a dead objectstack number written bare**, three numbers in all (**Acceptance notes** 1). Triage item 3 puts a dead number found later in these classes on this card, so the card is not finished. Whether it carries them as a batch 8 or closes is the seat's call. ## Premise, re-measured on `origin/main` `3b469c8ea` (the branch point) - **Every distinct `objectstack#N` in the two in-scope classes.** 372 numbers (the one objectstack issue URL in these classes names 6227, which is among them). Each was read once with REST `GET /repos/objectstack-ai/objectstack/issues/N`: - 341 answer 200; - 1 answers 301: objectstack#14026, transferred to objectui#10102, which batch 6 re-qualified; - **30 answer 404**, exactly the 30 batch 6 listed. - **The 30, read again.** A second `issues/N` read of each answers 404 (30 of 30), and `pulls/N` answers 404 for all 30. Lit controls in the same run: objectstack#3391, objectstack-ai#3720 and objectstack-ai#3546 answer 200 as issues, and `pulls/13267` answers 200. - **objectstack-ai#3391 and objectstack-ai#3546, both repositories.** - objectui#3391 is the record-header api-action placeholder card, unrelated. - objectstack#3391 is the apiMethods whitelist contract card: "UI 操作按钮与 apiMethods 白名单一致性契约落地". Its body names the effective operation set, `/me/permissions`, the 405 import refusal and export derived from list, which is what each of the 28 sentences says. - objectui#3546 is the missing-i18n-keys card, unrelated. - objectstack#3546 is "detail/form 面的 edit/delete 按钮接入服务端 effective 操作集", the inline-edit gate the two paired lines describe. - **objectstack history.** Read from a full, not shallow, treeless clone of objectstack `main` (`git rev-parse --is-shallow-repository`: false). - Every objectstack sha this PR cites is an ancestor of objectstack `main` (`git merge-base --is-ancestor`, exit 0): the 24 this PR adds to the tree, the 3 its edited sentences already cited (`c459da6bc`, `89448a52b`, `9bd4344e4`), and the 7 this body names besides. - `git rev-parse --short=9` returns the same 9 characters for each. - Control legs in the same clone: the head of the open PR objectstack#20421 (`a22b90fc0`) answers exit 1; the known ancestor `51789064` answers exit 0. - **The one objectui sha.** `7a197e7c5` is an ancestor of the branch point, exit 0. Control legs: the head of PR objectui#10945 answers exit 1, and `5f789538d` answers exit 0. This checkout is not shallow. - **A cross-check, not the method.** objectstack's own sweep of dead tracker citations in its tree (objectstack#19123's landing `66e266c93`, its stages `21ab41041`, `5cf58eb16` and `0d7ed5a37`, and `f415bcf18`) anchored eight of these numbers in its own files. For each of the eight (objectstack-ai#5970, objectstack-ai#6483, objectstack-ai#9934, objectstack-ai#10485, objectstack-ai#11330, objectstack-ai#11846, #12868 and #17147) it chose the same commit this PR cites. - Every edited changeset is pending: it is present in `.changeset/` on `main`. ## Census (the enumeration pin for this batch) The 30 numbers (REF = a commit or tree): ``` git grep -nE 'objectstack#(5970|5976|6038|6124|6281|6331|6450|6483|6515|9933|9934|10354|10485|10695|11330|11507|11513|11658|11703|11753|11846|12009|12868|13117|13670|16126|17147|17762|17987|18012)([^0-9]|$)' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l ``` The bare `objectstack-ai#3391`, with the same pathspec: ``` git grep -nE '(^|[^0-9A-Za-z_#/])objectstack-ai#3391([^0-9]|$)' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l ``` | REF | the 30 | bare `objectstack-ai#3391` | |:--|:--|:--| | `3b469c8ea` (branch point) | **82**: 26 changeset lines in 24 files, 56 src lines in 39 files | **28** in 10 files | | `c292a6400` (this head) | **0** | **0** | | this head merged with `main` `5c94589f0` (`git merge-tree --write-tree`, clean, tree `bba27eece`) | **0** | **0** | | `5c94589f0` (`main` alone) | 82 | 28 | - Lit controls on the same instruments at this head: live objectstack#10856 reads 4 lines (4 at the branch point); `objectstack#3391` reads 32 lines, against 3 at the branch point (28 re-qualified here, and this PR's sweep changeset names it once). - **Out of scope, as it stands** (whole tree at this head, unfiltered): - the 30 numbers: 84 test lines in 47 files, 1 scripts line, 2 lines in 2 `apps/console` files, 2 package READMEs (`auth`, `react`), 11 lines of published `CHANGELOG.md` history in 8 files; 0 `.github`, 0 governed, 0 `content/docs`; - bare `objectstack-ai#3391`: 12 test lines in 9 files and 23 `CHANGELOG.md` lines in 8 files. ## Citation form - **An objectstack commit** is written the way batch 6 wrote its stand-ins: objectstack and the 9-character backticked sha of the commit on objectstack `main` that landed the change the sentence rests on. - **A dead card beside its own dead pull request** collapses to that pull request's squash commit (objectstack-ai#5970 with PR objectstack-ai#6450, objectstack-ai#10485 with PR objectstack-ai#10695). - **A dead number beside the live landing it already names** is dropped: objectstack-ai#11846 beside PR objectstack#12718, #16126 beside PR objectstack#16920, #12868 beside objectstack `c459da6bc`, and objectstack-ai#12009 beside objectstack `89448a52b` (**Special cases** 3). - **A ruling the dead card carried** is cited by its date, with the commit that executed or recorded it, as batches 3 to 6 did for objectui rulings. - **One sentence cites this repository's commit**, `7a197e7c5`, because the change it names landed here (objectstack-ai#6331). - **Runtime text carries no sha.** In the two console warnings the dead pointer is dropped (**Special cases** 8). - **The bare `objectstack-ai#3391`** becomes `objectstack#3391`, and on the two lines that write `objectstack-ai#3391/objectstack-ai#3546`, `objectstack#3391/objectstack#3546`. ## Mapping, the 30 numbers Lines / files are the branch-point census for that number (a line naming two of them counts under both). | dead number | resolution | what that commit carries | lines / files | |:--|:--|:--|:--| | objectstack-ai#5970 | objectstack `97e7e3caa` | "unify ActionSchema.visible/disabled on one condition shape (objectstack-ai#6450)", body "(objectstack-ai#5970)": `visible` gains the boolean arm | 2 / 1 | | objectstack-ai#6450 (PR) | the same `97e7e3caa`, its squash | as above; the card / PR pair collapses | 2 / 1 | | objectstack-ai#5976 (PR) | objectstack `795b6e1aa`, its squash | "5 值子集改名 `HttpMethodSubset`" | 1 / 1 | | objectstack-ai#6038 | objectstack `7618ee814` | "key a container's default `list` `_views` name by the runtime identity": leg 2 of 3 of the objectstack#5164 ruling, the `packages/lint` half | 1 / 1 | | objectstack-ai#6124 (PR) | objectstack `b3c1f3cd5`, its squash | "key `_views` translations by the runtime view identity"; "The extractor now ASKS the composer for the key" | 1 / 1 | | objectstack-ai#6281 (PR) | objectstack `85ec26d28`, its squash, 2026-08-07 | "SDUI props — enforce or remove (objectstack-ai#5775) (objectstack-ai#6281)": the shared `PageContainerProps`, whose single key is `children`, for `page:section` / `page:footer` / `page:sidebar`, which were `EmptyProps` | 1 / 1 | | objectstack-ai#6331 | objectui `7a197e7c5` | this repository's "SchemaForm reads the canonical `visibleWhen`, reviving every metadata-form predicate (objectstack#6331)" | 1 / 1 | | objectstack-ai#6483 | objectstack `ee58392e1` | "ADR-0005 白名单强制 … (objectstack-ai#6483)"; its diff carries the sentence the comment quotes, "Runtime-created sets … ride `allowRuntimeCreate` (still `true`) and keep working" | 1 / 1 | | objectstack-ai#6515 (PR) | objectstack `2fdb36eb9`, its squash | "SpecifierSchema gains a closed `valueDomain` enum": "`bcp47_locale` is deliberately not in the vocabulary", because `localization.locale`'s options ARE the shipped catalogs | 1 / 1 | | objectstack-ai#9933 | objectstack `d5552ca13` | "admit columnState as an explicitly runtime-only view-overlay key" (subject ending "(objectstack-ai#9996)"; "(objectstack-ai#9933)" is on the message's first body line), on the overlay faces including `viewItemWireFields` | 3 / 3 | | objectstack-ai#9934 | objectstack `79c46da90` | "producer-side user-facing marking for hook refusal messages — userMessage channel (objectstack-ai#9934)": `ApiErrorSchema.userMessage`, the contract half of the objectui#5210 split | 10 / 9 | | objectstack-ai#10354 (PR) | objectstack `9e04c3e35`, its squash | "let the publish door state the package it is promoting"; its changeset and code comment carry the key-presence / `no_draft` warning `ResourceEditPage.tsx` points at | 4 / 3 | | objectstack-ai#10485 | objectstack `35ad101bc` | "retire the `themes` carrier key and ThemeSchema (objectstack-ai#10485, ADR-0049) (objectstack-ai#10695)": "Ruled B (退役授权面, 2026-08-21)", "delete ui/theme.zod.ts whole" | 15 / 13 | | objectstack-ai#10695 (PR) | the same `35ad101bc`, its squash | as above; the card / PR pair collapses | 6 / 6 | | objectstack-ai#11330 | objectstack `a9ee98992` | "manifest.runtime trust-tier text states publish-gate-only enforcement truthfully", the trust-tier half (**Special cases** 2) | 1 / 1 | | objectstack-ai#11507 | objectstack `88b9d749a` | "declare sys_activity.type as an open, author-extensible vocabulary": "Maintainer ruling 2026-08-24, direction 4" | 13 / 9 | | objectstack-ai#11513 | objectstack `e170b0ae5` | "lock package-declared permission sets at the save door; clone to customize", quoting the 2026-08-24 ruling 「同意 第一步(创业阶段,Salesforce 式)」 | 3 / 3 | | objectstack-ai#11658 | objectstack `1a6a19c31` | "open RecordActivityProps.types to author-contributed activity kinds"; its message names objectstack-ai#11658 as the card it settles, and it executes the 2026-08-24 ruling | 1 / 1 | | objectstack-ai#11703 | objectstack `5cb62d88b` | "make clone_permission_set carry all five copied facets"; its message names objectstack-ai#11703 as the card it settles: the silent-grant-loss shape | 1 / 1 | | objectstack-ai#11753 | "the 2026-08-25 ruling whose spec half is objectstack `0e4e51b0a`" | `ActionParamSchema.carryOver`, whose changeset reads "(objectstack-ai#11753 ruling, spec half; objectstack-ai#11992)" and "The maintainer's 2026-08-25 ruling on objectstack-ai#11753" | 2 / 2 | | objectstack-ai#11846 | objectstack `0c2334f6c`; dropped beside PR objectstack#12718 | "retire preview mode — the RuntimeMode 'preview' value and the whole PreviewModeConfig block (#12718)" | 3 / 3 | | objectstack-ai#12009 | dropped beside objectstack `89448a52b` | the card of the `AUTH_SSO_PROVIDER_SCHEMA` removal, whose landing the line already cites (**Special cases** 3) | 1 / 1 | | #12868 | dropped beside objectstack `c459da6bc` | the line already cites the commit that executed the ruling; objectstack's own `f415bcf18` anchors #12868 to the same `c459da6bc` | 1 / 1 | | #13117 (PR) | objectstack `225e7690f`, its squash | "Readiness read for the Phase-2 members … global:search and global:notifications both have shipped platform data sources, so per the ruling both STAY declared" | 1 / 1 | | #13670 | "maintainer ruling 2026-08-31, option 2, recorded in objectstack `8c6a7fc0b`" | "The #13670 ruling settled the question: text's intended evaluation channel is `content` alone" | 1 / 1 | | #16126 | dropped beside PR objectstack#16920 | PR objectstack#16920 (200) names #16126 in its body as the card it settles; merged 2026-09-08 as `859ded3ec` | 2 / 2 | | #17147 | objectstack `aaacf1d5c` | "the install-time granted permission set is REGISTERED at load and refuses nothing — say so, and pin the measurement (#17147)", the measurement on `9bd4344e4` | 2 / 2 | | #17762 | objectstack `4342c9923` | "guard three data lookups against Object.prototype fall-through"; its message names #17762 as a card it settles, `classifyFilterToken` among the three lookups | 1 / 1 | | #17987 | objectstack `e233db9db` | "declare element-level `navigation` on object-kanban / object-calendar …"; its message names #17987 as the card it settles, and its Downstream note: objectui#8652 waits on it, unlock criterion a released, installable `@objectstack/spec` (**Special cases** 1) | 2 / 2 | | #18012 | objectstack `176b03582` | "`$between` requires two non-blank endpoints (#18012)": "Ruling executed: decision batch objectstack-ai#146 item 5, **letter A**" | 3 / 3 | The 28 `objectstack-ai#3391` lines, all now `objectstack#3391`: `ObjectDataPage.tsx` (3) and `ObjectView.tsx` (2) in app-shell; `managedBy.ts` (5); `MePermissionsProvider.tsx` (2), `PermissionContext.ts`, `PermissionProvider.tsx`; `fieldWriteGate.ts`; `ImportWizard.tsx` (6), `ObjectGrid.tsx` (4); `ListView.tsx` (3). Each was read: every one names the server's effective API operation set, `/me/permissions` `apiOperations`, or the 405 import refusal. ## Special cases (the judgement calls) 1. **#17987, two sentences.** - `ObjectTree.tsx`: "blocked on objectstack#17987, whose unlock criterion is a released `@objectstack/spec` carrying the declaration being installable here" becomes "blocked on objectstack `e233db9db`, whose unlock criterion …". That commit's Downstream note states the same criterion. - `ObjectCalendar.tsx`: "that card is `pm:blocked` on objectstack#17987" becomes "that card waits on objectstack `e233db9db`". The label word is not kept, because objectui#8652's label reads `pm:on-hold` today (measured); "waits on" is the phrase `e233db9db`'s own note uses for that card. 2. **objectstack-ai#11330.** "it is objectstack#11330's half of the same panel" becomes "it is the trust-tier half of the same panel, which objectstack `a9ee98992` settled separately". `aaacf1d5c`'s message calls objectstack-ai#11330 "the sibling half of this very sentence", ruled the same way on 2026-08-30, and `a9ee98992` (2026-08-30) is that half's landing. 3. **objectstack-ai#12009 collapses into the sha beside it.** objectui#6910's body and ruling comment `5534414562` name "objectstack#12009 / PR #13413" together as the one `AUTH_SSO_PROVIDER_SCHEMA` precedent, a card and its pull request. Batch 6 replaced PR #13413 with its squash `89448a52b`, so the card goes the way of batch 3's objectstack-ai#5401 / objectstack-ai#5505 pair. 4. **objectstack-ai#11753, two sites.** The card carried the ruling, and `0e4e51b0a` is its spec half. Both sites keep "ruling" as the antecedent that `ActionParamDialog.tsx`'s next paragraph ("The ruling's point …") reads. 5. **objectstack-ai#10354 in `ResourceEditPage.tsx`.** "since objectstack#10354 `doPublish` states" gains a comma, "since objectstack `9e04c3e35`, `doPublish` states", so two adjacent code spans do not read as one. 6. **objectstack-ai#11507 in the 8137 changeset.** "objectstack#11658 executing the maintainer's 2026-08-24 ruling on objectstack#11507" becomes "objectstack `1a6a19c31` executing the maintainer's 2026-08-24 ruling": the executing commit is named, and the ruling is cited by its date. 7. **Line breaks moved** where the stand-in is longer or shorter: `ActionRunner.ts` (two sites), `ActionParamDialog.tsx`, `theme.ts`, `theme.zod.ts` (two sites), `index.zod.ts` and the metadata-admin `i18n.ts` comment, where "ruling on" became "ruling of 2026-08-24,". 8. **The runtime strings.** Only the listed text moves. | file | member | before | after | |:--|:--|:--|:--| | `app-shell/src/layout/activityItemType.ts` | the `console.warn` in `warnUnmappedActivityType` | "… `sys_activity.type` is author-extensible (objectstack#11507, ruled 2026-08-24) and is not validated on write …" | "… `sys_activity.type` is author-extensible (ruled 2026-08-24) and is not validated on write …" | | `plugin-detail/src/renderers/recordActivityFeed.ts` | the `warnOnce` message in `warnUnknownActivityType` | "… `sys_activity.type` is author-extensible (objectstack#11507, ruled 2026-08-24) and is not validated on write …" | "… `sys_activity.type` is author-extensible (ruled 2026-08-24) and is not validated on write …" | No test, doc or changeset quotes either message with the pointer: the census reads 0 in `.changeset/`, and the anchor sweep finds no test literal that drops. 9. **`objectstack-ai#3391/objectstack-ai#3546`.** On the two lines that pair them (`managedBy.ts`, `ObjectGrid.tsx`), both halves are qualified, as batch 6 qualified both halves of "#13337/#13086". The other bare `objectstack-ai#3546` lines are not in this batch's lists and are left (**Acceptance notes** 2). ## The literal-anchor sweep (both test-pin classes, ruling `5861900779`) - **Instrument.** Every string, template and regex literal in all 4073 tracked test and script files (106544 distinct literals), read with the TypeScript scanner. - **Candidate filter.** A literal is a candidate if it matches the diff's removed lines with two lines of context, raw or comment-flattened: 1983. - **Test.** Does its occurrence count DROP between `3b469c8ea` and `c292a6400` in any of the 74 changed files, raw or comment-flattened? 136 do. - **Every one is generic:** digits, punctuation, single words ("object", "blocked", "locked"), character classes, and two regexes that read no changed file: `/objectui#\d+|objectstack#\d+/` in `registry-inputs-spec-parity`, which asserts over its own ledger's reasons, and the older spelling of the three submitRedirect tests' ruling matcher, quoted in their own doc comments (the live `CITES_ITS_RULING` asserts over their own refusal text). None is a changed phrase, a dead number or a changed warning. ## Held **By the serial rule: nothing.** Open PRs were mapped at branch time (9 open) and again after the push, before this PR opened (11 open). The second mapping came after the push, not before it; the same three files were shared both times. Three open PRs share a file with this PR: - _Both PRs below have merged since this PR opened (objectui#10945 as `06a96e948`, objectui#10908 as `b45d463a9`). The trial merge with today's `main` is clean, and both censuses read 0 on it (contract review `5870922323`), so nothing is owed. The two rows are kept as the record at the time._ - **objectui#10945, `RecordDetailView.tsx`.** The blob at its merge-base equals the branch point's. Its hunks are the imports and one block far below; this PR's one changed line in that file is far from both. - **objectui#10908, `types/src/zod/index.zod.ts`.** Its one insertion is in the export list, far below this PR's two changed comment lines. - **objectui#10278, `plugin-grid/src/ObjectGrid.tsx`.** The file drifted between its merge-base and the branch point, so this PR's four changed lines were mapped onto its merge-base by a line alignment: the nearest of its hunks is more than 150 lines from any of them. Trial merges with this head (`git merge-tree --write-tree`): - clean for objectui#10952, objectstack-ai#10950, objectstack-ai#10949, objectstack-ai#10947, objectstack-ai#10945, objectstack-ai#10944, objectstack-ai#10930, objectstack-ai#10908 and objectstack-ai#10777; - objectui#10278 conflicts in `ObjectGrid.tsx`, `plugin-grid/README.md` and `content/docs/plugins/plugin-grid.mdx`, and conflicts in the same three files against `main` alone; - objectui#5400 (Version Packages) regenerates and is not a hold. `.changeset/9954-read-rate-banner.md` is held by this seat's objectui#10913 dispatch (PR objectui#10949) and is untouched here. It carries none of this batch's numbers. ## Changesets - `.changeset/10803-dead-citation-sweep-seventh-batch.md`, EMPTY frontmatter. It covers the comment-only edits in 17 released packages; no published behaviour changes through them. It points at the second file for the runtime text. - `.changeset/10803-seventh-batch-runtime-strings.md`, `'@object-ui/app-shell': patch` and `'@object-ui/plugin-detail': patch`: the two warnings lose their pointer. What renders, and when and how often each warning fires, are unchanged. ## Proof of prose-only (C4), against `3b469c8ea` - **Source.** Each of the 48 touched `.ts` / `.tsx` files was parsed at `3b469c8ea` and at this head with TypeScript 6.0.3's `createSourceFile`, and re-printed by `createPrinter({ removeComments: true })`. - 46 of 48 prints are identical. - `activityItemType.ts` and `recordActivityFeed.ts` are equal once the one listed substitution each (**Special cases** 8) is applied to the base print, each matched once. - 0 parse diagnostics. - Lit controls on the same instrument: editing a string literal moves the print; re-spacing a comment does not. - **Changesets.** The frontmatter block of every one of the 24 edited changesets is byte-identical at `3b469c8ea` and this head (24 of 24, md5). The overwrite gate below agrees. - **Scope of the diff:** 74 files, +157 / −115: 24 edited and 2 new changesets, and 48 non-test source files in 17 released packages. No test file. ## Gates, on this head `c292a6400` Each line is the gate's own verdict and exit code, captured by redirect-then-`$?`. - `node scripts/check-changeset-presence.mjs`, exit 0: "48 source file(s) of 17 released package(s) changed, and this change declares 2 changeset(s): .changeset/10803-dead-citation-sweep-seventh-batch.md, .changeset/10803-seventh-batch-runtime-strings.md." - `pnpm changeset:check`, exit 0: "All workspace packages are in the changeset fixed group." / "No changeset declares a `major` bump." - `node scripts/check-changeset-overwrite.mjs` (report-only), exit 0: "2 changeset(s) added, 24 modified, 0 deleted". `declared at base` equals `declares now` for each of the 24. - `pnpm check:changeset-claims` (report-only), exit 0: - "Every one of those 1 address(es) either names the tree it was read from, or points at a line this change does not move"; - "Every package declared across those 22 body(ies) is either not negated …"; - the standing notice "87 pending changeset(s) describe a file this change touches". Read against the diff: a pending changeset quoting a replaced pointer would itself carry a dead number and sit in the census, which reads 0. - `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned 9229 tracked text file(s); skipped 85 binary)." A `grep -P` control-byte self-scan of the 74 files finds none. - `pnpm check:new-line-citations`, exit 0: "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0". - `pnpm check:pending-changeset-literals`, exit 0: "No test source names a pending changeset." - Also run over the touched comments: - `pnpm check:spec-symbols`, exit 0: "spec member citations: 1421 sources + 184 documentation pages; nothing cites a key its spec symbol does not declare."; - `pnpm check:installed-pin-claims`, exit 0 ("OK"); - `pnpm check:comment-mask-corpus`, exit 0 (1 disagreeing file, within the ceiling objectui#7882 holds open); - `node scripts/check-hand-rolled-comment-mask.mjs`, exit 0 ("OK every carrier is a DEBT entry, and every DEBT entry still carries one."); - `pnpm check:handler-key-reads`, exit 0 ("every judged read is a declared member of it"). - The governed-surface predicate over the 74 paths, exit 0: "NOT GOVERNED — 74 path(s) checked against 5 governed surface(s); none matched." Lit control `AGENTS.md`: exit 3. **Tests and type-check**, through the shared verify lock, on `c292a6400`. Each is `VERDICT command-exit 0`. - `scripts/__tests__/`, the whole directory, whose whole-tree scanners read the touched files and changesets: `Test Files 177 passed | 2 skipped (179)`, `Tests 5332 passed | 2 skipped (5334)`. The two skipped files are the network-escape fixtures that run only as a child. - `packages/types/`, `core/`, `react/`, `i18n/`, `providers/`, `permissions/` and `data-objectstack/`, whole packages, in one run: `Test Files 704 passed (704)`, `Tests 13170 passed | 13 skipped (13183)`. - The eight touched plugin packages (calendar, designer, detail, form, grid, kanban, list, tree): `Test Files 787 passed | 1 skipped (788)`, `Tests 7521 passed | 27 skipped (7548)`. - `packages/components/`: `Test Files 324 passed | 1 skipped (325)`, `Tests 3148 passed | 24 skipped (3172)`. - `packages/app-shell/`: `Test Files 854 passed | 1 skipped (855)`, `Tests 8789 passed | 9 skipped (8798)`. - Type-check: `turbo run build` of the 28-package dependency closure (`Tasks: 28 successful, 28 total`), then `pnpm --workspace-concurrency=2` with the 17 package filters `run type-check`: 17 script echoes, 17 `Done`. A first attempt before the build exited 2 on an unbuilt dependency (`Cannot find module '@object-ui/types'`) and measured nothing. - No red leg: the sweep found no anchor to move, so there is no pin whose old copy should fail. CI on `c292a6400`: 43 check-runs, 40 success, 3 skipped, 0 failed; `Spec Main Shape Gate` success. ## Acceptance notes 1. **Dead objectstack numbers written bare: 10 more lines in the same two classes.** A bare number resolves to this repository, where each of these is a live, unrelated card, so no `objectstack#` census sees them. - **Measurement.** The bare-number instrument of PRs objectui#10875 / objectstack-ai#10892 / objectstack-ai#10914 reads 965 distinct numbers at this head. The 916 between 100 and 25000 were each read once as objectstack issues: 877 answer 200 and 39 answer 404. Reading the sentences of those 39, three mean an objectstack card or pull request (below); the other 36 cite objectui cards or objectui pull requests. - **objectstack-ai#9934**, 7 lines in 7 files: `.changeset/7980-agent-key-envelope-read.md`, and in app-shell `index.ts`, `apiErrorEnvelope.ts` (2), `PackageFormDialog.tsx`, `StudioDesignSurface.tsx` and `packages-io.ts`. All mean the `userMessage` channel, objectstack `79c46da90`. - **"PR objectstack-ai#6281"**, 2 lines in `containers.tsx`, beside objectstack#5775. The landing is objectstack `85ec26d28`. - **"objectstack PR objectstack-ai#8452"**, 1 line in `useRecordCrudVerdicts.ts`. The landing is objectstack `27358d517` ("add batch recordIds to security/explain (objectstack-ai#8326) (objectstack-ai#8452)"). - None of them sits in a sentence this PR edits, so they are outside this batch's lists and left. Carrier: this card, triage item 3. 2. **The rest of the bare `objectstack-ai#3546` population.** Five more comment lines write objectstack#3546 as a bare `objectstack-ai#3546` (`RecordDetailView.tsx` 2, `RelatedRecordActionsBridge.tsx`, `record-details.tsx`, `fieldWriteGate.ts`), and one writes it as `objectui#3546` (`plugin-detail`'s `index.tsx`). Each names the server's effective API operation set on a detail or form surface. It is live-but-wrong, not a 404, like the `objectstack-ai#3391` class this batch closed. Carrier: none. 3. **A stale label in a comment.** The `ObjectCalendar.tsx` sentence said objectui#8652 is `pm:blocked`; that card reads `pm:on-hold` today. **Special cases** 1 says how the repaired sentence avoids the label. 4. **Filenames are not citations.** `.changeset/17147-plugin-disclosure-not-enforced.md` carries one of the 30 numbers in its name; it stays, as in PRs objectui#10707, objectstack-ai#10797, objectstack-ai#10854, objectstack-ai#10869, objectstack-ai#10875, objectstack-ai#10892 and objectstack-ai#10914. --- _Generated by [Claude Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_ _Tests block completed by the `domain:ui` seat objectstack-ai#1 from the dev report `5870507620` (the suites that finished after this PR opened), and three figures corrected after contract review `5870922323` (the objectstack-ai#9933 and objectstack-ai#6515 quotes, and the Held rows); no code claim moved._ Co-authored-by: Claude <noreply@anthropic.com>
…re-qualify objectstack#3546 (objectui#10803, batch 8) (objectstack-ai#11012) Fixes objectstack-ai#10803 Clause-②: no Dispatched implementation of the `domain:ui` seat objectstack-ai#1 claim (comment `5873413663`) on objectui#10803, batch 8, session `https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. Citations only: no sentence's claim moves, the one edited pending changeset keeps a byte-identical frontmatter, and no runtime string moves, so the only new changeset is an empty-frontmatter sweep. No test pins any changed text (**The literal-anchor sweep**), so no test file is edited. This batch carries release `5872268200`'s two lists (the seat's ruling A in ACCEPT `5870953708`, which review `5870922323` read the same way): - the **10 in-class lines that cite a dead objectstack number written bare**: `objectstack-ai#9934` seven times, "PR `objectstack-ai#6281`" twice, "objectstack PR `objectstack-ai#8452`" once; - the **6 live-but-wrong `objectstack-ai#3546` lines** that mean objectstack#3546: five written bare, one written with this repository's name. Numbers are written in code spans in this body when they are the dead or wrong spelling, so GitHub does not link them to the unrelated objectui items they would resolve to. ## Why `Fixes` Both lists read **0** after this batch (**Census**), and the closure census over the card's two classes (pending `.changeset/*.md` prose and non-test `packages/*/src`) finds nothing else dead: - **Bare numbers**, batch 7's instrument (printed under **Census**): 965 distinct at the branch point, 961 at this head. The 916 between 100 and 25000 at the branch point were each read once as objectstack issues: 877 answer 200 and 39 answer 404. The 39 were read again as objectstack issues and as pulls (404 each) and as objectui issues (200 each). Three of them are this batch's (`objectstack-ai#9934`, `objectstack-ai#6281`, `objectstack-ai#8452`), gone at this head. The other 36 remain, and each sentence was read: every one cites an objectui card or pull request, and each answers 200 in objectui with the subject its sentence names. - **The instrument's blind spot** (a number written after `/`, `-` or `.`, as in `objectui#9469/objectstack-ai#8649`): 46 numbers between 100 and 25000 appear only in that form at this head. Read as objectstack issues: 45 answer 200; one answers 404, `objectstack-ai#8649`, which sits in two lines as `objectui#9469/objectstack-ai#8649` and means objectui#8649 (200). - **Qualified citations** at this head: every distinct `objectui#N` and objectui issue or pull URL (2723 numbers) answers 200; every distinct `objectstack#N` and objectstack issue or pull URL (345 numbers) answers 200, except objectstack#14026, which answers 301 (transferred to objectui#10102, re-qualified by batch 6). So nothing dead is left in the card's classes, and nothing in them resolves to the wrong repository as far as these instruments see. **Acceptance notes** 1 and 2 record what is outside the classes, and what could not be measured. ## Premise, re-measured on `origin/main` `665025908` (the branch point) - **The three dead numbers.** objectstack `issues/N` and `pulls/N` answer 404 for 9934, 6281 and 8452 (the in-range census read each as an issue a second time). Lit controls in the same run: objectstack issues 3546, 3391, 5775 and 8326 answer 200; objectstack `pulls/13267` and `pulls/16920` answer 200. - **What they resolve to here.** objectui `objectstack-ai#9934` is a CI finding about the `needs:contract-review` label; objectui PR `objectstack-ai#6281` is an app-shell Studio grid change; objectui `objectstack-ai#8452` is a `not_contains` operator bug. None is what its sentence meant. - **`objectstack-ai#3546`, both repositories.** objectstack#3546 (200) is "detail/form 面的 edit/delete 按钮接入服务端 effective 操作集", the effective operation set on detail and form surfaces, which each of the six sentences describes. objectui `objectstack-ai#3546` (200) is the missing-i18n-keys card, unrelated. The 96 `objectui#3546` lines in the locale packs and the "slice seven" line in `.changeset/3880-es-done-listo.md` are that i18n card, and are untouched. - **The landing commits**, each read over REST (`commits/SHA`) and checked with `compare/SHA...main`, which answers `behind_by 0` for each, i.e. each is an ancestor of objectstack `main`: - `79c46da90`, "feat(contract): producer-side user-facing marking for hook refusal messages — userMessage channel (objectstack-ai#9934) (objectstack-ai#9992)", 2026-08-19: `ApiErrorSchema.userMessage`, and the dispatcher door (`packages/runtime/src/http-dispatcher.ts`) emits it as a declared sibling of `code` and `message`; - `85ec26d28`, "spec: SDUI props — enforce or remove (objectstack-ai#5775) (objectstack-ai#6281)", 2026-08-07: retires `page:card.body` in favour of `children`, with an ADR-0087 D2 conversion; - `27358d517`, "feat(spec,rest): add batch recordIds to security/explain (objectstack-ai#8326) (objectstack-ai#8452)", 2026-08-13. - These are the landings release `5872268200` names; review `5870922323` read all three against their messages. Batch 7 already cites `79c46da90` and `85ec26d28` at other sites in these classes. - The one edited changeset, `.changeset/7980-agent-key-envelope-read.md`, is pending: it is present in `.changeset/` on `main`. This checkout is not shallow. ## Census (the enumeration pin for this batch) The two lists (REF = a commit or tree): ``` git grep -nP '(?:^|(?<=[^\w#&/.\-]))#(9934|6281|8452|3546)(?![0-9A-Za-z_])' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l git grep -n 'objectui#3546' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' ':!packages/i18n/src/locales/**' | wc -l ``` | REF | bare four | `objectui#3546` outside the locale packs | |:--|:--|:--| | `665025908` (branch point) | **15**: `objectstack-ai#9934` 7, `objectstack-ai#6281` 2, `objectstack-ai#8452` 1, `objectstack-ai#3546` 5 | **2**: the `plugin-detail` line and the i18n card's own line in `3880-es-done-listo.md` | | `8fe632d96` (this head) | **0** | **1**: the `3880-es-done-listo.md` line, which is the i18n card and stays | - Lit controls at this head: `objectstack#3546` reads 10 lines against 3 at the branch point (the 6 re-qualified here, and one in this PR's sweep changeset); the 96 `objectui#3546` lines in the locale packs are unchanged. - The bare-number instrument, batch 7's (the census of **Why `Fixes`**): ``` git grep -hoP '(?:^|(?<=[^\w#&/.\-]))#\d+(?![0-9A-Za-z_])' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | sort -u | wc -l ``` 965 at the branch point, 961 at this head: exactly `objectstack-ai#3546`, `objectstack-ai#6281`, `objectstack-ai#8452` and `objectstack-ai#9934` are gone, and nothing is new. The blind-spot reading replaces the lookbehind class with `[^\w#&]`. ## Citation form - **An objectstack commit** is written as batches 6 and 7 wrote their stand-ins: objectstack and the 9-character backticked sha of the commit on objectstack `main` that landed the change the sentence rests on. - **A bare `objectstack-ai#3546`** becomes `objectstack#3546`, including inside the `[objectstack-ai#3546]` tags that open four comments; the one written with this repository's name is re-qualified the same way. ## Mapping | site | before | after | |:--|:--|:--| | `.changeset/7980-agent-key-envelope-read.md` | "(the `objectstack-ai#9934` channel, whose presence *is* the marking)" | "(the objectstack `79c46da90` channel, …)" | | app-shell `index.ts`, the note above the `readEnvelopeFailureText` export | "(the `objectstack-ai#9934` channel, which rides through the 5xx prose withhold untouched)" | "(the objectstack `79c46da90` channel, …)" | | app-shell `utils/apiErrorEnvelope.ts`, the `userMessage` bullet | "the END USER (`objectstack-ai#9934`)" | "the END USER (objectstack `79c46da90`)" | | app-shell `utils/apiErrorEnvelope.ts`, the `errorFromThrown` bullet | "… has emitted the channel since `objectstack-ai#9934`" (a quotation) | "… has emitted the channel since [objectstack `79c46da90`]" (**Special cases** 1) | | app-shell `PackageFormDialog.tsx`, `readEnvelopeUserMessage`'s docblock | "(`error.userMessage`, `objectstack-ai#9934`)" | "(`error.userMessage`, objectstack `79c46da90`)" | | app-shell `StudioDesignSurface.tsx`, the package-switcher failure read | "the producer's `objectstack-ai#9934` channel" | "the producer's channel (objectstack `79c46da90`)" | | app-shell `packages-io.ts`, `duplicatePackage`'s failure arm | "has emitted since `objectstack-ai#9934`" | "has emitted since objectstack `79c46da90`" | | components `containers.tsx`, the `PageCardRenderer` `body` read | "objectstack#5775 (PR `objectstack-ai#6281`, ADR-0087 D2)" | "objectstack#5775 (objectstack `85ec26d28`, ADR-0087 D2)" | | components `containers.tsx`, the `card` registration's `children` input | "objectstack#5775 (PR `objectstack-ai#6281`)" | "objectstack#5775 (objectstack `85ec26d28`)" | | plugin-grid `hooks/useRecordCrudVerdicts.ts` | "shipped in objectstack PR `objectstack-ai#8452`" | "shipped in objectstack `27358d517`" | | app-shell `RecordDetailView.tsx`, two comments | "[`objectstack-ai#3546`] …" | "[objectstack#3546] …" | | app-shell `RelatedRecordActionsBridge.tsx` | "[`objectstack-ai#3546`] Intersect the child object's …" | "[objectstack#3546] …" | | plugin-detail `renderers/record-details.tsx` | "[`objectstack-ai#3546`] Also AND inline-editability …" | "[objectstack#3546] …" | | plugin-form `fieldWriteGate.ts` | "`objectstack-ai#3546` intersects that with the server's effective API operation set" | "objectstack#3546 intersects …" | | plugin-detail `index.tsx`, the `inlineEdit` input comment | "(`objectui#3546`)" | "(objectstack#3546)" | Each `objectstack-ai#9934` sentence is about the `userMessage` channel that `79c46da90` added; each `objectstack-ai#6281` sentence is about the `page:card` `body` retirement that `85ec26d28` carries; the `objectstack-ai#8452` sentence is about the batch `recordIds` form that `27358d517` shipped. ## Special cases (the judgement calls) 1. **A quotation.** The `apiErrorEnvelope.ts` bullet quotes the note beside `sendThrownError` in objectstack's `packages/rest/src/package-routes.ts`, verbatim. That note still reads "since objectstack-ai#9934" on objectstack `main` (`e956924` at the time of this PR); there the number means objectstack's card, which answers 404, and here it resolves to the unrelated objectui item. The quote keeps its words and takes the commit in square brackets, the editorial mark for a substituted word. The note's claim is the one `79c46da90` carries: its `http-dispatcher.ts` hunk adds the `userMessage` spread to the dispatcher door's `extra`. 2. **Line breaks moved** only where the stand-in made a line markedly longer: app-shell `index.ts`, `StudioDesignSurface.tsx` and `packages-io.ts`. Elsewhere the one line just grows, as batch 7's `objectstack#3391/objectstack#3546` lines did. 3. **Wording kept.** "the `objectstack-ai#9934` channel" becomes "the objectstack `79c46da90` channel" in two places, and "the producer's channel (objectstack `79c46da90`)" in `StudioDesignSurface.tsx`, where "the producer's objectstack … channel" would read as if the producer were objectstack. No other word moves. ## The literal-anchor sweep (both test-pin classes, ruling `5861900779`) - **Instrument.** Every string, template and regex literal in the 4108 tracked test and script files (104609 distinct strings, 2339 regexes), read with the TypeScript scanner. - **Test.** Does its occurrence count DROP between `665025908` and this head in any of the 14 changed files, raw or comment-flattened? - **Result.** 30 distinct literals drop, every one generic: digits, punctuation, whitespace and character classes (`#\d+`, `\d{3}`, `[:#]`, `\s+`), the bare words `objectui` and `objectui#`, `/objectui#\d+/`, and comma-and-line-break fragments. None is a changed phrase, a dead number or a `objectstack-ai#3546` tag. A search of tests for the changed spellings finds them only in test doc comments (listed in **Acceptance notes** 1), which no assertion reads. ## Held **By the serial rule: nothing.** Open PRs were mapped at the branch point before the first edit (7 open) and again before the push (7 open; objectui#11003 and objectstack-ai#10997 have since merged, and objectui#11010 and objectstack-ai#11006 have opened). - **objectui#10990** and **objectui#11010** share `packages/app-shell/src/index.ts`. objectui#10990's hunk is in the side-effect import list, near the Cloud widgets; objectui#11010's is one line in the `views` type export list. This PR's two changed lines are in the note above the `readEnvelopeFailureText` export, more than ninety lines above both hunks. - **objectui#5400** (Version Packages) carries `.changeset/7980-agent-key-envelope-read.md`, which it consumes at release; it regenerates and is not a hold. If it lands first, that file's one changed line has already published, and this PR's hunk there conflicts with the deletion. - Trial merges with this head (`git merge-tree --write-tree`): clean for objectui#11010, objectstack-ai#11006, objectstack-ai#10990, objectstack-ai#10930 and objectstack-ai#10777. objectui#10278 conflicts in `ObjectGrid.tsx`, `plugin-grid/README.md` and `content/docs/plugins/plugin-grid.mdx`, and conflicts in the same three files against the branch point alone; none is in this diff. ## Changesets - `.changeset/10803-dead-citation-sweep-eighth-batch.md`, EMPTY frontmatter. It covers the comment-only edits in 5 released packages (`app-shell`, `components`, `plugin-detail`, `plugin-form`, `plugin-grid`) and the one edited pending changeset; no published behaviour changes through them. It writes no bare number itself. - No runtime string moves, so there is no `patch` changeset (amendment `5860244997` Q1 = A applies to none of these lines: the `plugin-detail` `index.tsx` line is a comment beside the `inlineEdit` input, not its `description` string). ## Proof of prose-only (C4), against `665025908` - **Source.** Each of the 12 touched `.ts` / `.tsx` files was parsed at `665025908` and at this head with TypeScript 6.0.3's `createSourceFile`, and re-printed by `createPrinter({ removeComments: true })`: 12 of 12 prints are identical, with 0 parse diagnostics. Lit controls on the same instrument: editing a string literal moves the print; re-spacing a comment does not. - **Changesets.** The frontmatter block of `.changeset/7980-agent-key-envelope-read.md` is byte-identical at `665025908` and this head (md5); the overwrite gate below agrees. - **Scope of the diff:** 14 files, +39 / −20: 1 edited and 1 new changeset, and 12 non-test source files in 5 released packages. No test file. ## Gates, on this head `8fe632d96` Each line is the gate's own verdict and exit code, captured by redirect-then-`$?`. - `node scripts/check-changeset-presence.mjs`, exit 0: "12 source file(s) of 5 released package(s) changed, and this change declares 1 changeset(s): .changeset/10803-dead-citation-sweep-eighth-batch.md." - `pnpm changeset:check`, exit 0: "All workspace packages are in the changeset fixed group." / "No changeset declares a `major` bump." - `node scripts/check-changeset-overwrite.mjs` (report-only), exit 0: "1 changeset(s) added, 1 modified, 0 deleted"; for the modified one, `declared at base` equals `declares now` ("@object-ui/app-shell: minor, @object-ui/console: patch"). - `pnpm check:changeset-claims` (report-only), exit 0: "Every package declared across those 1 body(ies) is either not negated …", and the standing notice "15 pending changeset(s) describe a file this change touches". Read against the diff: a pending changeset quoting a replaced spelling would carry a bare number the census sees, and the census reads 0 in `.changeset/`. - `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned 9291 tracked text file(s); skipped 85 binary)." A `grep -P` control-byte self-scan of the 14 files finds none. - `pnpm check:new-line-citations`, exit 0: "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0". - `pnpm check:pending-changeset-literals`, exit 0: "No test source names a pending changeset." - Also run over the touched comments, on the tree this head carries: `pnpm check:spec-symbols` exit 0 ("spec member citations: 1424 sources + 184 documentation pages; nothing cites a key its spec symbol does not declare."); `pnpm check:installed-pin-claims` exit 0 ("OK"); `pnpm check:comment-mask-corpus` exit 0 (1 disagreeing file, within the ceiling objectui#7882 holds open); `node scripts/check-hand-rolled-comment-mask.mjs` exit 0 ("OK every carrier is a DEBT entry, and every DEBT entry still carries one."); `pnpm check:handler-key-reads` exit 0 ("every judged read is a declared member of its arm"). - The governed-surface predicate over the 14 paths, exit 0: "NOT GOVERNED — 14 path(s) checked against 5 governed surface(s); none matched." Lit control `AGENTS.md`: exit 3. **Tests and type-check**, through the shared verify lock, on `8fe632d96`. - `scripts/__tests__/`, the whole directory, whose whole-tree scanners read the touched files and changesets: `Test Files 177 passed | 2 skipped (179)`, `Tests 5350 passed | 2 skipped (5352)`, `VERDICT command-exit 0`. - `packages/components/`, `plugin-detail/`, `plugin-form/` and `plugin-grid/`, whole packages, in one run: `Test Files 847 passed | 2 skipped (849)`, `Tests 8441 passed | 33 skipped (8474)`, `VERDICT command-exit 0`; the source files were byte-identical to this head's. - `packages/app-shell/`, whole package: `Test Files 1 failed | 863 passed | 1 skipped (865)`, `Tests 1 failed | 8835 passed | 9 skipped (8845)`. The one failure is `AppContent.inaccessibleAppStrand.test.tsx`, 'bounces to the DECLARED landing where the deployment declares one', a network-escape guard trip (a real socket at `localhost:3000/api/v1/meta`) under the full suite on a shared box. That file is not in this diff, and C4 shows no code token moves; re-run alone three times on this head it reads `Tests 6 passed (6)` each, and the CI test shards are green on this head. - Type-check: `turbo run build` of the 28-package dependency closure (`Tasks: 28 successful, 28 total`), then `pnpm --workspace-concurrency=2` with the 5 package filters `run type-check`: 5 script echoes, 5 `Done`, `VERDICT command-exit 0`. - No red leg: the sweep found no anchor to move, so there is no pin whose old copy should fail. CI on `8fe632d96`: 43 check-runs, 39 success, 3 skipped, 1 failure; `Spec Main Shape Gate` success. The failure is `Bundle Analysis`, step "Check console performance budget", which fails the same way on `main` at this PR's branch point `665025908` and at `d6d8fb9d7` and `2049b03df` after it; this diff moves no code token. ## Acceptance notes 1. **Outside the card's two classes, as it stands** (whole tree at this head, bare spelling): `objectstack-ai#9934` in 3 test doc-comment lines in 2 files and in 1 `apps/console` source line (`AgentConnectSection.tsx`, "the objectstack-ai#9934 channel"); `objectstack-ai#6281` in 1 test doc-comment line; `objectstack-ai#8452` in 1 test doc-comment line; bare `objectstack-ai#3546` in 23 test lines in 14 files and 19 published `CHANGELOG.md` lines in 8 files. `apps/console`, tests and `CHANGELOG` history are outside the classes this card carries (triage `5858031244`; release `5861553562`: recorded, not carried). 2. **Not measured: `framework#N` and `cloud#N`.** The two classes also cite 82 distinct `framework#N` and 43 distinct `cloud#N` numbers. Neither repository is on this card's lists, and both answer 403 through this container's proxy, so no reading of them is offered. 3. **Left as the brief allows.** The two test comments that call objectui#8652 `pm:blocked` (it reads `pm:on-hold`) and batch 7's sweep-changeset phrase "declared separately below" are in files this PR does not touch, and stay. --- _Generated by [Claude Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_ Co-authored-by: Claude <noreply@anthropic.com>
…he invalidation bus instead of remounting the page (objectui#10519) (objectstack-ai#10777) Fixes objectstack-ai#10519 Clause-②: no Dispatched by the `domain:ui` seat 2 PM, session `session_014mXUNuFomfj24w7s1pZzhN` (`mode:subagent`, so that id is also this run's: `https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN`), from claim comment 5855640194 on objectui#10519, executing the unblock 5854561985 and seat decision B 5830213285. Branch `claude/issue-10519-pageview-refresh-in-place`, cut from `origin/main` `b5b6f37531` (BASE); round-1 head `4620ba39d2`. Round 2 (after the contract review 5856227410, FAIL; the ruling 5856239953, readers first; the `Blocked-by` amendment 5857953021) merged `origin/main` `195052fff6` as `25c9aa6d3a` and re-ran the census there. It stopped at the census: see **H1, round 2**. Round 3 (after the seat ruling 5860118207, readers first, and objectui#10853 landing through PR objectui#10873 as `de1b879a6`) merged `origin/main` `1c5ee334a2` as `c5b63fb514`, re-ran the census there with every row green (**H1, round 3**), and carried review items 2 to 4 and the ratchet titles; its head was `42226f9ed1`. Round 4 (after the contract review 5862674975, FAIL; the ruling 5862691924, readers first; objectui#10887 landing through PR objectui#10898 as `9f0c84a44` and PR objectui#10973 as `deca847a8`; seat 2's release 5876662981; and the `domain:ui` seat 1 claim 5877617401, session `session_01DuWo5bdP9SdVebamn99GGk`, `https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`) merged `origin/main` `f6fb83f0c5` as `e021bfaf24`, measured the rows that ruling names on the merged head through a real page action, every one green (**H1, round 4**), pinned four of them in this PR's pin, and carried the changeset and body items. Head `fecce55c33`. ## What changed - `packages/app-shell/src/views/PageView.tsx`: the `refreshKey` counter is gone, together with the `key` on both render branches (`InterfaceListPage`, ADR-0047 interface mode, and `SchemaRenderer`, the page). The console action runtime's `onRefresh` now points at a module-level `declarePageDataChanged`, which is `notifyDataChanged({ objectName: '*' })` from `@object-ui/react`: a page binds no object and the runtime's refresh carries none, so the bus's documented unknown-scope value is the page's scope. The node `context` handed to `SchemaRenderer` is `{ params }` alone. `useState` is no longer imported. No prop, export or schema key moves. - `packages/app-shell/src/views/__tests__/PageView.context-9673.test.tsx`: the expected node context is `{ params }` (it was `params` plus `refreshKey: 0`); the header sentence says why. - `packages/app-shell/src/no-refresh-key-remount.ratchet.test.ts`: the header no longer calls `PageView.onRefresh` a user "Refresh this page" affordance. By a source reading of `useConsoleActionRuntime`, `onRefresh` is fed only by the runtime's post-action refresh (`api`, flow and server-action success, undo, screen-flow completion), and `PageView` has no refresh control. `packages/app-shell/src/views/PageView` joins `IN_SCOPE`. Every other assertion is kept. Its two `it` titles now name the `PageView` page host beside the record-detail surfaces (round 3, titles only). - New pin `packages/app-shell/src/views/__tests__/PageView.refreshInPlace-10519.test.tsx` (below). Round 4 adds a describe block for the objectui#10887 readers (**H1, round 4**) and a second object (`account`) to its fixture; the round-1 cases are unchanged. - `.changeset/10519-pageview-refresh-in-place.md`: `'@object-ui/app-shell': patch`. Round 3: the refresh follows a page-level action "that asks for a refresh"; `PageView`, not the runtime, declares the change; the block list is exactly the set the round-3 census measured green. Round 4: the list adds a `dashboard`'s `optionsFrom` filter options and an `object-view` drawn as a kanban, calendar, gallery or timeline (the rows **H1, round 4** measured green), the dashboard filter's kept value is said beside the form's, and a sentence says what a page action now does to a `kind: 'react'` page: its own read re-runs in place when its effect names the injected `useDataInvalidation` nonce, and a read keyed on `useAdapter` alone is not re-run. - `.changeset/9673-pageview-drop-context-spread.md` (pending, not in the app-shell `CHANGELOG`): "The node's `context` is now exactly `{ params, refreshKey }`, built from the route and the refresh counter, and the `as any` cast at that spot is gone." becomes "The node's `context` is now built from the route alone, and the `as any` cast at that spot is gone." A plain replacement, not a dated note: the edit can only reach a release together with this PR's code (a release that consumed the entry first would ship the old sentence, true for that release, and turn this edit into a modify/delete conflict), so the replacement is the entry's one release-scoped reading. Frontmatter byte-identical (md5 of lines 1 to 3 `4e8ce55021fe3b14329c8b4cb3f9f354` before and after); `check-changeset-overwrite` reads `@object-ui/app-shell: patch` at base and now. Round 4 rewraps the replaced sentence to the file's width: the words are unchanged (the whitespace-normalised text is identical) and the frontmatter md5 is the same. - `.changeset/10887-react-page-data-invalidation.md` (pending, from PR objectui#10973, not in the components `CHANGELOG`): "(as `PageView` does after a page action)" becomes "(as `PageView` did after a page action)". This branch stops that remount, and the two entries ship in the same release. A plain replacement, on the `9673` entry's argument. Frontmatter byte-identical (md5 of lines 1 to 3 `3d9e2a1e59bc11924bb99ffd9eea81e3` before and after); `check-changeset-overwrite` reads `@object-ui/components: minor` at base and now. This file is outside the claim's listed surface; see Acceptance notes. ## H1 — premise: the census rows re-read in place on `origin/main` Runtime census on BASE `b5b6f37531`, through a scratch probe (deleted, never committed): the real `SchemaRenderer`, each plugin's real registration, one counting fake data source per row; reads on mount, then `notifyDataChanged({ objectName: '*' })`, then reads again. A bare `useDataInvalidation` reader beside each block is the positive control, and its nonce moved 0 → 1 in every row. The probe lived under `app-shell` because the root vitest alias map resolves every `@object-ui/plugin-*` specifier to source there; committed, it would trip `check:phantom-deps` (the objectui#10572 PR made the same call). | block | reads on mount → after the bus event | re-reads in place | |:--|:--|:--| | `object-grid` | 1 → 2 | yes | | `object-chart` (aggregate) | 1 → 2 | yes | | `detail-view` (findOne) | 1 → 2 | yes | | `list-view` | 1 → 2 | yes | | `object-kanban` | 1 → 2 | yes | | `object-metric` (aggregate) | 1 → 2 | yes | | `object-form`, default arm (findOne) | 1 → 2 | yes | | `object-form` `formType: drawer` | 1 → 2 | yes | | `object-form` `formType: modal` | 1 → 2 | yes | | `object-form` `formType: split` | 1 → 2 | yes | | `object-form` `formType: tabbed` | 1 → 2 | yes | | `object-form` `formType: wizard` | 1 → 2 | yes | | `object-calendar` | 1 → 2 | yes | | `object-gallery` | 1 → 2 | yes | | `object-timeline` | 1 → 2 | yes | | `object-gantt` | 1 → 2 | yes | | `element:number` (aggregate) | 1 → 2 | yes | | `element:repeater` | 1 → 2 | yes | | `object-map` | not probed here: its committed pin `ObjectMap.invalidationRefetch-10623` mocks `react-map-gl/maplibre`, which does not resolve from app-shell; that pin ran green on BASE in the same lock run (3 cases) | yes | | `list` (bare key) | n/a | not a data block: `@object-ui/components` owns the bare key as the display list, and plugin-list registers `ListViewRenderer` as `view:list` with `skipFallback: true` so that it never takes that key; `view:list` is the `list-view` row's renderer. | The studio palette's data category (`BLOCK_TYPE_META`) is `grid`, `object-grid`, `object-form`, `object-metric`, `object-kanban`; every object-bound entry is in the table. Every block in this table, the six `object-form` arms included, re-reads exactly once after the bus event on BASE. What this PR claims covers the census rows only, not every block a stored page can hold: this table; the rows of the round-2 table (`object-tree`, `object-pivot`, `object-data-table`, `report` / `spec-report` over a dataset, `record:line_items` with an authored parent, a `dashboard` dataset table widget), whose readers landed with objectui#10778 and objectui#10814; and the rows round 3 adds, `object-master-detail-form`'s edit-mode detail lines and `element:record_picker`'s options, whose readers landed with objectui#10853. On the merged head `c5b63fb514` every one of them re-reads once in place with no remount (**H1, round 3**). Round 4 adds the rows the ruling 5862691924 names, whose readers landed with objectui#10887: an `object-view` drawn as a kanban, calendar, gallery or timeline, a `dashboard`'s `optionsFrom` filter options, and a `kind: 'react'` page whose effect names the injected `useDataInvalidation` nonce. Each re-reads once in place after a real page action on the merged head (**H1, round 4**). ## H1, round 2 — census on the merged head `25c9aa6d3a` The round-1 probe, re-run unchanged on its 18 rows, plus the rows the ruling names and one for the `dashboard` block, whose dataset widgets read on a page since objectui#10815. Scratch probes again, never committed, archived in the seat scratchpad (`issue-10519/census-10519-r2.probe.test.tsx.archived`, md5 `ba70dc05bf7a266d6a7535c9c7990b2e`; `issue-10519/census-10519-r2-md.probe.test.tsx.archived`, md5 `2d830df32bf6975b2be2edc5cb2050ef`). New this round, a remount witness: the row's own registry entry (`ComponentRegistry.get`, which `SchemaRenderer` resolves the node through) is wrapped in a counter whose dependency-free mount effect runs once per instance and whose cleanup runs once per unmount. Witness control: `object-metric` under a host re-keyed on the bus nonce, the shape of the removed `key={refreshKey}`, reads `1 → 2` like an in-place re-read, and the witness reads mounts `1 → 2`, unmounts `+1`. So the read count alone cannot tell the two apart, and the witness can. Run under the verify lock, `Tests 29 passed (29)`, `VERDICT command-exit 0` (26 probe cases, the three `ObjectMap.invalidationRefetch-10623` cases). | block | reads on mount → after the bus event | mounts at the event → after · unmounts | re-reads in place, no remount | |:--|:--|:--|:--| | the 18 round-1 rows (`object-grid`, `object-chart`, `detail-view`, `list-view`, `object-kanban`, `object-metric`, `object-form` default / drawer / modal / split / tabbed / wizard, `object-calendar`, `object-gallery`, `object-timeline`, `object-gantt`, `element:number`, `element:repeater`) | 1 → 2 each | 1 → 1 · +0 each | yes | | `object-tree` | 1 → 2 | 1 → 1 · +0 | yes | | `object-pivot` | 1 → 2 | 1 → 1 · +0 | yes | | `object-data-table` | 1 → 2 | 1 → 1 · +0 | yes | | `report` over a dataset (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `spec-report` over a dataset (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `record:line_items` with an authored `parentId` | 1 → 2 | 1 → 1 · +0 | yes | | `dashboard`, a dataset table widget (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `object-map` | its committed pin `ObjectMap.invalidationRefetch-10623`, same run: one re-read after `'*'`, the map node unchanged (3 cases) | n/a | yes | | witness control: `object-metric` under a host re-keyed on the nonce | 1 → 2 | 1 → 2 · +1 | by remount only | **Two blocks a stored page can hold do not re-read. This is the stop condition 5854561985, and round 2 stopped here.** | block, as a stored page holds it | reads on mount → after the bus event | mounts · unmounts | re-reads in place | |:--|:--|:--|:--| | `object-master-detail-form`, `mode: 'edit'`, authored `recordId`: the header (`findOne` of the parent) | 1 → 2 | 1 → 1 · +0 | yes | | the same block: its detail lines (`find` of the child object) | 2 → 2 | 1 → 1 · +0 | **no** | | the same block under a host re-keyed on the nonce: its detail lines | 2 → 4 | 1 → 2 | by remount only | | `element:record_picker`: its options (`find` of its object) | 1 → 1 | 1 → 1 · +0 | **no** | - **A stored page can hold both.** Both are `ComponentPropsMap` keys in `@objectstack/spec` 17.4.0, the installed version. `PageSchema.safeParse` accepts a page holding `object-master-detail-form` with `mode: 'edit'`, `recordId` and `details`, and `ComponentPropsMap['object-master-detail-form']` parses those properties. objectui registers both: `object-master-detail-form` in `plugin-form`, which the console's `register-plugins.ts` imports eagerly, and `record_picker` in `components`. Both read through the page's adapter (`SchemaRendererContext`, `useAdapter()`). - **Source reading (context, not the measurement).** `MasterDetailForm`'s edit-mode children effect keys on `isEdit`, `dataSource`, `schema.recordId` and `resolvedEntries`. The picker's option effect keys on `adapter`, `object`, `filterKey`, `sortKey` and `limit`. Neither names a bus nonce, so after a page action nothing re-runs them, and before this PR the remount did. - **Recorded, not gating.** `object-form` with `subforms` routes to the same `MasterDetailForm` and measured the same (lines 2 → 2). But `ComponentPropsMap['object-form']` refuses `subforms` by name (`unrecognized_keys`), so a spec-valid page does not hold that route. - **Resolved in round 3.** objectui#10853 (PR objectui#10873, `de1b879a6`) gave both blocks a bus reader, and round 3 measures them green below. Review items 2 to 4 and the ratchet `it` titles are on the branch. ## H1, round 3 — census on the merged head `c5b63fb514` The round-2 probe, re-run unchanged on its rows, plus `element:record_picker`'s options in the same harness and a new section for `object-master-detail-form` in edit mode with an authored `recordId` (the `po` / `po_line` shape objectui#10853's own pin uses, with the real line grid). Real `SchemaRenderer`, real registrations, one counting fake data source per row, one `notifyDataChanged({ objectName: '*' })`, the same remount witness and its control. Scratch probe, never committed, archived in the seat scratchpad as `issue-10519/census-10519-r3.probe.test.tsx.archived` (md5 `b1d116c257b12b3e35928dc35ddb113b`), rows in `issue-10519/census-rows-r3-head.txt`. Run under the verify lock with `ObjectMap.invalidationRefetch-10623`: `Test Files 2 passed (2)`, `Tests 33 passed (33)`, `VERDICT command-exit 0` (30 probe cases, 3 map cases). The control nonce read 1 in every row. | block | reads on mount → after the bus event | mounts at the event → after · unmounts | re-reads in place, no remount | |:--|:--|:--|:--| | `object-grid` | 1 → 2 | 1 → 1 · +0 | yes | | `object-chart` (aggregate) | 1 → 2 | 1 → 1 · +0 | yes | | `detail-view` (findOne) | 1 → 2 | 1 → 1 · +0 | yes | | `list-view` | 1 → 2 | 1 → 1 · +0 | yes | | `object-kanban` | 1 → 2 | 1 → 1 · +0 | yes | | `object-metric` (aggregate) | 1 → 2 | 1 → 1 · +0 | yes | | `object-form`, default arm (findOne) | 1 → 2 | 1 → 1 · +0 | yes | | `object-form` `formType: drawer` | 1 → 2 | 1 → 1 · +0 | yes | | `object-form` `formType: modal` | 1 → 2 | 1 → 1 · +0 | yes | | `object-form` `formType: split` | 1 → 2 | 1 → 1 · +0 | yes | | `object-form` `formType: tabbed` | 1 → 2 | 1 → 1 · +0 | yes | | `object-form` `formType: wizard` | 1 → 2 | 1 → 1 · +0 | yes | | `object-calendar` | 1 → 2 | 1 → 1 · +0 | yes | | `object-gallery` | 1 → 2 | 1 → 1 · +0 | yes | | `object-timeline` | 1 → 2 | 1 → 1 · +0 | yes | | `object-gantt` | 1 → 2 | 1 → 1 · +0 | yes | | `element:number` (aggregate) | 1 → 2 | 1 → 1 · +0 | yes | | `element:repeater` | 1 → 2 | 1 → 1 · +0 | yes | | `object-tree` | 1 → 2 | 1 → 1 · +0 | yes | | `object-pivot` | 1 → 2 | 1 → 1 · +0 | yes | | `object-data-table` | 1 → 2 | 1 → 1 · +0 | yes | | `report` over a dataset (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `spec-report` over a dataset (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `record:line_items` with an authored `parentId` | 1 → 2 | 1 → 1 · +0 | yes | | `dashboard`, a dataset table widget (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | yes | | `element:record_picker`: its options (`find` of its object) | 1 → 2 | 1 → 1 · +0 | yes | | `object-master-detail-form`, `mode: 'edit'`, authored `recordId`: its detail lines (`find` of the child object) | 2 → 3 | 1 → 1 · +0; the first line input is the same DOM node | yes | | the same block: its header (`findOne` of the parent) | 1 → 2 | the block node of the row above | yes | | `object-map` | its committed pin `ObjectMap.invalidationRefetch-10623`, same run: one re-read after `'*'`, the map node unchanged (3 cases) | n/a | yes | | witness control: `object-metric` under a host re-keyed on the nonce | 1 → 2 | 1 → 2 · +1 | by remount only | The detail lines read twice on mount, as objectui#10853 measured at its BASE (the load effect runs again once the resolved details are set); the bus event adds exactly one read. **Unsaved lines are held, then re-read once after a revert or a save** (objectui#10853's rule, measured through the same block, one bus event each): | case | line reads at the event → while dirty → after | mounts · unmounts | lines on screen while held | |:--|:--|:--|:--| | type into the first line, `'*'`, then type the stored value back (revert) | 2 → 2 → 3 | 1 → 1 · +0 | `["first edited"]` | | type into the first line, `'*'`, then the form's save (`batchTransaction` once) | 2 → 2 → 3 | 1 → 1 · +0 | n/a | No block a stored page can hold, among the census rows, still refreshes only through the remount. The stop condition 5854561985 does not hold this round. ## H1, round 4 — census on the merged head, through a real page action The ruling 5862691924 names three rows: `object-view` (kanban, plus at least one of calendar, gallery, timeline, map or tree), a `dashboard` with an `optionsFrom` filter, and a `kind: 'react'` page, since objectui#10887 member 3 injected `useDataInvalidation`. This round measures them through the real `PageView` and one real page action each, not through a bare bus event. Each row is a stored page in the spec shape (`regions`, blocks as `{ type, properties }`) holding the page's `api` action (`target: /api/v1/environments`, run through the console runtime's `apiHandler`, so no `onMutation` fires), a stand-in with an instance id, and the block. The probe counts the block's reads of the object it queries on a counting fake data source. It wraps the block's own registry entry (`ComponentRegistry.get`, which `SchemaRenderer` resolves the node through) in a mount and unmount witness, and it reads state a remount resets. Scratch probe, never committed: `census-10519-r4.probe.test.tsx`, archived in the round-4 scratchpad (md5 `af5ede3a47187d8a201e3df4548b6499`). At head `fecce55c33`, under the verify lock: `Tests 8 passed (8)`, `VERDICT command-exit 0`. The probe writes each row to a file; it asserts nothing about the counts. | row | reads of its object, at the action → after | witness: mounts · unmounts | page stand-in | state a remount resets | pinned in this PR | |:--|:--|:--|:--|:--|:--| | `object-view`, `defaultViewType: 'kanban'` | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the `Deal 1` card is the same node | yes | | `object-view`, calendar (a named view with `calendar.startDateField`) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the navigated month (October 2026) is kept | yes | | `object-view`, `defaultViewType: 'gallery'` | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the `Deal 1` card is the same node | yes | | `object-view`, timeline (a named view with `timeline.startDateField`) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the `Deal 1` item is the same node | no, probe only | | `dashboard`, a `globalFilters` select with `optionsFrom` (the `find` fallback read) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | the selected `retail` is kept; the trigger is the same node | yes | | the same filter, the dataset read (`queryDataset`) | 1 → 2 | 1 → 1 · +0 | 1 → 1 | as above | no, probe only | | `kind: 'react'` page whose effect names `useDataInvalidation('project')`; the action is a `Block` of type `action:button` inside the page | 1 → 2 | the page's `app` entry 1 → 1 · +0; the author's own mount effect 1 → 1 | n/a (the page is the block) | the counter clicked before the action still reads 1; the button is the same node | no, probe only | | control: the same react page, its read keyed on `[adapter]` alone | 1 → 1 | 1 → 1 · +0; author mounts 1 → 1 | n/a | the counter is kept | no | - **No row re-reads only through the remount.** No new remount-only reader was found, so "readers first" does not stop this round. - **The remount key.** `PageView` carries no `key` on either branch (the `no-refresh-key-remount` ratchet holds the file), and the witness saw no remount at or below each block's registry entry. - **The calendar row.** Its region node is not the same node after the action. While the view re-reads, `ObjectView` hands the calendar `loading`, and `ObjectCalendar` shows its loading placeholder in place of the grid, as PR objectui#10973's note (c) records. `ObjectCalendar` itself is not remounted: the witness reads 1 → 1, and the month it holds in its own state is kept. In the legs where nothing re-reads (A2, R3 below), the region node is the same, so the swap follows the re-read. - **What is pinned.** The round-4 describe block of `PageView.refreshInPlace-10519` holds four rows: the kanban, gallery and calendar `object-view`s, and the dashboard filter over the `find` read. Each asserts both halves: (a) the stand-in's instance id, plus the card node, the navigated month, or the filter's trigger node and selected value; (b) exactly one re-read of the block's object. Timeline, the dataset read and the react page stay probe-only. `@object-ui/app-shell` declares neither `@object-ui/plugin-timeline` nor `@object-ui/react-runtime`, and neither does the workspace root, which is what `check:phantom-deps` asks of a test import. A react page also loads its runtime with a dynamic `import()`, which a committed test would race inside a bounded wait (AGENTS.md, flaky-test discipline). Their readers' own pins fail on the round-3 code path: `ObjectView.busReread-10887` covers the timeline, `DashboardFilterBar.busReread-10887` covers both reads, and `react-page-invalidation` covers the react page scope (its base leg is a `ReferenceError`, per PR objectui#10973). Legs, each on committed state (`beac974876`; the commit after it changes changesets only), with the probe's first seven cases (the adapter-only control came after) and the pin. `PageView.tsx` and the three readers resolve to `src` (the relative import, and the root vitest alias for `@object-ui/plugin-view`, `@object-ui/plugin-dashboard` and `@object-ui/components`), so no `dist/` preflight applies. Every restore is by blob hash against `HEAD`, with `git diff HEAD` empty. | leg | mutation (on-disk proof) | probe | pin | |:--|:--|:--|:--| | A1, the pre-fix host | `PageView.tsx` = `main`'s blob `1ace6962da` (HEAD blob `979bb11eb7`; two `key={refreshKey}` sites) | every row: mounts 1 → 2, unmounts +1, stand-in 1 → 2, the node replaced; the month reset to September 2026, the filter back to `Industry: All`, the react counter back to 0 and the author's mount effect 1 → 2 | `Tests 6 failed \| 8 passed (14)` over probe and pin: all six in-place cases red on (a); the lit control green | | A2, the host declares nothing | `ablation-replace`: anchor `notifyDataChanged({ objectName: '*' });` 1 → 0, blob `979bb11eb7` → `7bb3888fd2` | every row: reads 1 → 1, no remount | `Tests 6 failed \| 8 passed (14)`: the same six red on (b) (metric, list, kanban, gallery, calendar, dashboard); the lit control green | | R3, `ObjectView` on the round-3 path | `ablation-replace`: the `invalidationNonce` entry deleted from the non-grid fetch's dependency list (1 → 0), blob `db64424727` → `90e198e990` | the four `object-view` rows: reads 1 → 1, no remount; the others unchanged | `Tests 3 failed \| 11 passed (14)`: kanban, gallery and calendar red on (b) | | R3, `SelectFilter` on the round-3 path | `ablation-replace`: `, dataSource, invalidationNonce]);` → `, dataSource]);` (1 → 0), blob `9d93a32b56` → `b636341b96` | both dashboard rows: reads 1 → 1, the value kept | `Tests 1 failed \| 13 passed (14)`: the dashboard case red on (b) | | R3, the react page scope on the round-3 path | `react-page.tsx` = its `42226f9ed1` blob `f70b8f4251` (HEAD blob `ab712dface`) | the react row fails at render: `ReferenceError: useDataInvalidation is not defined` in the page's error panel | not in the pin | | R3', the hook injected but never moving | `ablation-replace`: `useDataInvalidation,` in the scope becomes a function that always returns 0 (1 → 0), blob `ab712dface` → `ade9a50713` | the react row: reads 1 → 1, no remount | not in the pin | **The round 1 to 3 rows on this head.** They were not re-run through a census probe this round. Each block that sentence 5 names from those rounds has its own committed bus pin, and all of them ran green on `fecce55c33` in one lock hold: `Test Files 27 passed (27)`, `Tests 171 passed (171)`. The files are `ObjectGrid.invalidationRefetch-10035`, `ObjectChart.invalidationRefetch-10035`, `DetailView.invalidation`, `ListView.invalidationRefetch-10572`, `ObjectKanban.invalidationRefetch-10572`, `ObjectMetricWidget.invalidationRefetch-10572`, `ObjectCalendar.invalidationRefetch-10572`, `ObjectGallery.invalidationRefetch-10623`, `ObjectTimeline.invalidationRefetch-10623`, `ObjectGantt.invalidationRefetch-10035`, `ObjectMap.invalidationRefetch-10623`, `ObjectMap.busReread-10623`, `ObjectTree.invalidationRefetch-10778`, `ObjectPivotTable.invalidationRefetch-10778`, `ObjectDataTable.invalidationRefetch-10778`, `ObjectForm.invalidationRefetch-10572`, `formArmsInvalidation-10715`, `MasterDetailForm.busReread-10853`, `DatasetReportRenderer.invalidationRefetch-10814`, `DatasetWidget.invalidationRefetch-10815`, `LineItemsPanel.invalidationRefetch-10814`, `elementReaders.invalidationRefetch-10623`, `record-picker.busReread-10853`, `ObjectView.busReread-10887`, `DashboardFilterBar.busReread-10887`, `react-page-invalidation` and `drillDownReport.invalidationRefetch-10814`. ## H2 — the remount, reproduced on BASE The new pin run against BASE `PageView.tsx` (byte-identical to BASE by blob hash `9de300582c`) with the head pins kept, before any source edit: `Tests 2 failed | 1 passed (3)`. - SchemaRenderer branch, red on (a): `The page action REMOUNTED the page: the SchemaRenderer consumer mounted a second time` — the `object-gantt` stand-in's instance counter read 2 after one `api` action. - interface branch, red on (a): `The page action REMOUNTED the interface page` — the `interface-list-page` node after the action was not the node before it. - lit control green: a change declared on the bus directly reached the metric and the stand-in once each, with no action run. ## Pins and ablations `PageView.refreshInPlace-10519.test.tsx` renders `PageView` for real under `MemoryRouter`, `AdapterCtx` and `SchemaRendererProvider`, with the real console action runtime, the real `page`, `page:accordion` and `action:button` renderers, the real `object-metric` (plugin-dashboard) and `object-form` (plugin-form) blocks, and, on the interface branch, the real `InterfaceListPage` over plugin-list's `ListView`. The stand-in registered as `object-gantt` follows the `ObjectView.refreshInPlace-10035` precedent: it reads the real bus hook, counts its own queries, and carries an instance id and a scroll box. The page action is a real `api` action (`target: /api/v1/environments`) through the runtime's `apiHandler`, an authenticated raw-HTTP call that fires no dataSource `onMutation`. After one page action: - (a) the same tree: the stand-in mounted exactly once (instance id unchanged), its scroll box is the same node with `scrollTop` still 120, the metric node is the same, the form's `name` input is the same node and still reads the half-typed `User typed`, and the collapsed accordion panel still has `aria-expanded="false"`; on the interface branch the `interface-list-page` node and `ListView`'s own `refresh-button` node are unchanged. - (b) each bus reader re-read exactly once: the metric's `aggregate` delta is 1, the stand-in's query delta is 1, and `ListView`'s list query delta (its `$top: 0` count probe excluded) is 1. - lit control: `notifyDataChanged({ objectName: '*' })` with no action reaches the metric and the stand-in once each. Red on base, green on head, and two ablation legs, all on committed state with every restore spelled `git checkout HEAD -- ABSOLUTE_PATH` and proven by blob hash, `git diff HEAD` empty and `git status` clean: | leg | mutation (on-disk proof) | result over the pin, the ratchet and context-9673 | |:--|:--|:--| | A1, reverse run | `PageView.tsx` checked out at BASE (blob `9de300582c` == BASE blob; `key={refreshKey}` sites 2, `notifyDataChanged` sites 0), head pins kept | `Tests 5 failed | 2 passed (7)`: ratchet red (`expected [ …(2) ] to deeply equal []`, the two key sites), context-9673 red on both cases (`+ "refreshKey": 0`), the pin red on (a) on both branches; the ratchet's scan-path guard and the pin's lit control green. Restore: blob `8e96f4adbf` == HEAD blob, diff empty, status clean | | A2, the host declares nothing | through `ablation-replace.mjs`: anchor `notifyDataChanged({ objectName: '*' });` hit 1 → 0, replacement 0 → 1, blob `8e96f4adbf` → `83a296ea0a` | `Tests 2 failed | 5 passed (7)`: the pin red on (b) on both branches (`The metric did not re-read after the action`, `The list did not re-query after the action`); (a), the lit control, the ratchet and context-9673 green. Restore by the tool: blob == HEAD, `git diff HEAD` empty | Each leg fails on the half the defect predicts and nowhere else, so each pin fails for the reason it names. No `dist/` preflight applies: every test here imports `PageView` from `src`, and the root vitest alias map resolves every `@object-ui/*` specifier to source. ## Gates Round 1, at `4620ba39d2`: - Pins at head: the five `PageView` test files (`PageView.refreshInPlace-10519`, `no-refresh-key-remount.ratchet`, `PageView.context-9673`, `PageView.test`, `page-kind-writing-end-9718`): `Test Files 5 passed (5)`, `Tests 12 passed (12)`, `VERDICT command-exit 0`. - `pnpm --filter @object-ui/app-shell type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`, script name echoed), after `turbo run build` of the 28-task dependency closure (cache-restored, `dist/index.d.ts` present): `VERDICT command-exit 0`. - Lint, narrowed and measured: `eslint --format json` over the four touched files reads 4 files, 0 errors, 27 warnings, all `@typescript-eslint/no-explicit-any`, the warning-level rule the package already carries on every `as any` (the base `PageView.tsx` had them). Population: `eslint --format json .` in `packages/app-shell` (what CI's `turbo run lint` runs there) reads 1298 files, 0 errors, 3207 warnings. Invariance: `eslint.config.js` enables no type-aware linting (`projectService` / `parserOptions.project` absent), so this diff moves no untouched file's verdict. - `check-changeset-presence`: 4 source files of 1 released package changed, 1 changeset declared, exit 0. `check:new-line-citations`: 0 new citations. `check:test-path-roots`, `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape`, `check:control-bytes`, `check:phantom-deps`, `changeset:check` (fixed, no-major), `check:changeset-claims`, `check:pending-changeset-literals`, `check:shell-escape-residue`, `check:unreferenced-sources`: all exit 0 with their OK line. - Declared to CI: the full app-shell test suite and the repo-wide `pnpm test`; the scope named above is the union of every test file that imports `PageView` or that this diff edits. - Round 2, at `25c9aa6d3a`: the same five `PageView` pin files, `Test Files 5 passed (5)`, `Tests 12 passed (12)`, `VERDICT command-exit 0`. `check-changeset-presence`, `check-changeset-no-major`, `check-changeset-overwrite`, `check:changeset-claims`, `check:control-bytes` and `check:new-line-citations` each exit 0 there with their OK line. The type-check was not re-run this round, which stopped at the census; CI runs on `25c9aa6d3a`. - Round 3, at `42226f9ed1` (the final commit): the same five `PageView` pin files, `Test Files 5 passed (5)`, `Tests 12 passed (12)`, `VERDICT command-exit 0`. `pnpm --filter @object-ui/app-shell type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`, script name echoed), after `turbo run build --concurrency=2 --filter=@object-ui/app-shell^...` (`Tasks: 28 successful, 28 total`), in one lock hold: `VERDICT command-exit 0`. `check-changeset-presence` (4 source files of 1 released package, 1 changeset declared), `check-changeset-no-major`, `check-changeset-overwrite` (report-only: 1 added, 1 modified, the `9673` entry, declared bump unchanged), `check:changeset-claims`, `check:control-bytes`, `check:new-line-citations` (0 new citations), `check:pending-changeset-literals` and `changeset:check`'s fixed-group half each exit 0 with their OK line. `eslint --format json` over the edited ratchet file: 1 file, 0 errors, 0 warnings. Source-text pins: no test or script quotes a sentence this round changes (the two `9673` sentences, this changeset's sentences 2 and 5, the ratchet titles). Round 4: - At the merged head `e021bfaf24`, before anything was added: the six `app-shell` test files that import or read `PageView` (the five above, plus `PageView.editInStudioNeedsAuthoring-10899`, which the merge brought): `Test Files 6 passed (6)`, `Tests 16 passed (16)`, `VERDICT command-exit 0`. - At the final head `fecce55c33`, in one lock hold: the census probe, `Tests 8 passed (8)`; the same six `PageView` files, `Test Files 6 passed (6)`, `Tests 20 passed (20)`; the 27 reader pins (**H1, round 4**), `Test Files 27 passed (27)`, `Tests 171 passed (171)`; `VERDICT command-exit 0`. The other test that reads `PageView.tsx`, `residue-namespaces-3546` in `@object-ui/i18n`: `Tests 43 passed (43)`, `VERDICT command-exit 0`; the two in `scripts/__tests__` ran in the `scripts/` project below. - `pnpm --filter @object-ui/app-shell type-check` (echoed `tsc --noEmit && tsc -p tsconfig.test.json`), after `turbo run build --concurrency=2 --filter=@object-ui/app-shell^...` (`Tasks: 28 successful, 28 total`): `VERDICT command-exit 0`. `tsc -p tsconfig.test.json --listFiles` names the pin once, so the test program compiles it. - `pnpm exec vitest run scripts/ --maxWorkers=2`, the whole `scripts/` project, so every `scripts/__tests__` gate whose corpus reads a touched file: `Test Files 177 passed | 2 skipped (179)`, `Tests 5350 passed | 2 skipped (5352)`, `VERDICT command-exit 0`. - Each exits 0 with its OK line: `check-changeset-presence` (4 source files of 1 released package, 1 changeset declared), `check-changeset-no-major`, `check-changeset-fixed`, `changeset:check`, `check-changeset-overwrite` (report-only: 1 added, 2 modified, the `9673` and `10887-react-page` entries, declared bumps unchanged), `check:changeset-claims`, `check:pending-changeset-literals`, `check:control-bytes`, `check:new-line-citations` (0 new citations), `check:test-path-roots`, `check:vi-mock-specifiers`, `check:vi-mock-inherit`, `check:vi-mock-override-shape`, `check:phantom-deps`, `check:unused-deps`, `check:shell-escape-residue` and `check:unreferenced-sources`. `check-governed-queue-guard --test` over the 7 changed paths: NOT GOVERNED. - Lint, narrowed and measured: `eslint --no-inline-config --format json` over the four touched source and test files reads 4 files, 0 errors and 29 warnings, all `@typescript-eslint/no-explicit-any`. The pin carries 19, one more than its round-3 blob; `PageView.tsx` carries 5, as `main`'s blob does; context-9673 carries 5 and the ratchet 0. `eslint.config.js` names no `projectService` and no `parserOptions`, so type-aware linting is off and the diff moves no untouched file's verdict. CI `Lint` is success on the head. - Eager closure, head against `main`. The only source file this PR changes against `main` is `PageView.tsx`. A console `vite build` at the head, and again with `main`'s `PageView.tsx` blob (restored by blob hash afterwards), both read `Console eager closure is 3105.0 KB gzipped across 330 of 2446 chunks (budget: 3149.4 KB, headroom: 44.4 KB)`, with equal gzip byte totals. `PageView` is its own lazy chunk, and the two eager sets differ only in the entry chunk's hashed name. CI `Bundle Analysis` is success on the head and on `main` `f6fb83f0c5`. - The full `app-shell` suite (869 test files): NOT MEASURED locally. The run was stopped after 20 minutes under the lock (its own PIDs, nobody else's) to free the shared lock. CI's `Test (shard 1/8)` to `Test (shard 8/8)` are success on `fecce55c33`. - CI on `fecce55c33`: 43 check runs, 40 success and 3 skipped (the two coverage runs and dependabot), `Spec Main Shape Gate` and `Bundle Analysis` among the successes. ## Serial Before the final push, `origin/main` was fetched into the private ref `refs/issue-10519/main` (`805abe240c`): one commit landed since BASE (objectui#10763), and it touched no file this branch touches (only two other cards' changesets). `git merge-tree --write-tree HEAD refs/issue-10519/main` exit 0 (clean). No merge commit was needed. Round 2: `origin/main` was fetched into the same private ref (`195052fff6`, 47 commits past `4620ba39d2`'s base, objectui#10809 and objectui#10836 among them) and merged as the merge commit `25c9aa6d3a` (parents `4620ba39d2`, `195052fff6`). There were no conflicts. It was pushed fast-forward, with no rebase and no force. Round 3: `origin/main` was fetched into the same private ref (`1c5ee334a2`, 17 commits past `195052fff6`, objectui#10873 among them) and merged as the merge commit `c5b63fb514` (parents `25c9aa6d3a`, `1c5ee334a2`), with no conflicts, pushed fast-forward. Before the final push, `origin/main` had moved two more commits (`24845c4f14`: objectui#8284 and objectui#7189); neither touches a file this branch touches, and `git merge-tree --write-tree HEAD refs/issue-10519/main` at `42226f9ed1` exits 0 (tree `fe8c05b333`). No further merge was made. Round 4: `origin/main` was fetched into the private ref `refs/issue-10519d/main` (`f6fb83f0c5`, 82 commits past `1c5ee334a2`, objectui#10898 and objectui#10973 among them). `git merge-tree --write-tree` against it exited 0 (tree `2808afb79b`), and the merge commit `e021bfaf24` (parents `42226f9ed1`, `f6fb83f0c5`) carries exactly that tree. The one file both sides changed is `PageView.tsx`: `main`'s hunk (PR objectui#10924, the `useCanAuthorMetadata` gate on the edit-in-studio button) and this branch's hunks do not touch the same lines, and both are kept. Pushed fast-forward, with no rebase and no force. Before the final push `origin/main` had not moved, and the open-PR file lists (objectui#11006, objectstack-ai#10930, objectstack-ai#10278, objectstack-ai#5400) name neither `PageView.tsx` nor anything under `packages/app-shell/src/views/`. ## Acceptance notes - The bare `list` key is the display list by design (plugin-list `src/index.tsx`); no finding. - Not filed, nit: `useConsoleActionRuntime`'s `onRefresh` doc comment still describes the refresh as "bump a refresh key to re-fetch embedded data"; `ObjectView` still bumps one (plus the bus, objectui#10035), so the sentence is stale only for this host. - A `Refresh` button inside `ListView`'s toolbar (`refresh-button`) is a user refresh affordance and is unrelated to `PageView.onRefresh`; the pin uses that node only as an identity witness. - Round 4, the calendar's loading placeholder (**H1, round 4**): `ObjectView`'s non-grid fetch sets `loading` on every run, a bus re-read included, and `ObjectCalendar` swaps its grid for the placeholder while it is set. That is PR objectui#10973's note (c), recorded there as not filed. Not filed here either: carrier none. The month and the view mode live in `ObjectCalendar`'s own state and are kept. - Round 4, the `10887-react-page` changeset edit: the claim's file surface does not list that file. It is edited under the in-place rule for a same-class planned edit: a pending entry this branch makes false in the same release, fixed the way the `9673` entry was (a plain replacement, frontmatter unchanged, same changeset gate family). The seat may take it out and file it instead. - The release PR objectui#5400 deletes `.changeset/9673-pageview-drop-context-spread.md` and `.changeset/10887-react-page-data-invalidation.md` when it consumes them. If it lands first, both entries ship with their current sentences, which are true for that release, and this branch's edits to them become modify/delete conflicts, resolved by dropping the edits. That is the round-2 argument for the `9673` edit, and it covers both. --- _Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…drillDown that PR objectstack-ai#10972 made false (objectui#10974) (objectstack-ai#11045) Fixes objectstack-ai#10974 Clause-②: no — release-note prose only; no accept set or public surface changes. ## What this does PR objectui#10972 (objectui#10932, merged as `cc4e47638`) retired `drillDown` on the bare `pivot` node in this same unreleased batch: `PivotTableSchema.drillDown` is a `?: never` tombstone on the TypeScript face, and the zod `pivot` arm declares it as a `retirementTombstone()`. Three pending changesets still read that key as the shared `DrillDownConfigSchema`, and they publish verbatim into the `@object-ui/types` CHANGELOG before the objectui#10972 entry's correction is met. Each now carries one dated 2026-09-29 supersession note, appended at the end of the file, in the objectui#10877 / PR objectui#10891 form: a bold headline naming the date and objectui#10932, what holds now, the superseding entry by file and PR, and "the text above is kept as the reading of this change". **Authorisation.** The maintainer's 2026-09-29 ruling, recorded on this card as comment `5883365010` (and on objectui#4356 as `5883360525`), quoted: > You allow developers to append dated correction notes to pending .changeset files (appended only, no existing line or frontmatter edited). ## The three sentences and the appended notes Each before-sentence was re-quoted from `main` `2eaf5be27` before writing. ### 1. `.changeset/10859-pivot-object-block-zod-arms.md` Before: "`drillDown` is the shared `DrillDownConfigSchema`" Appended: >⚠️ **Dated note, 2026-09-29 — `drillDown` is retired on the `pivot` node — objectui#10932.** Later in this same release `drillDown` was retired on the `pivot` node on both faces: the TypeScript `PivotTableSchema.drillDown` is a `?: never` tombstone, and the `pivot` arm of `@object-ui/types/zod` declares `drillDown` as a `retirementTombstone()`, which refuses the key by name whatever it holds (`invalid_type` at `drillDown`, with a message naming `object-pivot`). So "`drillDown` is the shared `DrillDownConfigSchema`" above no longer holds: the arm accepts no `drillDown` at all. Nothing ever drilled a `pivot` node; a pivot whose cells open the records behind a value is authored as `object-pivot`, whose `drillDown` is typed `ObjectPivotDrillDownConfig`. The rest of the `PivotTableSchema` bullet stands, including "a member for member restatement of the TypeScript `PivotTableSchema`", because both faces retired the key together. `.changeset/10932-pivot-drilldown-retired.md` (PR objectui#10972) states what ships; the text above is kept as the reading of this change. ### 2. `.changeset/7352-drill-down-config-mirror.md` (its 2026-09-28 dated note) Before: "its `drillDown` is this entry's `DrillDownConfigSchema`, so the opening paragraph's two referencing declarations are three for the release" Appended: >⚠️ **Dated note, 2026-09-29 — the `pivot` arm no longer references this mirror — > objectui#10932.** Later in this same release `drillDown` was retired on the `pivot` > node on both faces: `PivotTableSchema.drillDown` is a `?: never` tombstone on the > TypeScript face, and the zod arm declares it as a `retirementTombstone()`, which > refuses the key by name whatever it holds. So "its `drillDown` is this entry's > `DrillDownConfigSchema`, so the opening paragraph's two referencing declarations > are three for the release" in the 2026-09-28 note above no longer holds: for the > release the referencing declarations are the opening paragraph's two, `ChartSchema` > and `ObjectDataTableSchema` (whose member extends this mirror per block, > objectui#10685). The rest of that note stands: `PivotTableSchema` does have a zod > mirror, so "`PivotTableSchema.drillDown` has no zod mirror at all" is still this > change's reading, not the release's, and that mirror's `drillDown` member refuses > the key rather than mirroring it. `.changeset/10932-pivot-drilldown-retired.md` > (PR objectui#10972) states what ships; the text above is kept as the reading of > this change. ### 3. `.changeset/10685-drilldown-per-block.md` (its 2026-09-28 dated note) Before: "a validator does read the members of a `pivot` node's `drillDown`, and accepts `mode` there" Appended: >⚠️ **Dated note, 2026-09-29 — the `pivot` node refuses `drillDown` whole now — objectui#10932.** > Later in this same release `drillDown` was retired on the `pivot` node on both faces: > `PivotTableSchema.drillDown` is a `?: never` tombstone, and the `pivot` arm of the zod mirror > declares it as a `retirementTombstone()`, which refuses the key by name whatever it holds > (`invalid_type` at `drillDown`). So "a validator does read the members of a `pivot` node's > `drillDown`, and accepts `mode` there" in the 2026-09-28 note above no longer holds: the > validator refuses the whole key before reading any member, so it accepts no `mode` on a `pivot` > node. Nothing ever drilled a `pivot` node; a pivot drill is authored on `object-pivot`, whose > `drillDown` type, `ObjectPivotDrillDownConfig`, refuses `mode` at the TypeScript door, as this > entry says. `object-pivot` still has no zod mirror, so what this entry says of a stored > `object-pivot` config stands. `.changeset/10932-pivot-drilldown-retired.md` (PR objectui#10972) > states what ships; the text above is kept as the reading of this change. ## What the notes state, measured on `main` `2eaf5be27` - `PivotTableSchema.drillDown?: never` on the TypeScript face; the zod arm's `drillDown` is `retirementTombstone(PIVOT_DRILL_DOWN_RETIRED)`, i.e. `z.never(...).optional()`, so any present value is refused at `drillDown` whatever it holds. The pin `pivot-drilldown-retired-10932.test.ts` ran 8 of 8 passed on this tree, including "the arm refuses it at `drillDown`, `invalid_type` against `never`" and "the refusal names the remedy, `object-pivot`, in one string on both channels". - `DrillDownConfigSchema` is referenced by `ChartSchema` (`drillDown: DrillDownConfigSchema.optional()`) and by `ObjectDataTableSchema` (`drillDown: DrillDownConfigSchema.extend({`), and by no `pivot` member: two referencing declarations for the release. - The node that drills is `object-pivot`: `ObjectPivotTable` passes `onDrillDown={handleDrillDown}` to `PivotTable`, whose `drillEnabled` is `typeof onDrillDown === 'function'`. `object-pivot` has no zod arm (every `object-pivot` mention under `packages/types/src/zod` is a comment or a message string; the control literals `z.literal('chart')` / `z.literal('pivot')` hit). ## Verification, per file | File | frontmatter md5, base = head | base file is a byte prefix of head | `git diff --numstat` vs `2eaf5be27` | angle brackets in note | | --- | --- | --- | --- | --- | | `10859-pivot-object-block-zod-arms.md` | `ecedb10c` = `ecedb10c` | yes | 2 / 0 | 0 | | `7352-drill-down-config-mirror.md` | `ecedb10c` = `ecedb10c` | yes | 16 / 0 | 0 | | `10685-drilldown-per-block.md` | `ac1bfd04` = `ac1bfd04` | yes | 13 / 0 | 0 | No note cites a line address; sentences are cited by quotation. ## Completeness sweep (pending changesets only) Enumerated from `git ls-tree --name-only 2eaf5be .changeset/` (2325 `.md`, equal to the working-tree count): - `pivot` (case-insensitive) AND `drillDown`: 10 files. Three are this PR's carriers and one is `10932-pivot-drilldown-retired.md` itself. The other six (`10528-grid-object-table-fetch`, `10778-dashboard-bus-readers`, `8885-object-chart-drilldown-title-compareto`, `8970-object-metric-shared-drill-drawer`, `9000-drill-columns-header-labels`, `9002-metric-drilldown-refusal`) were read: each speaks of `object-pivot` or the shared `DrillDownConfig` type, and none is made false by PR objectui#10972. - Widened, `pivot` AND `drill` without `drillDown`: 4 more (`10789-render-time-filter-refusals`, `9024-pivot-drill-filter-composition`, `9137-dataset-drill-conjoin-widget-filter`, `dashboard-table-widget-polish-5827`), all about `object-pivot` drill filters or the cross-tab renderer; none false. - `DrillDownConfigSchema`: 4 files, all inside the first set. - Positive control: the three named carriers are hits of the first query. - `7759-dashboard-header-global-filters-spec.md` is untouched; that note belongs to PR objectstack-ai#10930 (objectui#4356 W4). No same-class extra sentence was found, so no fourth note. ## Changeset presence `check-changeset-presence` exit 0: "no changeset is owed". The diff is `.changeset/` only, so no new changeset is added. ## Gates (head `4f412d5a8`) - `node scripts/check-changeset-presence.mjs`: exit 0 - `node scripts/check-changeset-no-major.mjs`: exit 0 - `node scripts/check-changeset-fixed.mjs`: exit 0 - `node scripts/check-changeset-claims.mjs`: exit 0 ("Corpus: 3 changeset(s) this change adds or modifies") - `node scripts/check-changeset-overwrite.mjs`: exit 0, report-only; it lists the three files as modified with the same declarations at base and now (its case 2, a prose correction) - `pnpm check:pending-changeset-literals`: exit 0 - `pnpm check:control-bytes`: exit 0 - `pnpm check:new-line-citations`: exit 0, "0 new citation(s)" - `pnpm exec vitest run` over the five `scripts/__tests__/check-changeset-*.test.ts` files and `check-pending-changeset-literals.test.ts`: 6 files, 220 of 220 tests passed - `node scripts/check-governed-queue-guard.mjs --test` over the three paths: exit 0, NOT GOVERNED Implemented by a dispatched `os-dev` agent for the `domain:spec @ objectui` seat, session `https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm`. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #4356
Clause-②: yes — published runtime behaviour is removed (the bare-string lift, and the exported
resetDashboardFilterWarningsif it retires with its memo), per the execution parameters in5511028727.The claim's "if" resolved to stays: the memo survives (it now dedupes the dropped-member warning, see "The audible-skip question"), so
resetDashboardFilterWarningskeeps its job and its export; the changeset names no removed export.Final head
a890654027a69911412efbcb146e326da8476256. Commits on this branch since the branch point733fd5ac6:bc4ea976e(the retirement),98623a24d(rework round 1: W1–W3),e0647486b(patch round 2: W5), the two merges oforigin/main(1daf7edf7at29c76e573,a1058f90eat2eaf5be27), anda89065402(W4, the dated note). Implemented in sessionsession_012UwY3ahMixEFkfTUxMVkYm.What changed
Phase 2 of objectui#4356 — the runtime bare-string
globalFilters[].optionslift retires.packages/core/src/utils/dashboard-filters.ts— innormalizeFilterOptions, the non-object arm no longer pushes{ value: String(o), label: String(o) }. A member that is not an object (string, number, boolean) yields NO option: a shorthand-only filter resolves with nooptions, a mixed array keeps only its object members. The nullish / value-less skips are unchanged. The Phase 1 deprecation warning (warnShorthandOptions, whose text said "Still lifted here") is deleted; a dev-mode, once-per-filterwarnDroppedShorthandOptionsnames the filter, the dropped members and the rewrite, and promises no lift. TheDashboardFilterDef.optionsJSDoc, thenormalizeFilterOptionsdocblock, the memo docblock and theresetDashboardFilterWarningsdocblock stop describing a lift. No lift, alias, fallback or migration is added anywhere.packages/core/src/utils/__tests__/dashboard-filters.test.ts— every lift-pinning test is flipped to pin the new contract (red-first, quoted under Tests):normalizes options: spec {value,label} objects AND bare-string shorthandbecamenormalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it; the[#4356]describe isretired: dropped, not lifted— a shorthand-only filter yields nooptions; a mixed array keeps only its object members; a number and a boolean are dropped too; warn ONCE across renders while dropping on every render; a different filter warns separately; canonical{ value, label }options and anI18nLabelmap are untouched and silent;resetDashboardFilterWarningsre-arms the memo. The stale Phase 1 header above the block is removed (W3).content/docs/guide/dashboard-filters.md— the "Deprecated: … still lifted by the runtime" callout became false and now reads "Not accepted": refused at publish, no longer lifted, a bare-string member yields no option, rewrite"X"as{ "value": "X", "label": "X" }. No fenced block changed.packages/types/src/zod/complex.zod.ts— the JSDoc of the exportedGlobalFilterSchemasaid the runtime "still LIFTS a stored bare-string option on read"; that sentence ships in@object-ui/types' emitted declarations and is corrected in place. Outside the claim's original surface; accepted by the seat's addendum, see "Deviations".packages/plugin-dashboard/README.md(W1, ships in that package'sfiles) — theglobalFiltersexample'soptionscomment no longer says a stored bare string "is still lifted at runtime, and warns"; it says NOT accepted, the runtime no longer lifts it (objectui#4356), a bare member yields no option, a dev-mode warning names it, rewrite each X as{ "value": X, "label": X }.packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts,packages/types/src/__tests__/report-chart-query-spec-parity.test.ts(its header docblock only — nothing near objectui#10916'sJoinedReportBlockpin, which PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934 rewrote and this branch has merged),packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx. A fourth (W5),packages/plugin-dashboard/src/__tests__/DashboardFilterBar.i18nLabel.test.tsx, no longer says the shorthand "is deprecated and now warns" nor names the renamed lift pin; it names the current DROP pin. No assertion moved in any of the four..changeset/retire-options-shorthand-lift-4356.md—@object-ui/core: minor, opening with the ruled wording: "bare-stringglobalFilters[].optionsis no longer lifted; use{ value, label }objects, the spec's form". Nevermajor(AGENTS.md §9 version policy: the break is stated in the body, the bump staysminor);check-changeset-no-majorexit 0..changeset/7759-dashboard-header-global-filters-spec.md(W4) — a dated note appended, nothing else touched. That pending entry says "The runtime is unchanged too:@object-ui/corestill converts a STORED bare-string option into a pair when it reads the document, and logs a deprecation warning. That conversion keeps its own retirement schedule (objectstack#7917)." — a sentence this PR makes false in the same release. The note (PR docs(changeset): date-note six pending entries that PRs #10793, #10802 and #10821 made false (objectui#10877) #10891 form, dated 2026-09-29) says which sentences no longer hold and what ships. Authorised by the maintainer's ruling of 2026-09-29, option 「Allow the appended note (Recommended)」, recorded on the card as5883360525; the option reads, verbatim: "You allow developers to append dated correction notes to pending .changeset files (appended only, no existing line or frontmatter edited). The feat(core)!: retire the bare-stringglobalFilters[].optionslift (objectui#4356) #10930 developer adds the W4 note inside that PR, then it merges main, gets a final review, and merges." Measured: 16 insertions, 0 deletions; frontmatter md5ac1bfd0472c0d0b981d923c32d435f18before and after; whole-file md594edd5855709afe137f9184c630f298c→5c8f0508bed5ed6f5b76254bb74d622d; 0 angle-bracket characters;diffagainstorigin/mainshows only the appended block.Untouched on purpose:
examples/schema-catalog/test/plugin-dashboard-global-filters-spec.test.ts(theGlobalFilterSchemacatalog guardrail from PR #4601 — the positive control; its docblock narrates history and spec facts only, and stays true); the assertions of the twopackages/typestests that pin the SPEC's refusal ofoptions: ['EMEA'](rejection pins, not lift pins);@objectstack/spec;content/docs/releases/**;skills/**(no file there becomes false — measured below).Rulings executed
5511028727on objectui#4356) — Phase 2 on release cadence alone; no live-tenant survey, no migration entry.domain:spec @ objectuiseat, verbatim 「这个你也接管继续 https://github.com/objectstack-ai/objectui/issues/4356」.5883360525) — authorises the W4 append in exactly the form landed here.The audible-skip question — governing text found
The dispatch asked whether any governing text requires the post-removal skip to be audible. What was read, verbatim:
AGENTS.md, "Degradation log levels —warnvserror": "Functional degradation →warn/info. A screen is missing, a trigger is not armed, a capability is not enabled … The system is visibly smaller than it should be, and the next person to use the missing thing finds out." and "prefer failing to falling back, and when you must fall back, say what was lost." — Aselectfilter whose members were all bare strings now renders with an empty option list (DashboardFilterBar:const authored = def.options?.length ? def.options : (dynamicOptions ?? [])): visibly smaller, functional,warn-level — and "say what was lost" is the operative sentence.docs/adr/0078-no-silently-inert-metadata.md, §4 "Loud-not-silent at the runtime seam": "Where the runtime skips an inert instance … it emits a dev-mode diagnostic at registration rather than swallowing it silently. This is the only layer that is authoring-tool-agnostic". The ADR's completeness gate (§1) is about Zod-VALID instances and a bare string is Zod-invalid, but §4 is exactly the case here: a STORED dashboard is the one document no author-time gate re-reads.AGENTS.md: no clause on audibility; #0.1 (contract-first, no lenient fallback) is honoured — a diagnostic is not a fallback, and nothing is lifted.buildFilterCondition— "anything else → skipped with a console warning, never silently downgraded";buildWidgetScopedFilterskips an unknown default field "with a console warning instead of emitting a query".Conclusion: yes, at
warn, dev-mode, deduped — the smallest audible path consistent with the ruling. Implemented aswarnDroppedShorthandOptions: sameisDev()gate and memo key as Phase 1 (no per-render flood), new sentence (DROPPED, "no longer lifted", the rewrite), no lift. Consequence: the memo and thereforeresetDashboardFilterWarningsstay. The contract review (5866198610) judged this RIGHT; dev-only rather than always-on is the same review's answer for this PR.Measurements against the dispatch's mechanism assumptions
origin/main733fd5ac6— confirmed: theelsearm pushed{ value: String(o), label: String(o) }and calledwarnShorthandOptionsfor EVERY non-nullish primitive. Pinned now indrops EVERY non-object member — a number and a boolean, not only a string.resetDashboardFilterWarnings—git greprepo-wide: importers aredashboard-filters.test.tsonly (plus apackages/core/CHANGELOG.mdparagraph); it clears onlywarnedShorthandOptions. The inference "with the memo gone it has no job" is falsified by (4): the memo does not go. It stays, with a docblock saying why.globalFilters(tests included; positive control: the same matcher found the callout it was meant to find): lift-pinning bare-string members existed only indashboard-filters.test.ts— six members across five tests (the dispatch's list lacked the bare'lost'of the mixed-array test; the director's "three" and the dispatch's "five" were both stale). All six retired or flipped. The twopackages/typesoccurrences pin the spec's REFUSAL and keep their assertions.git grep -n -i -E 'lift' -- ':!**/CHANGELOG.md' ':!content/docs/releases/**' ':!pnpm-lock.yaml' | grep -i -E 'shorthand|bare[- ]string|4356'over the whole tracked tree (a superset of every package'sfiles[],content/docs,skills/,examples/): 20 hits onbc4ea976ewithpackages/plugin-dashboard/README.md's "still lifted at runtime" line as the found positive control; 18 ona89065402— every hit either states the retirement (this PR's changeset, source, docs and test text) or is the unrelateddata-array shorthand inplugin-grid/plugin-map. The first-round sweep'soption|filterpost-filter is what hid the README line (W1) and thei18nLabelcomment (W5); the rework sweep has no post-filter. When the sweep runs on a ref, the ref prefix is stripped before the second grep — this branch's name contains both "shorthand" and "lift". Three other sites authoroptions: ['…']onselectFIELDS (form / object field schemas:content/docs/guide/expressions.md,content/docs/guide/schema-playground.md,skills/objectui/guides/project-setup.md) — a different contract, out of this card, untouched; the governedskills/**file does not become false.packages/core/package.jsonis17.6.0;.changeset/config.jsonhas@object-ui/corein thefixedgroup; aminorchangeset makes the next release ≥ 17.7.0; release PR chore: release packages #5400 is open (not touched).git grep -n -i -E 'normalizeFilterOptions|bare[- ]string|shorthand|lift' -- '.changeset/*.md'filtered to option /globalFilters/ 4356 / 7917 context: exactly ONE pending sentence this PR makes false, the7759paragraph quoted above (W4). Its earlier sentence "the bare-string option shorthand (options: ['EMEA']) is refused withinvalid_type" stays true. Every other hit is a different contract (export-options bare array, object-map / ganttdataarrays, kanbancolumns, picklist / fieldoptions, widget shorthand). The nine entries thechangeset-claim-re-readcomment5865883972lists were re-read: none says anything about the options lift; all still true.Merges of
main1daf7edf7—origin/main29c76e573(PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934, objectui#10916). No conflicts;report-chart-query-spec-parity.test.tsauto-merged (disjoint hunks); both sides verified — my header-docblock correction present, PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934's pin text byte-for-byte asmainhas it.a1058f90e—origin/main2eaf5be27(70 commits, among them PR docs: cite landing commits where pending changesets and source comments name 23 objectui issues that answer 404, and re-qualify 19 bare objectstack numbers (objectui#10803, batch 6) #10914's citation edits indashboard-filters.tsand threecomplex.zod.tscommits). No conflicts; after the merge the files differing fromorigin/mainare exactly this PR's ten, andgit diff origin/mainoncomplex.zod.tsand on the parity test is exactly this PR's docblock hunks.No rebase, no amend, no force-push at any point; every push was a plain
git push.Tests
Red-first (at
bc4ea976e, the new pins against the UNTOUCHED source,pnpm exec vitest run packages/core/src/utils/__tests__/dashboard-filters.test.ts, exit 1):The three that stayed green on the old code are the ones the block labels as non-discriminating (different-filter memo, canonical silence, memo reset). Green after the source change, same command:
Tests 49 passed (49).Final head
a89065402, onepnpm exec vitest runfrom the repo root under the shared verify lock overpackages/core/plus the twopackages/typesglobal-filter tests and the twoplugin-dashboarddashboard-filter tests this PR touched:Test Files 188 passed (188)·Tests 3725 passed | 27 skipped (3752), exit 0. The changeset script tests (check-changeset-{presence,no-major,fixed,overwrite,claims},check-pending-changeset-literals,changeset-polarity-census):Test Files 7 passed (7)·Tests 292 passed (292), exit 0.Consumer sweep at
bc4ea976e(resolveDashboardFilterDefsconsumers re-derived bygit grep:core,types,plugin-dashboard,plugin-designer,app-shell,examples/schema-catalog), exit 0:packages/core182 files / 3643 tests;packages/plugin-dashboard147 / 1346 (6 pre-existing skips);packages/types3 / 58;packages/plugin-designer(DashboardDesignPage.presetAlias) 1 / 4;packages/app-shell(DashboardWidgetInspector.test.tsx) 1 / 19;examples/schema-catalog(the guardrail) 1 / 9 — totals335 passed (335)·5073 passed | 6 skipped (5079). No consumer fixture asserted the old lift. The code has not changed since that head (every later commit is prose or a merge), and the final-head run above re-coverscore,typesand the touchedplugin-dashboardtests.Gates on the final head
a89065402(hand-derived from rootpackage.jsonand.github/workflows/; exit codes captured before any pipe)pnpm --filter '@object-ui/core^...' buildthenpnpm --filter @object-ui/core buildpnpm --filter @object-ui/core type-check·pnpm --filter @object-ui/types type-checkpnpm --filter @object-ui/core lint·pnpm --filter @object-ui/types lintno-explicit-any— on the touched files theas anycounts equal the base (2 / 10 / 0), unchanged sincebc4ea976epackages/core/+ the four touched dashboard-filter tests (under the lock)node scripts/check-changeset-presence.mjs2eaf5be27, 7 published source files of 3 released packages, 1 changeset addednode scripts/check-changeset-no-major.mjs·check-changeset-fixed.mjs·check-changeset-claims.mjs·check-changeset-overwrite.mjspnpm check:pending-changeset-literalsnode scripts/check-control-bytes.mjsnode scripts/check-new-cross-file-line-citations.mjsnode scripts/check-governed-queue-guard.mjs --test(every touched path)pnpm check:doc-fences·check:doc-types·check:doc-example-ids·check:spec-symbols·node scripts/check-doc-links.mjs·check:test-path-roots·check:vi-mock-specifiers·check:phantom-depsbc4ea976e/98623a24d; the files they read have not changed sincepnpm check:doc-snippets·pnpm check:readme-exportsPRECONDITION NOT MET(unbuilt packages); CI's Doc Snippet Type Check and README Exports are the readings. The docs hunk changes no fenced block; the README hunk is a JSON comment with no import bindingNot run locally, CI-owned: repo-wide
pnpm lint,pnpm test,pnpm type-check(the public type face is unchanged — no export added or removed, no signature changed; emitted declarations differ in JSDoc text only),pnpm check, the i18n gates,plugin-dashboard's own type-check (comment-only hunks in two test files).Deviations
packages/types/src/zod/complex.zod.tswas outside claim5865368364's original file surface; edited under the bounded in-place-fix exemption (same defect class; mechanical; no other open PR on the file; inside core's type-checked closure) and accepted by the seat's addendum5865980488. The later addenda5866223135,5866346457,5866480991added the W1–W5 files and the7759changeset.option|filterpost-filter; corrected above.5866443287), held and not re-routed; it landed only after the maintainer's 2026-09-29 ruling.Acceptance notes
warnDroppedShorthandOptionsis dev-only, exactly as the Phase 1 warning was; the module's other skip warnings are not dev-gated. Always-on is a one-line product decision the review left to the maintainer; not this PR's.selectschemas teachingoptions: ['…']are a different contract and were not judged here — observation only, not filed.packages/core/CHANGELOG.mdandpackages/plugin-dashboard/CHANGELOG.mdnarrate the lift as history; published record, not re-addressed (the CHANGELOG clause under AGENTS.md [WIP] Update documentation for project #11: never re-address a paragraph in a published CHANGELOG).mainand is merged into this branch; no other open PR touches this PR's files.Generated by Claude Code