Skip to content

console analytics: a formula declared returnType currency renders as a bare number, a KPI tile cannot show its description, percent fields always draw a progress bar, and report charts cannot split a series by a second dimension #27430

Closed

Description

@objectstack-fleet

Source

Measured by HotCRM on platform 18.2.0 while implementing hotcrm#2370, its dashboards and number-formats card. The implementer's report is hotcrm#2370 comment 6489085271, and the HotCRM side is hotcrm#2379 (merged as c362ccd). HotCRM shipped everything it could inside 18.2.0. Each row below is the half it could not.

# Observed on 18.2.0 Evidence Suggestion
1 A Field.formula with returnType: 'currency' (the spec key added by #22727) renders as a bare grouped number in list cells, related lists and the record page: a line's Total reads "24,000" beside its Sales Price "$6,000.00". HotCRM crm_quote_line_item.total_price and crm_opportunity_line_item.total_price since hotcrm#2379, tenant currency USD. The console's formula cell reads returnType and draws a currency result with the currency face a currency field uses.
2 A metric dashboard tile prints the measure's label (for example "Total Amount") under its value. The widget's description, which says what the number counts, is not shown on the tile. HotCRM Service and Sales dashboards; the card's row 2 asked each KPI to state its base. Show the widget description as the tile caption when one is declared, and fall back to the measure label.
3 Field.percent always draws a progress bar in list cells and on the record page. No field or page key turns it off, so a probability or rate that is not progress reads as a bar. HotCRM opportunity probability and campaign rates. Related: #5066 (closed, chip clipping). A display key on the field (or the view column) that shows the plain percentage.
4 A report chart takes one xAxis dimension and one yAxis measure, so it cannot split a series by a second dimension (a bar per rep, split by stage). A summary report with two rows levels repeats the outer value on every inner row and shows no group subtotal; the spec has no subtotal key. HotCRM opportunity_funnel_owner_stage, which hotcrm#2379 turned into an owner × stage matrix with a bar per owner as the workaround. A series (or groupBy) dimension on the report chart, and per-group subtotals on a multi-level summary.

Not filed: a dataset sum over a formula field answers DATASET_INVALID. That is the ruled behaviour (#24426: store the value as a number field), so HotCRM renamed its tile to say it sums catalog list prices.

Acceptance

Each row is either taken, as a card of its own where it needs one, or answered "will not do" with the reason.

Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2370's report. Dedupe: searched objectstack issues for formula currency face, metric tile caption/description, percent progress bar, report series by second dimension and summary subtotals. Related: #22727 (closed, the spec half of row 1), #5066 (closed), #27188 (record-mode currency, a different gap). No open card.

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Row 1 status · 2026-10-29T18:26Z · ⛔ not a claim on rows 2–4

    Row 1 (a returnType: 'currency' formula reads as a bare number) is taken by #27494 (triage 6495354739 folded it here), PR #27689:

    Rows 2–4 are untouched. Each still needs its own card or a "will not do" answer, per this issue's Acceptance.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Claim: PM loop round 2 · row 1 remainder only (⛔ rows 2–4 stay open and unclaimed) · 2026-10-29T22:36Z
    Session: session_01Utp6CY2n1JmZZKrHgtp9jC (seat; implementation by a card thread, mode:thread)
    Account: os-bill
    Branch: claude/project-thread-28figz (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
    Worktree: objectstack-issue-27430
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/plugin-detail/src/RelatedList.tsx (the related-list column builder forwarding returnType beside currencyConfig), ui/packages/plugin-detail/src/RecordDetailPanel.tsx (buildRecordDetailFields), tests beside, a changeset; ⛔ packages/spec/**, every server file, examples/**; PR carries Refs #27430 (not Fixes)
    Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
    Clause-②: no (expected; the dev measures from the diff)
    Metadata-protocol: none (expected; the dev measures from the diff)
    Responsibility: the related-list cell and buildRecordDetailFields rebuilding a field without returnType | FormulaField / FormulaCellRenderer's currency branch and the spec's returnType (#22727) are fixed inputs | every reader of a returnType: 'currency' formula in a related list or a RecordDetailPanel, today
    Thread-read: 6505010281
    Premise: holds — measured by the #27494 dev and the seat (PR #27689 fixed the sibling sites; these two were deferred by name in the seat's status note on this card)
    Serial constraints cleared: seat 1's PR #27636 (held RelatedList.tsx) merged 2026-10-29T20:03Z; the spec seat's draft PR #27713 also edits RelatedList.tsx (record-page open request), so whichever lands second rebases; no open PR touches RecordDetailPanel.tsx (seat read of every open PR's files); seat 1 has signed off

    Dispatch order: /mnt/project-files/ui-seat2/dispatch-27430.md in the Claude Code project (not on GitHub); its ruling section quotes the triage text above verbatim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
    os-dev-report

    {
      "issue": 27430,
      "status": "done",
      "branch": "claude/project-thread-28figz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27800",
      "session": "session_01Utp6CYEbuRp8dS61F3RKwo (card thread; seat session_01Utp6CY2n1JmZZKrHgtp9jC)",
      "premise_still_valid": true,
      "summary": "Row 1 remainder only (PR carries Refs #27430, Refs #27494; rows 2-4 untouched). RelatedList makeCell bag and buildRecordDetailFields now forward returnType beside currencyConfig, so a returnType currency formula draws through the existing currency face ($90,000.00) and a returnType number formula keeps the bare grouped number. No second formatter, no spec change, no new key; buildRecordDetailFields signature/return type unchanged: Clause-2 no, Metadata-protocol none, as the claim expected. Claim 6510068741 names dispatch-27563.md as the order file; the order this thread received is dispatch-27430.md (text noted, no effect).",
      "tests": "new ui/packages/plugin-detail/src/__tests__/formulaReturnTypeRelay-27430.test.tsx: Test Files 1 passed, Tests 4 passed. pnpm --filter @object-ui/plugin-detail test (pre-merge head): Test Files 338 passed | 1 skipped (339), Tests 3078 passed | 18 skipped (3096), VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (anchor 1->0, restored blob == HEAD, git diff HEAD empty; vitest resolves plugin-detail from src, no dist leg): RelatedList line deleted -> 1 failed | 3 passed, cells [Tower, $150,000.00, 90,000.00, 1,234.50]; RecordDetailPanel line deleted -> 2 failed | 2 passed (expected undefined to be currency; panel lacks $90,000.00). At merged head 60638520: new file + currencyConfigRelay-12242 + formulaReturnTypeRelay-27494 exit 0.",
      "gates": "dispatch-gates --commands: 34 families, run at pre-merge head and again at merged head 60638520 (derivation identical); all 34 :: exit 0 incl. node scripts/check-sdui-manifest.mjs :: exit 0 and --self-test :: exit 0, pnpm check:nul-bytes :: exit 0, pnpm check:type-check-debt :: exit 0. dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: plugin-detail type-check :: exit 0 (both heads), lint :: exit 0, test :: exit 0. CI check-runs: in_progress at report time; seat reads them.",
      "line_budget": "132 added / 0 removed, 4 files; under the 3000 human-merge threshold",
      "files_changed": [
        ".changeset/27430-formula-currency-related-list.md",
        "ui/packages/plugin-detail/src/RecordDetailPanel.tsx",
        "ui/packages/plugin-detail/src/RelatedList.tsx",
        "ui/packages/plugin-detail/src/__tests__/formulaReturnTypeRelay-27430.test.tsx"
      ],
      "deviations": "none. Harness post-PR assign/review step skipped per the dispatch order (no assignee, no reviewer).",
      "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no labels) via repository_dispatch run 40974483602, body read back 5433/5433 bytes identical; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27430/comments). Plus git pushes of the branch (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "observation, not filed · the related list's page totals (BodyTableTotals) total only currency/number/integer/decimal columns, so a returnType currency formula column gets no total; unchanged by this PR · carrier: seat"
      ]
    }
  4. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    ACCEPT → PR #27800 comment 6511916566 (head 1aea8b8f): row 1's remainder (the related list and the quick-look panel forward a formula's returnType). Contract review not owed (Clause-②: no, Metadata-protocol: none). Landing through the merge queue. The PR says Refs, so this card stays open for rows 2–4.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC

    Landed (row 1) · 2026-10-30T00:51Z

    probe reading
    PR #27800 merged through the queue at 2026-10-30T00:50Z, merge commit 359e935df26ddb067a9a60e2a7f81c5f80256bcb. It covers row 1's remainder: the related list and the quick-look panel now forward a formula's returnType.
    row 1 done: the list grid and record page in PR #27689 (#27494), and the related list and quick-look panel in PR #27800
    rows 2–4 still open. Seat 2 carried only row 1 and does not hold them.

    State: seat 2 releases its claim. pm:dispatched → pm:queue, assignee cleared, so any ui seat can claim rows 2–4.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

    Claim: PM loop round 1
    Session: session_01Utp6CaJoinvGQSbHeDaWBe
    Account: os-bill
    Branch: claude/project-thread-qtzjxg (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27430
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: the dashboard metric tile renderer (row 2), the percent cell face if row 3 is taken, tests beside, root .changeset/; i18n only if a sentence changes (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier on 178b5238: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: ui code produces the defect | no platform path covers it | every Studio / console author reaches it today
    Thread-read: 6512166362
    Premise: holds
    Serial constraints cleared: none (row 1 PRs #27689 and #27800 merged)

    Scope of this claim: rows 2–4 only (row 1 is done by PR #27689 and PR #27800; seat 2 released the card). Rows that need a new metadata key, spec, or report-engine change are filed as their own cards by the seat. Seat 4's in-flight PR files (#27912, #27907, #27893, #27793) and seat 2's record page v4 PRs are off limits.

    Dispatched 2026-10-30T05:07Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CXAa3A4UiNf9JKBZ7R
    os-dev-report

    {
      "issue": 27430,
      "status": "done",
      "branch": "claude/project-thread-qtzjxg",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27972",
      "session": "session_01Utp6CXAa3A4UiNf9JKBZ7R (card thread; seat session_01Utp6CaJoinvGQSbHeDaWBe)",
      "premise_still_valid": true,
      "summary": "Rows 2-4 re-measured on main ed0e71db. Row 2 TAKEN: a dataset-bound metric tile (DatasetWidget given a heading, on DashboardRenderer and DashboardGridLayout) now captions its value with the widget description when authored, else the measure label; the measure label keeps riding in the title tooltip. description is the existing widget-level DashboardWidgetSchema key, already resolved and handed in through heading, so no new key, no spec change: Clause-2 no, Metadata-protocol none. This deliberately reverses the 'description drawn nowhere else' pins of objectui#12528 and objectui#12573 (both rewritten, density unchanged: the caption takes the measure label's slot). Rows 3 and 4 are CARD (findings below); PR says Part of #27430, the seat flips it to Fixes once both cards are filed.",
      "tests": "new ui/packages/plugin-dashboard/src/__tests__/DashboardSurfaces.metricTileCaption-27430.test.tsx (10 tests, both surfaces) plus rewritten pins in DashboardRenderer.metricTileDensity-12528 and DashboardSurfaces.metricTileTwoLines-12573: the four metric-tile files (with metricTileLayout-12623) Test Files 4 passed, Tests 31 passed. pnpm --filter @object-ui/plugin-dashboard test at 6c8d7e20: Test Files 198 passed (198), Tests 1908 passed | 6 skipped, lock VERDICT command-exit 0. type-check exit 0; eslint on the six touched files exit 0. Ablation via scripts/ablation-replace.mjs at 6c8d7e20 (anchor 1 -> 0; vitest resolves plugin-dashboard from src, no dist leg): caption set back to the measure label -> Test Files 2 failed, Tests 5 failed | 8 passed (described + per-locale pins on both surfaces, the 12528 description pin; fallback and control pins stay green); restore blob == HEAD, git diff HEAD empty.",
      "gates": "dispatch-gates --commands at 6c8d7e20: 34 families, all 34 :: exit 0, including node scripts/check-sdui-manifest.mjs :: exit 0 and --self-test :: exit 0, pnpm check:nul-bytes :: exit 0, pnpm check:type-check-debt :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (169 files passed, 2 skipped). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside derivation: ui pnpm check:new-line-citations: 0 new citations, exit 0. CI check-runs: not yet reported at report time; the seat reads them.",
      "line_budget": "213 added / 31 removed, 7 files (git diff --shortstat origin/main...HEAD); under the 3000 human-merge threshold",
      "files_changed": [
        ".changeset/27430-metric-tile-description-caption.md",
        "ui/packages/plugin-dashboard/src/DashboardRenderer.tsx",
        "ui/packages/plugin-dashboard/src/DatasetWidget.tsx",
        "ui/packages/plugin-dashboard/src/metricTile.ts",
        "ui/packages/plugin-dashboard/src/__tests__/DashboardRenderer.metricTileDensity-12528.test.tsx",
        "ui/packages/plugin-dashboard/src/__tests__/DashboardSurfaces.metricTileCaption-27430.test.tsx",
        "ui/packages/plugin-dashboard/src/__tests__/DashboardSurfaces.metricTileTwoLines-12573.test.tsx"
      ],
      "deviations": "PR body lines 1-2: the order's attribution block names the seat thread link; the harness session context binds this card thread's link to the same marker line, and it outranks the order, so the PR carries the card thread's link. Harness post-PR assign/review step skipped per the order (no assignee, no reviewer). Harness commit-trailer reminder names a model; the commit carries the model-free pair AGENTS.md requires.",
      "dogfood_battery": "n/a - no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no labels) via repository_dispatch run 41010019230, body read back 6676/6676 bytes identical; this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27430/comments). Plus a git push of the branch (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "CARD row 3 · class: a (user-visible: a rate or probability reads as progress) · reach: named producer, HotCRM crm_opportunity.probability and campaign rate fields on 18.2.0 (#27430 row 3) · title: 'percent field: a display key that shows a plain percentage instead of the progress bar, in list cells and on the record page' · body: PercentCellRenderer (ui/packages/fields) draws a progress bar for every percent value, with no key to turn it off; it is the face list-grid cells and the record page's percent fields (DetailView, HeaderHighlight chips) share. Nothing existing selects a plain face: Field.format has no declared vocabulary and only promotes textual types, Field.widget is the form override, and ListColumn.type is a renderer override whose number face would print the stored fraction (0.25) rather than 25%. Needs a declared display key on the field (and optionally the list column) read by PercentCellRenderer, with the bar as the default. Seam: spec:Field (new key, e.g. a percent display mode) -> renderer:PercentCellRenderer · landing: packages/spec (data/field.zod.ts) + ui/packages/fields PercentCellRenderer · lane: spec first (contract), then ui · Clause-2 yes (widening), Metadata-protocol added · note: fields/src/index.tsx is held by seat 4's PR #27912 today · dedupe words: percent progress bar, percent display, plain percentage, probability bar, PercentCellRenderer",
        "CARD row 4a · class: a (missing capability with a named producer) · reach: named producer, HotCRM opportunity_funnel_owner_stage (hotcrm#2379 turned it into an owner x stage matrix as the workaround) · title: 'report chart: split a series by a second dataset dimension' · body: ReportChartSchema (packages/spec ui/report.zod.ts) extends ChartConfig with one xAxis dimension and one yAxis measure, and DatasetReportRenderer queries exactly chart.xAxis x chart.yAxis, so a report cannot draw a bar per owner split by stage. The dashboard dataset path already pivots a second dimension into grouped series (buildChartSeries in ui/packages/core), so the renderer half can reuse it. Needs a declared series dimension key on ReportChartSchema (e.g. a series or groupBy dimension name validated against the report's dataset), the report query widened to it, and the renderer pivot. Seam: spec:ReportChartSchema (new key) -> renderer:DatasetReportRenderer chart query · landing: packages/spec + ui/packages/plugin-report · lane: spec then ui · Metadata-protocol added · dedupe words: report chart series, second dimension, groupBy series, stacked bar report, ReportChartSchema",
        "CARD row 4b · class: a (user-visible: a two-level summary repeats the outer value and has no group subtotal) · reach: named producer, HotCRM opportunity_funnel_owner_stage as a summary with two rows levels (#27430 row 4) · title: 'summary report with two row levels: per-group subtotals and no repeated outer value' · body: a summary report asks the server only for the grand total (totals.groupings with the empty grouping) and draws every inner row with its outer value repeated; the spec has no subtotal key. The matrix branch already asks for row and column subtotals through totals.groupings and only places the server's pre-aggregated rows, so the server half exists. Decide whether subtotals are the summary default for two or more rows levels or a declared key (e.g. a subtotal flag on the report); then the renderer asks for each rows prefix grouping and draws group header and subtotal rows. Seam: spec:ReportSchema (subtotal key, if declared) -> renderer:DatasetReportRenderer summary branch · landing: ui/packages/plugin-report, plus packages/spec if a key is declared · lane: ui (spec if keyed) · dedupe words: summary subtotal, group subtotal, report subtotals, multi-level summary, repeated group value",
        "observation, not filed · the DashboardWidgetOptionsSchema doc comment (packages/spec ui/dashboard.zod.ts) says widget.description is rendered as the card-header subtitle; a dataset-bound metric tile has no card header and now draws it as the caption under the value · carrier: seat"
      ]
    }
  8. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

    ACCEPT → PR #27972 comment 6516214847 · 2026-10-30T06:07Z


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions