Repository navigation
fix(spec)!: a dashboard that declares two global filters under one name is refused - #29162
Conversation
DashboardSchema.globalFilters now declares each filter key (name, else field) once. One custom issue at the second declaration's name (or field) names the key, every index and the fix. Semantic migration entry ui-dashboard-global-filter-duplicate-name-refused carries the prescription; no conversion renames on the author's behalf. Claude-Session: https://claude.ai/code/session_01ADHkmNmHGTQ6fDSRm1srQr Co-authored-by: Claude <noreply@anthropic.com>
…ate global filter name refusal dropped-refinements.baseline.json gains the five sites the array refinement adds (ui/Dashboard and the four installed-package manifests); the dashboard reference page carries the new describe. Claude-Session: https://claude.ai/code/session_01ADHkmNmHGTQ6fDSRm1srQr Co-authored-by: Claude <noreply@anthropic.com>
…ed row does Measured at the save door: a stored dashboard that repeats a filter key still loads with _diagnostics valid:false carrying the refusal; its next save, draft or publish, answers 422 INVALID_METADATA. Claude-Session: https://claude.ai/code/session_01ADHkmNmHGTQ6fDSRm1srQr Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cd4c79ba46690a1bc20a080d6fdd6986acaed15c && git checkout cd4c79ba46690a1bc20a080d6fdd6986acaed15c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0d6d4061f8083f158f3beade5928340b47e129a2 6172f51e5673a0b43faeb76af11d53317ab173e3 && git checkout -B drift-repro 0d6d4061f8083f158f3beade5928340b47e129a2 && git merge --no-ff 6172f51e5673a0b43faeb76af11d53317ab173e3
node scripts/docs-audit/affected-docs.mjs --json 0d6d4061f8083f158f3beade5928340b47e129a2
|
…etadata-protocol narrowed A refused value is narrowed in the AGENTS.md taxonomy; the claim is amended to match. Level, BREAKING banner and ADR-0087 marker unchanged. Claude-Session: https://claude.ai/code/session_01ADHkmNmHGTQ6fDSRm1srQr Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等契约复审。 席位核对 — ACCEPT ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Taken on the lane's Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract reviewServed-tier: Inputs read: card #29023 body and all four comments (claim 6571453894 as amended, triage, dev reports 6572629846 and 6573734445); PR #29162 body, file list and net diff vs ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Security: nothing security-relevant. The refusal echoes the author's own filter key inside a located issue, the same posture as Must-fix: none. Should-fix: the three D3 prose points above and the Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head 6172f51 · 2026-11-02T10:44Z Triage ruling 6575141496 returned contract review to the lane, so the spec seat reviews this head itself at |
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Contract reviewServed-tier: Read: card #29023 (body and every comment, the claim 6571453894 with its amendment, triage 6571468780, the two os-dev-reports 6572629846 and 6573734445), PR #29162 (body, 7-path file list, net diff against ① Derived judgments
② Semver level
③ Boundary flagsDev flags (card #29023, reports 6572629846 and 6573734445):
Check-runs on ④ Design judgment
Nothing in the three Implemented-by: VERDICT: PASS |
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Requested by Steve · project thread
Fixes #29023
Clause-②: no (narrowing)
Metadata-protocol: narrowed
维护者速读
globalFilters里两个筛选器同名(无name时按field算)现在在所有解析入口被拒收,保存门答 422INVALID_METADATA,报错落在第二个的name上并点名两个下标和改法。status上,卡片读 0。_diagnostics标valid: false。回滚即还原本 PR。packages/spec/src/ui/duplicate-global-filter-names.ts(判定与报错文字)、packages/spec/src/ui/dashboard.zod.ts(挂在数组上的 superRefine)、迁移条目18.ui-dashboard-global-filter-duplicate-name-refused.ts。pnpm --filter @objectstack/spec exec vitest run src/ui/dashboard-global-filter-duplicate-name.test.ts,预期 11 项全绿,其中第三项断言报错位置为globalFilters[1].name。What changes
DashboardSchema.globalFiltersrefuses two or more entries under one filter key. The key is the entry'sname, or itsfieldwhen it has noname(the same derivation asglobalFilterKeyinsystem/i18n-resolver.ts, which the dashboard schema cannot import without a cycle throughui/component.zod.ts; a test holds the two equal). Onecustomissue per repeated key, at the SECOND declaration'sname(or itsfieldwhen that entry is keyed byfield), naming the key, every index and the remedy: rename all but one (and every widgetfilterBindingskey that meant a renamed one) or delete the copy.packages/spec/src/ui/duplicate-global-filter-names.ts, the shape ofdata/duplicate-action-names.ts. Neither the predicate nor the refinement is exported, so the public surface does not move.DashboardSchema.shape.globalFilterscarries it into any mirror that spreads the shape (pinned).ui-dashboard-global-filter-duplicate-name-refused(step 18), modelled onui-dashboard-filter-binding-unknown-refused: refused with a prescription, no conversion. Which definition keeps the name is the author's call, and an automatic rename would silently unbind every widget that meant the renamed one.dropped-refinements.baseline.jsongains the five sites the array refinement adds (ui/DashboardglobalFiltersand the four installed-package manifest projections;droppedRefinementSites983 to 988). JSON Schema cannot state unique-by-key, so the rule reaches the runtime and not the file, as the ledger records.content/docs/references/ui/dashboard.mdx(theglobalFiltersdescribe)..changeset/29023-dashboard-global-filter-duplicate-name.md,@objectstack/specminor,adr-0087: registered ui-dashboard-global-filter-duplicate-name-refused.Tests
packages/spec/src/ui/dashboard-global-filter-duplicate-name.test.ts, 11 cases: two CONTROL accepts (distinct names; distinct names over the same field), the card's reproduction refused at['globalFilters', 1, 'name']with the message's first sentence pinned, unnamed pair located at.field, name colliding with another entry's unnamed field, emptynamekeyed byfield, three declarations (one issue at the second, every index listed), two repeated keys (one issue each), the rule travelling with.shape.globalFilters, parity withglobalFilterKey, and the predicate leaving malformed input to the schema.src, so no build leg): deleting the.superRefine(refuseDuplicateGlobalFilterNames)line throughscripts/ablation-replace.mjs --delete(anchor 1 to 0) turns 7 of 11 red, the seven refusal cases; the four that stay green are the two controls and the two predicate-only cases. Restore verified: blob equals HEAD,git diff HEADempty. A first attempt with an empty--replacementwas refused by the tool (no mutation landed) and is not counted.saveMetaItemtest over the stub engine ofprotocol.draft-publish-verdict.test.ts(not committed): the card's body answers422 INVALID_METADATAwith one issue atglobalFilters.1.namein default,publishanddraftmode, and stores nothing; the distinct-name control saves. A stored row that repeats a key still loads throughgetMetaItem, with_diagnosticsvalid: falsecarrying the refusal.Round 1 (seat):
Metadata-protocolamended fromsemantictonarrowedon the claim and in the changeset (a refused value isnarrowed);origin/mainmerged at0d6d4061throughscripts/pm/os-regen-merge.sh(no conflict; the branch's regenerateddashboard.mdxkept, step 4 rebuilt andcheck:generatedreads 14/14 up to date). Readings below are on the merged head6172f51e(local, shared box; counts only):Package suites (contract face: spec plus the two consumers the delta names, plus dogfood for the narrowing arm)
pnpm --filter @objectstack/spec test: Test Files 791 passed (791), Tests 23378 passed, 1 todo. Lock VERDICT command-exit 0.pnpm --filter @objectstack/lint test(widget bindings consumer): Test Files 189 passed (189), Tests 7830 passed. VERDICT command-exit 0.pnpm --filter @objectstack/metadata-protocol test(the draft-publish verdict test that names the binding refusal): Test Files 298 passed, 4 skipped (302), Tests 22069 passed, 26 skipped. VERDICT command-exit 0.pnpm --filter @objectstack/dogfood test(closure built first; no shard, no path): Test Files 387 passed, 3 skipped (390), Tests 3204 passed, 28 skipped. VERDICT command-exit 0.pnpm --filter @objectstack/spec typecheck(on8331c756, before the merge, which moved no file of this diff): exit 0; the test layer (tsconfig.test.json) compiles the new test file with no new debt.Acceptance notes
globalFiltersarrays across 136 files (examples, packages, skills, docs, ui), 14 with two or more entries, zero repeat a key in authored metadata. One hit is a ui renderer unit test (ui/packages/core/src/utils/__tests__/dashboard-filters.test.ts, "lets duplicate names win last") that feedsresolveDashboardFilterDefsdirectly and never parses through the spec; it stays green and is outside this card (ui/**not touched). HotCRM: NOT MEASURED (outside this session's repository scope).packages/lintvalidate-widget-bindings.tskeys filters by name with later-wins semantics; once the schema refuses duplicates it needs no change, and no gate asked for one.dateRange: aglobalFiltersentry nameddateRangeon a dashboard that also declaresdateRangeis not refused by this rule (the lint resolver lets the later definition win). Out of scope here; reported to the seat.os migrate meta --storeddoes not list stored dashboards this refuses: the v18 stored-row listing (conversions/refused-shapes.ts) carries a detector for the action-name sibling but none was added here (outside the claim's file surface). Reported to the seat as an option.Verification
Derived gates: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon6172f51e(7 paths vs merge base0d6d4061): 106 commands, every one exit 0 on the first pass.8331c756, three first exited 3 on prerequisites, a shallow clone and missing built output, and were re-run green; this round built those packages up front.)dispatch-gates --ran: 106 derived famil(ies) accounted for, 106 run, 0 NOT-MEASURED.check-changeset-no-major.mjs --eventwith this body's two lines, base0d6d4061): exit 0,Clause-②: no (narrowing)withMetadata-protocol: narrowedaccepted.Generated by Claude Code