Skip to content

fix(service-analytics): translate select dimension option labels on dataset charts - #17061

Merged
huangyiirene merged 3 commits into
mainfrom
claude/issue-16773-select-dimension-label-translation
Sep 9, 2026
Merged

fix(service-analytics): translate select dimension option labels on dataset charts#17061
huangyiirene merged 3 commits into
mainfrom
claude/issue-16773-select-dimension-label-translation

Conversation

@os-trump

@os-trump os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16773

Clause-②: yes

DimensionLabelDeps is re-exported wholesale from packages/services/service-analytics/src/index.ts,
so translateSelectOptions is a new key on a published exported type — the wire shape and
the accept/refuse behaviour are otherwise unchanged, and a host wiring no i18n service falls
back to today's authored-label behaviour, but the published-type surface itself grew and that
is the mechanical floor for this clause regardless of the key being optional. Corrected from
this PR's own provisional no — see "Clause-② correction" below for the measurement.

Reproduction

Re-driven at code level (no browser/hotclm access from this seat): a unit-level reproduction
against resolveDimensionLabels on origin/main confirmed both halves of the correlation:

  • Same-object select dimension (field: 'direction'): the raw stored value (purchase) is
    unconditionally overwritten with the field's AUTHORED options[].label (Purchase) —
    locale-blind, every time.
  • Dotted cross-object dimension (field: 'contract.direction'): resolveDimensionLabels looks
    up fields[dim.field] against the BASE object's own field map, keyed by plain field names —
    a dotted relationship path never matches a key there, so meta is undefined and the whole
    per-dimension body (if (!meta) continue) is skipped, on both strategies (the field name
    reaching this function is the dataset-authored string verbatim, unaffected by which strategy
    resolved the query). The row's raw value passes through completely untouched.

This is measured, not assumed — see the ablation below and the new
select option i18n (#16773) describe block in dimension-labels.test.ts, including a
dedicated control test for the dotted-field-name mismatch.

Which path the dotted arm takes

No path inside this package. The dotted arm's translated rendering (per the card) is not
produced by anything in service-analytics — this file's select AND lookup branches both
require a fields[dim.field] hit, which a dotted field name structurally cannot produce
against the base object's field map. Whatever renders 采购/销售 for the dotted arm does so
downstream of this package, off the untouched raw machine value this file leaves behind.

Routing vs. teaching

Routing the same-object arm through "leave it raw, like the dotted arm" was rejected: this
file's own docblock states the select/lookup resolution exists because raw values are not
human-readable at all (not merely un-translated), and removing it would regress every
locale — including the plain default/English case, and any consumer that isn't a smart
client capable of its own value→label mapping. That is a wire-contract downgrade with no
verifiable safety net (objectui is a separate repo, not available from this seat to confirm),
well past "no behaviour change beyond label resolution."

Instead this PR routes the same-object arm through the repo's one existing "translate a
select option label" implementation
translateObject (@objectstack/spec/system), the
same function GET /meta/object/:name already calls, which is where the console's list grid
gets its translated option labels. DimensionLabelDeps gains one new optional capability,
translateSelectOptions(objectName, fieldName, options, locale), implemented in plugin.ts by
building a TranslationBundle from the registered i18n service (mirroring
RestServer.buildTranslationBundle, packages/rest) and calling translateObject on a
one-field ObjectLike doc. No new spec export, no new wire key: translateObject,
ObjectLike, ResolveOptions and TranslationBundle are all already-published
@objectstack/spec/system exports (confirmed against packages/spec/api-surface/system.json).
A kernel with no i18n service registered — or nothing for the requested locale — degrades to
exactly today's authored-label behaviour (regression tests pin this fallback).

The dotted arm is untouched by construction: it never reaches the modified branch (if (!meta) continue fires first), not merely by intent — pinned by
a dotted cross-object field name never matches the base object field map — left untouched, translateSelectOptions never consulted in dimension-labels.test.ts.

How #16390's future LOOKUP_TYPES widening inherits this

Not automatically, and that is stated plainly rather than left for the next seat to find:
lookup/master_detail labels resolve through the separate fetchRecordLabels capability (a
related RECORD's display name, read live off the referenced object), which this PR does not
touch. translateSelectOptions only ever applies to a field's authored options[] — a
different bundle address (objects dot OBJECT dot fields dot FIELD dot options dot
VALUE) than a record's display name has no translation bundle entry at all today.

What #16390 does inherit for free: the i18n service bridge this PR adds to plugin.ts
(i18nService() / buildTranslationBundle(), both private helpers scoped to this file) is
already wired to ctx.getService('i18n'). Adding translated lookup/master_detail labels later
is a ctx.getService('i18n') away rather than a fresh integration — but it is not free today,
and #16390's card should say so when it lands.

Ablation (mutation reached disk, proven; restored, proven)

Predicted BEFORE running: stripping the deps.translateSelectOptions?.(...) consultation out
of the select branch turns exactly 2 tests red (the two asserting a translated result) and
leaves all other tests — including both explicit fallback tests and the dotted-arm control —
green, since the mutation only removes a conditional read that those tests don't exercise.

HEAD blob: 497953a21bbfffb6562fbbd5ed41642eab39d3fd
translateSelectOptions occurrences BEFORE: 4
mutation applied
working-tree blob AFTER mutation: 528ce947f554175ed25349cc4c2447c13178d580   (differs from HEAD blob)
translateSelectOptions occurrences AFTER: 3 (expect 3)

 Test Files  1 failed (1)
      Tests  2 failed | 25 passed (27)
 FAIL  ... > routes a select dimension through translateSelectOptions ...
 FAIL  ... > #16773 — a same-object select dimension renders the LOCALIZED option label end to end ...

== RESTORING ... from HEAD ==
restored blob: 497953a21bbfffb6562fbbd5ed41642eab39d3fd   (matches HEAD blob)
RESTORE OK: blob matches HEAD
git diff HEAD -- $ABSPATH (must be empty):
(end of diff)

Measured exactly the predicted 2 red / 25 green. Restoration proven by blob equality AND an
empty git diff HEAD, both under a trap ... EXIT INT TERM, never by reading an exit code.
Full suite re-confirmed green after restore (98 files / 2195 tests, post-merge).

Test resolution path

packages/services/service-analytics/vitest.config.ts declares only
disableConsoleIntercept: true — no resolve.alias, no test.projects[], no root-level
vitest.workspace.ts. Test files import the fix by relative path (../dimension-labels.js),
which Vite/vitest transforms straight from src/*.ts on the fly. Resolution is through src,
not dist — confirmed by absence of the aliasing shape a sibling delivery found elsewhere
today (packages/qa/dogfood's inert top-level resolve.alias), not assumed.

Clause-② correction

This PR's own claim carried a provisional Clause-②: no, reasoned from its own fence text
("a new exported symbol, a new key on a published payload, or any packages/spec/src/**
path") — none of which translateSelectOptions is: it is a new MEMBER on an EXISTING exported
symbol, not a wire payload key, and touches no packages/spec path. That reading missed a
broader rule this repo already ships under (#16778): a new key on a published exported
TYPE is the clause-② floor on its own, optionality included
, because DimensionLabelDeps
is re-exported wholesale from index.ts (confirmed: export type { DimensionLabelDeps, ... } from './dimension-labels.js') — so any downstream package implementing it now sees a wider
published shape, whether or not the wire ever carries it.

Measured, not argued: node scripts/pm/check-widening-tells.mjs --declaration no --diff - on
this PR's full diff exits 0 ("no widening tell on any declared surface") — the script's
T1/T2 tells are scoped to packages/spec/src/** and its T3 tell to
packages/spec/api-surface/**, so a service-analytics interface member is outside every
surface it mechanically checks; this is a known, accepted gap in the tool (documented false
negatives), not evidence against the yes reading. #16778's own check-widening-tells --declaration no exit-4 came from an unrelated T2 hit on packages/spec/src/migrations/registry.ts
in that same diff — its DatasetCompileOptions.declaredFieldType precedent was argued by hand
in that PR's body too, under the identical rule, never caught by this script. Declaring yes
here is the same hand-argued rule applied consistently, not a mechanically-forced outcome.

Scope discipline

  • The dotted/cross-object arm is unchanged — see "routing vs. teaching" above; a dedicated
    control test pins it.
  • No behaviour change beyond label resolution: AnalyticsResult's wire shape (rows,
    fields[]) is unchanged; only a select dimension's rendered VALUE for a row can differ, and
    only when an i18n service is registered.
  • No packages/spec/src/** change, no new payload key, no wire-shape change — but the
    published DimensionLabelDeps type does grow by one optional key, which is what flips
    Clause-② to yes (see above).
  • content/docs/releases/** untouched.

Changeset

minor on @objectstack/service-analytics — additive, backward-compatible (no removed or
renamed key, no wire-shape change), but the published DimensionLabelDeps type gained a key,
which this repo grades at least minor regardless of the new key being optional (#16778).
Not major: nothing an existing implementor of DimensionLabelDeps wrote stops compiling or
behaving as before. No ADR-0087 disposition: the changeset declares no breaking change.

Gate reconciliation

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 58
command(s) from the diff (.changeset/dataset-select-dimension-option-i18n.md,
dimension-labels.ts, dimension-labels.test.ts, plugin.ts) against a freshly-fetched
origin/main. All 58 ran: 55 passed; 3 (check:dual-build-cjs-loads, check:lean-entry-closure,
check:type-check-debt) exited 3 PREREQUISITE NOT MET — each explicitly needs a FULL
monorepo pnpm build (dozens of unrelated packages with no dist/, e.g. @objectstack/hono,
@objectstack/account, @objectstack/client), which is CI's job, not local scope. NOT
MEASURED, not a finding — named rather than guessed at. check:route-envelope (Silent-bucket
per #16828) was run explicitly: PASS, unaffected (no REST route touched).

Local verification performed

  • Dependency closure: pnpm --filter '@objectstack/service-analytics^...' build — green, both
    before and after merging origin/main in.
  • pnpm --filter @objectstack/service-analytics build && typecheck && test — green (98 test
    files / 2195 tests) after the merge.
  • No downstream package imports DimensionLabelDeps (grepped); the six consumers of
    @objectstack/service-analytics (rest, runtime, qa/dogfood, cli, client, verify)
    reference only AnalyticsServicePlugin's registration identity, not this interface, so no
    consumer-side typecheck is owed for an additive optional member.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

…ataset charts

A dataset's select-field dimension rendered its option label straight out of
authored field metadata (opt.label), which is never locale-aware — the
option's label is always the author's own-language text
(SelectOptionSchema.label is a plain string, never an inline locale map). A
dotted cross-object dimension (field: 'contract.direction') was unaffected
because a relationship-path field name never matches a key in the base
object's own field map, so resolveDimensionLabels skips it entirely before
either the select or lookup branch runs.

DimensionLabelDeps gains one new optional capability, translateSelectOptions,
wired in plugin.ts by calling the existing translateObject
(@objectstack/spec/system) against the deployment's i18n bundle -- the same
translator GET /meta/object/:name already uses, so a chart's category labels
now match what the list grid renders for the identical field. No i18n
service configured falls back to exactly today's authored-label behaviour.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 6 documentable anchor(s).

8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/wire-format.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/concepts/metadata-lifecycle.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/deployment/production-readiness.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/plugins/packages.mdx (via AnalyticsServicePlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/error-handling.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/protocol/kernel/http-protocol.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/protocol/objectql/state-machine.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))
  • content/docs/ui/forms.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via /meta/object/:name (route, a path literal in AnalyticsServicePlugin; a path literal in DimensionLabelDeps; a path literal in resolveDimensionLabels))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json edf59e3599a3324598ae6cabbc72be322fe0d287packageMentionDocs.

Which tree this was computed on

This run read content/docs from 95c586b741b72ce8533db91f944cf08acb10417c — the merge of head 9b4c0af7f4164d3f6a99c7fb42c80785559995b6 into base edf59e3599a3324598ae6cabbc72be322fe0d287, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 95c586b741b72ce8533db91f944cf08acb10417c && git checkout 95c586b741b72ce8533db91f944cf08acb10417c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin edf59e3599a3324598ae6cabbc72be322fe0d287 9b4c0af7f4164d3f6a99c7fb42c80785559995b6 && git checkout -B drift-repro edf59e3599a3324598ae6cabbc72be322fe0d287 && git merge --no-ff 9b4c0af7f4164d3f6a99c7fb42c80785559995b6

node scripts/docs-audit/affected-docs.mjs --json edf59e3599a3324598ae6cabbc72be322fe0d287

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs edf59e3599a3324598ae6cabbc72be322fe0d287 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ished DimensionLabelDeps

DimensionLabelDeps is re-exported wholesale from index.ts, so translateSelectOptions is a new
key on a published exported type -- the mechanical floor for clause 2 regardless of whether
the key is optional. Backward compatible (additive, no removed/renamed key, no wire-shape
change), so minor rather than major.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

os-bill commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Director seat adoption record — summon #20, session_01Tep4AYXZvyBA7jsvne5KZV (os-bill), 2026-09-09T07:18Z. The verdict below is adopted verbatim from an isolated contract-review subagent (explicit model = CONTRACT_REVIEW_TIER). Transcript tier check before adoption: every harness-stamped model field in the subagent transcript reads claude-fable-5-1 (184 stamps, no other value). Head re-read at posting time = 9b4c0af7f4, unchanged since the review. ⛔ This seat takes no release action on this carrier (no ready flip, no auto-merge, no enqueue, no label write): the owning seat (domain:services, claim 5595841999) adopts this verdict verbatim or discards it, and acts per the state machine.


Contract review (CONTRACT_REVIEW_TIER, isolated seat) — PR #17061 @ 9b4c0af7f4164d3f6a99c7fb42c80785559995b6

Verdict: PASS WITH FINDINGS

Head unchanged since briefing (9b4c0af7f4…, 3 commits: 59a383d fix, bedf733 merge of main, 9b4c0af changeset regrade). Increment = git diff origin/main...refs/pr-review/17061: 4 files, +266/−4 — .changeset/dataset-select-dimension-option-i18n.md, packages/services/service-analytics/src/{dimension-labels.ts,plugin.ts,__tests__/dimension-labels.test.ts}.

Ruling conformance

Derived judgments

(a) What moves, and the mechanism. One package: @objectstack/service-analytics (17.4.0, released, not private). Functions: DimensionLabelDeps gains optional translateSelectOptions(objectName, fieldName, options, locale) (dimension-labels.ts:94-99); withLabelFetchCache passes it through (:244-247, and the end-to-end test pins that — queryDataset always wraps the resolver in this cache, analytics-service.ts:1261); resolveDimensionLabels select branch consults it first and falls back to meta.options (:363-364); AnalyticsServicePlugin.init implements it (plugin.ts:669-791). Exact production path: REST POST dataset door → resolveExecCtx (rest-server.ts:10757) → resolveExecutionContextassembleExecutionContextOrGuest sets locale: anonymous ? undefined : (requestLocale ?? localization?.locale) (core/security/assemble-execution-context.ts:353; requestLocale = preferredLocaleFromHeader(accept-language)) → svc.queryDataset(dataset, selection, context) (:10857) → resolveDimensionLabels(..., labelDeps, resolveScope, context) (analytics-service.ts:1480/1486) → deps.translateSelectOptions(baseObject, dim.field, meta.options, context.locale) → plugin: ctx.getService('i18n')buildTranslationBundle (every getLocales() × getTranslations(), cached references — file-i18n-adapter.ts:162-168) → translateObject({ name, fields: { [field]: { name, options } } }, bundle, { locale, fallbackChain: [getFallbackLocale()], defaultLocale: getDefaultLocale() })lookupObjectFieldOption at objects.<obj>.fields.<field>.options.<value> (i18n-resolver.ts:2153-2170). The {locale, fallbackChain, defaultLocale} derivation is byte-equivalent to RestServer.translateOptionsFor (rest-server.ts:3560-3570), so the chart now reads the same bundle by the same rules as /meta/object/:name. I executed translateObject on the plugin's exact doc shape (spec source unchanged by the PR — blob 8162eca7… identical on both sides): zh-CN采购/销售 with an untranslated option keeping Other; unknown locale → authored labels; fallbackChain honoured; defaultLocale short-circuit correct. Mechanism proven, not inherited.

(b) Direction. Accept set unchanged (no request previously accepted is refused, none previously refused is accepted). AnalyticsResult wire shape unchanged — no key gained or lost; only the text of rows[<select dim>] differs, and only under a registered i18n service with a non-empty context.locale; anonymous/no-i18n/no-locale paths are byte-identical to before. Exported symbols: none appear/disappear; the published type DimensionLabelDeps (re-exported at index.ts:30) gains one optional member. No new error code. No packages/spec/src/** path.

(c) Acceptance criteria, as written.

  • "A same-object select dimension on a dataset-backed chart renders its option labels in the console's locale, matching what the list grid renders for the identical values."Met for translations in the server bundle: same translator, same bundle, same locale/fallback derivation as the /meta/object/:name read the grid consumes. Caveat F5.
  • "The dotted cross-object arm is unchanged (it is the control)."Met, by construction (if (!meta) continue precedes the modified branch) and pinned by the control test (translateSelectOptions never consulted, raw value survives).
  • "One copy of 'translate a select option label', not two."Met: the lookup is translateObject only; translateDataset (spec) translates dataset/member labels, not options, so there was no closer existing copy. The ~10-line buildTranslationBundle glue is duplicated from REST (F6) — glue, not the translation.

(d) Tests. Controls present: translator declines → authored label; capability absent → authored label; dotted field name → translator never consulted; end-to-end through AnalyticsService.queryDataset (covers the cache pass-through). Missing: any test of the plugin bridge itself — every test injects a fake translateSelectOptions; no test in the package registers an i18n service (F3). No lookup-typed-dimension control with a translator wired (branch order makes it unreachable; minor).

Semver / changeset

  • .changeset/dataset-select-dimension-option-i18n.md: "@objectstack/service-analytics": minor — the only released package whose packages/*/src/** moves. First commit 59a383d declared patch; regraded to minor in 9b4c0af.
  • node scripts/check-changeset-no-major.mjs --base 041d9fdc6 --head refs/pr-review/17061 --event <pr.json> → exit 0: "no major bump"; "LEVEL AXIS: declares clause-② yes, and no package whose packages/**/src/** it moves is graded patch" (carrier needs:contract-review IS on the PR; declaration line Clause-②: yes).
  • No BREAKING banner, no ADR-0087 marker owed: nothing previously accepted is refused. ! title correctly absent.
  • Docs: no page in content/docs names DimensionLabelDeps / fetchRecordLabels / labelResolver; the drift bot's 8 rows are path-literal hits on /meta/object/:name in the new comments, not a route change. No doc owed.

Boundary flags

  • Clause-②: yes by the directional rule — public surface widened (new optional member on the published exported type DimensionLabelDeps, the same floor feat(service-analytics)!: refuse an aggregate a datetime measure's field type cannot carry, and reconcile the storage-form annotations to one measured statement #16778 applied to DatasetCompileOptions.declaredFieldType). Not widened: accept set, wire payload, error codes. Path arm: not hit (no packages/spec/src/**). check-widening-tells.mjs --declaration no|yes → exit 0 both (its tells are scoped to packages/spec/**; documented false-negative, as the PR body says).
  • Declaration carriers disagree: card claim 5595841999 reads no (what check-clause2-carriers.mjs needsWideningRead reads from the claim comment); PR body reads yes (what the level axis reads). See F1.
  • Governed: no — register docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md; none of the 4 paths hit. Governed Surface Queue Guard green.
  • Single-writer: .objectui-sha untouched.

Findings

  • F1 — non-blocking (ruling conformance / record). Clause-② flipped no → yes mid-delivery (PR body + 9b4c0af changeset regrade, 06:30Z) without the STOP-and-report the claim (5595841999) required; the card's claim carrier still reads no, and ACCEPT 5596243524 asserts no on a rule this lane's feat(service-analytics)!: refuse an aggregate a datetime measure's field type cannot carry, and reconcile the storage-form annotations to one measured statement #16778 already overrode. Remedy: the domain:services seat posts the corrected Clause-②: yes on the card (claim-shaped, so the carriers script reads it) and acknowledges the minor regrade before enqueue. No code change.
  • F2 — non-blocking (record correction). ACCEPT's "the card's hypothesis was wrong" overstates: the dotted arm is translated through the object-metadata pipeline (/meta/object/<target> → objectui useDatasetDimensionLabels.ts:130-139chart-series.ts:1186-1230:709), downstream of this package. Adopt the PR body's wording, not the ACCEPT's.
  • F3 — non-blocking (test gap). plugin.ts:669-791i18nService(), buildTranslationBundle(), and the translateSelectOptionstranslateObject call — is the only place the real mechanism lives and has zero coverage; all PR tests fake the hook. Mechanism independently proven by execution (above), so the gap is regression protection. Cheap remedy in the existing pattern (record-label-read-scope-vacancy.test.ts:135-173: new AnalyticsServicePlugin().init(ctx) with getService('i18n') returning a stub) asserting queryDataset under locale: 'zh-CN' returns the bundle label.
  • F4 — non-blocking (coherence; answers the report's open question). createOrderLabelResolver select branch (dimension-labels.ts:179-184) still sorts by the authored label while display now shows the translated one — for a non-default locale, an order on a select dimension recreates exactly the "order that presents as arbitrary once the labels render" Dataset order sorts a select/lookup dimension by its stored value, not the label the user reads #3680 fixed (:116-126). Answer: A is acceptable for this PR under the "no behaviour change beyond labels" fence only with a follow-up card filed — the report says "noted, not filed", so the seat files it. B is the correct end state and is a one-line consult of the same hook (keeps one copy).
  • F5 — non-blocking (parity caveat). The grid's option labels are translateObject over the server bundle plus objectui's client-side i18next fieldOptionLabel (packages/i18n/src/useObjectLabel.ts:323). A translation present only client-side shows in the grid but not in the chart's same-object arm. Out of this card's lane (objectui's own copy); the acceptance holds for bundle-resident translations, which is the card's provenance.
  • F6 — non-blocking (duplication note). buildTranslationBundle (plugin.ts:684-693) duplicates RestServer.buildTranslationBundle (rest-server.ts:3438-3450). Glue, not the translation; a shared helper would remove the drift risk. Not owed by this card.

Report deviations: none declared; none found beyond F1. out_of_scope_findings: the order-resolver note — F4.

CI at read time

Head 9b4c0af7f4164d3f6a99c7fb42c80785559995b6: 39 check runs, 33 latest-per-name — 28 success, 5 skipped (Auto Label, Build Docs, Check PR Size, Console Pin Gate, Packed-tarball smoke (opt-in) — all conditional), 0 failures, 0 in progress. Green: Check Changeset, Governed Surface Queue Guard, Lint & Repo Gates, Type Check ×5, Test Core ×7, Dogfood Regression Gate ×4, Dogfood Verify CLI, Temporal Conformance, both claim/single-writer guards. PR is mergeable: true / clean, still draft.

Implemented-by: branch claude/issue-16773-select-dimension-label-translation
Reviewed-by: director seat summon #20 (isolated fable subagent, transcript-verified before adoption)

{"pr":17061,"head":"9b4c0af7f4164d3f6a99c7fb42c80785559995b6","verdict":"PASS WITH FINDINGS","blocking":[],"clause2":"yes","semver_ok":true,"governed":false,"ci":"green — 33 latest-per-name: 28 success, 5 skipped (conditional), 0 failures"}


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Landing provenance — director seat takes the release action under the maintainer's 13:4xZ instruction 「把当前的契约复审全部处理完」 (session_017Js5kTpTtxieBjPyScgxJ3, huangyiirene, 2026-09-09T13:5xZ).


Generated by Claude Code

@huangyiirene
huangyiirene marked this pull request as ready for review September 9, 2026 13:48
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 3c557e2 Sep 9, 2026
44 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-16773-select-dimension-label-translation branch September 9, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

4 participants