feat(core)!: retire the bare-string globalFilters[].options lift (objectui#4356) - #10930
Conversation
…bjectui#4356)
`normalizeFilterOptions` no longer lifts a non-object `options` member to a
`{ value, label }` pair: the member yields no option, a shorthand-only filter
resolves with no `options`, and a mixed array keeps only its object members.
The Phase 1 deprecation warning (PR #4601), whose text promised the lift, goes
with the arm; a dev-mode, once-per-filter warning names the dropped members
and the rewrite instead (ADR-0078 section 4), so `resetDashboardFilterWarnings`
keeps its memo and stays exported.
Rulings: objectstack#7917 「7917 ②」 (2026-08-12) and 「objectstack#7917
不考虑现有数据」 (2026-09-02). The docs callout and the `GlobalFilterSchema`
docblock in @object-ui/types stop describing the lift. Changeset: core minor.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
The at-tier contract review of this head is in progress. |
Contract reviewServed-tier: Inputs: card #4356 (body and all 16 comments, the ① Derived judgmentsRight.
Wrong.
None of W1–W3 is code, a test assertion, a type or a changeset shape; each is a prose edit. They are what stands between this head and PASS. ② Semver level
③ Boundary flags
Check-runs on the head (43 runs at the reading above): 39 Remedy for the FAIL, bounded: (a) rewrite the two README comment lines to the "not accepted / no longer lifted / a bare-string member yields no option / rewrite as Implemented-by: VERDICT: FAIL |
…the README and three test docblocks (objectui#4356) Rework round 1 after contract review 5866198610 on bc4ea97 — prose only, no assertion, type, changeset level or Clause-② moves: - packages/plugin-dashboard/README.md (ships in `files`): the globalFilters example's options comment no longer says a stored bare string "is still lifted at runtime, and warns"; it says not accepted, no longer lifted, a bare member yields no option, a dev-mode warning names it, rewrite X as { value: X, label: X }. - packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts, packages/types/src/__tests__/report-chart-query-spec-parity.test.ts (its header docblock only) and packages/plugin-dashboard/src/__tests__/ DashboardRenderer.filters.test.tsx: docblocks that stated the lift now state the retirement. - packages/core/src/utils/__tests__/dashboard-filters.test.ts: the stale Phase 1 "DEPRECATED and says so" header above the RETIRED one is removed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…he retired mixed-array lift (objectui#4356)
Patch round 2 (W5, claim addendum 5866480991), comment only: the '(e) leaves
plain-string labels and options exactly as authored' case said the bare-string
shorthand "is deprecated and now warns" and that "the mixed-array lift has its
own pin" named `names ONLY the bare members of a MIXED array`, asserting a
lifted `{ value: 'lost', label: 'lost' }`. The shorthand is retired — a bare
member yields no option — and the pin is now `keeps ONLY the object members of
a MIXED array, and names only the dropped ones`. No assertion moves.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…o claude/issue-4356-retire-shorthand-lift Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…nce this PR made false (objectui#4356) W4 (claim addendum 5866346457), authorised by the maintainer's 2026-09-29 ruling 「Allow the appended note (Recommended)」 (card comment 5883360525): a dated note in the PR #10891 form is appended to .changeset/7759-dashboard-header-global-filters-spec.md, whose pending paragraph "The runtime is unchanged too: @object-ui/core still converts a STORED bare-string option into a pair when it reads the document, and logs a deprecation warning" would publish in the same release as this PR's "no longer lifted" entry. Append only: frontmatter byte-identical, 16 insertions, 0 deletions, no existing line edited. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs: card #4356 (body and all 25 comments — the rulings ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged.
Teaching sweep, re-derived read-only on the head — W1–W5 of record
② Semver level
③ Boundary flagsEvery
Check-runs on the head (polled until none was in progress, then re-read right before this record was written): 43 runs — 40 Implemented-by: VERDICT: PASS |
Fixes #4356
Clause-②: yes — published runtime behaviour is removed (the bare-string lift, and the exported
resetDashboardFilterWarningsif it retires with its memo), per the execution parameters in5511028727.The claim's "if" resolved to stays: the memo survives (it now dedupes the dropped-member warning, see "The audible-skip question"), so
resetDashboardFilterWarningskeeps its job and its export; the changeset names no removed export.Final head
a890654027a69911412efbcb146e326da8476256. Commits on this branch since the branch point733fd5ac6:bc4ea976e(the retirement),98623a24d(rework round 1: W1–W3),e0647486b(patch round 2: W5), the two merges oforigin/main(1daf7edf7at29c76e573,a1058f90eat2eaf5be27), anda89065402(W4, the dated note). Implemented in sessionsession_012UwY3ahMixEFkfTUxMVkYm.What changed
Phase 2 of objectui#4356 — the runtime bare-string
globalFilters[].optionslift retires.packages/core/src/utils/dashboard-filters.ts— innormalizeFilterOptions, the non-object arm no longer pushes{ value: String(o), label: String(o) }. A member that is not an object (string, number, boolean) yields NO option: a shorthand-only filter resolves with nooptions, a mixed array keeps only its object members. The nullish / value-less skips are unchanged. The Phase 1 deprecation warning (warnShorthandOptions, whose text said "Still lifted here") is deleted; a dev-mode, once-per-filterwarnDroppedShorthandOptionsnames the filter, the dropped members and the rewrite, and promises no lift. TheDashboardFilterDef.optionsJSDoc, thenormalizeFilterOptionsdocblock, the memo docblock and theresetDashboardFilterWarningsdocblock stop describing a lift. No lift, alias, fallback or migration is added anywhere.packages/core/src/utils/__tests__/dashboard-filters.test.ts— every lift-pinning test is flipped to pin the new contract (red-first, quoted under Tests):normalizes options: spec {value,label} objects AND bare-string shorthandbecamenormalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it; the[#4356]describe isretired: dropped, not lifted— a shorthand-only filter yields nooptions; a mixed array keeps only its object members; a number and a boolean are dropped too; warn ONCE across renders while dropping on every render; a different filter warns separately; canonical{ value, label }options and anI18nLabelmap are untouched and silent;resetDashboardFilterWarningsre-arms the memo. The stale Phase 1 header above the block is removed (W3).content/docs/guide/dashboard-filters.md— the "Deprecated: … still lifted by the runtime" callout became false and now reads "Not accepted": refused at publish, no longer lifted, a bare-string member yields no option, rewrite"X"as{ "value": "X", "label": "X" }. No fenced block changed.packages/types/src/zod/complex.zod.ts— the JSDoc of the exportedGlobalFilterSchemasaid the runtime "still LIFTS a stored bare-string option on read"; that sentence ships in@object-ui/types' emitted declarations and is corrected in place. Outside the claim's original surface; accepted by the seat's addendum, see "Deviations".packages/plugin-dashboard/README.md(W1, ships in that package'sfiles) — theglobalFiltersexample'soptionscomment no longer says a stored bare string "is still lifted at runtime, and warns"; it says NOT accepted, the runtime no longer lifts it (objectui#4356), a bare member yields no option, a dev-mode warning names it, rewrite each X as{ "value": X, "label": X }.packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts,packages/types/src/__tests__/report-chart-query-spec-parity.test.ts(its header docblock only — nothing near objectui#10916'sJoinedReportBlockpin, which PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934 rewrote and this branch has merged),packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx. A fourth (W5),packages/plugin-dashboard/src/__tests__/DashboardFilterBar.i18nLabel.test.tsx, no longer says the shorthand "is deprecated and now warns" nor names the renamed lift pin; it names the current DROP pin. No assertion moved in any of the four..changeset/retire-options-shorthand-lift-4356.md—@object-ui/core: minor, opening with the ruled wording: "bare-stringglobalFilters[].optionsis no longer lifted; use{ value, label }objects, the spec's form". Nevermajor(AGENTS.md §9 version policy: the break is stated in the body, the bump staysminor);check-changeset-no-majorexit 0..changeset/7759-dashboard-header-global-filters-spec.md(W4) — a dated note appended, nothing else touched. That pending entry says "The runtime is unchanged too:@object-ui/corestill converts a STORED bare-string option into a pair when it reads the document, and logs a deprecation warning. That conversion keeps its own retirement schedule (objectstack#7917)." — a sentence this PR makes false in the same release. The note (PR docs(changeset): date-note six pending entries that PRs #10793, #10802 and #10821 made false (objectui#10877) #10891 form, dated 2026-09-29) says which sentences no longer hold and what ships. Authorised by the maintainer's ruling of 2026-09-29, option 「Allow the appended note (Recommended)」, recorded on the card as5883360525; the option reads, verbatim: "You allow developers to append dated correction notes to pending .changeset files (appended only, no existing line or frontmatter edited). The feat(core)!: retire the bare-stringglobalFilters[].optionslift (objectui#4356) #10930 developer adds the W4 note inside that PR, then it merges main, gets a final review, and merges." Measured: 16 insertions, 0 deletions; frontmatter md5ac1bfd0472c0d0b981d923c32d435f18before and after; whole-file md594edd5855709afe137f9184c630f298c→5c8f0508bed5ed6f5b76254bb74d622d; 0 angle-bracket characters;diffagainstorigin/mainshows only the appended block.Untouched on purpose:
examples/schema-catalog/test/plugin-dashboard-global-filters-spec.test.ts(theGlobalFilterSchemacatalog guardrail from PR #4601 — the positive control; its docblock narrates history and spec facts only, and stays true); the assertions of the twopackages/typestests that pin the SPEC's refusal ofoptions: ['EMEA'](rejection pins, not lift pins);@objectstack/spec;content/docs/releases/**;skills/**(no file there becomes false — measured below).Rulings executed
5511028727on objectui#4356) — Phase 2 on release cadence alone; no live-tenant survey, no migration entry.domain:spec @ objectuiseat, verbatim 「这个你也接管继续 https://github.com/objectstack-ai/objectui/issues/4356」.5883360525) — authorises the W4 append in exactly the form landed here.The audible-skip question — governing text found
The dispatch asked whether any governing text requires the post-removal skip to be audible. What was read, verbatim:
AGENTS.md, "Degradation log levels —warnvserror": "Functional degradation →warn/info. A screen is missing, a trigger is not armed, a capability is not enabled … The system is visibly smaller than it should be, and the next person to use the missing thing finds out." and "prefer failing to falling back, and when you must fall back, say what was lost." — Aselectfilter whose members were all bare strings now renders with an empty option list (DashboardFilterBar:const authored = def.options?.length ? def.options : (dynamicOptions ?? [])): visibly smaller, functional,warn-level — and "say what was lost" is the operative sentence.docs/adr/0078-no-silently-inert-metadata.md, §4 "Loud-not-silent at the runtime seam": "Where the runtime skips an inert instance … it emits a dev-mode diagnostic at registration rather than swallowing it silently. This is the only layer that is authoring-tool-agnostic". The ADR's completeness gate (§1) is about Zod-VALID instances and a bare string is Zod-invalid, but §4 is exactly the case here: a STORED dashboard is the one document no author-time gate re-reads.AGENTS.md: no clause on audibility; #0.1 (contract-first, no lenient fallback) is honoured — a diagnostic is not a fallback, and nothing is lifted.buildFilterCondition— "anything else → skipped with a console warning, never silently downgraded";buildWidgetScopedFilterskips an unknown default field "with a console warning instead of emitting a query".Conclusion: yes, at
warn, dev-mode, deduped — the smallest audible path consistent with the ruling. Implemented aswarnDroppedShorthandOptions: sameisDev()gate and memo key as Phase 1 (no per-render flood), new sentence (DROPPED, "no longer lifted", the rewrite), no lift. Consequence: the memo and thereforeresetDashboardFilterWarningsstay. The contract review (5866198610) judged this RIGHT; dev-only rather than always-on is the same review's answer for this PR.Measurements against the dispatch's mechanism assumptions
origin/main733fd5ac6— confirmed: theelsearm pushed{ value: String(o), label: String(o) }and calledwarnShorthandOptionsfor EVERY non-nullish primitive. Pinned now indrops EVERY non-object member — a number and a boolean, not only a string.resetDashboardFilterWarnings—git greprepo-wide: importers aredashboard-filters.test.tsonly (plus apackages/core/CHANGELOG.mdparagraph); it clears onlywarnedShorthandOptions. The inference "with the memo gone it has no job" is falsified by (4): the memo does not go. It stays, with a docblock saying why.globalFilters(tests included; positive control: the same matcher found the callout it was meant to find): lift-pinning bare-string members existed only indashboard-filters.test.ts— six members across five tests (the dispatch's list lacked the bare'lost'of the mixed-array test; the director's "three" and the dispatch's "five" were both stale). All six retired or flipped. The twopackages/typesoccurrences pin the spec's REFUSAL and keep their assertions.git grep -n -i -E 'lift' -- ':!**/CHANGELOG.md' ':!content/docs/releases/**' ':!pnpm-lock.yaml' | grep -i -E 'shorthand|bare[- ]string|4356'over the whole tracked tree (a superset of every package'sfiles[],content/docs,skills/,examples/): 20 hits onbc4ea976ewithpackages/plugin-dashboard/README.md's "still lifted at runtime" line as the found positive control; 18 ona89065402— every hit either states the retirement (this PR's changeset, source, docs and test text) or is the unrelateddata-array shorthand inplugin-grid/plugin-map. The first-round sweep'soption|filterpost-filter is what hid the README line (W1) and thei18nLabelcomment (W5); the rework sweep has no post-filter. When the sweep runs on a ref, the ref prefix is stripped before the second grep — this branch's name contains both "shorthand" and "lift". Three other sites authoroptions: ['…']onselectFIELDS (form / object field schemas:content/docs/guide/expressions.md,content/docs/guide/schema-playground.md,skills/objectui/guides/project-setup.md) — a different contract, out of this card, untouched; the governedskills/**file does not become false.packages/core/package.jsonis17.6.0;.changeset/config.jsonhas@object-ui/corein thefixedgroup; aminorchangeset makes the next release ≥ 17.7.0; release PR chore: release packages #5400 is open (not touched).git grep -n -i -E 'normalizeFilterOptions|bare[- ]string|shorthand|lift' -- '.changeset/*.md'filtered to option /globalFilters/ 4356 / 7917 context: exactly ONE pending sentence this PR makes false, the7759paragraph quoted above (W4). Its earlier sentence "the bare-string option shorthand (options: ['EMEA']) is refused withinvalid_type" stays true. Every other hit is a different contract (export-options bare array, object-map / ganttdataarrays, kanbancolumns, picklist / fieldoptions, widget shorthand). The nine entries thechangeset-claim-re-readcomment5865883972lists were re-read: none says anything about the options lift; all still true.Merges of
main1daf7edf7—origin/main29c76e573(PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934, objectui#10916). No conflicts;report-chart-query-spec-parity.test.tsauto-merged (disjoint hunks); both sides verified — my header-docblock correction present, PR test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) #10934's pin text byte-for-byte asmainhas it.a1058f90e—origin/main2eaf5be27(70 commits, among them PR docs: cite landing commits where pending changesets and source comments name 23 objectui issues that answer 404, and re-qualify 19 bare objectstack numbers (objectui#10803, batch 6) #10914's citation edits indashboard-filters.tsand threecomplex.zod.tscommits). No conflicts; after the merge the files differing fromorigin/mainare exactly this PR's ten, andgit diff origin/mainoncomplex.zod.tsand on the parity test is exactly this PR's docblock hunks.No rebase, no amend, no force-push at any point; every push was a plain
git push.Tests
Red-first (at
bc4ea976e, the new pins against the UNTOUCHED source,pnpm exec vitest run packages/core/src/utils/__tests__/dashboard-filters.test.ts, exit 1):The three that stayed green on the old code are the ones the block labels as non-discriminating (different-filter memo, canonical silence, memo reset). Green after the source change, same command:
Tests 49 passed (49).Final head
a89065402, onepnpm exec vitest runfrom the repo root under the shared verify lock overpackages/core/plus the twopackages/typesglobal-filter tests and the twoplugin-dashboarddashboard-filter tests this PR touched:Test Files 188 passed (188)·Tests 3725 passed | 27 skipped (3752), exit 0. The changeset script tests (check-changeset-{presence,no-major,fixed,overwrite,claims},check-pending-changeset-literals,changeset-polarity-census):Test Files 7 passed (7)·Tests 292 passed (292), exit 0.Consumer sweep at
bc4ea976e(resolveDashboardFilterDefsconsumers re-derived bygit grep:core,types,plugin-dashboard,plugin-designer,app-shell,examples/schema-catalog), exit 0:packages/core182 files / 3643 tests;packages/plugin-dashboard147 / 1346 (6 pre-existing skips);packages/types3 / 58;packages/plugin-designer(DashboardDesignPage.presetAlias) 1 / 4;packages/app-shell(DashboardWidgetInspector.test.tsx) 1 / 19;examples/schema-catalog(the guardrail) 1 / 9 — totals335 passed (335)·5073 passed | 6 skipped (5079). No consumer fixture asserted the old lift. The code has not changed since that head (every later commit is prose or a merge), and the final-head run above re-coverscore,typesand the touchedplugin-dashboardtests.Gates on the final head
a89065402(hand-derived from rootpackage.jsonand.github/workflows/; exit codes captured before any pipe)pnpm --filter '@object-ui/core^...' buildthenpnpm --filter @object-ui/core buildpnpm --filter @object-ui/core type-check·pnpm --filter @object-ui/types type-checkpnpm --filter @object-ui/core lint·pnpm --filter @object-ui/types lintno-explicit-any— on the touched files theas anycounts equal the base (2 / 10 / 0), unchanged sincebc4ea976epackages/core/+ the four touched dashboard-filter tests (under the lock)node scripts/check-changeset-presence.mjs2eaf5be27, 7 published source files of 3 released packages, 1 changeset addednode scripts/check-changeset-no-major.mjs·check-changeset-fixed.mjs·check-changeset-claims.mjs·check-changeset-overwrite.mjspnpm check:pending-changeset-literalsnode scripts/check-control-bytes.mjsnode scripts/check-new-cross-file-line-citations.mjsnode scripts/check-governed-queue-guard.mjs --test(every touched path)pnpm check:doc-fences·check:doc-types·check:doc-example-ids·check:spec-symbols·node scripts/check-doc-links.mjs·check:test-path-roots·check:vi-mock-specifiers·check:phantom-depsbc4ea976e/98623a24d; the files they read have not changed sincepnpm check:doc-snippets·pnpm check:readme-exportsPRECONDITION NOT MET(unbuilt packages); CI's Doc Snippet Type Check and README Exports are the readings. The docs hunk changes no fenced block; the README hunk is a JSON comment with no import bindingNot run locally, CI-owned: repo-wide
pnpm lint,pnpm test,pnpm type-check(the public type face is unchanged — no export added or removed, no signature changed; emitted declarations differ in JSDoc text only),pnpm check, the i18n gates,plugin-dashboard's own type-check (comment-only hunks in two test files).Deviations
packages/types/src/zod/complex.zod.tswas outside claim5865368364's original file surface; edited under the bounded in-place-fix exemption (same defect class; mechanical; no other open PR on the file; inside core's type-checked closure) and accepted by the seat's addendum5865980488. The later addenda5866223135,5866346457,5866480991added the W1–W5 files and the7759changeset.option|filterpost-filter; corrected above.5866443287), held and not re-routed; it landed only after the maintainer's 2026-09-29 ruling.Acceptance notes
warnDroppedShorthandOptionsis dev-only, exactly as the Phase 1 warning was; the module's other skip warnings are not dev-gated. Always-on is a one-line product decision the review left to the maintainer; not this PR's.selectschemas teachingoptions: ['…']are a different contract and were not judged here — observation only, not filed.packages/core/CHANGELOG.mdandpackages/plugin-dashboard/CHANGELOG.mdnarrate the lift as history; published record, not re-addressed (the CHANGELOG clause under AGENTS.md [WIP] Update documentation for project #11: never re-address a paragraph in a published CHANGELOG).mainand is merged into this branch; no other open PR touches this PR's files.Generated by Claude Code