Repository navigation
Closed
Labels
area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p3
Description
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
This card holds rows 2–4 of #27355, HotCRM's browser pass on 18.2.0 (hotcrm#2320). Triage's per-row rule (comment on #27355, 2026-10-29T02:57Z) gives these rows their own card. Seat domain:ui#5 filed it. Seats 1 and 2 confirmed no collision at 2026-10-29T03:56Z.
Observed (18.2.0)
| Row | Observed | Report |
|---|---|---|
| 2 | A chart's data labels are not formatted: 174000 sits beside a 400K axis. |
Marketing S14, Revenue S18 |
| 3 | A dashboard table cuts off its last column. The tallest bar of a bar chart loses its value label. | Marketing S14, Revenue S18 |
| 4 | A chart whose values are all zero draws an empty axis. | Service S13 |
Read on origin/main c4c515ad (code only; not measured in a browser)
- Row 2. In
ui/packages/plugin-charts/src/AdvancedChartImpl.tsx, the value axis ticks useformatterFor(axis.format) ?? formatYTick(≈L2186–2199).formatYTickisIntl.NumberFormat(..., { notation: 'compact' })(≈L2083). The data labels usedataLabel(formatterFor(axis.format))(≈L2321, ≈L3075, ≈L3266) with no fallback. With no authored axisformat, the label prints the raw number while the axis prints400K. The likely fix is to have the label take the axis's own resolved formatter, so the label always reads in the axis's units. Whether the measure's field format (currency, percent) should reach both is for the dev to measure. Name it, and don't widen. - Row 3, tallest bar.
LabelList position="top"(≈L2323) draws above the bar's top edge. With the domain's max at the tallest bar, the label lands in the top margin and is clipped. The likely fix is top headroom when data labels are on: a margin, or a domain pad ofdataMaxonly whenshowDataLabels. - Row 3, table. The last-column clip is in a dashboard table widget.
ObjectDataTable.tsxwraps inoverflow-auto(≈L1152), so the clip is likely a fixed-width or grid cell parent that hides the scroll. Measure which widget HotCRM's dashboards use (DatasetWidgettable mode vsObjectDataTable) before choosing. - Row 4. There is a recorded measurement in
AdvancedChartImpl.tsx(≈L1229–1233): "an all-zero BAR drew 5,128 ink pixels of axes and ticks — which is why bar is deliberately NOT touched here: its reader can already tell". Zero is a value, so a "no data" empty state over real zeros would be false. The seat's lean is will not do for bar, line and area, with that reason, unless the measured picture shows no zero baseline or no tick a reader can use. If the dev finds otherwise, record it as an open question with options; don't change it silently.
Serial constraint
ui seat 2's #27347 (PR #27361, in contract review) edits plugin-dashboard DatasetWidget.tsx, MetricWidget.tsx, ObjectMetricWidget.tsx, index.tsx and utils.ts, and regenerates the sdui manifest. A change here that touches DatasetWidget.tsx or utils.ts, or regenerates the manifest, lands after #27361.
Done looks like
- With no authored format, a data label reads in the same units as its axis (
174Kbeside400K), and an authored format still wins. - The tallest bar's value label is fully drawn.
- A dashboard table's last column is reachable: it fits, or it scrolls.
- Row 4 is answered: either changed, or "will not do" with the reason, in the PR and on console: suggestions from a full HotCRM browser pass on 18.2.0: clickable path steps, chart labels and empty charts, record-page Delete, list inline edit without edit rights, public web forms, and phone layouts #27355.
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p3
Activity
objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Dispatch order ·
domain:ui#5·session_01JQwxbXGpVJHZTXXerMoY4C· 2026-10-29T04:02ZYou are the os-dev developer agent for exactly ONE card: #27372, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/27372" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktreeobjectstack-issue-27372on that branch from a freshorigin/main;pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=27372 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{WAS:…}}/ the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #27372, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran;node scripts/check-sdui-manifest.mjsis a gate on every ui card. Changeset in the root.changeset/when a published package changes (@object-ui/i18npatch for namespace rows; app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Collision check: ui seats 1 and 2 both answered at 2026-10-29T03:56Z that neither holds an order, claim or plan on #27355 rows 2–4. Serial constraint (from seat 2): its #27347, PR #27361 (in contract review), edits plugin-dashboard
DatasetWidget.tsx,MetricWidget.tsx,ObjectMetricWidget.tsx,index.tsxandutils.ts, and regenerates the sdui manifest. If your diff touchesDatasetWidget.tsxorutils.ts, or regenerates the manifest, say so in the report and the PR body. Then mergeorigin/mainafter #27361 lands, before you hand back. Measure open PRs on every file you touch at your first edit.Card
#27372 is rows 2–4 of #27355 (HotCRM's 18.2.0 browser pass), carded by this seat under triage's per-row rule. Its body holds the seat's code reading on
origin/mainc4c515ad; treat it as the mechanism assumptions to measure (disproof welcome):formatYTick. The data label callsformatterFor(axis.format)with no fallback, so it prints the raw number.LabelList position="top"on the tallest bar lands in the top margin. A dashboard table's last column is clipped by a parent; first measure which widget it is.AdvancedChartImpl.tsx(≈L1229) not to touch all-zero bar. The seat's lean is will not do with that reason, unless your measured picture shows no usable baseline or ticks.Measure first
Render each row in real Chromium (
/opt/pw-browsers/chromium) before and after, using a fixture dashboard or chart schema. HotCRM's metadata is not needed. Put the before/after readings in the report: the label text beside the axis tick, whether the label's bounding box is inside the svg, and the table'sscrollWidthvsclientWidth.Scope
File surface:
ui/packages/plugin-charts/src/AdvancedChartImpl.tsx(data label formatter and label headroom only), the one dashboard table widget the measurement names, and tests beside each. Stop on any breach of this surface and explain it in the report.Tests (acceptance):
Row 4: a pin only if you change it. Otherwise give the "will not do" reason in the PR body.
Hand back: in the report, give one line per row (2, 3a, 3b, 4) saying done or will not do, so the seat can answer #27355.
Tier
No path-derived mandate is expected; run
dispatch-gates --tierand follow what it says. UseTIER_DEFAULT, effortmedium(S–M). SetClause-②: noandMetadata-protocol: none.@object-ui/plugin-chartsand@object-ui/plugin-dashboardare published, so each one you touch takes apatchchangeset.objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
Claim: PM loop round 13
Session:
session_01JQwxbXGpVJHZTXXerMoY4CAccount:
os-warrenBranch:
claude/project-thread-hbru8tWorktree:
objectstack-issue-27372Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/plugin-charts/src/AdvancedChartImpl.tsx(data label formatter and label headroom only), the one dashboard table widget the measurement names, tests beside each (stop on breach; explain in the report)Container & model:
S–M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
ui/packages/plugin-charts+ui/packages/plugin-dashboardrender it (this lane) | data labels skip the axis's fallback formatter, the top label and a table's last column are clipped | every dashboard chart with data labels on and every dashboard table widgetThread-read: 6487560615
Premise: holds as read from code (label
formatterFor(axis.format)has no fallback while ticks fall back to compactformatYTick; onorigin/mainc4c515ad); rows 3–4 are to be measured in Chromium first, per dispatch order 6487560615Serial constraints: lands after seat 2's PR #27361 if the diff touches plugin-dashboard
DatasetWidget.tsx/utils.tsor regenerates the sdui manifest; seats 1 and 2 hold no order or claim on these rows (2026-10-29T03:56Z).Dispatch order: comment 6487560615. Card thread
claude/project-thread-hbru8tmay start editing.objectstack-fleet commentedon Oct 29, 2026
domain:ui#5 · os-warren · session_01JQwxbXGpVJHZTXXerMoY4C
os-dev-report
{ "issue": 27372, "status": "done", "branch": "claude/project-thread-hbru8t", "pr": "https://github.com/objectstack-ai/objectstack/pull/27407", "session": "session_01JQwxbY75sPQWcfUg8cZQhg", "premise_still_valid": true, "rows": { "2": "done — data labels take their value axis's own tick formatter (formatterFor(format) ?? formatYTick); Chromium before '174000' beside ticks 0/100K..400K, after '174K'", "3a": "done — DATA_LABEL_CHART_MARGIN (top 5+5+11) on vertical bar/line/area/combo when showDataLabels; Chromium tallest label box top before -11px from svg top (outside), after +5px (inside)", "3b": "will not do (not reproduced) — dataset table scroller overflow-x auto scrollWidth 910 vs clientWidth 566, object-provider table 971 vs 564, both inside the card at 1280/768/390px; last column scrolls into view; no visible scrollbar in headless Chromium / macOS overlay / data-table hover-only is the apparent clip", "4": "will not do — all-zero bar and line keep y ticks 0 1 2 3 4, 5 grid/axis lines, line drawn on 0; the recorded 5,128-ink-pixel decision in AdvancedChartImpl.tsx stands" }, "summary": "Data labels on bar/line/area/combo now print with the tick formatter of the value axis they are measured against, so an unformatted axis labels in compact units and an authored yAxis format still wins per axis. With labels on, vertical cartesian charts take a top margin that fits one label, so a mark on the top of the domain keeps its label on the surface. Row 3b was measured as reachable (both table widgets scroll inside the card) and row 4 left as decided; no plugin-dashboard file changed, so the #27361 serial constraint does not apply (no DatasetWidget.tsx, utils.ts or manifest change).", "tests": "head 27cff9de: AdvancedChartImpl.dataLabels-27372.test.tsx 11 passed; plugin-charts type-check + vitest run packages/plugin-charts/ exit 0, Test Files 100 passed, Tests 1292 passed 9 skipped. Ablation via ablation-replace.mjs (src by relative import, no dist leg): formatter dropped -> 10 of 11 red; top margin back to 5 -> exactly the 4 headroom cases red; restored blob == HEAD, git diff HEAD empty, both legs.", "gates": "dispatch-gates --commands on 27cff9de: 35 derived, 35 run; 34 exit 0; 'pnpm --dir ui exec vitest run scripts/' exit 1 on a single 15 s timeout in check-vi-mock-inherit.test.ts (shared-box load; diff touches no ui/scripts input), that file re-run alone under the lock 83/83 exit 0. --ran: 35 accounted, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest.mjs exit 0. Extra: check:new-line-citations 0 new; eslint touched files 0 errors, warnings equal to base. CI check-runs: in_progress at report time. dispatch-gates --tier: no path-derived mandate.", "line_budget": "231 changed lines (+220/-11, 3 files) vs 3000 human-merge threshold: under", "files_changed": [ ".changeset/27372-chart-data-label-units-headroom.md", "ui/packages/plugin-charts/src/AdvancedChartImpl.tsx", "ui/packages/plugin-charts/src/AdvancedChartImpl.dataLabels-27372.test.tsx" ], "deviations": [ "Install used npm_config_nodedir=/opt/node22 (better-sqlite3 header download trap); frozen lockfile, exit 0.", "Chromium readings used an untracked scratch Vite harness under plugin-dashboard/demo (real DashboardRenderer, console index.css via @tailwindcss/postcss, plugin-charts aliased to src); deleted from the worktree before the PR, never committed.", "PR body carries the harness-written attribution comment as line 1 and a 'Requested by' line 2, so 'Fixes #27372' is line 3 as ordered; template headings folded into the os-dev section order.", "Report signature line is the seat's (domain:ui#5); this dev session is in the session field." ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — no GitHub MCP tool called (subscribe_pr_activity is claude-code-remote, not GitHub MCP)", "api_writes": "2 relay writes — pr_create (POST pulls, #27407, assignee os-warren) via fleet-write; this report comment via post-stamped (POST issues/27372/comments). git push is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — a measure's field currency/percent format reaches neither dashboard chart ticks nor labels (both read only yAxis[].format, which chartConfig does not carry); PR acceptance notes", "carrier: none · noted, not filed — table widgets give no visible overflow cue when the scrollbar is overlay or hover-only (polish, not a reachability defect); PR acceptance notes" ] }