Repository navigation
Commit bb91ae8
feat(spec): a report declares its period (
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBMfEB4BKutj59fyVsomGnz7)_
Fixes #26548
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up owed —
skills/objectstack-ui/evals/analytics-inline-vs-dataset.json
## 维护者速读
- **改了什么:** 报表元数据新增两个可选键。`dateRange`
是报表覆盖的时间段,预设表、默认值和改名提示与仪表盘逐字相同,作用于数据集里一个 `type: 'date'`
的维度;`exportOptions` 是报表表格的下载(csv / xlsx,默认开启,`formats: []` 关闭)。另加一条 lint
规则(判定维度是否存在、是否日期、省略时是否唯一)、表单「时间段与导出」分区、文档和三种语言的翻译。
- **为什么改:** 裁决
B(6408567749,#25873)要求报表有时间范围和导出,并复用现有的导出权限,不做订阅。这是规格的一半;Console 报表页的读取在
#25873。
- **风险与回滚:** 只加可选键,不声明这两个键的报表解析和渲染都不变。渲染器落地前两个键没有读取方,作者写了会收到 planned
警告,说明暂时不生效。回滚就是 revert 本 PR。
- **先看:** `packages/spec/src/ui/report.zod.ts`(两个新键和 joined
报表的拒绝),`packages/lint/src/validate-report-date-range.ts`(四种判定,也接在报表发布门上),`packages/spec/src/ui/list-view-export-options.ts`(`ReportExportOptionsSchema`,只留
csv / xlsx)。
- **两分钟验证:** `pnpm --filter @objectstack/spec exec vitest run
src/ui/report-date-range-export.test.ts`,预期 48 个用例全部通过,其中一条断言报表的
`defaultRange` / `allowCustomRange` 与仪表盘的声明逐字相等。
- **需要你做的:** 本 PR 改到 `skills/**`,属受管面:席位合同评审通过后由你合并。
## What changes
**`ReportSchema` gains two optional top-level keys**
(`packages/spec/src/ui/report.zod.ts`). A report that declares neither
parses exactly as before, with neither key in its output.
- **`dateRange: { dimension?, defaultRange, allowCustomRange }`**, a
closed block. `defaultRange` and `allowCustomRange` are the dashboard's
declaration: the same `DATE_RANGE_DEFAULT_RANGES` import (no second
preset table), the same defaults (`this_month`, `true`), the same
describes and the same rename hints (`preset` / `range` / `default`,
`allowCustom` / `custom`). `report-date-range-export.test.ts` holds the
two declarations equal through `strictObjectDeclarations()` and their
JSON Schema, so `dashboard.zod.ts` is untouched. `dimension` names a
dataset **dimension** that declares `type: 'date'` (ADR-0021: a report
selects by dimension name), with the dashboard `compareTo` hints `field`
/ `dateField` / `fieldName` / `timeDimension` renamed onto it. The TSDoc
states the renderer contract: the window rides
`selection.timeDimensions: [{ dimension, dateRange }]` with no
`granularity`, a filter window the analytics service already accepts.
Top-level aliases `dateFilter` / `timeRange` → `dateRange` (the
dashboard's).
- **`exportOptions: { formats?, includeHeaders?, fileNamePrefix? }`**,
declared as `ReportExportOptionsSchema` beside its list sibling in
`list-view-export-options.ts`. `includeHeaders` and `fileNamePrefix` are
the list block's members by identity; `formats` is
`ListViewExportFormatSchema.exclude(['json'])`, so `csv` / `xlsx`.
`maxRecords` / `streaming` are answered as list-only. No permission key:
the TSDoc and describe name `ObjectPermission.allowExport` on the
dataset base object, the predicate the list toolbar uses. Default on
(both formats), `formats: []` off.
- **Joined container.** A `joined` report refuses both keys, in the
superRefine arm that already refuses the container `chart`, with the
same no-pointer style. `JoinedReportBlockSchema` gets `guidance` for
both keys.
- **Guidance** on `ReportSchema` for `allowExport` (points at the
permission-set axis and `exportOptions: { formats: [] }`),
`subscription` and `schedule` (no scheduled delivery of a report).
- **Lint rule `validateReportDateRange`**
(`packages/lint/src/validate-report-date-range.ts`), in the
reference-integrity suite beside `validateChartBindings`, so it runs on
`os validate` / `os lint` / `os build` and at the `report` publish door
(`runtimeTypes: ['flow', 'report']`). Four `error` verdicts, each with
an `os explain` entry: `report-date-range-dimension-unknown`,
`-not-date`, `-ambiguous` (omitted, two or more date dimensions) and
`-none` (omitted, none). The omitted-dimension rule is the dashboard
executor's `compareTo` rule: exactly one candidate is used.
- **Liveness.** `liveness/report.json` rows for both keys, `planned`,
`authorWarn` with an author hint, notes naming the renderer read point
in `DatasetReportRenderer.tsx` and #25873 as carrier. Both containers
are drilled (`children`, six child rows) so the gate's
undeclared-inheritance check is satisfied. `lint-liveness-properties`
now walks `reports`, so the warning reaches authors.
- **Form.** `report.form.ts` gets a collapsed "Period & export" section,
`visibleWhen: "data.type != 'joined'"`, with both composites. Translated
in `@objectstack/platform-objects` for zh-CN, ja-JP and es-ES.
- **Docs.** `content/docs/ui/reports.mdx` § "Period and export" (an
`os:check` example, the dimension rule, the window lowering, the export
permission axis, a "declared, not drawn yet" callout, what is not
offered) and a paragraph under "Permissions and protection".
`skills/objectstack-ui/rules/dashboards.md` § Report Configuration gets
the `dateRange` example.
- **Generated:** `content/docs/references/ui/report.mdx`,
`authorable-surface/ui.json`, the strictness-ledger counts and the
liveness state counts.
- **Changeset:** `@objectstack/spec` minor, `@objectstack/lint` minor,
`@objectstack/platform-objects` patch, with both declaration lines.
**Skills readings** (`skills/**` touched):
| Reading | Before | After |
| --- | --- | --- |
| `skills/objectstack-ui/rules/dashboards.md` lines | 464 | 463 |
| `skills/objectstack-ui/rules/dashboards.md` tokens (ratchet, ceiling
6239) | 6239 | 6236 |
| `skills/objectstack-ui/**` lines | 2260 | 2259 |
| all `SKILL.md` lines | 4600 | 4600 |
| bundle total tokens | 213407 | 213404 |
The three-line `order` comment becomes a trailing comment on `order`,
and the freed lines carry a one-line period comment plus the `dateRange`
example, so the file still shrinks by one line and 3 tokens. Before is
`origin/main` at the round-3 merge. The eval is the owed half (filed as
#26633): both `skills/objectstack-ui/evals/*.json` files sit at their
token ceiling (headroom 0), so a report-period prompt cannot be added
here without deleting another prompt.
## Tests
- `packages/spec/src/ui/report-date-range-export.test.ts` (new, 48
cases): untouched reports, `dateRange: {}` defaults, every preset
accepted, `last_6_months` refused, every rename hint, unknown member,
`dateFilter` / `timeRange`, joined refusal on container and block,
declaration parity with the dashboard, `exportOptions` accepts and
refusals (`json`, `pdf`, `docx`, `maxRecords`, `streaming`, bare array,
joined, block), `allowExport` / `subscription` / `schedule` guidance,
`defineReport` and `getMetadataTypeSchema('report')`.
- `packages/lint/src/validate-report-date-range.test.ts` (new): all four
verdicts on the `app-todo` task-dataset shape, nearest-name suggestion
among date dimensions only, silence on unbound / joined / malformed
input, suite integration, verdict length and `os explain` per id.
- `runtime-gate.inert-type-writes.test.ts`: a new block proving the
`report` publish door judges a date range against the dataset it binds
(four cases and a control); the door's member pin is now two members.
- `lint-liveness-properties.test.ts`: both keys warn with the authored
hint; a report without them is silent.
- Pins moved by the new form section:
`report-form-echo-decisions.test.ts` (population 44 → 50), the
catalog-wide positive control in
`object-lifecycle-panel-echo-decisions.test.ts` (683 → 686),
`validate-predicate-path-refs.test.ts` predicate census (88 → 89,
literal comparisons 63 → 64). Each carries a dated comment.
-
`packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts`:
the served `/meta/types` census pins `report` at 23 top-level properties
(was 21), with a JSDoc line saying why. The round-3 merge of `main` kept
main's lowered counts for the other types (#26599).
## Acceptance notes
Read against the card's Acceptance list. Deviations, each a choice made
here:
1. **`exportOptions` is also refused on a `joined` container.** The card
decides the joined case for `dateRange` only. A joined report draws one
table per block, so "the table the report draws" has no single answer.
Widening later is non-breaking. Raised as an open question in the
report.
2. **`json` and `pdf` get report-side messages.** The card says `pdf`
keeps the list's retirement prescription, but that text sends the author
to list behaviour that is false for a report. The report message for
`pdf` points at a page with `print`.
3. **The `subscription` / `schedule` guidance cites no ruling.** Runtime
strings carry no tracker numbers in this repo, so the text says what is
true (no scheduled delivery) without the ruling reference.
4. **The lint rule sits on the `report` publish door** as well as `flow`
(the chart-bindings precedent), so publishing a report with a bad
`dimension` is refused, as the docs say.
5. **The skills example uses `this_year`, not `this_quarter`.** The
example's matrix buckets `close_date` by quarter, so a quarter window
would draw one column.
6. **Translations.** Only `@objectstack/platform-objects` bundles
metadata forms, so the six new leaves landed there (three locales,
authored, not filled); `pnpm check:i18n` reports all ten packages in
sync.
7. **No new public export.** `ReportExportOptionsSchema` lives in
`list-view-export-options.ts`, which, like its list sibling, is outside
the `ui` barrel; `check:api-surface` is unchanged.
Out of scope, untouched: `ui/` (the renderer is #25873), any server
export route, `dashboard.zod.ts`.
## Verification
All readings local, final head `be4a0204cdad`. It merges `origin/main`
at `86ae47e13` (no rebase); `main` moved 6 commits during the test run,
none touching a file this PR changes, and `git merge-tree` against
`8222fa805` is clean. CI check-runs are not cited here: they report
after this push.
- **Package suites (round 1, at `0c548c59a8ca`):** `@objectstack/spec`
729 files / 22095 tests passed (1 skipped, 1 todo); `@objectstack/lint`
167 files / 7411 passed; `@objectstack/platform-objects` 96 files / 1361
passed. Typecheck of all three: exit 0. `eslint` on the 15 changed
source files: exit 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands`
named 113 families at `4dca57f78`; all were run on a full workspace
build (118 turbo tasks) with each exit code recorded before any pipe.
One was red: `check:empty-state` (exit 1), because the `dateRange` TSDoc
says an absent window shows every period and nothing classified it.
Fixed in `8c03abecb`: the sentence now sits beside the key and
`dateRange` is registered as a `scope` empty state (it widens the
period, never the access). That commit derived one more family
(`check:pnpm-flag-spellings`). Re-run after it: `check:empty-state`,
`check:pnpm-flag-spellings`, `check:spec-docblock-symbol-anchors`,
`check:liveness`, `check:docs`, `check:api-surface`, `check:nul-bytes`,
all exit 0. `dispatch-gates --ran`: 114 derived, 114 run, 0
NOT-MEASURED, 0 UNRUN.
- **Round 3, on the merged tree (`be4a0204cdad`):** the merge conflicted
only in `CARD_PROPERTY_COUNTS`
(`protocol.meta-types-degenerate-derivation.test.ts`), resolved to
main's lowered counts with `report: 23` and both comment blocks. `pnpm
install`, then `check:generated --fix` regenerated
`authorable-surface/ui.json` (main's file plus `ui/Report:dateRange` and
`ui/Report:exportOptions`), then 0 stale. `check:empty-state` and
`check:liveness` exit 0. Suites, each exit 0: `@objectstack/spec` 732
files / 21956 passed (1 todo); `@objectstack/lint` 167 / 7411;
`@objectstack/platform-objects` 96 / 1361;
`@objectstack/metadata-protocol` 269 files (3 skipped) / 21580 passed
(19 skipped), census file 18 / 18; `@objectstack/plugin-security` 230 /
4991 passed (45 skipped).
- **Local-only families:** `check:dev-prereqs` exit 0,
`check:skill-frame-freshness` exit 0.
<details>
<summary>check:liveness (report rows)</summary>
```
report 27 classified (live 21, planned 6)
✓ every governed-type property, at every depth the ledger drills, is classified, … every container inheritance is declared, …
total across the shards: 1061 live · 1 experimental · 1 live-elsewhere · 113 dead · 29 planned = 1205 classified.
```
</details>
<details>
<summary>check:i18n and the translation ledgers</summary>
```
check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys).
platform-objects src/apps/translations: Test Files 25 passed (25), Tests 438 passed (438)
```
</details>
<details>
<summary>Skills token ratchet</summary>
```
✓ check-skills-token-ratchet: 81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.
skills/objectstack-ui/rules/dashboards.md is 6236 tokens (ceiling 6239; headroom 3)
```
</details>
<details>
<summary>Focused tests</summary>
```
packages/spec src/ui/report-date-range-export.test.ts Tests 48 passed (48)
packages/spec report.test.ts, report-date-range-export, alias-integrity,
metadata-form-zod-reconciliation Test Files 4 passed, Tests 216 passed
packages/lint validate-report-date-range, runtime-gate.inert-type-writes, lint-liveness-properties,
reference-integrity-suite, rule-explanations Tests 191 passed (191)
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzhMJrMdCkQS33DYtmu)_
---------
Co-authored-by: Claude <noreply@anthropic.com>dateRange) and the export of its table (exportOptions) (#26628)1 parent af1e1e0 commit bb91ae8
30 files changed
Lines changed: 1266 additions & 24 deletions
File tree
- .changeset
- content/docs
- references/ui
- ui
- docs/audits/2026-07-unknown-key-strictness-ledger.counts
- packages
- lint/src
- metadata-protocol/src
- platform-objects/src/apps/translations
- spec
- authorable-surface
- liveness
- state-counts
- scripts/liveness
- src/ui
- skills/objectstack-ui/rules
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
70 | 72 | | |
71 | 73 | | |
72 | 74 | | |
| |||
103 | 105 | | |
104 | 106 | | |
105 | 107 | | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
106 | 124 | | |
107 | 125 | | |
108 | 126 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
210 | 210 | | |
211 | 211 | | |
212 | 212 | | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
213 | 274 | | |
214 | 275 | | |
215 | 276 | | |
| |||
256 | 317 | | |
257 | 318 | | |
258 | 319 | | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
259 | 324 | | |
260 | 325 | | |
261 | 326 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
646 | 646 | | |
647 | 647 | | |
648 | 648 | | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
649 | 661 | | |
650 | 662 | | |
651 | 663 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2287 | 2287 | | |
2288 | 2288 | | |
2289 | 2289 | | |
| 2290 | + | |
| 2291 | + | |
| 2292 | + | |
| 2293 | + | |
| 2294 | + | |
| 2295 | + | |
| 2296 | + | |
| 2297 | + | |
| 2298 | + | |
| 2299 | + | |
| 2300 | + | |
| 2301 | + | |
| 2302 | + | |
| 2303 | + | |
| 2304 | + | |
| 2305 | + | |
| 2306 | + | |
| 2307 | + | |
| 2308 | + | |
| 2309 | + | |
| 2310 | + | |
| 2311 | + | |
| 2312 | + | |
| 2313 | + | |
| 2314 | + | |
| 2315 | + | |
| 2316 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
585 | 585 | | |
586 | 586 | | |
587 | 587 | | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
588 | 593 | | |
589 | 594 | | |
590 | 595 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
48 | 52 | | |
49 | 53 | | |
50 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
| 112 | + | |
112 | 113 | | |
113 | 114 | | |
114 | 115 | | |
| |||
438 | 439 | | |
439 | 440 | | |
440 | 441 | | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
441 | 459 | | |
442 | 460 | | |
443 | 461 | | |
| |||
0 commit comments