Repository navigation
fix(spec)!: refuse a dashboard globalFilters entry keyed dateRange beside a declared dateRange - #29370
Conversation
…rd that declares dateRange The built-in date range is declared under the reserved key `dateRange`; an entry keyed `dateRange` beside it parsed and replaced the date range's control and binding in the console and lint resolvers. Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
…nces, baseline +5 sites Semantic ADR-0087 entry ui-dashboard-global-filter-reserved-date-range-refused, the changeset, the dropped-refinements baseline (the dashboard-level refine adds ui/Dashboard root and four installed-package manifest projections) and the regenerated dashboard reference; one docs sentence naming the refusal. Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 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 e9688516cc2998ea7b4479b45755abd693452d36 && git checkout e9688516cc2998ea7b4479b45755abd693452d36
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7f277a86f670a956dcdf677937c1894706992bee 6b7710c9c0e97e31bc478da45b74fa5a3644acfd && git checkout -B drift-repro 7f277a86f670a956dcdf677937c1894706992bee && git merge --no-ff 6b7710c9c0e97e31bc478da45b74fa5a3644acfd
node scripts/docs-audit/affected-docs.mjs --json 7f277a86f670a956dcdf677937c1894706992bee
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 6d7e238 · 2026-11-02T13:24Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs: card #29213 (body, triage 6574201278, claim 6576684400, os-dev-report 6578177651), PR #29370 (body, 8-file list, net diff against merge base Gate state on the head at review time: completed success for ① Derived judgments
② Semver level
Clause-②: no (narrowing) — correct for this diff. ③ Boundary flagsDev deviations (os-dev-report 6578177651):
Open questions:
Out-of-scope note ( Escalations: none. ④ Design judgment
Amended claim surface. Should-fix (non-blocking):
Must-fix: none. Implemented-by: VERDICT: PASS |
…es dateRange Contract-review should-fix: the dashboards page and the GlobalFilterSchema.name TSDoc now say one thing. The name is reserved, and an entry keyed dateRange refused, only when the dashboard declares the built-in date range; otherwise the name is free. Prose only, no behaviour change. Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the push after the PASS record 6578522383 (head Implemented-by: VERDICT: PASS |
…rs and chain it on the console's dashboard mirror The census in spec-object-refinements-7715 counts every object-level check on DashboardSchema: rebuilding DashboardComponentSchema from the spec's shape dropped the new reserved-dateRange check, so the console's authoring door accepted a dashboard the spec refuses. Export it in the house check* shape, chain it on DashboardComponentSchema, name it in the census row, and add @object-ui/types to the changeset. Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
…oardReservedDateRangeGlobalFilters Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
…the import-boundary refinement list Claude-Session: https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut 待裁:新公开名字
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the pushes after the delta PASS 6578639267 (head
Out of scope, already filed: the mirror redeclares Check-runs on Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:内容可落地,等名字裁定。契约复审 PASS(6578522383 @ 席位核对 — ACCEPT ·
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Withdrawn: the name question on this PR. The pm-dispatch skill carries no rule that a new public name needs the maintainer's approval; that line lived only in this seat's standard order. Under SKILL.md 〈升级与决策〉 a name escalates only when the options truly diverge on product semantics or contract shape and no existing norm decides; here the dev's name is fit and no fork exists, so it is the seat's call: the name stays as built. Prompted by the maintainer's question at 2026-11-02T17:03Z. |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41489494232 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41491860190 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Steve · project thread
Fixes #29213
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none —
skills/objectstack-ui/rules/dashboards.mdteachesdateRangeas the reserved name of the built-in date range and no file underskills/**keys aglobalFiltersentrydateRange; every taught use stays true.content/docs/ui/dashboards.mdxgains one sentence naming the refusal.维护者速读
dateRange时,globalFilters里再写一个键为dateRange的筛选器(按name,无name时按field)现在会被拒收,报错落在该条目上并点名保留字。dateRange的组件被另一个字段过滤,只有浏览器控制台一条警告。packages/spec/src/ui/duplicate-global-filter-names.ts(新谓词与报错文案)、packages/spec/src/ui/dashboard.zod.ts(挂在仪表盘对象上的 refine)、packages/spec/dropped-refinements.baseline.json(+5 个站点)。pnpm --filter @objectstack/spec exec vitest run src/ui/dashboard-global-filter-reserved-date-range.test.ts,预期 11 个用例全绿。What changes
packages/spec/src/ui/duplicate-global-filter-names.tsgainsreservedDateRangeGlobalFilters(dateRange, globalFilters)besideduplicateGlobalFilterNames, sharing itsfilterKeyOfkey derivation. I extended the module rather than adding a sibling because it is the same defect class: the built-in date range is a filter declared under the keydateRange, so an entry keyeddateRangeis a second definition behind one key, exactly what the module's existing predicate refuses inside the array. The header no longer saysdateRange"is not judged here".packages/spec/src/ui/dashboard.zod.tsattachesrefuseReservedDateRangeGlobalFiltersto the dashboard OBJECT (strictObject(...).superRefine(...)), not to the array, because the verdict reads the siblingdateRange. One issue per entry, atglobalFilters[i].name(or.fieldwhen unnamed), naming the reserved word and the built-in date range. TheGlobalFilterSchema.namedescribe, theglobalFiltersdescribe and thefilterBindingsTSDoc now say the entry is refused, not only reserved.dateRangeis declared (any non-null object, the test both resolvers use). On a dashboard with nodateRangethe key is free and an entry keyeddateRangestays legal (see Acceptance notes).packages/spec/dropped-refinements.baseline.json: the build named five new sites (ui/Dashboardroot andmanifest.dashboards.elementon the four installed-package projections);measured.droppedRefinementSites988 → 993.ui-dashboard-global-filter-reserved-date-range-refused(packages/spec/src/migrations/entries/semantic/), changeset.changeset/29213-dashboard-global-filter-reserved-date-range.md(minor,registered), regeneratedcontent/docs/references/ui/dashboard.mdx, one sentence incontent/docs/ui/dashboards.mdx.Why
Measured on
main@5a2bdf9cbefore the change, withdateRange: { field: 'name' | 'close_date' }andglobalFilters: [{ name: 'dateRange', field: … }]:DashboardSchema.safeParseansweredsuccess: truefor the named entry, the unnamed{ field: 'dateRange' }entry and the undeclared case alike.saveMetaItem(thePUT /api/v1/meta/dashboardhandler, stub-engine harness ofprotocol.draft-publish-verdict.test.ts) answeredsuccess: trueindraft(thenpublishMetaItemsucceeded) and inpublish; the row was storedactive.resolveDashboardFilterDefs(ui/packages/core) returned ONE def,{"name":"dateRange","field":"region","type":"text"}, plus the console warningduplicate filter name "dateRange" — the later definition wins. The date-range control was gone and everyfilterBindings.dateRangeboundregion. Lint'sdashboardFilterDefskeeps the later definition the same way, and@objectstack/verify's window proof still proves the declared window the board no longer draws.After the change (head commit)
draftandpublish:INVALID_METADATA422, one issue atglobalFilters.0.name, nothing stored._diagnostics.valid: false, one error atglobalFilters.0.namecarrying the refusal.Tests
packages/spec/src/ui/dashboard-global-filter-reserved-date-range.test.ts(11 cases): three controls (declareddateRangewith another name; undeclareddateRangewith an entry nameddateRange; a named entry over a field calleddateRange), the card reproduction located at.namewith the message's first sentence, an emptydateRange: {}, an unnamed entry located at.field, twodateRangeentries (one reserved issue each beside the duplicate-key issue), the refusal still answering when another key fails, and three predicate cases on stored-body shapes.scripts/ablation-replace.mjs,dashboard.zod.tsanchor}).superRefine(refuseReservedDateRangeGlobalFilters));→}));): 5 failed | 6 passed (11), the five refusal cases red and the controls and predicate cases green; restored blob == HEAD,git diff HEADempty. The spec suite imports./dashboard.zodfrom source, so no dist was involved.Package readings at head 6d7e238
pnpm --filter @objectstack/spec test: Test Files 792 passed (792), Tests 23402 passed | 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.pnpm --filter @objectstack/lint test: Test Files 189 passed (189), Tests 7830 passed.pnpm --filter @objectstack/metadata-protocol test: Test Files 301 passed | 4 skipped (305), Tests 22127 passed | 26 skipped.ui/:pnpm exec vitest run packages/core/src/utils packages/plugin-dashboard: Test Files 311 passed (311), Tests 3865 passed | 6 skipped.turbo run build --filter=./packages/**closure build, under the lock):pnpm --filter @objectstack/dogfood testVERDICT command-exit 0, Test Files 391 passed | 3 skipped (394), Tests 3223 passed | 28 skipped (3251).Acceptance notes
globalFiltersentry keyeddateRangeon a dashboard that does NOT declaredateRangestays accepted. It is meaningful there: the console draws its own control under that name,filterBindings.dateRangebinds it, lint'sdashboard-filter-binding-unknownaccepts the key against it, and verify's window proof skips the dashboard. Refusing it unconditionally would widen the refusal past the card; it is an open question to the seat.dateRange(git grepoverexamples,apps,packages,uiforname:/field:spelleddateRange: 5 hits, none aglobalFiltersentry; 115 files mentionglobalFilters).DashboardSchema.shape.globalFilterskeeps the duplicate-key rule and not this one. Every in-repo door parsesDashboardSchemawhole (the dropped-refinements build names the four manifest projections that carry it).packages/spec/src/conversions/refused-shapes.ts) does not list this refusal; it is the sibling card spec(dashboard): the duplicate globalFilters key refusal (#29023) has no stored-row detector and no save-door pin #29212's surface and was not touched here.Verification
Head
6d7e2382.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 106 families from 8 paths (331 changed lines); each ran after the last commit with its exit code recorded, and--rananswered: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN (exit 0).Gate notes
node scripts/check-plugin-teardown-shape.mjs --self-testfirst answered exit 3 (PREREQUISITE NOT MET: its pinned positive-control commit was absent from the shallow clone); aftergit fetch --depth=1 origin 621a4876it answered exit 0, 48 cases.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date (aftergen:docsregeneratedcontent/docs/references/ui/dashboard.mdx).node scripts/check-changeset-no-major.mjs --base 5a2bdf9c --event(this body, offline): exit 0, level axisno (narrowing)/narrowed, nopatchgrading.check-partof-closing-keyword.mjson this body: exit 0.node scripts/check-dev-prereqs.mjs: exit 1, "the workspace is not built", naming 35ui/workspace packages with nodist/(only./packages/**was built here). It judges this container's dev-server precondition, not the spec dist, so it is reported and not acted on.Lint & Repo Gates,TypeScript Type Check,Test Core,Build Core, the UI jobs) are read from this PR's check-runs, not re-run here.Human Minimum
packages/specholds no runtime logic.pnpm lintis CI-owned and was not run locally.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code