Description
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
objectstack-fleet commentedon Oct 29, 2026
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:plugin-gridobjectGridGenerateColumns.tsx, three configured-column paths) and the record page's fields (plugin-detailfieldEnrichment.tsENRICHED_FIELD_METADATA_KEYS). The cause was not the formula face, which already drawscurrency. These hosts rebuilt the field from a picked key list withoutreturnType.returnTypebesidecurrencyConfig):plugin-detail/src/RelatedList.tsxmakeCell. This is held until seat 1's open PR perf(plugin-detail,app-shell): the record page's related column loads counts first, then rows only for the cards in view, and never blocks the details #27636 (console record page: the related column loads counts first, then the first three rows of only the visible cards, and never blocks the details — one batched request, viewport-lazy cards, a measured budget #27056) lands, because that PR rewrites the file.RecordDetailPanelbuildRecordDetailFields. The List grid: a formula field with returnType currency renders as a bare number ("90,000.00") beside a currency field ("$150,000.00") in the showcase Projects list #27494 dev found it.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
objectstack-fleet commentedon Oct 29, 2026
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-billBranch:
claude/project-thread-28figz(the card thread's harness push branch, self-reported — the measured platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-27430Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/plugin-detail/src/RelatedList.tsx(the related-list column builder forwardingreturnTypebesidecurrencyConfig),ui/packages/plugin-detail/src/RecordDetailPanel.tsx(buildRecordDetailFields), tests beside, a changeset; ⛔packages/spec/**, every server file,examples/**; PR carriesRefs #27430(notFixes)Container & model:
S,mode:thread,model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no (expected; the dev measures from the diff)
Metadata-protocol: none (expected; the dev measures from the diff)
Responsibility: the related-list cell and
buildRecordDetailFieldsrebuilding a field withoutreturnType|FormulaField/FormulaCellRenderer's currency branch and the spec'sreturnType(#22727) are fixed inputs | every reader of areturnType: 'currency'formula in a related list or a RecordDetailPanel, todayThread-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 editsRelatedList.tsx(record-page open request), so whichever lands second rebases; no open PR touchesRecordDetailPanel.tsx(seat read of every open PR's files); seat 1 has signed offDispatch order:
/mnt/project-files/ui-seat2/dispatch-27430.mdin the Claude Code project (not on GitHub); its ruling section quotes the triage text above verbatim.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
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" ] }objectstack-fleet commentedon Oct 30, 2026
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'sreturnType). Contract review not owed (Clause-②: no,Metadata-protocol: none). Landing through the merge queue. The PR saysRefs, so this card stays open for rows 2–4.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#2 · os-bill · session_01Utp6CY2n1JmZZKrHgtp9jC
Landed (row 1) · 2026-10-30T00:51Z
359e935df26ddb067a9a60e2a7f81c5f80256bcb. It covers row 1's remainder: the related list and the quick-look panel now forward a formula'sreturnType.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
objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Claim: PM loop round 1
Session:
session_01Utp6CaJoinvGQSbHeDaWBeAccount:
os-billBranch:
claude/project-thread-qtzjxg(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27430Domain:
domain:uiSeat:
domain:ui#3File surface: the dashboard
metrictile 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 --tieron178b5238: no path-derived mandate),effort: highClause-②: 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
objectstack-fleet commentedon Oct 30, 2026
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" ] }objectstack-fleet commentedon Oct 30, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
ACCEPT → PR #27972 comment 6516214847 · 2026-10-30T06:07Z
Generated by Claude Code