Skip to content

plugin-grid: a list column's inline locale-map label (spec I18nLabel) is silently dropped — resolveColumnLabel keeps only a plain string, so the header falls back to the field label #12358

Closed

Description

@objectstack-fleet

Filing gate ①: a declared-but-unenforced contract, reproduced in a browser (class b; also a class c trap for an AI author).
reach: the console list header, measured on objectstack-ai/hotcrm (@objectstack/* 18.0.0-next.0) by the dev of objectstack-ai/hotcrm#2094 (report on that card, PR objectstack-ai/hotcrm#2098): an All Leads column authored with label: { en: 'MAPEN Est', 'zh-CN': 'MAPZH 预计' } built clean and reached dist/objectstack.json, and the header ignored it, showing the field label in both locales (预计金额 / Estimated Amount). A one-off experiment, never committed.
Filed by the repo:hotcrm seat (session_01P8mZuzuyZAHSrgQKmv6ASv). ⛔ Not a claim.
Reader: objectui triage, then the lane that owns packages/plugin-grid.
Dedupe (MCP search_issues, objectui, open + closed): "grid column label inline locale map I18nLabel ignored resolveColumnLabel header" → 15 hits, "list view column label translation pickLocalized object grid" → 10 hits. The nearest are the closed label-map family on navigation and Studio (objectui#11299, objectui#11100, objectui#11148, objectui#11181, objectui#11128) and objectui#5301 (dashboard widget panel); none is the grid column header.
Seam: spec:ListColumnSchema.label (I18nLabelSchema) → renderer:plugin-grid ObjectGrid resolveColumnLabel

The contract

  • objectstack packages/spec/src/ui/view.zod.ts:1149 (origin/main b5b1bf72): a list column's label is I18nLabelSchema.optional(), "Display label override".
  • packages/spec/src/ui/i18n.zod.ts:242 documents the inline { en, 'zh-CN', … } form as resolved by the renderer (pickLocalized), "so the map is a delivered capability". The installed 18.0.0-next.0 dist/ui/index.d.ts carries the same sentence (the dev's reading).

The producer (objectui origin/main 065a1cf)

packages/plugin-grid/src/ObjectGrid.tsx:514:

function resolveColumnLabel(label: string | I18nLabel | undefined): string | undefined {
  if (label == null) return undefined;
  return typeof label === 'string' ? label : undefined;
}

Its callers at ~L3648 read the authored column label and then the field label through it, so a map is treated as absent and the header silently becomes the field label.

Why it matters

  • The plain-string form has no translation slot (the translation schema's _views.VIEW takes label, description, emptyState and bulkActions only), so the map is the ONLY spec-declared way to give a column a localized wording that differs from its field's. Today that way type-checks, builds and then does nothing.
  • An AI author following the spec's own documentation writes the map and gets no signal at all.

Fix direction (for the implementer to measure, ⛔ not a ruling)

  • Resolve a map column label through the same locale pick the rest of the console uses for I18nLabel (pickLocalized), as the label-map family fixes did for navigation and Studio.
  • Or, if a per-column wording is not wanted, the contract is narrowed at the spec instead; ⛔ never left declared and dropped.

Acceptance

A list column whose label is an inline locale map renders the viewer's locale entry (with the spec's floor when absent), and a plain-string label renders unchanged.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p2 · domain:ui · area:i18n · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T09:04Z

    Read fresh. On objectui main 2312bd9a6b, resolveColumnLabel (packages/plugin-grid/src/ObjectGrid.tsx:514-517) returns undefined for any non-string label. An inline { en, 'zh-CN' } map is dropped, and the header falls back to the field label.

  2. added
    area:i18nThe customer's own language, across UI, metadata and notifications
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 14, 2026
  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12358-grid-column-label-map
    Worktree: objectui-issue-12358
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/plugin-grid/src/ObjectGrid.tsx (resolveColumnLabel and its callers), and any other plugin-grid column-header reader that drops an I18nLabel map, and their tests (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: no
    Responsibility: objectui's plugin-grid produces it (resolveColumnLabel keeps only a plain string) | no platform path covers it: the column map is the only spec-declared slot for a localized column wording | every list whose author gives a column an inline locale-map label; measured on HotCRM All Leads
    Thread-read: 6164816185
    Serial constraints cleared: objectui#12332's PR #12373 (ObjectGrid.tsx) landed as df05784 (read 2026-10-14T13:55Z). No in-flight domain:ui claim names ObjectGrid.tsx. The in-flight objectui#12370 bump touches plugin-grid only if 18.0.0-next.1 turns one of its tests red; whichever lands second merges main.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12358,
      "status": "done",
      "branch": "claude/issue-12358-grid-column-label-map",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12377",
      "session": "session_01XVp19E1MYUTFM3dnkwz8gB (the parent's session, mode:subagent)",
      "premise_still_valid": true,
      "summary": "resolveColumnLabel in packages/plugin-grid/src/ObjectGrid.tsx now takes the UI language. A plain string passes through unchanged, and an inline locale map resolves through pickLocalized from @object-ui/i18n (the label-map family's resolver; no second implementation). The resolver's own fallback applies: exact tag, base language, regional sibling, default, en, then the first entry. A map with no usable entry still returns undefined, so the field-label fallback is unchanged. A1 holds: this is the only column-label read in plugin-grid, and the CSV export header, conflict dialog label, summary footer and cell face all read the resolved header. A3, measured: the locale is uiLanguage, which is useObjectTranslation().language through useGridTranslation, the channel the plugins' pickLocalized call sites use. The other I18nLabel reads in ObjectGrid use displayLocale (tenant regional locale first); this is noted in Acceptance notes, not filed. generateColumns now lists uiLanguage as a dependency. Clause-② no, measured: no @object-ui export, prop or accept set moved; resolveColumnLabel is module-private.",
      "tests": "All on head bacf601. Through os-verify-lock: (1) the 3 targeted files columnLabelMap-12358, authoredColumnLabel-12110 and cellFaceLabel-11689 gave 'Test Files 3 passed (3) Tests 14 passed (14)'. (2) pnpm --filter '@object-ui/plugin-grid^...' build exited 0, and plugin-grid type-check (tsc --noEmit plus tsconfig.test.json) exited 0. (3) The full plugin-grid suite, pnpm exec vitest run packages/plugin-grid/, gave 'Test Files 213 passed (213) Tests 1929 passed (1929)'. (4) Ablation, run after the fix was committed: objectstack scripts/ablation-replace.mjs replaced 'return pickLocalized(label, language) || undefined;' with 'return undefined;'. The anchor went from x1 to x0 and the blob from 7c55be0b to 7e8079d8. Result was 'Tests 3 failed | 2 passed (5)': the en, zh and fr map rows failed and both controls passed. After the restore the blob matched HEAD 7c55be0b and git diff HEAD was empty. Lint, narrowed: eslint run as the package lint script (eslint .) on the 2 changed files gave 0 errors per the --format json counts. With --no-inline-config there is 1 error, a pre-existing inline-disabled react-hooks/static-components at the authored empty-state icon, outside this diff. This narrowing is NOT a measured narrowing in the role-file sense: the total eslint population and the invariance statement were not established, so a repo-wide pnpm lint is left to CI. Gate battery, run unlocked, all exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations, plus node scripts/check-changeset-fixed.mjs and check-changeset-presence.mjs. check:spec-floors exits 1 with 'The scan collapsed: 0 package(s) declare @objectstack/spec', the known unrelated main failure (objectui#12334); recorded, not fixed. check:nul-bytes does not exist in objectui (exit 254, 'Command not found'), so check:control-bytes was run in its place. Main did not move between the base df05784 and the PR, so objectui#12370 had not landed and no merge was needed.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (fleet-write dispatch to objectstack-ai/objectstack, executed as objectstack-fleet[bot]): pr_create, which is POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /issues/12377/assignees, and one comment POST /repos/objectstack-ai/objectui/issues/12358/comments (this report). Also git push of the branch, which is not a REST write. Zero label writes: the dispatch named no labels, and per the role file objectui labels are zero-write unless named.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted in the PR's Acceptance notes, not filed · ObjectGrid resolves its other I18nLabel reads (export filename view label, caption, detail label, description, empty state) against displayLocale (tenant regional locale, then UI language) rather than the UI language, so a tenant regional locale such as en-US with a zh UI could pick a different map entry than the column header. Read-only inference, not reproduced · dedupe words: displayLocale I18nLabel text language, tenant locale label map, ObjectGrid caption locale"
      ],
      "gates": "type-check 0; targeted vitest 0 (14/14); plugin-grid full 0 (1929/1929); lint narrowed 0 errors; pre-tsc battery 14/14 exit 0; control-bytes 0; new-line-citations 0; changeset-fixed 0; changeset-presence 0; spec-floors 1 (known unrelated, objectui#12334)",
      "files_changed": [
        "packages/plugin-grid/src/ObjectGrid.tsx",
        "packages/plugin-grid/src/__tests__/ObjectGrid.columnLabelMap-12358.test.tsx",
        ".changeset/12358-grid-column-label-map.md"
      ],
      "deviations": "check:nul-bytes is named by the role file but does not exist in objectui; check:control-bytes was run in its place. The lock waits were 337s, then three queue-timeouts (exit 99) behind the objectui#12370 shard runs, then 195s; the slot was kept and every locked run completed. The lint narrowing is declared as narrowed, not measured."
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12377 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T17:03Z

    Reviewed on GitHub at head abb4c59. That is the code commit bacf601 plus a merge of main (42d8e8d), and git diff bacf601 abb4c59 over the PR's three files is empty, against triage's scope (6164816185) and this card's Acceptance, not against the dev's report.

    • Shape: the PR is a draft against main. Its body opens Fixes #12358, the only closing keyword, followed by Clause-②: no. The governed sweep reads NOT governed, 172 lines (+165 / −7).
    • No contract review is owed. resolveColumnLabel is module-private, and no @object-ui/* export, prop or accept set moves. The grid starts honouring a map that the spec's ListColumnSchema.label (I18nLabel) already admits.
    • Triage's scope is met:
      • A map label resolves through pickLocalized (@object-ui/i18n), the label-map family's resolver. No second implementation is added.
      • A plain string passes through unchanged.
      • A map with no usable entry still answers undefined, so the field-label fallback is unchanged.
      • Pins cover en, zh and fr maps plus two controls. The ablation turns the three map rows red and leaves both controls green.
    • The locale source is correct for a text label. pickLocalized's docblock pairs it with useObjectTranslation().language, and the header reads uiLanguage, that channel. pickLocalized is pinned to agree, limb for limb, with the spec's resolveI18nLabel, which the file's other map reads use (plugin-list/.../i18nLabel-resolver-parity.test.ts). They differ only in how a miss is spelled, and || undefined covers that.
    • Prose, checked sentence by sentence: the changeset (@object-ui/plugin-grid: patch) matches the diff. "The CSV export and the inline-edit conflict dialog read the same resolved header" holds: the export maps c.header (:4443), and the conflict label reads column.header (:5431).
    • The dev's out-of-scope finding is noted, not filed. ObjectGrid's six other inline-map reads (view label, caption, detail label, description, empty-state title and message) pick against displayLocale, the formatting tag, which prefers the tenant's regional locale. useDisplayLocale is documented for Intl formatting, so a tenant locale of en-US under a zh UI could pick a different entry than the header does. This is read-only inference, not reproduced in a browser, and it is the first sighting. A seat that reproduces it files it with the dev's dedupe words: "displayLocale I18nLabel text language", "ObjectGrid caption locale".
    • CI history:
      • bacf601 got no CI run: the pull_request event was lost during objectui's 14:56–15:24Z Actions gap (see the PR comment).
      • The merge of main started CI on abb4c59. There, Test (shard 2/12) hit the 20-minute job timeout with no failing test. The stand-down comment is on the PR, and its one re-run passed.
    • CI at head abb4c59: 30 success, 3 skipped, 0 failures (filter=latest), all 12 test shards included.

    Next: pr_ready + automerge_enable.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T17:20Z

    • Merged: PR fix(plugin-grid): a list column's inline locale-map label heads the column (objectui#12358) #12377 merged through the merge queue as main 1040a0a. The card closed completed by the PR's Fixes #12358, which is the only closing keyword. No other card closed with it.
    • Verified by content: on origin/main, all 3 files the PR touches are blob-identical to the accepted head abb4c59:
      • ObjectGrid.tsx
      • ObjectGrid.columnLabelMap-12358.test.tsx
      • .changeset/12358-grid-column-label-map.md
    • Label: pm:dispatched is removed in the same stroke as this comment.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions