Repository navigation
Commit c268b72
fix(spec)!: refuse a dashboard globalFilters entry keyed dateRange beside a declared dateRange (#29370)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9XkoBAMEDmazTVe2qJYMJPG)_
Fixes #29213
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — `skills/objectstack-ui/rules/dashboards.md` teaches
`dateRange` as the reserved name of the built-in date range and no file
under `skills/**` keys a `globalFilters` entry `dateRange`; every taught
use stays true. `content/docs/ui/dashboards.mdx` gains one sentence
naming the refusal.
## 维护者速读
- 改了什么:仪表盘声明了内置日期范围 `dateRange` 时,`globalFilters` 里再写一个键为 `dateRange`
的筛选器(按 `name`,无 `name` 时按 `field`)现在会被拒收,报错落在该条目上并点名保留字。
- 为什么改:以前这样写能保存,控制台和 lint 都让后写的定义覆盖内置日期范围,看板上日期选择器消失,所有绑定 `dateRange`
的组件被另一个字段过滤,只有浏览器控制台一条警告。
- 风险与回滚:收紧契约,原本能通过的这类仪表盘会被拒;仓库内实测 0 个仪表盘这样写。回滚即还原本 PR 的两个提交。
-
先看:`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.ts` gains
`reservedDateRangeGlobalFilters(dateRange, globalFilters)` beside
`duplicateGlobalFilterNames`, sharing its `filterKeyOf` key 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 key `dateRange`, so an entry keyed `dateRange` is a second
definition behind one key, exactly what the module's existing predicate
refuses inside the array. The header no longer says `dateRange` "is not
judged here".
- `packages/spec/src/ui/dashboard.zod.ts` attaches
`refuseReservedDateRangeGlobalFilters` to the dashboard OBJECT
(`strictObject(...).superRefine(...)`), not to the array, because the
verdict reads the sibling `dateRange`. One issue per entry, at
`globalFilters[i].name` (or `.field` when unnamed), naming the reserved
word and the built-in date range. The `GlobalFilterSchema.name`
describe, the `globalFilters` describe and the `filterBindings` TSDoc
now say the entry is refused, not only reserved.
- The refusal fires only when `dateRange` is declared (any non-null
object, the test both resolvers use). On a dashboard with no `dateRange`
the key is free and an entry keyed `dateRange` stays legal (see
Acceptance notes).
- `packages/spec/dropped-refinements.baseline.json`: the build named
five new sites (`ui/Dashboard` root and `manifest.dashboards.element` on
the four installed-package projections);
`measured.droppedRefinementSites` 988 → 993.
- ADR-0087 semantic entry
`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`), regenerated
`content/docs/references/ui/dashboard.mdx`, one sentence in
`content/docs/ui/dashboards.mdx`.
- Forced test edits: none. No existing test or fixture changed.
## Why
Measured on `main` @ `5a2bdf9c` before the change, with `dateRange: {
field: 'name' | 'close_date' }` and `globalFilters: [{ name:
'dateRange', field: … }]`:
- Parse: `DashboardSchema.safeParse` answered `success: true` for the
named entry, the unnamed `{ field: 'dateRange' }` entry and the
undeclared case alike.
- Save door: `saveMetaItem` (the `PUT /api/v1/meta/dashboard` handler,
stub-engine harness of `protocol.draft-publish-verdict.test.ts`)
answered `success: true` in `draft` (then `publishMetaItem` succeeded)
and in `publish`; the row was stored `active`.
- Console board: `resolveDashboardFilterDefs` (`ui/packages/core`)
returned ONE def, `{"name":"dateRange","field":"region","type":"text"}`,
plus the console warning `duplicate filter name "dateRange" — the later
definition wins`. The date-range control was gone and every
`filterBindings.dateRange` bound `region`. Lint's `dashboardFilterDefs`
keeps the later definition the same way, and `@objectstack/verify`'s
window proof still proves the declared window the board no longer draws.
<details><summary>After the change (head commit)</summary>
- Save door, `draft` and `publish`: `INVALID_METADATA` 422, one issue at
`globalFilters.0.name`, nothing stored.
- A row already stored with the pair still loads: `_diagnostics.valid:
false`, one error at `globalFilters.0.name` carrying the refusal.
- The probes were scratch tests, deleted after the run (no permanent
file).
</details>
## Tests
- New
`packages/spec/src/ui/dashboard-global-filter-reserved-date-range.test.ts`
(11 cases): three controls (declared `dateRange` with another name;
undeclared `dateRange` with an entry named `dateRange`; a named entry
over a field called `dateRange`), the card reproduction located at
`.name` with the message's first sentence, an empty `dateRange: {}`, an
unnamed entry located at `.field`, two `dateRange` entries (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.
- Ablation (committed fix first, `scripts/ablation-replace.mjs`,
`dashboard.zod.ts` anchor
`}).superRefine(refuseReservedDateRangeGlobalFilters));` → `}));`): 5
failed | 6 passed (11), the five refusal cases red and the controls and
predicate cases green; restored blob == HEAD, `git diff HEAD` empty. The
spec suite imports `./dashboard.zod` from source, so no dist was
involved.
<details><summary>Package readings at head 6d7e238</summary>
- `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.
- Dogfood (full battery, after a `turbo run build
--filter=./packages/**` closure build, under the lock): `pnpm --filter
@objectstack/dogfood test` VERDICT command-exit 0, Test Files 391 passed
| 3 skipped (394), Tests 3223 passed | 28 skipped (3251).
</details>
## Acceptance notes
- A `globalFilters` entry keyed `dateRange` on a dashboard that does NOT
declare `dateRange` stays accepted. It is meaningful there: the console
draws its own control under that name, `filterBindings.dateRange` binds
it, lint's `dashboard-filter-binding-unknown` accepts 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.
- Blast radius: zero authored dashboards key a global filter `dateRange`
(`git grep` over `examples`, `apps`, `packages`, `ui` for
`name:`/`field:` spelled `dateRange`: 5 hits, none a `globalFilters`
entry; 115 files mention `globalFilters`).
- Mirror reach: the rule sits on the dashboard object, so a schema that
spreads only `DashboardSchema.shape.globalFilters` keeps the
duplicate-key rule and not this one. Every in-repo door parses
`DashboardSchema` whole (the dropped-refinements build names the four
manifest projections that carry it).
- The v18 stored-row listing
(`packages/spec/src/conversions/refused-shapes.ts`) does not list this
refusal; it is the sibling card #29212's surface and was not touched
here.
## Verification
Head `6d7e2382`. `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 106 families from 8 paths (331
changed lines); each ran after the last commit with its exit code
recorded, and `--ran` answered: 106 derived, 106 run, 0 NOT-MEASURED, 0
UNRUN (exit 0).
<details><summary>Gate notes</summary>
- `node scripts/check-plugin-teardown-shape.mjs --self-test` first
answered exit 3 (PREREQUISITE NOT MET: its pinned positive-control
commit was absent from the shallow clone); after `git fetch --depth=1
origin 621a487` it answered exit 0, 48 cases.
- `pnpm --filter @objectstack/spec check:generated`: all 14 generated
artifacts up to date (after `gen:docs` regenerated
`content/docs/references/ui/dashboard.mdx`).
- `node scripts/check-changeset-no-major.mjs --base 5a2bdf9 --event`
(this body, offline): exit 0, level axis `no (narrowing)` / `narrowed`,
no `patch` grading. `check-partof-closing-keyword.mjs` on this body:
exit 0.
- Local-only `node scripts/check-dev-prereqs.mjs`: exit 1, "the
workspace is not built", naming 35 `ui/` workspace packages with no
`dist/` (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.
- CI families (`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.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [ ] 3. No formatter ran; `pnpm lint` is CI-owned and was not run
locally.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmNvZN4TxxGwnpRp9yG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5651647 commit c268b72
13 files changed
Lines changed: 342 additions & 20 deletions
File tree
- .changeset
- content/docs
- references/ui
- ui
- packages/spec
- api-surface
- export-origins
- src
- migrations/entries/semantic
- ui
- ui/packages/types/src
- __tests__
- zod
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
| 95 | + | |
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| |||
317 | 317 | | |
318 | 318 | | |
319 | 319 | | |
320 | | - | |
| 320 | + | |
321 | 321 | | |
322 | 322 | | |
323 | 323 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
412 | 412 | | |
413 | 413 | | |
414 | 414 | | |
415 | | - | |
| 415 | + | |
416 | 416 | | |
417 | 417 | | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
418 | 423 | | |
419 | 424 | | |
420 | 425 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
526 | 526 | | |
527 | 527 | | |
528 | 528 | | |
| 529 | + | |
529 | 530 | | |
530 | 531 | | |
531 | 532 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| 84 | + | |
84 | 85 | | |
85 | 86 | | |
86 | 87 | | |
| |||
126 | 127 | | |
127 | 128 | | |
128 | 129 | | |
129 | | - | |
130 | 130 | | |
| 131 | + | |
131 | 132 | | |
132 | 133 | | |
133 | 134 | | |
| |||
228 | 229 | | |
229 | 230 | | |
230 | 231 | | |
| 232 | + | |
231 | 233 | | |
232 | 234 | | |
233 | 235 | | |
| |||
367 | 369 | | |
368 | 370 | | |
369 | 371 | | |
| 372 | + | |
370 | 373 | | |
371 | 374 | | |
372 | 375 | | |
| |||
409 | 412 | | |
410 | 413 | | |
411 | 414 | | |
412 | | - | |
413 | 415 | | |
| 416 | + | |
414 | 417 | | |
415 | 418 | | |
416 | 419 | | |
| |||
446 | 449 | | |
447 | 450 | | |
448 | 451 | | |
| 452 | + | |
449 | 453 | | |
450 | 454 | | |
451 | 455 | | |
| |||
1592 | 1596 | | |
1593 | 1597 | | |
1594 | 1598 | | |
| 1599 | + | |
1595 | 1600 | | |
1596 | 1601 | | |
1597 | 1602 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
509 | 509 | | |
510 | 510 | | |
511 | 511 | | |
| 512 | + | |
512 | 513 | | |
513 | 514 | | |
514 | 515 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
0 commit comments