Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/dataset-measure-format-date-styles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@objectstack/spec": patch
---

`DatasetMeasureSchema.format` now documents what a DATE-valued measure can and cannot say, and the numeral-pattern examples no longer stand as the whole story.

The field was silent about date measures while advertising `e.g. "$0,0.00", "0.0%"` — the pattern grammar a date measure is precisely unable to read. An author with a `min` / `max` over a date field read that line, wrote `format: 'YYYY-MM-DD'`, parsed clean, and got the locale default.

The statement is carried by a `.describe()` where there was none, so it reaches the published surfaces an author actually reads: the generated JSON Schema (`json-schema/ui/Dataset.json`, `DatasetMeasure.json`) and the reference table in `content/docs/references/ui/dataset.mdx`, whose Description cell for `format` had been rendering the silence as a blank. The docblock above it carries the longer measured record.

What it now says, measured rather than assumed against the objectui pin this repo builds against: a numeral pattern applies to a numeric measure; a date-valued measure never reads a date PATTERN — a date-only value reads `format` as a display STYLE (`short`, `relative`), and a datetime value ignores `format` altogether.

Nothing accepts or rejects differently: `format` remains `z.string().optional()` and no measure is refused. Documentation over a published schema (objectui#7178 ruled A).
4 changes: 2 additions & 2 deletions content/docs/references/ui/dataset.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const result = DatasetSchema.parse(data);
| **aggregate** | `Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>` | optional | Aggregation (sum/avg/count/...); omit when `derived` is set |
| **field** | `string` | optional | Aggregated field; optional for count(*) |
| **filter** | `any` | optional | |
| **format** | `string` | optional | |
| **format** | `string` | optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders the locale default. A date-only value reads `format` as a display style (`short`, `relative`); a datetime value ignores it. |
| **currency** | `string` | optional | Display currency code (ISO 4217) |
| **derived** | `{ op: Enum<'ratio' \| 'sum' \| 'difference' \| 'product'>; of: string[] }` | optional | |

Expand Down Expand Up @@ -125,7 +125,7 @@ const result = DatasetSchema.parse(data);
| **aggregate** | `Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct'>` | optional | Aggregation (sum/avg/count/...); omit when `derived` is set |
| **field** | `string` | optional | Aggregated field; optional for count(*) |
| **filter** | `any` | optional | |
| **format** | `string` | optional | |
| **format** | `string` | optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders the locale default. A date-only value reads `format` as a display style (`short`, `relative`); a datetime value ignores it. |
| **currency** | `string` | optional | Display currency code (ISO 4217) |
| **derived** | `{ op: Enum<'ratio' \| 'sum' \| 'difference' \| 'product'>; of: string[] }` | optional | |

Expand Down
35 changes: 33 additions & 2 deletions packages/spec/src/ui/dataset.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,39 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({
field: z.string().optional().describe('Aggregated field; optional for count(*)'),
/** Measure-scoped filter (e.g. only won deals for "won_amount"). */
filter: FilterConditionSchema.optional(),
/** Display format, e.g. "$0,0.00", "0.0%". */
format: z.string().optional(),
/**
* Display format — a NUMERAL pattern controlling grouping, decimals and
* percent: `"0,0.00"`, `"0.0%"`. A `$` in the pattern is still honoured as a
* legacy literal, but a real amount takes its symbol from `currency` below,
* never from the pattern — see that field's note.
*
* A DATE-valued measure (`min` / `max` over a date field) never reads a date
* PATTERN here: `"YYYY-MM-DD"` is accepted by this schema, reaches the
* renderer, and produces the locale default. The shared date path takes a
* named STYLE instead, so a date-only value reads `format` as `short`
* (`Jul 4, '24`) or `relative` (`3 days ago` inside a ±7-day window, the
* absolute locale form outside it) — the same two words `DateCellRenderer`
* honours from `field.format` — while a DATETIME value ignores `format`
* altogether.
*
* Measured at the pin this repo builds against (`.objectui-sha` =
* `a472b0716`; re-derived at that pin 2026-09-07) in objectui
* `packages/core/src/utils/dataset-format.ts`: `formatMeasure` routes a
* non-numeric value through `formatMeasureDate` (`:184-197`), whose
* date-only arm threads `format` into the STYLE parameter of `formatDate`
* (`utils/date-display.ts:104-137`, whose `relative` branch falls back to
* the absolute form beyond ±7 days at `:90`), while its datetime arm calls
* `formatDateTime(v, { locale })` with no style at all (`:194`). Teaching
* the shared path a pattern grammar would change every list cell that reads
* it, so the gap is DOCUMENTED here rather than closed (objectui#7178 ruled
* A; the datetime half is objectui#7443).
*/
format: z.string().optional().describe(
'Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". '
+ 'An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued '
+ 'measure never reads a date pattern: `"YYYY-MM-DD"` renders the locale default. A date-only '
+ 'value reads `format` as a display style (`short`, `relative`); a datetime value ignores it.',
),
/**
* Display currency (ISO 4217, e.g. "USD", "CNY"). Carried onto the result
* field so presentations render a locale-correct symbol via `Intl` rather
Expand Down
Loading