feat(spec): dashboard 日期预设名收敛为单一词汇表,date filter 的 defaultValue 作者时受检 (#4614) - #6652
Merged
Merged
Conversation
… and check a date filter's defaultValue against it (#4614) A dashboard's built-in `dateRange` validated its preset name and a `globalFilters` entry of `type: 'date'` did not, so the same typo was an author-time error on one surface and a silent wrong answer on the other. `GlobalFilterSchema.defaultValue` is `string | number | boolean`, which makes a bare preset name the only spelling available for a date filter's default — and nothing checked it. An unrecognised name cannot be lifted to a range, so it fell through to "a bare string date means equality on that day" and reached the backend as `created_at = 'last_7_dayz'`: a condition no row matches, answered 200 OK with a zero. Every tile read 0 while the filter bar showed "All time". WHAT LANDED (A案 two steps) 1. Vocabulary migrated into spec as the single source of truth. `DATE_RANGE_PRESETS` (13 names) + `DateRangePreset`, and `DATE_RANGE_DEFAULT_RANGES` (presets + `custom`) + `DateRangeDefaultRange`, in packages/spec/src/ui/dashboard.zod.ts. Shape copied from `DATE_MACRO_TOKENS`: `as const` array + `(typeof X)[number]` alias. `dateRange.defaultRange` now reads the second list, so its accepted set is unchanged member-for-member (asserted by a test). 2. `GlobalFilterSchema.superRefine` — on `type: 'date'`, a declared `defaultValue` must be a preset name, an ISO date, or a known date-macro token. The macro arm calls `isDateMacroToken`/`DATE_MACRO_WRAPPED_RE` rather than restating the grammar: one token vocabulary, no second dialect. The rejection quotes the offending value and lists all three legal spellings (strict gate + fixable text). Other filter types are untouched. PREMISE DIVERGENCE FOUND AND RESOLVED (no裁决 needed) The brief scoped the check to `type: date|dateRange`. Spec's `GlobalFilterSchema.type` enum has no `dateRange` member — it is ['text','select','date','number','lookup']. objectui's `DashboardFilterDef.type` does have `'dateRange'`, but `resolveDashboardFilterDefs` SYNTHESISES that def from `schema.dateRange` under the reserved name "dateRange"; the `globalFilters` loop only ever reads `f.type ?? 'text'`. So `dateRange` is an objectui-internal def type, not an authorable `globalFilters[].type`, and no enum member was added. The two halves of "date|dateRange" map to: `date` = the globalFilters entry (this superRefine), `dateRange` = the built-in, already an enum and now reading the shared constant. `custom` is deliberately NOT a preset: objectui's PRESET_RANGES has 13 keys, spec's old inline enum had those 13 + `custom`. `custom` names no window ("open the picker"), so it stays legal as `defaultRange` and is rejected as a bare filter default, which gives it no from/to to hand over. 存量 EVALUATION — no ADR-0087 conversion required Scanned the three example apps, content/docs, and packages/ for a date filter `defaultValue` carrying a misspelled preset name. ZERO hits. Reverse-check proving the scan was live (a known-good name must be findable): grepping `this_month` / `this_quarter` / `last_7_days` / `last_30_days` surfaced `defaultRange: 'this_month'` and `'this_quarter'` in content/docs/ui/dashboards.mdx plus its 14-row preset table — i.e. the scan does find preset names where they exist. The tree's ONLY date-filter default is packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts:158 `defaultValue: 'last_7_days'` — a VALID preset, unaffected. Verified by parsing the real shipped module through the new schema (not a fixture): parsed OK, defaultValue preserved as "last_7_days". Also pinned by a named test. `docs/notes/airtable-dashboard-analysis.mdx` has `defaultValue: 'this_quarter'` on a `type: 'select'` filter — a different surface, untouched by this rule. VERIFICATION — real readings - spec full suite: 343 files / 8819 tests passed - spec typecheck: tsc + scripts-typecheck + test-typecheck all clean - check:generated: 10/10 green (2 were stale — skill-refs, api-surface — and were regenerated with --fix, then re-checked green). api-surface delta is 4 pure ADDITIONS (2 const + 2 type), zero removals — no baseline debt (#4593). - check:spec-parsed-alias: OK (1443 bare / 749 pinned / 694 paired). No pin needed: the new types read `(typeof X)[number]`, not z.input/z.infer, so they are outside ADR-0122's population. - pnpm lint: clean - three examples `validate`: all REAL_EXIT=0 with "✓ Validation passed" (warnings present are pre-existing and unrelated — i18n section names, liveness, permissions, flow status) REVERSE VALIDATION (direction predicted BEFORE running) Predicted: neutralising the superRefine turns exactly 5 tests red (misspelled preset, `custom`, non-string, error-message, unknown-macro-token) and leaves the other 45 green. Measured: 5 failed / 45 passed, precisely those 5. Probe reverted; restored run 50/50 green, and the file greps clean of the probe. FOR PM — objectui 联动单 material Repo objectstack-ai/objectui @ 0cf8f0f, file packages/core/src/utils/dashboard-filters.ts: - line 73 `const PRESET_RANGES: Record<string, {from?,to?}>` — the 13 names with their date-macro bounds. Landing point: key it off the spec vocabulary so a spec-side addition becomes a compile error until bounds are supplied: `const PRESET_RANGES: Record<DateRangePreset, {from?: string; to?: string}> = {...}` - line 90 `export const DATE_RANGE_PRESETS = Object.keys(PRESET_RANGES)` becomes a re-export of spec's constant (same NAME, so no consumer churn), which also fixes its type: `string[]` today, a literal union after. - import: `import { DATE_RANGE_PRESETS, type DateRangePreset } from '@objectstack/spec/ui';` `@objectstack/spec/ui` is already objectui's most-used spec subpath (134 imports) and packages/core already imports from it; packages/core/package.json depends on `@objectstack/spec ^17.0.0-rc.5`, so the range covers this minor once published. - consumer: packages/plugin-dashboard/src/DashboardFilterBar.tsx:37,97 — display order now comes from spec; no code change expected. Nothing in objectui was modified by this commit. changeset: @objectstack/spec minor (new authorable validation surface). Docs: content/docs/ui/dashboards.mdx gains a "Date Filter Defaults" section and a pointer from the preset table to the source-of-truth constant.
… and check a date filter's defaultValue against it (#4614) A dashboard's built-in `dateRange` validated its preset name and a `globalFilters` entry of `type: 'date'` did not, so the same typo was an author-time error on one surface and a silent wrong answer on the other. `GlobalFilterSchema.defaultValue` is `string | number | boolean`, which makes a bare preset name the only spelling available for a date filter's default — and nothing checked it. An unrecognised name cannot be lifted to a range, so it fell through to "a bare string date means equality on that day" and reached the backend as `created_at = 'last_7_dayz'`: a condition no row matches, answered 200 OK with a zero. Every tile read 0 while the filter bar showed "All time". WHAT LANDED (A案 two steps) 1. Vocabulary migrated into spec as the single source of truth. `DATE_RANGE_PRESETS` (13 names) + `DateRangePreset`, and `DATE_RANGE_DEFAULT_RANGES` (presets + `custom`) + `DateRangeDefaultRange`, in packages/spec/src/ui/dashboard.zod.ts. Shape copied from `DATE_MACRO_TOKENS`: `as const` array + `(typeof X)[number]` alias. `dateRange.defaultRange` now reads the second list, so its accepted set is unchanged member-for-member (asserted by a test). 2. `GlobalFilterSchema.superRefine` — on `type: 'date'`, a declared `defaultValue` must be a preset name, an ISO date, or a known date-macro token. The macro arm calls `isDateMacroToken`/`DATE_MACRO_WRAPPED_RE` rather than restating the grammar: one token vocabulary, no second dialect. The rejection quotes the offending value and lists all three legal spellings (strict gate + fixable text). Other filter types are untouched. PREMISE DIVERGENCE FOUND AND RESOLVED (no裁决 needed) The brief scoped the check to `type: date|dateRange`. Spec's `GlobalFilterSchema.type` enum has no `dateRange` member — it is ['text','select','date','number','lookup']. objectui's `DashboardFilterDef.type` does have `'dateRange'`, but `resolveDashboardFilterDefs` SYNTHESISES that def from `schema.dateRange` under the reserved name "dateRange"; the `globalFilters` loop only ever reads `f.type ?? 'text'`. So `dateRange` is an objectui-internal def type, not an authorable `globalFilters[].type`, and no enum member was added. The two halves of "date|dateRange" map to: `date` = the globalFilters entry (this superRefine), `dateRange` = the built-in, already an enum and now reading the shared constant. `custom` is deliberately NOT a preset: objectui's PRESET_RANGES has 13 keys, spec's old inline enum had those 13 + `custom`. `custom` names no window ("open the picker"), so it stays legal as `defaultRange` and is rejected as a bare filter default, which gives it no from/to to hand over. 存量 EVALUATION — no ADR-0087 conversion required Scanned the three example apps, content/docs, and packages/ for a date filter `defaultValue` carrying a misspelled preset name. ZERO hits. Reverse-check proving the scan was live (a known-good name must be findable): grepping `this_month` / `this_quarter` / `last_7_days` / `last_30_days` surfaced `defaultRange: 'this_month'` and `'this_quarter'` in content/docs/ui/dashboards.mdx plus its 14-row preset table — i.e. the scan does find preset names where they exist. The tree's ONLY date-filter default is packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts:158 `defaultValue: 'last_7_days'` — a VALID preset, unaffected. Verified by parsing the real shipped module through the new schema (not a fixture): parsed OK, defaultValue preserved as "last_7_days". Also pinned by a named test. `docs/notes/airtable-dashboard-analysis.mdx` has `defaultValue: 'this_quarter'` on a `type: 'select'` filter — a different surface, untouched by this rule. VERIFICATION — real readings - spec full suite: 343 files / 8819 tests passed - spec typecheck: tsc + scripts-typecheck + test-typecheck all clean - check:generated: 10/10 green (2 were stale — skill-refs, api-surface — and were regenerated with --fix, then re-checked green). api-surface delta is 4 pure ADDITIONS (2 const + 2 type), zero removals — no baseline debt (#4593). - check:spec-parsed-alias: OK (1443 bare / 749 pinned / 694 paired). No pin needed: the new types read `(typeof X)[number]`, not z.input/z.infer, so they are outside ADR-0122's population. - pnpm lint: clean - three examples `validate`: all REAL_EXIT=0 with "✓ Validation passed" (warnings present are pre-existing and unrelated — i18n section names, liveness, permissions, flow status) REVERSE VALIDATION (direction predicted BEFORE running) Predicted: neutralising the superRefine turns exactly 5 tests red (misspelled preset, `custom`, non-string, error-message, unknown-macro-token) and leaves the other 45 green. Measured: 5 failed / 45 passed, precisely those 5. Probe reverted; restored run 50/50 green, and the file greps clean of the probe. FOR PM — objectui 联动单 material Repo objectstack-ai/objectui @ 0cf8f0f, file packages/core/src/utils/dashboard-filters.ts: - line 73 `const PRESET_RANGES: Record<string, {from?,to?}>` — the 13 names with their date-macro bounds. Landing point: key it off the spec vocabulary so a spec-side addition becomes a compile error until bounds are supplied: `const PRESET_RANGES: Record<DateRangePreset, {from?: string; to?: string}> = {...}` - line 90 `export const DATE_RANGE_PRESETS = Object.keys(PRESET_RANGES)` becomes a re-export of spec's constant (same NAME, so no consumer churn), which also fixes its type: `string[]` today, a literal union after. - import: `import { DATE_RANGE_PRESETS, type DateRangePreset } from '@objectstack/spec/ui';` `@objectstack/spec/ui` is already objectui's most-used spec subpath (134 imports) and packages/core already imports from it; packages/core/package.json depends on `@objectstack/spec ^17.0.0-rc.5`, so the range covers this minor once published. - consumer: packages/plugin-dashboard/src/DashboardFilterBar.tsx:37,97 — display order now comes from spec; no code change expected. Nothing in objectui was modified by this commit. changeset: @objectstack/spec minor (new authorable validation surface). Docs: content/docs/ui/dashboards.mdx gains a "Date Filter Defaults" section and a pointer from the preset table to the source-of-truth constant. POST-MERGE RE-VERIFY (merged origin/main e39dd66, which also touched packages/spec — spec-changes.json + migrations/registry.ts — so AGENTS.md §10's rebuild-and-recheck applied) - merge was conflict-free; no os-regen-pending entries (the two sides touched disjoint spec files) - rebuilt spec, re-ran check:generated on the merged tree: 10/10 green, nothing stale, no regeneration needed - re-ran the full spec suite on the merged tree: 343 files / 8819 tests passed - delta vs origin/main is exactly the 6 files below; api-surface diff vs main is 4 additions / 0 removals Files: .changeset/dashboard-date-filter-preset-vocab.md, content/docs/ui/dashboards.mdx, packages/spec/api-surface/ui.json, packages/spec/src/ui/dashboard.test.ts, packages/spec/src/ui/dashboard.zod.ts, skills/objectstack-ui/references/_index.md This branch is ready for a PR. objectui was NOT modified — see the 联动单 material above for its landing points.
…efilter-preset-vocab
…#4614) The merge driver defers generator-owned artifacts rather than text-merging them (AGENTS.md §11), so the merge commit carried this branch's pre-merge ui.json — which predates #4593's export-type backfill on main. Regenerated from the rebuilt dist so the file describes the MERGED source: main's 73-schema backfill (ActionType/PageComponentType/ReportType and friends reclassified const → type, plus the newly-named types) is restored alongside this branch's four DATE_RANGE_* additions. check:generated 10/10 green after regeneration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8pDLTt6UHXUYY9YE7T1cA
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
qq9340100
enabled auto-merge
August 8, 2026 08:41
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4614
A 案(2026-08-03 已裁)spec 半边落地。完整叙事见分支 feature commit(
985be0d4b);要点:改动
DATE_MACRO_TOKENS模式):DATE_RANGE_PRESETS(13 名)+DateRangePreset、DATE_RANGE_DEFAULT_RANGES(+custom)+DateRangeDefaultRange,dateRange.defaultRange改读共享常量 —— 接受集逐成员不变(有测试断言)。GlobalFilterSchema.superRefine:type: 'date'的defaultValue必须 ∈ 预设名 | ISO 日期 | 日期宏 token;宏臂直接调isDateMacroToken,不复述文法(一份词汇,无第二方言);拒绝消息引用非法值并列全三种合法拼写。修掉的症状:拼错预设名落到created_at = 'last_7_dayz',200 OK 全零 tile 而筛选条显示 All time。前提分歧,实施内自洽解决(免裁决)
派发词写
date|dateRange两 type —— 实测 spec 的GlobalFilterSchema.type枚举没有dateRange成员:objectui 的dateRangedef 是resolveDashboardFilterDefs从schema.dateRange合成的内部保留名,不是可授权的globalFilters[].type。两半各归其位:date= 本 superRefine,dateRange= 内置项(已改读共享常量),未添枚举成员。custom刻意不是 preset(不命名窗口),defaultRange合法、裸 filter default 拒绝。存量与验证
三示例 + docs + packages 扫描拼错预设名零命中,反查(已知好名)证扫描有效 —— 无 ADR-0087 conversion 欠账。合并 main 后按 §11 merge-driver 语义重新生成
api-surface/ui.json(恢复 #4593 的 73 条补齐 + 本支 4 条新增),check:generated10/10。objectui 消费半边(
PRESET_RANGES改读 spec 词汇表)由 PM 按跨仓 rule 3 另立联动单,不在本 PR。交付通道注记
云端工头 H(
session_01F8pDLTt6UHXUYY9YE7T1cA)无 GitHub 工具,走交付降级通道:dev push 分支,PM(session_011M7UwH25Unfi73UHim7ajY)代开本 PR 并跟进 CI 至合并。Generated by Claude Code