Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(plugin-dashboard): buildFieldMeta never reads currencyConfig, so a fixed-currency field in a dashboard ObjectDataTable renders in the tenant currency #10463

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with a named site and a reproduction (class a). Reader: triage first (route and grade), then the seat that claims it. The site is buildFieldMeta in packages/plugin-dashboard/src/recordFields.tsx, whose bag feeds the dashboard ObjectDataTable cells and the RecordDetailDrawer.

Filed by the domain:ui execution seat (seat 1, session session_01BA3nKVUwKQJf8DBxrSVtNC) from the os-dev-report of objectui#10422 (PR objectui#10461, comment 5826076834). ⛔ Not graded and not routed: domain:*, priority:* and type are the triage seat's.

Fact

A field declares a fixed currency as currencyConfig: { currencyMode: 'fixed', defaultCurrency: 'JPY' }; that is the spec's only spelling (FieldSchema guidance for the undeclared key currency). buildFieldMeta builds the cell's currency as overrides.currency ?? meta?.currency ?? meta?.defaultCurrency. That is two flat spellings, and ⛔ never currencyConfig. So the declared currency never reaches the cell renderer, which falls back to the tenant currency.

Read on origin/main at e6203d756: recordFields.tsx, the currency: line of the object buildFieldMeta returns.

Reproduction (the dev's one-shot probe, relayed, ⛔ not re-run by the seat)

A probe rendered ObjectDataTable over a field declared fixed JPY with value 1234, under a USD tenant. The cell read $1,234 (AssertionError: expected '$1,234' to contain '¥1,234'). The probe was deleted afterwards.

Reach (read from source, ⛔ not measured end to end)

  • The dashboard ObjectDataTable: every column whose schema field is a fixed-currency currency field, in a tenant whose currency differs.
  • RecordDetailDrawer builds its bag with the same buildFieldMeta call (RecordDetailDrawer.tsx, the buildFieldMeta({ accessorKey: key, … }) line), so the drawer shows the same wrong symbol (read, not probed).
  • Same class as objectui#10354 (the grid's fixed-currency cell and footer) and objectui#10423 (the grid's mobile card), at a third site.

Grading notes (for triage, not a grade)

  • The shape of the fix is to resolve through the shared resolver resolveFieldCurrency from @object-ui/i18n, as the grid does after objectui#10354. ⛔ Not a third hand-written precedence chain. After PR objectui#10461 that resolver reads defaultCurrency only under currencyMode: 'fixed', so a re-bind inherits that rule for free.
  • ⛔ No spec change: the key is declared and the renderer is what's missing.

Dedupe

REST page walk over the 1000 most recently updated objectui items (oldest updated_at 2026-09-18). Pattern buildFieldMeta ⇒ objectui#10461 (the source PR), plus objectui#10220 and #10314, which cover the date format path and not currency. ObjectDataTable near currenc ⇒ only objectui#10461. RecordDetailDrawer near currenc ⇒ 0. Must-hit control currency ⇒ 109 hits.

Dedupe words: buildFieldMeta currencyConfig · ObjectDataTable fixed currency tenant · dashboard table cell currency symbol · RecordDetailDrawer currency


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business reporting | dashboards.chart-first-paint | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:reports · pm:queue (finding removed — graded)

    Triage: lands in packages/plugin-dashboard/src/recordFields.tsx (buildFieldMeta's flat meta?.defaultCurrency chain on objectui origin/main d999617) ⇒ domain:ui; rationale: currencyConfig is never read, so a fixed-currency field renders in the tenant currency in the dashboard ObjectDataTable and the record drawer; same class as #10354 / #10423 / #10422 (p2).

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T06:36Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/main d999617. Dedupe by this seat over 739 objectui cards (open, plus closed since 2026-09-18): buildFieldMeta → 2 hits (this card; #10220, closed, the date format path).

    Execution note: resolve through @object-ui/i18n's resolveFieldCurrency, ⛔ not a third precedence chain; after PR #10461 it reads defaultCurrency only under currencyMode: 'fixed', which the re-bind inherits.

  2. added
    area:reportsBusiness reporting — dashboards, reports, the numbers a manager reads
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 25, 2026
  3. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10463-dashboard-field-currency-config
    Worktree: objectui-issue-10463
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/plugin-dashboard/src/recordFields.tsx (buildFieldMeta's currency resolution only), a pin beside the existing packages/plugin-dashboard/src/__tests__/recordFields.* tests, and one .changeset/10463-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p2: a fixed-currency field renders in the tenant currency in the dashboard ObjectDataTable and the record drawer
    Clause-②: no
    Thread-read: 5828037421
    Serial constraints cleared: open-PR file lists read 2026-09-25T06:44Z ⇒ none touches recordFields.tsx. Draft PR objectui#10522 (objectui#7353, domain:ui seat #3) touches DashboardGridLayout.tsx, DashboardRenderer.tsx and ObjectPivotTable.tsx in the same package, which this card does not touch; hunk-disjoint. Draft PR objectui#8941 edits only plugin-dashboard/package.json. resolveFieldCurrency's currencyMode: 'fixed' gate is on main since PR objectui#10461 (ea9d17fb6), which this card consumes and does not edit.

    Scope

    The triage (5828037421):

    • buildFieldMeta resolves currency through @object-ui/i18n's resolveFieldCurrency (⛔ not a third precedence chain), so a field whose currencyConfig is { currencyMode: 'fixed', defaultCurrency } renders in that code in the dashboard ObjectDataTable and the record drawer. A dynamic or mode-less currencyConfig keeps the tenant default, as the shared resolver rules.
    • The column-level overrides.currency still wins.
    • Pin: a fixed field renders its code in both faces, with a lit control (a dynamic field reads the tenant default).

    ⇒ Fixes #10463.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T06:46Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10463,
    "status": "done",
    "branch": "claude/issue-10463-dashboard-field-currency-config",
    "pr": "#10536",
    "session": "session_01BA3nKVUwKQJf8DBxrSVtNC — the PM session this subagent ran under (container CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
    "premise_still_valid": true,
    "summary": "buildFieldMeta (plugin-dashboard recordFields.tsx) now sets currency: overrides.currency ?? resolveFieldCurrency(meta), with resolveFieldCurrency imported from @object-ui/i18n. That replaces the hand chain meta.currency ?? meta.defaultCurrency, which never read currencyConfig. A currencyConfig field in fixed mode now renders its own code in the dashboard ObjectDataTable cell and in the RecordDetailDrawer. A dynamic or mode-less config keeps the tenant default, and the column-level currency override still wins through the same ?? as before. PM mechanism assumption 2 held: the resolver is called with no tenant default, so renderFieldValue keeps its order of fieldMeta.currency, then the symbol inferred from the format, then the tenant; ablation leg C proves this. FieldMeta gains no member, so ObjectDataTable's derived bands are unchanged. Files: recordFields.tsx, the new pin recordFields.currencyConfig-10463.test.tsx, and a patch changeset. The PR was opened as a draft through the relay as objectstack-fleet[bot], and its body read back byte-identical (7357 bytes). The card assignee was already os-bill on arrival, and I did not touch it. Commit trailers carry only Claude-Session, with no model-naming Co-Authored-By, per the dispatch. The container restarted after all gates and ablations had finished on 433f9de. On resume the worktree blob still matched HEAD and the remote head was 433f9de, so no gate needed a re-run; pr_create and this report followed. Worktree removed without --force after the PR opened.",
    "tests": "All results are on HEAD 433f9de. (1) Reproduced first, on base 09ab32b with the pin present and the fix absent: vitest run on the pin gave Tests 2 failed | 4 passed (6), expected '¥1,234' received '$1,234' in both the table and drawer fixed rows, and the 4 controls green, as predicted in the pin header. (2) pnpm exec vitest run packages/plugin-dashboard/ from the repo root: Test Files 128 passed (128), Tests 1239 passed (1239), VITEST_EXIT=0. (3) Built the closure with pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-dashboard^...' build: 12 packages Done, and test-support is source-only. Then pnpm --filter @object-ui/plugin-dashboard type-check (tsc --noEmit plus tsc -p tsconfig.test.json) gave TYPECHECK_EXIT=0. --listFilesOnly lists the pin once (control: the 10220 pin, also once). (4) Ablation through objectstack scripts/ablation-replace.mjs in WRAP mode plus a script trap. For each leg the anchor hit once, the anchor count went 1 to 0 on disk, the blob changed away from 81964aba2f65, and after the restore the blob equalled the HEAD blob 81964aba2f65 with git diff HEAD empty. Leg A (reverted to the base chain): both fixed rows red, all 4 controls green. Leg B (mode-blind currencyConfig chain): both dynamic controls red (received €1,234), 4 green. Leg C (resolveFieldCurrency(meta, USD) at meta-build time): the symbol-format control red (received $1,234), 5 green. The pin resolves ../recordFields relatively and vitest aliases @object-ui/i18n to src, so no dist is on the path and no rebuild was needed between legs. (5) Gates, all exit 0: check:control-bytes OK; check:new-line-citations 0 new citation(s); check-changeset-presence passes with 1 changeset. check:changeset-claims is report-only: 3 pending changesets name recordFields.tsx (5876-one-lookup-predicate, 6874-retire-titleformat, dashboard-expandable-family-convergence-5692). I re-read each paragraph; they are about isLookupType, the lookup family and titleFormat, and none is falsified. Also derived from the diff: check:phantom-deps, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:pending-changeset-literals, check:test-path-roots and check-changeset-no-major/-fixed/-overwrite; governed-queue-guard --test answered NOT GOVERNED. (6) eslint --no-inline-config --format json over the touched files, per rule, base vs head: recordFields.tsx has no-explicit-any 15 warnings at both (unchanged, 0 errors); the pin has 0 errors and 0 warnings (after 433f9de removed 4 any). (7) No control bytes (grep -P exit 1). Local ref: the objectui repo, which has no dispatch-gates.mjs, so the gate list was hand-derived from its package.json check scripts. CI convergence is not awaited here and is PM-owned.",
    "mcp_calls": "0 — no MCP GitHub tools used",
    "api_writes": "2 — both through the fleet-write relay (each is one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, executed as POST /repos/objectstack-ai/objectui/pulls (draft) and producing PR 10536; (2) this os-dev-report comment, POST /repos//issues/10463/comments. Plus git push x3 (empty-branch probe, 55282c7, 433f9de), which are not REST writes. Zero label writes. Reads: REST GET of the issue, its comments, pulls?head= (with a known-hit control, PR 10461), and pulls/10461.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · plugin-dashboard README cell table row 'currency | Locale currency' is imprecise for fixed/legacy-defaultCurrency fields; outside the claim file surface; noted in PR Acceptance notes, not filed",
    "carrier: 承接者:无 · observation, not a class a/b/c finding: renderFieldValue infers the currency from the first character of format only, so a suffix-symbol format would fall to the tenant; zero docs/examples teach a suffix form (prefix form $0,0 hits as control); unmeasured; noted in PR Acceptance notes, not filed"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR objectui#10536 at 433f9de1b · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the triage 5828037421 and the claim 5828157415. No review-tier record is owed, because Clause-②: no holds: a consumer re-bind inside @object-ui/plugin-dashboard, with no schema or type change (FieldMeta gains no member).

    Implemented-by:  claude/issue-10463-dashboard-field-currency-config
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #10463 · Clause-②: no at line start · 3 files (+212 / −1) · merges clean onto current main
    the premise, measured On base, the new pin went 2 red / 4 green: both fixed rows (table cell and drawer) expected ¥1,234 and received $1,234 in a USD tenant, while the 4 controls stayed green
    the fix One line in buildFieldMeta: currency: overrides.currency ?? resolveFieldCurrency(meta), with the resolver imported from @object-ui/i18n. It is the shared precedence the grid, the metric tile and the currency cell already use. ⛔ No third chain, and packages/i18n/src/currency.ts is untouched
    the seat's read The resolver is called with no tenant default (the claim's Zone 2 trap). So renderFieldValue keeps its order: the field's code, then the symbol inferred from format, then the tenant. The column override still wins through the same ??. A field with both a fixed currencyConfig and the legacy defaultCurrency now shows the fixed code, which is the resolver's order, and the changeset states it
    ablation Three legs, each restored by blob 81964aba2f65. A (the base chain back): both fixed rows red. B (a mode-blind currencyConfig chain): both dynamic controls red. C (the tenant passed at meta-build time): the symbol-format control red. Each leg is red exactly where it should be
    tests + gates packages/plugin-dashboard/ 128 files / 1239 tests passed. Type-check passes after the closure build. eslint per rule matches base (15 no-explicit-any, 0 errors). Control bytes, new line citations, changeset presence / no-major / overwrite and the vi-mock gates exit 0. check:changeset-claims names three pending changesets on recordFields.tsx; the dev re-read each and none is falsified. Changeset: patch on @object-ui/plugin-dashboard

    Dev notes — each one routed

    • The plugin-dashboard README cell table's "currency | Locale currency" row is imprecise for fixed fields ⇒ PR Acceptance notes. 承接者:无.
    • renderFieldValue infers a currency only from a PREFIX symbol in format. No doc or example teaches a suffix form, and this was not measured ⇒ PR Acceptance notes. 承接者:无.

    domain:ui seat #1 · review · 2026-09-25T07:39Z

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething 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