You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 96c8d55
Browse filesBrowse the repository at this point in the historyBrowse files
fix(objectql)!: inside one bulk create, a child rule that reads a parent summary counts the earlier rows of the same batch its rules admitted, so a batch is judged as its rows would be one by one (#25579)
6
+
7
+
Clause-②: no (narrowing) — a bulk create (`insert(array)`, `insertMany`, and the doors on them: `createManyData`, `insertManyData`, the import's batched creates) refuses a row whose rule reads a parent `summary` that the earlier admitted rows of the same batch move past the rule's limit, which it admitted before. Nothing is widened.
8
+
Metadata-protocol: none
9
+
10
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no export or type changes: an existing validation rule is now enforced on the rows of one bulk create the way it is already enforced one write at a time. The refusal is the rule's own VALIDATION_FAILED, worded by its author, so there is nothing for a migration to rewrite and no tombstone. -->
11
+
12
+
**BREAKING** accept-set narrowing at the bulk insert (`@objectstack/objectql`), served by every bulk create door.
13
+
14
+
-**Before.** A bulk insert read the related rows its rules read through a reference once, before any row of the batch was written, and recomputed parent summaries once, after. So "each supplier has at most one primary contact" (a `count` summary filtered on `{ is_primary: true }`, and the child rule `record.is_primary == true && record.supplier.primary_contact_count > 0`) refused a second primary contact written on its own, but admitted two primary contacts for one supplier in one import: both rows read `0`.
15
+
-**Now.** Each row's rules read the parent summary as it stands after the earlier rows of the same batch that the rules admitted. The batch's admitted rows are accumulated in memory per summary and per parent; ⛔ nothing is re-read from the database per row. In the card's import, one contact is created and the other row reports the rule's error. A multi-row insert preview (`validate` with an array, mode `insert`) answers the same way.
16
+
-**Unchanged.** Which rows land and how a refusal is reported stay each door's own: `insertMany` / `insertManyData` (the import) report the refused row and write the rest; `insert(array)` / `createManyData` refuse the whole batch. A row the rules refuse is not counted. A single write reads the stored summary as before. The summary written after the batch is the same recompute as before.
17
+
-**Which summaries accumulate.**`count`, and `sum` / `min` / `max` over number values, with the summary's `filter` evaluated on the new row. An `avg` summary, a `sum` / `min` / `max` that meets a value that is not a number, and a summary whose `filter` reads a dotted path through another reference keep reading the stored value, as before.
18
+
-**Not covered.** Concurrent writes: two writes in flight at the same time each read the summary they started from (批量等于逐条,并发不保证).
19
+
-**Who sees it.** An app whose child rule reads a parent summary and that bulk-creates children: a batch that broke the rule was admitted whole before, and now its offending rows are refused.
20
+
21
+
**The remedy.** None needed for a rule that meant what it says. A batch that must land whole has to satisfy the rule row by row, in order.
feat(spec): a report declares the period it covers (`dateRange`) and the export of its table (`exportOptions`)
8
+
9
+
Clause-②: yes (widening)
10
+
Metadata-protocol: added
11
+
12
+
`ReportSchema` gains two optional top-level keys. A report that declares neither parses and renders exactly as before.
13
+
14
+
-**`dateRange: { dimension?, defaultRange, allowCustomRange }`.** A date window over one dimension of the report's dataset. `defaultRange` and `allowCustomRange` are the dashboard's own declaration, verbatim: the same preset table (`DATE_RANGE_DEFAULT_RANGES`), the same defaults (`this_month`, `true`) and the same rename hints. `dimension` names a dataset **dimension** that declares `type: 'date'`, not a raw object field; omitted, the dataset's one `type: 'date'` dimension is used. The window is a filter, not a bucket. The renderer sends it as `selection.timeDimensions: [{ dimension, dateRange }]` with no `granularity`, which the analytics service already accepts. `dateFilter` / `timeRange` are renamed onto `dateRange`, the dashboard's own spellings.
15
+
-**`exportOptions: { formats?, includeHeaders?, fileNamePrefix? }`.** Three members of the list export block, under its key name. `formats` is `csv` / `xlsx` only (`json` and `pdf` are refused with a report-side message). Export is on by default (both formats) and `formats: []` turns it off. `maxRecords` and `streaming` are list-only and are refused with that reason. There is no report permission key: the control is gated on the dataset base object's `ObjectPermission.allowExport`, the axis the list toolbar uses. `allowExport` written on a report is answered with that pointer.
16
+
-**Refused on a `joined` report**, on the container and on a block. The blocks may bind different datasets, and the report draws one table per block.
17
+
-**No subscription.**`subscription` / `schedule` on a report are answered: there is no scheduled delivery of a report.
18
+
19
+
`@objectstack/lint` adds the dataset-aware half as a reference-integrity rule, `validateReportDateRange`. It runs on `os validate`, `os lint` and `os build`, and at the report publish door. Four `error` verdicts, each with an `os explain` entry: `report-date-range-dimension-unknown`, `report-date-range-dimension-not-date`, `report-date-range-dimension-ambiguous` (dimension omitted, two or more date dimensions) and `report-date-range-dimension-none` (dimension omitted, no date dimension). The liveness author-warning lint now walks `reports`, and both keys warn (`liveness-planned-property`) until the Console report page reads them. The report editor form gains a collapsed "Period & export" section, translated in `@objectstack/platform-objects` for zh-CN, ja-JP and es-ES.
20
+
21
+
Nothing renders either key yet. The Console report page's period chip and export control are the renderer's half, and until it lands an author declaring either key gets a planned-property warning that says so.
fix(app-shell): flow screen date, date-time and boolean fields use the platform controls
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A screen flow's input screen was the last form surface still drawing the browser's native date input, so a `date` field showed the machine's format (`mm/dd/yyyy` under a Chinese console), a `datetime` field was a bare text box, and a `boolean` field was a lone checkbox on the line under its label, which read like an empty radio button.
11
+
12
+
What changes:
13
+
14
+
- A `date` or `datetime` screen field is now the record form's date picker: a text box in the display locale's format, a calendar popover, a clear button and the today / tomorrow shortcuts. The value it hands the flow is the stored shape: `YYYY-MM-DD` for a date and an ISO instant for a date-time, and `''` when cleared, as the native date input wrote.
15
+
- A `boolean` (or `checkbox`) screen field is now the inline row the action-parameter dialog draws: the checkbox with its label beside it, and the help text under the row.
fix(ui): the stage path and the kanban lay a select's options out by their declared lifecycle `kind` (#26612)
7
+
8
+
Clause-②: no
9
+
Metadata-protocol: none
10
+
11
+
A select option may declare `kind: 'open' | 'won' | 'lost'` (`SelectOption.kind` in `@objectstack/spec`). Two readers now follow it instead of the option order or the value's spelling.
12
+
13
+
-**`record:path`.** Once the status field declares a kind on any option, an option without one reads as `open`, the spec's rule for an absent kind, and is never classified from a `closed_won` / `closed_lost` spelling. The stages are laid out by kind: the open options as the steps in their authored order, then every won option as the forward terminus, then the lost options apart from the rail. A lost option authored mid-list no longer pulls the options after it into the lost group. A field that declares no kind keeps the spelling fallback and its positional layout as before, and a dev build now warns once, naming the object, the field and the stages it guessed.
14
+
-**Kanban.** Lanes the board derives from the lane field's options run open → won → lost when the field declares a kind, each class in its authored order, and every lost lane starts collapsed: a spine with its card count that the viewer can open. Authored `columns` and fields that declare no kind keep their order; the board guesses nothing from spelling.
fix(plugin-audit): the compliance ledger keeps no copy of secret-store key material
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
The audit writer copies each audited row into `sys_audit_log` (`new_value` / `old_value`) and into `sys_activity.metadata`. For the secret store and the two settings holders, that copy carried the encrypted key-material column. Every new copy now omits those columns, the way a field declared `internal` is omitted:
11
+
12
+
-**What is still recorded.** Each create, update and delete of those rows keeps its ledger and activity row: which record, its producer-scoped label, key id, algorithm and version, who changed it and when. A write that changes only the key material still records its row, with the material on neither side.
13
+
-**Which columns.** The list is closed, held by the writer (`KEY_MATERIAL_COPY_COLUMNS`), and keyed by the platform-object definitions' own names. A column of the same name on any other object is recorded as before.
14
+
-**Copies already written.**`migrateKeyMaterialCopies(engine, logger, { apply })` and `migrateArchivedKeyMaterialCopies(driver, logger, { apply })` remove the columns from copies stored before this release, in the hot `sys_audit_log` / `sys_activity` tables and in an archive datasource. Both runs are dry by default, idempotent, change only the copy column, and keep each row's stored `updated_at`. Their reports hold counts only. The pure planners `planKeyMaterialAuditRowPatch` and `planKeyMaterialActivityRowPatch` are exported with them.
Copy file name to clipboardExpand all lines: content/docs/references/ui/report.mdx
+18Lines changed: 18 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,6 +67,8 @@ const result = JoinedReportBlockSchema.parse(data);
67
67
|**order**|`{ by: string; direction?: Enum<'asc' \| 'desc'> }[]`| optional | Result ordering, most significant key first |
68
68
|**drilldown**|`boolean`| optional (default: `true`) | Click-through to underlying records |
69
69
|**chart**|`{ type: Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'line' \| 'area' \| 'pie' \| 'donut' \| …>; title?: string \| Record<string, string>; subtitle?: string \| Record<string, string>; description?: string \| Record<string, string>; … }`| optional | Embedded chart configuration (refused on a joined report, which draws tables only) |
70
+
|**dateRange**|`{ dimension?: string; defaultRange?: Enum<'today' \| 'yesterday' \| 'this_week' \| 'last_week' \| 'this_month' \| 'last_month' \| …>; allowCustomRange?: boolean }`| optional | Report date range: a window over one dataset date dimension (refused on a joined report) |
71
+
|**exportOptions**|`{ formats?: Enum<'csv' \| 'xlsx'>[]; includeHeaders?: boolean; fileNamePrefix?: string }`| optional | Report export control: `{ formats?, includeHeaders?, fileNamePrefix? }`, csv / xlsx, on by default; who may export is the base object's export permission (refused on a joined report) |
| **aria** | `never` | optional | [REMOVED] `ChartConfig.aria` — authored as `dashboard.widgets[].chartConfig.aria`, `report.chart.aria` and `report.blocks[].chart.aria` — was removed in @objectstack/spec 17 (ADR-0049 D2). No chart renderer ever applied it: the chart implementation declares no `aria` prop, the presentation lowering names it nowhere, and the react `<ObjectChart>` block never published it, so ARIA attributes declared here parsed and then silently did not reach the DOM. Delete the key. The accessible name that IS applied on this same chart config is its sibling `description`, which the chart renderer lowers onto the chart graphic as `role="img"` plus `aria-label`. The shared `AriaProps` shape is NOT gone — `ariaLabel` / `ariaDescribedBy` / `role` stay live in the `aria` block on `page.aria`, `page.components[].aria` and the list view `aria`. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
105
107
108
+
### Nested Shape: `Report.dateRange`
109
+
110
+
| Property | Type | Required | Description |
111
+
| :--- | :--- | :--- | :--- |
112
+
|**dimension**|`string`| optional | Dataset dimension (declared `type: 'date'`) the window applies to; omitted = the dataset's one date dimension |
|**allowCustomRange**|`boolean`| optional (default: `true`) | Allow users to pick a custom date range |
115
+
116
+
### Nested Shape: `Report.exportOptions`
117
+
118
+
| Property | Type | Required | Description |
119
+
| :--- | :--- | :--- | :--- |
120
+
|**formats**|`Enum<'csv' \| 'xlsx'>[]`| optional | Formats the report's export control offers (default: ['csv', 'xlsx']); an empty list turns the control off |
121
+
|**includeHeaders**|`boolean`| optional | Include column headers in the exported file (default true) |
122
+
|**fileNamePrefix**|`string`| optional | Download file name prefix — replaces the report label in the generated file name |
0 commit comments