Skip to content

feat(core)!: retire the bare-string globalFilters[].options lift (objectui#4356) - #10930

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-4356-retire-shorthand-lift
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-4356-retire-shorthand-lift

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4356

Clause-②: yes — published runtime behaviour is removed (the bare-string lift, and the exported resetDashboardFilterWarnings if it retires with its memo), per the execution parameters in 5511028727.

The claim's "if" resolved to stays: the memo survives (it now dedupes the dropped-member warning, see "The audible-skip question"), so resetDashboardFilterWarnings keeps its job and its export; the changeset names no removed export.

Final head a890654027a69911412efbcb146e326da8476256. Commits on this branch since the branch point 733fd5ac6: bc4ea976e (the retirement), 98623a24d (rework round 1: W1–W3), e0647486b (patch round 2: W5), the two merges of origin/main (1daf7edf7 at 29c76e573, a1058f90e at 2eaf5be27), and a89065402 (W4, the dated note). Implemented in session session_012UwY3ahMixEFkfTUxMVkYm.

What changed

Phase 2 of objectui#4356 — the runtime bare-string globalFilters[].options lift retires.

  • packages/core/src/utils/dashboard-filters.ts — in normalizeFilterOptions, 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 no options, 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-filter warnDroppedShorthandOptions names the filter, the dropped members and the rewrite, and promises no lift. The DashboardFilterDef.options JSDoc, the normalizeFilterOptions docblock, the memo docblock and the resetDashboardFilterWarnings docblock 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 shorthand became normalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it; the [#4356] describe is retired: dropped, not lifted — a shorthand-only filter yields no options; 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 an I18nLabel map are untouched and silent; resetDashboardFilterWarnings re-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 exported GlobalFilterSchema said 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's 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, 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 }.
  • Three test docblocks state the retirement instead of the lift (W2): 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's JoinedReportBlock pin, 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-string globalFilters[].options is no longer lifted; use { value, label } objects, the spec's form". Never major (AGENTS.md §9 version policy: the break is stated in the body, the bump stays minor); check-changeset-no-major exit 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/core still 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 as 5883360525; 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-string globalFilters[].options lift (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 md5 ac1bfd0472c0d0b981d923c32d435f18 before and after; whole-file md5 94edd5855709afe137f9184c630f298c → 5c8f0508bed5ed6f5b76254bb74d622d; 0 angle-bracket characters; diff against origin/main shows only the appended block.

Untouched on purpose: examples/schema-catalog/test/plugin-dashboard-global-filters-spec.test.ts (the GlobalFilterSchema catalog guardrail from PR #4601 — the positive control; its docblock narrates history and spec facts only, and stays true); the assertions of the two packages/types tests that pin the SPEC's refusal of options: ['EMEA'] (rejection pins, not lift pins); @objectstack/spec; content/docs/releases/**; skills/** (no file there becomes false — measured below).

Rulings executed

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:

  • objectstack AGENTS.md, "Degradation log levels — warn vs error": "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." — A select filter 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.
  • objectstack 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.
  • objectui 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.
  • The module's own convention, same file: buildFilterCondition — "anything else → skipped with a console warning, never silently downgraded"; buildWidgetScopedFilter skips 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 as warnDroppedShorthandOptions: same isDev() 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 therefore resetDashboardFilterWarnings stay. 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

  1. Lift live on origin/main 733fd5ac6 — confirmed: the else arm pushed { value: String(o), label: String(o) } and called warnShorthandOptions for EVERY non-nullish primitive. Pinned now in drops EVERY non-object member — a number and a boolean, not only a string.
  2. resetDashboardFilterWarnings — git grep repo-wide: importers are dashboard-filters.test.ts only (plus a packages/core/CHANGELOG.md paragraph); it clears only warnedShorthandOptions. 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.
  3. Fixture recount, repo-wide over every file containing globalFilters (tests included; positive control: the same matcher found the callout it was meant to find): lift-pinning bare-string members existed only in dashboard-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 two packages/types occurrences pin the spec's REFUSAL and keep their assertions.
  4. Skip audibility — see the section above.
  5. Teaching sites — swept at the final head with 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's files[], content/docs, skills/, examples/): 20 hits on bc4ea976e with packages/plugin-dashboard/README.md's "still lifted at runtime" line as the found positive control; 18 on a89065402 — every hit either states the retirement (this PR's changeset, source, docs and test text) or is the unrelated data-array shorthand in plugin-grid / plugin-map. The first-round sweep's option|filter post-filter is what hid the README line (W1) and the i18nLabel comment (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 author options: ['…'] on select FIELDS (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 governed skills/** file does not become false.
  6. Release — packages/core/package.json is 17.6.0; .changeset/config.json has @object-ui/core in the fixed group; a minor changeset makes the next release ≥ 17.7.0; release PR chore: release packages #5400 is open (not touched).
  7. Pending changesets — swept on the merged tree (2326 pending entries) with 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, the 7759 paragraph quoted above (W4). Its earlier sentence "the bare-string option shorthand (options: ['EMEA']) is refused with invalid_type" stays true. Every other hit is a different contract (export-options bare array, object-map / gantt data arrays, kanban columns, picklist / field options, widget shorthand). The nine entries the changeset-claim-re-read comment 5865883972 lists were re-read: none says anything about the options lift; all still true.

Merges of main

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):

× normalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it (objectui#4356)
× DROPS a bare string: a shorthand-only filter resolves with no `options`, and says so
× keeps ONLY the object members of a MIXED array, and names only the dropped ones
× drops EVERY non-object member — a number and a boolean, not only a string
× warns ONCE per offending filter across repeated renders, and drops on every one of them
AssertionError: expected [ …(2) ] to be undefined
AssertionError: expected [ Array(2) ] to deeply equal [ { value: 'won', label: 'Won' } ]
AssertionError: expected [ { value: '42', label: '42' }, …(2) ] to deeply equal [ { value: 'gold', label: 'Gold' } ]
 Test Files  1 failed (1)
      Tests  5 failed | 44 passed (49)

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, one pnpm exec vitest run from the repo root under the shared verify lock over packages/core/ plus the two packages/types global-filter tests and the two plugin-dashboard dashboard-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 (resolveDashboardFilterDefs consumers re-derived by git grep: core, types, plugin-dashboard, plugin-designer, app-shell, examples/schema-catalog), exit 0: packages/core 182 files / 3643 tests; packages/plugin-dashboard 147 / 1346 (6 pre-existing skips); packages/types 3 / 58; packages/plugin-designer (DashboardDesignPage.presetAlias) 1 / 4; packages/app-shell (DashboardWidgetInspector.test.tsx) 1 / 19; examples/schema-catalog (the guardrail) 1 / 9 — totals 335 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-covers core, types and the touched plugin-dashboard tests.

Gates on the final head a89065402 (hand-derived from root package.json and .github/workflows/; exit codes captured before any pipe)

gate exit note
pnpm --filter '@object-ui/core^...' build then pnpm --filter @object-ui/core build 0 / 0 dependency closure first, so the type-checks read rebuilt declarations
pnpm --filter @object-ui/core type-check · pnpm --filter @object-ui/types type-check 0 / 0
pnpm --filter @object-ui/core lint · pnpm --filter @object-ui/types lint 0 / 0 0 errors; the warnings are pre-existing no-explicit-any — on the touched files the as any counts equal the base (2 / 10 / 0), unchanged since bc4ea976e
vitest packages/core/ + the four touched dashboard-filter tests (under the lock) 0 188 files, 3725 passed, 27 skipped
vitest changeset script tests ×7 0 292 passed
node scripts/check-changeset-presence.mjs 0 11 files changed vs merge-base 2eaf5be27, 7 published source files of 3 released packages, 1 changeset added
node scripts/check-changeset-no-major.mjs · check-changeset-fixed.mjs · check-changeset-claims.mjs · check-changeset-overwrite.mjs 0 ×4 claims and overwrite are report-only; the W4 append is overwrite's documented "correcting a declaration on purpose" case
pnpm check:pending-changeset-literals 0 "No test source names a pending changeset"
node scripts/check-control-bytes.mjs 0 9331 tracked text files
node scripts/check-new-cross-file-line-citations.mjs 0 0 new citations (the W4 note cites by quoted sentence)
node scripts/check-governed-queue-guard.mjs --test (every touched path) 0 NOT GOVERNED
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-deps 0 ×8 measured at bc4ea976e / 98623a24d; the files they read have not changed since
pnpm check:doc-snippets · pnpm check:readme-exports 2 / 1 NOT MEASURED locally — both PRECONDITION 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 binding

Not 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.ts was outside claim 5865368364'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 addendum 5865980488. The later addenda 5866223135, 5866346457, 5866480991 added the W1–W5 files and the 7759 changeset.
  • The first-round teaching sweep under-reported (W1, W5) because of its option|filter post-filter; corrected above.
  • The W4 append was first refused by the auto-mode classifier ("Modify Shared Resources", record 5866443287), held and not re-routed; it landed only after the maintainer's 2026-09-29 ruling.

Acceptance notes


Generated by Claude Code

…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
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 9 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6939-kanban-column-cards.md

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/7113-chart-data-model.md

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7703-chatbot-dark-keys-retired.md

  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

.changeset/8114-detail-tab-activity-timeline.md

  • names plugin-dashboard/README.md → packages/plugin-dashboard/README.md — edited by this change

    README.md ships in this package's files, so the example went out in every tarball. DetailTabs renders a tab's content through ANGLE-BRACKETS(SchemaRenderer schema={toRenderableSchema(tab.content)} /), which makes content.type an SDUI node position judged by the component registry — so a reader copying the tab got the registry's Unknown component type panel (OBJUI-001) where the timeline should be. Same shape as the line-chart widget in plugin-dashboard/README.md (objectui#7896's census; fixed by objectui#7951) and the fourth known instance.

.changeset/8415-filter-builder-condition-id.md

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-complex.md

.changeset/8478-zod-pins-form-layout.md

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    The remaining 6 addresses (zod/complex.zod.ts) stayed out of scope for this PR and returned to the queue rather than riding this PR's scope — the card did not close here.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 2eaf5be27 (merge-base with origin/main): 9 file(s) changed outside .changeset/, read against 1719 pending declaration(s) that publish a body (2323 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3096.0 KB 3104.5 KB
Main entry chunk (gzip) 148.6 KB 350 KB
Entry file index-2BDgfYvV.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.70KB 2.23KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Spec Main Shape Gate is red on this head (bc4ea976e5), and the red is not this PR's. From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm.

The at-tier contract review of this head is in progress.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bc4ea976e5e142ffbeb4583326298fc58eaf69ba
Local-runs: none

Inputs: card #4356 (body and all 16 comments, the os-dev-report 5865928783 and the claim addendum 5865980488 included), PR #10930 (body, file list, net diff origin/main 733fd5ac6...bc4ea976e, five files, +197/-148), the check-runs on the head (read 2026-09-28T08:17Z). Governing text read at current HEAD: objectui AGENTS.md (#0.1, #11, §9 版本号策略, the changeset-presence rule), objectstack AGENTS.md ("Degradation log levels", Post-Task Checklist item 3), docs/adr/0078-no-silently-inert-metadata.md §4. Read-only git only (git grep / git show / git diff on the fetched ref); nothing built, run or re-run.

① Derived judgments

Right.

  1. Accept-set narrowing (runtime). resolveDashboardFilterDefs → normalizeFilterOptions: a globalFilters[].options[] member that is not an object (string, number, boolean) used to be lifted to { value: String(o), label: String(o) } and now yields no option — a shorthand-only filter resolves with options undefined, a mixed array keeps only its object members. The nullish and value-less skips are unchanged. The runtime now reads the document exactly as GlobalFilterSchema does: one strict contract, no renderer dialect beside it (objectui AGENTS.md #0.1). This is the end state the rulings named — 「7917 ②」 (2026-08-12) and 「objectstack#7917 不考虑现有数据」 (2026-09-02, director release 5511028727). No lift, alias, fallback or migration is added. RIGHT.
  2. Whole arm, not the string case. Numbers and booleans, which Phase 1 also lifted through String(o), are dropped too and pinned (drops EVERY non-object member); the warning spells "42" and 42 distinguishably. They were never spec-valid. RIGHT.
  3. Public export surface of @object-ui/core unchanged. packages/core/src/index.ts re-exports ./utils/dashboard-filters.js; the module's export lines are identical between base and head (DashboardFilterDef, DateRangeValue, DATE_RANGE_PRESETS, resetDashboardFilterWarnings, resolveDashboardFilterDefs, dashboardFilterVariableDefs, buildFilterCondition, buildWidgetScopedFilter). resetDashboardFilterWarnings keeps a job — the memo it clears now dedupes the dropped-member warning — so the dispatch's conditional ("if it retires with its memo") resolved to "stays", and the changeset says so. No signature moved; normalizeFilterOptions is module-private. RIGHT.
  4. The skip is audible, at the right level, and is not a fallback. warnDroppedShorthandOptions: dev-mode, once per filter per session (same memo key discipline as Phase 1), names the filter, the dropped members and the { value, label } rewrite, and promises no lift — the negative pin not.toContain('lifted here') guards the sentence. Governing text supports it: objectstack AGENTS.md "Degradation log levels" puts a functional degradation (a visibly emptier select) at warn and says "when you must fall back, say what was lost"; ADR-0078 §4 asks the runtime seam to emit a dev-mode diagnostic where it skips rather than swallow it. A diagnostic is not a lenient fallback, so #0.1 is intact. RIGHT.
  5. @object-ui/types. GlobalFilterSchema JSDoc corrected in place; the schema itself is untouched, so the emitted declaration changes in comment text only. The { preset } alias (liftLegacyGlobalFilterDefault) is left on its own retirement window and the docblock now says so. RIGHT.
  6. Docs callout (content/docs/guide/dashboard-filters.md) flipped from "Deprecated … still lifted" to "Not accepted … no longer lifts … yields no option … rewrite". No fenced block changed. RIGHT.
  7. Pins. Five DROP pins reported red-first against the untouched source and green after; the once-memo, different-filter, canonical-silence (honestly labelled vacuous) and reset pins are retained. The diff matches the report. RIGHT.

Wrong.

  • W1 — a shipped README still promises the lift (verdict-carrying). packages/plugin-dashboard/README.md, the globalFilters example's options comment, at the head: 「The bare-string shorthand (["EMEA", …]) is deprecated: a STORED one is still lifted at runtime, and warns (objectui#4356).」 README.md is in that package's files, so it ships in the npm tarball and is the package page. This diff makes the sentence false and leaves the file untouched (byte-identical to main). It is the Phase-0 teaching site the survey named (5276031321, then rewritten by PR fix(core): bare-string filter options — docs/examples stop teaching it, runtime lift warns (#4356) #4601 to the current text) — known to this card's thread. It is the same defect class the dev repaired in complex.zod.ts under the in-place-fix exemption, and the seat's addendum 5865980488 states the rule in so many words: 「A published text that the change makes false is fixed in the same PR.」 The PR body's assumption-5 sweep claims packages/plugin-dashboard/README.md was covered by the pattern shorthand|still lifted|is lifted and yielded nothing beyond the callout; the line matches both shorthand and still lifted, so that measurement claim is falsified. WRONG.
  • W2 — three in-repo test docblocks now state a lift that does not exist. packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts (「NOT a runtime removal. normalizeFilterOptions … still lifts a bare string for STORED documents … The runtime half is filed separately.」), packages/types/src/__tests__/report-chart-query-spec-parity.test.ts (「⚠️ Retired at the SCHEMA only. normalizeFilterOptions … still lifts a stored bare-string option on read, with its deprecation warning」), packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx (「… which pins normalizeFilterOptions lifting it」). The PR body judged the two types tests "still true": their assertions are (they pin the spec's refusal), their prose is not. Not published; secondary. WRONG.
  • W3 — a stale header inside the PR's own hunk. packages/core/src/utils/__tests__/dashboard-filters.test.ts at the head carries both rule lines back to back: the Phase-1 「normalizeFilterOptions accepts a bare-string globalFilters[].options shorthand that @objectstack/spec rejects at publish #4356 — the bare-string options shorthand is DEPRECATED and says so.」 directly above the new 「normalizeFilterOptions accepts a bare-string globalFilters[].options shorthand that @objectstack/spec rejects at publish #4356 — … RETIRED: dropped, not lifted.」 The file the PR rewrote to stop describing the lift still opens the block by describing it. Secondary. 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

  • Changeset .changeset/retire-options-shorthand-lift-4356.md: '@object-ui/core': minor. RIGHT under objectui AGENTS.md §9 版本号策略: objectui's own breaking changes are declared minor with the break stated in the body, major is reserved for the objectstack cross-major sync and is mechanically refused (check-changeset-no-major). The body states the break, the FROM → TO mapping ('EMEA' → { value: 'EMEA', label: 'EMEA' }) and the one-line rewrite, which is what objectstack AGENTS.md Post-Task item 3 requires of a breaking changeset; the opening sentence is the director's ruled wording verbatim. @object-ui/core is 17.6.0 in the fixed group, so minor publishes at ≥ 17.7.0 and never as a 17.6.x patch — as ruled. skip-changeset is correctly not used.
  • Clause-②: yes — RIGHT: published runtime behaviour is removed, so yes, and yes takes at least minor, which is satisfied. No arm is written; Post-Task item 3 allows at most one, so none is a legal spelling. Were one written, the accurate arm for a shrinking accept-set is (narrowing) — observation, not a fault. The line's conditional sub-clause about resetDashboardFilterWarnings resolved to "stays" and the changeset names no removed export: consistent.
  • @object-ui/types is not named in the changeset though its emitted declaration text changes. Fixed group bumps together, the hunk is JSDoc only with no type moved, and the presence and fixed-group gates accepted it. Acceptable; the fix for W2 adds test-file hunks in packages/types and packages/plugin-dashboard, which the same one-changeset shape already covered here — re-read the presence gate's verdict on the new head rather than assume.
  • Gate verdicts on the head: Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report — all success.

③ Boundary flags

deviations in the os-dev-report 5865928783.

  1. packages/types/src/zod/complex.zod.ts outside the claimed surface — answered: accepted. The seat amended the claim's file surface in 5865980488, and on this reading the four conditions hold (JSDoc-only hunk, same class as the in-scope docblocks, inside core's type-check closure, no other open PR on the file per the seat's file-list read). Note that the rule the seat wrote to accept it is the rule W1 fails.
  2. check:doc-snippets not measured locally — answered: closed by CI. "Doc Snippet Type Check" is success on the head; the docs hunk has no fenced block.
  3. Assumption 2 falsified (the reset export stays) — answered: confirmed by the base/head export comparison in ①.3; Clause-② line and changeset are consistent with it.
  4. Assumption 3 recount (six bare-string fixtures in five tests) — answered: consistent with the diff (one fixture in each of four tests, two in the different-filter test); every one flipped or retired; the two packages/types occurrences are spec-refusal pins and are correctly untouched as assertions — their docblocks are W2.

open_questions.

  1. Audible skip — option A (dev-mode, deduped warning; memo and resetDashboardFilterWarnings stay) vs B (silent skip, delete the export). Answered: A, on the governing text already cited in ①.4; no escalation needed — the text answers it, and the module's own sibling skips (buildFilterCondition's unknown preset, buildWidgetScopedFilter's unknown field) already warn rather than swallow.
  2. Dev-only vs always-on — answered: A for this PR. It matches the Phase-1 gate it replaces and the actionKeys.ts precedent the module cites, and a removal card is not the place to introduce production console output. B is a one-line product decision for the maintainer; it does not block this PR and needs no ruling to land it — the seat may file it as its own card if wanted. Not escalated as blocking.

out_of_scope_findings. Select-FIELD options: ['…'] sites are a different contract — agreed, correctly untouched. CHANGELOGs narrating the lift as history — agreed, published record; the PR body's citation "AGENTS.md #11" for that point is mis-numbered (objectui #11 is the cite-by-content rule) — cosmetic, PR body only.

Check-runs on the head (43 runs at the reading above): 39 success; 3 skipped — Test (coverage), Test (coverage shard), matrix-conditional, and dependabot, not a dependabot PR; 1 failure — Spec Main Shape Gate. That gate compiles objectui against @objectstack/spec built from objectstack main (this run: dcd3bceaa068). Its annotation attributes the red to packages/types/src/__tests__/report-chart-query-spec-parity.test.ts, the true satisfies IsUnknown pin on SpecJoinedReportBlock ("TS1360: Type 'true' does not satisfy the expected type 'false'"): the spec has typed that schema upstream and the pin's own text calls this flip "the instruction: re-run the triage and burn it down". This PR leaves that file byte-identical to main; the workflow's own header says its subject is cross-repo drift, not the pull request's diff; and its run history shows every run on every branch and every merge-queue build in the visible window red on the same commit (a claude/issue-10916-joined-report-block-pin branch is already in flight). It is requirable and not required. Reported as it stands: not caused by this diff, and not this record's to clear.

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 { value, label }" sentence; (b) correct the three test docblocks in W2; (c) delete the stale Phase-1 header line in W3; (d) re-run the teaching sweep with an alternation-capable pattern (git grep -i -E) and quote its hits in the PR body; (e) push, and a new record is rendered on the new head. No code, assertion or changeset change is expected.

Implemented-by: claude/issue-4356-retire-shorthand-lift
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

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
@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3096.0 KB 3104.5 KB
Main entry chunk (gzip) 148.6 KB 350 KB
Entry file index-2BDgfYvV.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.70KB 2.23KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3096.0 KB 3104.5 KB
Main entry chunk (gzip) 148.6 KB 350 KB
Entry file index-2BDgfYvV.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.70KB 2.23KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…o claude/issue-4356-retire-shorthand-lift

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3096.0 KB 3104.5 KB
Main entry chunk (gzip) 148.6 KB 350 KB
Entry file index-2BDgfYvV.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.70KB 2.23KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

This was referenced Sep 28, 2026
…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3105.5 KB 3149.4 KB
Main entry chunk (gzip) 149.4 KB 350 KB
Entry file index-pKfO7ATe.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.72KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.02KB 14.87KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 163.08KB 42.10KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.59KB 22.47KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.30KB 6.99KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a890654027a69911412efbcb146e326da8476256
Local-runs: none

Inputs: card #4356 (body and all 25 comments — the rulings 5264143301 「7917 ②」, 5511028727 (director release) and 5883360525 (W4 append); claim 5865368364 and its addenda 5865980488, 5866223135, 5866346457, 5866480991; the six os-dev-reports 5865928783, 5866362802, 5866443287, 5866532227, 5866654956, 5883501541), PR #10930 (body, the 11-file list — identical to the diff's, all 8 comments including the earlier record 5866198610 FAIL on bc4ea976e and the changeset-claim-re-read 5865883972, and the net diff origin/main 2eaf5be27...a89065402, +237/−171), and the check-runs on the head. Governing text read at current HEAD: objectui AGENTS.md (#0.1, #9, #11, §9 版本号策略, the changeset-presence rule, the dispatched-PR draft rule), objectstack AGENTS.md ("Degradation log levels", Post-Task Checklist item 3, the packages/*/CHANGELOG.md row), objectstack docs/adr/0078-no-silently-inert-metadata.md §4; objectui docs/adr/** holds no ADR on this surface. Read-only git only — the branch fetched into a review ref of its own, then git diff / git show / git grep / git merge-base on it; nothing built, run or re-run.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged.

  1. Runtime accept-set — @object-ui/core normalizeFilterOptions (module-private, reached through the exported resolveDashboardFilterDefs). A globalFilters[].options[] member that is not an object — string, number or boolean — used to yield { value: String(o), label: String(o) } under a deprecation warning; on this head it yields nothing and is recorded for one warning. Nullish members and value-less objects are skipped exactly as before; object members normalize as before (value stringified, label carried through as a string or an I18nLabel map). So a shorthand-only filter resolves with options undefined and a mixed array keeps only its object members; the accept-set shrinks to the spec's object arm. RIGHT — this is the end state 「7917 ②」 named and the director release 5511028727 dispatched under 「objectstack#7917 不考虑现有数据」: no lift, alias, fallback or migration entry anywhere (#0.1). Retiring the WHOLE non-object arm rather than the string case is right too — Phase 1 lifted every primitive through String(o), so a string-only removal would have left a number/boolean dialect; pinned by drops EVERY non-object member. Observation, not a fault and not this card's: the object arm still fills a missing label with String(value) while the spec refuses a label-less option (the pending 7759 entry states that refusal). That tolerance predates this card and the card, the director release and the changeset all name the bare-string lift only; it is the same #0.1 class and can be its own card.
  2. The dev-mode warning. warnShorthandOptions (whose text promised "Still lifted here") is deleted — no residue of the name anywhere on the head — and warnDroppedShorthandOptions replaces it: the same isDev() gate (NODE_ENV !== 'production'), the same warnedShorthandOptions memo keyed on filter name plus the spelled members (strings JSON-quoted, other primitives String()ed, so "42" and 42 do not collide), one console.warn through warnDateFilter per offending filter per session that names the filter, the dropped members, says DROPPED and "no longer lifted (objectui#4356)", prints the { value, label } rewrite and promises no lift (the negative pin not.toContain('lifted here') guards that). RIGHT: a select rendering with an empty option list is a functional degradation, which objectstack AGENTS.md "Degradation log levels" puts at warn with "when you must fall back, say what was lost"; ADR-0078 §4 asks the runtime seam to emit a dev-mode diagnostic where it skips rather than swallow; the module's own sibling skips (buildFilterCondition, buildWidgetScopedFilter) already warn. A diagnostic is not a lenient fallback, so #0.1 is intact; the memo protects the per-render path. Consequence: the memo and resetDashboardFilterWarnings stay, and the changeset says so.
  3. Public export surface of @object-ui/core — unchanged. packages/core/src/index.ts re-exports ./utils/dashboard-filters.js; the module's export lines are identical by content between base and head (DashboardFilterDef, DateRangeValue, DATE_RANGE_PRESETS, resetDashboardFilterWarnings, resolveDashboardFilterDefs, dashboardFilterVariableDefs, buildFilterCondition, buildWidgetScopedFilter). No signature moved. The emitted declarations change in JSDoc text only (DashboardFilterDef.options, the memo and resetDashboardFilterWarnings docblocks now describe the drop, not a lift). RIGHT; the dispatch's conditional ("if it retires with its memo") resolved to "stays", and the PR body and changeset both say so.
  4. GlobalFilterSchema docblock (packages/types/src/zod/complex.zod.ts, +11/−12, comment only). The "still LIFTS a stored bare-string option on read" paragraph is replaced by "no longer lifts … the SCHEMA and the READ PATH now refuse the same spelling"; the { preset } paragraph now reads as "the split options has now left behind", still pointing at liftLegacyGlobalFilterDefault in ../dashboard-filter-alias.ts, which is present on the head, so that sentence stays true. The schema itself is untouched; the text ships in @object-ui/types' emitted declarations. RIGHT. Outside the claim's original surface — accepted by addendum 5865980488; ③ below.
  5. The docs guide (content/docs/guide/dashboard-filters.md). The callout flipped from "Deprecated … still lifted … scheduled for removal" to "Not accepted … refused by GlobalFilterSchema at publish, and the runtime no longer lifts it either … a bare-string member yields no option … in development a single console.warn names the filter and the dropped members … Rewrite each string X as { "value": "X", "label": "X" }". Every sentence is true of the head; no other line of the guide names the shorthand; no fenced block changed (Doc Snippet Type Check success). RIGHT.
  6. The README (packages/plugin-dashboard/README.md, in that package's files, so it ships). The globalFilters example's comment now reads 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 }; the example's options array is already the pair form. True of the head; README Export Check success. RIGHT.
  7. Four test docblocks, comment only, no assertion moved (each hunk read): dashboard-global-filters-spec-binding.test.ts ("This card was the TYPE half only. The runtime half followed …"), report-chart-query-spec-parity.test.ts header ("Retired at the SCHEMA first, and since objectui#4356 at the READ PATH too"; nothing near the JoinedReportBlock pin, which arrived from main through the 1daf7edf7 merge), DashboardRenderer.filters.test.tsx ("no longer lifted by normalizeFilterOptions either"), DashboardFilterBar.i18nLabel.test.tsx (names the pin keeps ONLY the object members of a MIXED array, and names only the dropped ones, which exists verbatim in dashboard-filters.test.ts on the head). RIGHT.
  8. Pins in dashboard-filters.test.ts. The top-level lift pin now expects toBeUndefined(); the [#4356] block pins the drop (shorthand-only → no options; mixed → object members only, the warning naming only the dropped; a number and a boolean dropped; once across renders while dropping on every render; a different filter warns separately; canonical options and an I18nLabel map silent and intact; resetDashboardFilterWarnings re-arms). The old lift pin names ONLY the bare members of a MIXED array is gone, replaced by the DROP pin the i18nLabel comment now cites. The block's own header carries one rule line. Red-first output is quoted in the PR body against the untouched source. RIGHT.

Teaching sweep, re-derived read-only on the head — git grep -n -i -E 'lift' over the tracked tree with CHANGELOGs, content/docs/releases/** and the lockfile excluded, post-filtered shorthand|bare[- ]string|4356, the review ref's prefix stripped first: 18 hits. Thirteen are this PR's own changesets, source, docs, README and test text, all stating the retirement; five are the unrelated data-array shorthand in plugin-grid / plugin-map. None is false. skills/**, packages/core/README.md, content/docs/plugins/plugin-dashboard.mdx and the schema-catalog plugin-dashboard entries carry no bare-string globalFilters teaching.

W1–W5 of record 5866198610 and the seat's addenda, on this head:

  • W1 (README promising the lift) — RESOLVED. The two comment lines are rewritten (+4/−2); 「still lifted at runtime, and warns」 is absent from the head.
  • W2 (three test docblocks stating a lift) — RESOLVED. All three hunks are in the diff (+6/−5, +5/−4, +5/−5), prose only.
  • W3 (stale Phase-1 header in dashboard-filters.test.ts) — RESOLVED. The block opens with the single 「RETIRED: dropped, not lifted」 line; 「DEPRECATED and says so」 is absent from the head.
  • W4 (pending 7759 changeset made false) — RESOLVED, under ruling 5883360525; judged in ②.
  • W5 (DashboardFilterBar.i18nLabel.test.tsx comment) — RESOLVED. A +6/−6 comment-only hunk naming the current DROP pin.

② Semver level

  • .changeset/retire-options-shorthand-lift-4356.md — '@object-ui/core': minor. RIGHT under objectui AGENTS.md §9 版本号策略: an objectui break ships as minor with the break stated in the body, major is reserved for the objectstack cross-major sync and is refused mechanically (Changeset Bump Policy success). The body states the break in its first bold sentence, the FROM → TO ('EMEA' used to become { value: 'EMEA', label: 'EMEA' }; now no option), what a stored dashboard sees, the one-line rewrite, both rulings, and that resetDashboardFilterWarnings stays exported — each of which the diff bears out. The opening sentence is the director's ruled wording verbatim. @object-ui/core is 17.6.0 in the fixed group with @object-ui/types and @object-ui/plugin-dashboard, so one minor entry publishes all three at or above 17.7.0 and never as a 17.6.x patch, as ruled; the types and plugin-dashboard hunks are JSDoc, comments and a README comment, so no separate entry is owed (Changeset Declaration, Changeset Fixed Group Check success). No ADR-0087 disposition marker is owed here: that is objectstack's check:adr-0087-registration, and objectui's root package.json declares no such script.
  • Clause-②: on the PR body — Clause-②: yes — published runtime behaviour is removed (the bare-string lift, and the exported resetDashboardFilterWarnings if it retires with its memo), per the execution parameters in 5511028727. RIGHT: yes for a removed published behaviour, and yes takes at least minor, satisfied. No arm is written, a legal spelling (at most one; (narrowing) would be the accurate one — observation). The conditional sub-clause resolved to "stays", which the body's next paragraph says, and the changeset names no removed export — consistent. The line is the claim's, verbatim.
  • The W4 note on .changeset/7759-dashboard-header-global-filters-spec.md.
    • Inside the ruling's form? Yes. Measured on the two refs: origin/main's 52-line file is a byte prefix of the head's 68-line file (cmp over the base's length: identical), so no existing line was edited or deleted; numstat +16/−0; the frontmatter ('@object-ui/types': minor, '@object-ui/plugin-dashboard': patch) is byte-identical, md5 ac1bfd0472c0d0b981d923c32d435f18 on both refs; whole-file md5 94edd5855709afe137f9184c630f298c → 5c8f0508bed5ed6f5b76254bb74d622d, as the PR body states; 0 angle-bracket characters. The note is in the objectui#10877 / PR docs(changeset): date-note six pending entries that PRs #10793, #10802 and #10821 made false (objectui#10877) #10891 form the ruling names (⚠️ Dated note, DATE — … — objectui#NNNN. …, closing with "the text above is kept as the reading of this change"), the shape of the notes docs(changeset): date-note six pending entries that PRs #10793, #10802 and #10821 made false (objectui#10877) #10891 landed on main (for instance the objectui#10563 note on 10156-edit-form-writes-only-changed-fields.md). Dated 2026-09-29, the ruling's day and the commit's. The ruling's sequence — the note inside this PR, main merged, a final review — holds: origin/main 2eaf5be27 is an ancestor of the head and the head differs from origin/main in exactly the PR's 11 files.
    • True on this head? Yes. Each statement is borne out by the diff: normalizeFilterOptions stopped converting a stored bare-string option into a pair; a shorthand-only filter resolves with no options; a mixed array keeps only object members; a once-per-filter dev-mode console.warn names the filter and the dropped members; the window closed on the 2026-09-02 ruling; the two quoted sentences no longer hold; the validator's refusal of options: ['EMEA'] is unchanged and now matches the read path; "later in this same release" holds because both entries are pending in the same fixed group; the pointer to .changeset/retire-options-shorthand-lift-4356.md (PR objectui#10930) is correct. One precision note, not verdict-carrying: "a member that is not a { value, label } object now yields no option" is, read strictly, wider than the code — a { value } object with no label still yields an option (①.1's observation); the note's subject sentence is the stored bare-string option, and the PR's own changeset carries the qualifying "(a string, a number, a boolean)".
    • The only pending sentence this PR made false? Yes, on my own sweep of the head's pending .changeset/*.md (normalizeFilterOptions|bare[- ]string|shorthand|lifted|lifts, filtered to option|globalFilter|4356|7917): the 7759 paragraph is the only hit about globalFilters[].options. The other hits are the export-options bare array (4535, 6956, 7762), the object-map / gantt data arrays (8348), kanban columns (8990), picklist / field / detail-view options (8632, 6844, 10296), the widget shorthand (5709) and filterableFields (7779) — other contracts, still true. The nine entries the changeset-claim-re-read gate lists (5865883972, refreshed on this head) name complex.zod.ts or the README for other reasons and say nothing about the options lift.
  • Gate verdicts on the head: Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report — all success. Overwrite Report is report-only by design and the append is its documented legitimate-modification case.

③ Boundary flags

Every deviations, open_questions and out_of_scope_findings entry across the six reports, answered or escalated.

5865928783 (head bc4ea976e). deviations: (1) complex.zod.ts outside the claimed surface — answered, accepted: addendum 5865980488 amended the surface; the hunk is JSDoc only and ships in emitted declarations; the three complex.zod.ts commits that landed on main since merged cleanly (a1058f90e) and the file's diff against origin/main is exactly this hunk. (2) check:doc-snippets not measured — closed by CI: Doc Snippet Type Check success. (3) assumption 2 falsified, the reset export stays — confirmed by ①.3; the Clause-② line and changeset are consistent with it. (4) assumption 3 recount, six members in five tests — consistent with the diff; the two packages/types refusal pins keep their assertions. open_questions: (1) audible skip, A or B — A, on the governing text in ①.2; no escalation. (2) dev-only or always-on — A for this PR; always-on is a one-line product decision for the maintainer, not blocking, and the seat may file it as its own card. out_of_scope_findings: (1) select-FIELD options: ['…'] sites — another contract, correctly untouched, not judged here. (2) CHANGELOGs narrating the lift — published record, not re-addressed, per objectstack AGENTS.md's packages/*/CHANGELOG.md row and objectui #11's CHANGELOG clause.

5866362802 (head 98623a24d). deviations: (1) the i18nLabel comment outside the amended surface — resolved as W5 (addendum 5866480991, landed e0647486b). (2) check:readme-exports not measured — closed by CI: README Export Check success. (3) plugin-dashboard type-check not run — closed by CI: Type Check success on the head. (4) the first-round sweep's option|filter post-filter — accepted as the cause; the rework sweep has no post-filter and my own re-run agrees with its 18-hit reading. (5) PR body not patched — superseded: the final round refreshed the body (5883501541), which now carries W1–W5, the corrected sweep and the W4 ruling. open_questions: who lands the i18nLabel fix — A, surface amended and the dev patched it; done. out_of_scope_findings: the i18nLabel comment — landed as W5.

5866443287 (W4 blocked). deviations: (1) W4 not written, the classifier refusal held and not re-routed — resolved by ruling 5883360525; landed in a89065402 in the ruled form. (2) i18nLabel outstanding — W5, landed. (3) the PR body's 7759 bullet half right — superseded by the refreshed body. open_questions: who lands W4 — the maintainer's option: the dev, inside this PR; done.

5866532227 (head e0647486b) and 5866654956 (head 1daf7edf7). deviations: W4 held — resolved as above; PR body not patched / sentences to refresh — superseded by the refresh. No open questions, no out-of-scope findings.

5883501541 (head a89065402). deviations: (1) PR body refreshed via post-stamped — noted; I read the body: it matches the diff, carries 0 angle brackets, and its only closing keyword is the intended Fixes #4356. (2) check:doc-snippets / check:readme-exports not measured — closed by CI, both success. (3) no pr_ready, no auto-merge, no label write — correct: a dispatched PR stays draft until the seat lands it (objectui AGENTS.md, exception ② under the self-merge rule). No open questions, no out-of-scope findings.

Check-runs on the head (polled until none was in progress, then re-read right before this record was written): 43 runs — 40 success, 3 skipped, 0 failure, 0 in progress. The skipped three are Test (coverage) and Test (coverage shard), matrix-conditional, and dependabot, not a Dependabot PR. Every gate the earlier record read on bc4ea976e is green here, and the one it reported red — Spec Main Shape Gate — is success on this head: PR #10934's JoinedReportBlock pin fix is merged in through 1daf7edf7, as the seat's 5866118569 predicted. All eight Test shards, Test (dist pins), Type Check, Lint, Build & E2E, Build Docs, Bundle Analysis, Doc Snippet Type Check, README Export Check, Line Citation Gate, Control Byte Scan and the five changeset gates are success. Governed Surface Queue Guard is success on its pull_request leg (exit 0 by design; every touched path is NOT GOVERNED — .changeset/** is not a governed surface), and the PR carries no needs:contract-review label, so the merge_group leg's label check has nothing to refuse. The PR is a draft, as a dispatched PR must be until the seat lands it.

Implemented-by: claude/issue-4356-retire-shorthand-lift
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 04:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit a51fa0c Sep 29, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-4356-retire-shorthand-lift branch September 29, 2026 04:43
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
… 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>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

normalizeFilterOptions accepts a bare-string globalFilters[].options shorthand that @objectstack/spec rejects at publish

2 participants