Skip to content

Commit 54145cc

Browse files
docs(analytics): key the label-resolution prose on the field carrying options, not on the select type (#18909)
Fixes #18475 Both docs sentences named `select` as what makes the analytics label-resolution option arm fire. The implementation keys on the field **carrying options** instead, so a `radio` axis resolves its label while the docs said it did not. ## The measurement `packages/services/service-analytics/src/dimension-labels.ts`, read on this branch at `84ba4a847` (identical to `origin/main` at dispatch): ``` :204 if (Array.isArray(meta.options) && meta.options.length > 0) return true; :210 if (Array.isArray(meta.options) && meta.options.length > 0) { :388 if (Array.isArray(meta.options) && meta.options.length > 0) { occurrences of `type === 'select'` in that file: 0 ``` The firing control is taken from the probed file itself and reads **0** there; it appears nowhere in this diff, which touches only `content/docs/**`. ## Why a property, and not a type list The card left open whether an option-side **noun** exists or whether the prose must grow a type enumeration. Two readings settle it against the enumeration: - `options` is an ordinary optional key on the field schema (`packages/spec/src/data/field.zod.ts:1228`) — it is **not** gated on type, so a field of any type that carries options enters the arm. - `MULTI_OPTION_TYPES` (`packages/spec/src/data/field-value.zod.ts:145`) includes `tags`, which that file documents as **free-form**. A `tags` field need not declare `options` at all. So an enumeration would be wrong in **both** directions: it would over-claim on `tags` and under-claim on anything else carrying options. Naming the property the implementation names is the only formulation that matches the resolver, and it is the same move the sibling half took — name what the implementation already names rather than growing a list. ## What this deliberately does not say The prose states **what makes the arm fire**, and stops there. It does not assert a per-type outcome — in particular it does not claim what a multi-valued stored value resolves to. That behavioural question is named by the card as a different question it did not measure, and it stays unmeasured here. Scope held to the two sentences. `packages/services/service-analytics/**` and every schema are untouched: the resolver accepts the wider set and is correct; narrowing it would shrink a published accept set, which is another class of change entirely. ## Acceptance notes - `content/docs/releases/v9.mdx` and `content/docs/releases/v17/17-0.mdx` carry the same `select`-only phrasing. Release-owned and never edited in a code PR; they are also accurate as historical records of what those releases said. Noted, not filed. - The module header of `dimension-labels.ts` (`:8`) describes the same arm as **select** only, one layer up from the two sentences repaired here. Out of the file surface this PR was dispatched with, so it is reported rather than touched. Dedupe words: `dimension-labels header`, `module doc comment select`, `option arm prose`, `service-analytics tsdoc`. The seat disagreed with the noted-not-filed grading and filed it separately; it is now tracked on its own card, #18923 (named without a closing keyword on purpose — this PR does not deliver it). Nothing in this PR waits on it. - `content/docs/data-modeling/import-mappings.mdx` names `select / multiselect` for label-to-value resolution on **import**. That is the opposite direction through a different subsystem, not this arm. Noted, not filed. ## Verification No changeset: measured, nothing published moves. No package's `files[]` mentions `content/docs` (0 of the workspace manifests); its only consumer `@objectstack/docs` is `private: true`. Positive control: the same reader returns `@objectstack/spec`'s non-empty `files[]`, so the absence is a reading and not a broken probe. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 921eac0 commit 54145cc

2 files changed

Lines changed: 13 additions & 9 deletions

File tree

‎content/docs/data-modeling/analytics.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,12 @@ dataset-bound reports — so the numbers match everywhere.
207207
The query result is presentation-ready — authors do not format dimension or
208208
measure values by hand:
209209

210-
- **Dimensions** — a `select` dimension returns its option **label** (not the
211-
stored value), a **reference** dimension (any field whose stored value is
212-
another record's id, a `user` person axis included) returns the related
213-
record's **display name** (not the FK id), and a `date` dimension with a
210+
- **Dimensions** — a dimension **whose field carries `options`** returns the
211+
option **label** (not the stored value; resolution keys on the `options` list
212+
being there, not on the field's type, so this is not limited to `select`), a
213+
**reference** dimension (any field whose stored value is another record's id,
214+
a `user` person axis included) returns the related record's **display name**
215+
(not the FK id), and a `date` dimension with a
214216
`dateGranularity` returns a human bucket label (`month` → `2026-04`,
215217
`quarter` → `2026-Q2`, `year` → `2026`). Unresolved values pass through
216218
unchanged, never blank.

‎content/docs/ui/dashboards.mdx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@ Notes on behaviour:
143143
truncates a reproducible window instead of an arbitrary subset.
144144
- Ordering is applied to the finished grid, so a **derived measure** is a valid
145145
`sortBy` even though no single SQL statement computes it.
146-
- A `sortBy` naming a **select** or **reference** dimension orders by the
147-
**display label** the rows render (the option label / the related record's
148-
name), not the stored value or foreign-key id — and the label is resolved
149-
before `limit` applies, so a top-N by name truncates the right N.
150-
**Reference** is the whole class of fields whose stored value is another
146+
- A `sortBy` naming a dimension **whose field carries `options`**, or a
147+
**reference** dimension, orders by the **display label** the rows render (the
148+
option label / the related record's name), not the stored value or
149+
foreign-key id — and the label is resolved before `limit` applies, so a top-N
150+
by name truncates the right N. The option arm keys on the `options` list
151+
being there, not on the field's type, so it is not limited to `select`;
152+
**reference** is the whole class of fields whose stored value is another
151153
record's id, so a `user` person axis sorts by name too. Sorting by a measure
152154
(the common case) involves no label lookup and is unaffected.
153155
- A `funnel` with no declared stage order falls back to sorting by value

0 commit comments

Comments
 (0)