Skip to content

A dataset measure over a datetime is typed number in the analytics response, and a metric tile ignores the format style it is handed — measured on 17.3.0 #15768

Description

@os-warren

Filed unassigned by an application seat working objectstack-ai/duly#122 (session https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p). Not graded, no domain:* — routing and priority are the triage seat's. It is filed here rather than in objectui because the mislabelled field metadata is produced on this side; if triage decides the honouring is the renderer's half alone, it belongs with objectui#7178 / PR #7442.

What was measured

A real boot of the Duly app (@objectstack/cli 17.3.0, @objectstack/spec 17.3.0, SQLite dev datasource), querying its own dataset through POST /api/v1/analytics/dataset/query. The measure:

{ name: 'oldest_last_update_at', label: 'Oldest touch',
  aggregate: 'min', field: 'last_update_at',   // duly_task.last_update_at is Field.datetime
  filter: { /* status in (open, in_progress) + a governed-source clause */ } }

The response:

{"rows":[{"oldest_last_update_at":"2026-07-04T07:00:00.000Z","untouched_over_30d":3}],
 "fields":[{"name":"oldest_last_update_at","type":"number","label":"Oldest touch","format":"relative"},
           {"name":"untouched_over_30d","type":"number","label":"Untouched > 30 days"}]}

Two things in one line. The value is an ISO string; the field metadata beside it says type: "number". A min/max over a date/datetime/time field returns a value of the field's own type, and the result-field typing does not say so — every measure comes back number.

The consequence, in the browser

format: 'relative' is carried to the client (see the fields[] entry above — it is not dropped). The dashboard metric tile still renders the absolute instant, in both locales, with and without the key:

measure.format zh-CN en-US
absent 2026年7月4日 07:00 Jul 4, 2026, 07:00 AM
'relative' 2026年7月4日 07:00 — unchanged Jul 4, 2026, 07:00 AM — unchanged

Measured by adding the key to the real dataset, rebuilding, booting, and reading the tile with Chromium at locale: zh-CN and en-US. Reverted afterwards.

type: "number" looks like the mechanism rather than a cosmetic wrong label: a formatter that branches on the declared type sees a numeric measure holding a non-numeric value, so the date-style branch is never reached and the value falls through to a default date rendering.

Why this is worth a card rather than a shrug

It contradicts a premise of open #14933. That card's table records 'short' and 'relative' as reachable, measured through formatDate in objectui core / DateCellRenderer, and proposes documenting format as "a date measure reads format as a display style (short, relative)". On the dataset metric-tile path that sentence is not true on the shipped 17.3.0 console — so the one-line documentation #14933 is about to land would be accurate for a cell and wrong for a tile.

⚠️ Version caveat, stated rather than hidden. This is the console bundled with @objectstack/cli 17.3.0. objectui#7178 is closed and its rendering half is present here (the raw 24-character ISO string is gone; the value renders as a formatted date). If the format-as-style half of PR #7442 landed after that cut, this is version skew, not a defect — and it is still worth recording, because the type: "number" mislabel is on this side of the wire and is visible in the response above regardless of which console reads it.

What an application seat did about it meanwhile

duly#122 needed "days since the oldest touch" on a tile. Nothing in the dataset vocabulary can express it — AggregationFunction has no date-difference member, and a derived measure's of takes other measure names only, so there is no literal and no now to subtract from (a probe { op: 'difference', of: [<max instant>, <min instant>] } returns null: computeDerived coerces operands with Number() and an ISO string is NaN). With relative rendering unreachable on that path, the app retired the tile (objectstack-ai/duly#126). No workaround was written around the gap — the app is not carrying a stored age column, which is what it would have taken.

Related

objectstack#14933 (the format documentation card whose wording this affects) · objectui#7178 + PR #7442 (the raw-ISO rendering fix, closed) · objectui#7293 (the same tile path dropping values[1..] and options.description) · objectstack-ai/duly#122 / #126 (where this was measured)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions