You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
plugin-charts: vertical bar charts drop most categorical x-axis labels in dashboard-width widgets (3 bars → 1 label, 5 bars → 2), horizontal bars keep all of them #7247
Showcase dashboards at ~200-290px widget width (three-column dashboard grid in an ~800px console):
widget
bars
x labels drawn
Delivery Operations → Projects by Health
3
Yellow only
Delivery Operations → Tasks by Status
5
Backlog, To Do
Chart Gallery → Tasks by Status
5
Backlog, To Do
Chart Gallery → Tasks by Priority
4
High, Urgent
Chart Gallery → Hours by Status (horizontal)
5
all 5
report Hours by Status (Chart) at ~650px
5
all 5
So a categorical bar chart becomes unreadable exactly where dashboards put it: the bars have no names, and the legend does not help because a single-series bar chart has none. The thinning is width-driven (the same chart in the wide report view keeps every label) and only affects the vertical orientation.
Expected
Categorical axes keep every label while there is room to rotate or wrap them (3-5 rotated labels fit easily in 200px); thinning should be the last resort and should never leave bars without a name when a tooltip-free surface (screenshot, print, big-screen page) is the consumer. Rotate → shrink → truncate with ellipsis → thin, in that order.
Related polish seen on the same widgets (can ride along or split): count y-axes use fractional ticks (0 / 0.8 / 1.5 / 2.3 / 3) for integer data; radar and funnel labels clip (gent, In Revie); the sankey draws no node labels at all.
objectui @ 67dadd6, objectstack main @ a39b02a6b. Found during the objectstack pin-bump dogfood (objectstack#14295).
Claim: PM loop round 3 (dogfood dispatch)
Session: session_62849a16-0144-4728-8942-9f60bb3a73f1
Branch: claude/issue-7247-bar-axis-label-thinning
Worktree: objectui-issue-7247
Domain: domain:ui
File surface: packages/plugin-charts/src/** (the categorical x-axis label policy of the vertical bar family and its tests), .changeset/ (stop on breach; explain in the report)
Container & model: M, model: opus, mode:subagent
Clause-②: no
Serial constraints cleared: #7248 (scatter, same package) is deliberately serialized AFTER this card; no open objectui PR touches plugin-charts (REST pulls list 2026-09-02 17:50); in-flight #7246 (fields) does not touch plugin-charts
Maintainer direct-dispatch channel (2026-08-10 standing authorization); instruction in this session, verbatim: 「P1/P2 都开 issue,packages 500 单独修,服务先留着」 → 「使用 pm-dispatch 派发」. Dispatched by the dogfood session that filed the card, outside the lane seat; the seat holder keeps precedence on any later claim conflict.
Claim: os-dev (developer seat) — recording the dev-side claim on the card. Posted late: this session was cut off by a usage wall mid-implementation and resumed from its transcript, so the marker lands alongside the finished work rather than ahead of it.
{
"issue": 7247,
"status": "done",
"branch": "claude/issue-7247-bar-axis-label-thinning",
"pr": "https://github.com/objectstack-ai/objectui/pull/7385",
"premise_still_valid": true,
"summary": "Premise confirmed on current main and reproduced at the reported widths. Root cause: AdvancedChartImpl's shared xAxisCommonProps applied one tick policy to time and category axes alike (interval 'preserveStartEnd' + minTickGap 48) and keyed it to the VIEWPORT (isMobile), never the widget, so a 174px chart in an 800px console was charged a full desktop inter-label gap; horizontal bars escape only because their category axis is the y axis, which uses recharts' defaults. Fix: a categorical x axis of 5 buckets or fewer now draws every label (interval 0), rotating and ellipsising over-long names; the bound is derived from the narrowest shipped widget (142px plot / rotated labels collide on perpendicular separation bandWidth*sin(35deg) vs a 14px line box => buckets at most 5.8), so nothing is measured at runtime. Above the bound recharts' measured thinning is untouched, which keeps the old 'hundreds of points paint a dense black bar' guard. Change surface declared in the PR body: CHANGED xAxisCommonProps (bar/column, line, area, combo); ALREADY COMPLIANT ChartImpl.tsx (hard-codes interval 0) and the horizontal-bar category axis (both untouched, the latter pinned by a no-regression test); OUT OF SCOPE the scatter axes (#7248, serialized after this card) and the issue's three ride-along polish items. One thing deliberately NOT done here and filed instead: relaxing minTickGap 48 for categorical axes ABOVE the bound (same root cause, but the replacement value is a judgement call and its effect is unmeasurable in happy-dom, which reports zero text metrics).",
"tests": "All on final commit 200063bc9 (git rev-parse --short HEAD after the last commit). RED FIRST: the new regression test ran against the unfixed source before the policy existed - 5 of 7 failed, e.g. expected ['Green','Red'] to deeply equal ['Green','Yellow','Red'] and expected ['Low','Urgent'] to deeply equal ['Low','Normal','High','Urgent']; the other 2 (high-cardinality thinning, horizontal-bar family) passed both before and after, which is their purpose. Dependency closure built once (pnpm --filter '@object-ui/plugin-charts^...' build, exit 0) before the red run and not rebuilt between red and green; the subject is imported from source, so both runs read the same tree. GREEN: pnpm exec vitest run packages/plugin-charts/ -> 'Test Files 41 passed (41)' / 'Tests 383 passed (383)'. pnpm --filter @object-ui/plugin-charts type-check -> exit 0 with 'tsc --noEmit' echoed (real run, not a zero-match filter); tsc --listFiles confirms the new test file IS in the type-check file set (1 hit), so its green covers the test too. pnpm --filter @object-ui/plugin-charts lint -> exit 0, '303 problems (0 errors, 303 warnings)', all pre-existing. check-control-bytes 'OK (scanned 6044 tracked text file(s))'; check-vi-mock-specifiers OK; check-vi-mock-inherit OK (this PR adds a vi.mock('recharts')); check-changeset-fixed OK; check-changeset-no-major OK. Exit codes were captured after redirect to a file, never through a pipe. BROWSER before/after, Chromium at an 800px viewport, plot box 174px, counted from the DOM: Projects by Health 3 bars 1 label -> 3; Tasks by Status 5 bars 2 labels -> 5; Tasks by Priority 4 bars 2 labels -> 4; Hours by Status horizontal 5 -> 5 unchanged; 654px report chart 3 -> 3 unchanged. The BEFORE column reproduces the issue's table exactly. That before-pass used the sanctioned mutate-then-restore shape (fix committed first): mutation 'git checkout origin/main -- FILE' confirmed on disk by blob hash 2acc581d3 -> aceb44fcb plus a zero count of the new policy identifier; restore 'git checkout HEAD -- FILE' proven by the on-disk hash matching the HEAD blob exactly and 'git diff HEAD' empty. DECLARED NARROWINGS/DEVIATIONS (all also in the PR body): (1) os-verify-lock.sh found no usable flock on this macOS host and ran in DECLARED UNLOCKED MODE - nothing was serialized against sibling agents; (2) the dispatched command 'pnpm --filter PKG exec vitest run FILE' is REFUSED by this repo's invocation guard (objectui#3378) because a package-cwd run silently executes @object-ui/console's 22 files and reports them green, so every run was started from the repository root, as CI does; (3) lint here is 'turbo run lint' i.e. a per-package 'eslint .', and this diff touches exactly one package, so the package lint run is the whole lint surface rather than a narrowing. Not measured: CI - reported at draft-PR time per contract.",
"mcp_calls": "0 - every GitHub read and write in this run went through `gh api` (REST); no MCP GitHub call was made",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7386: categorical x axes ABOVE the 5-bucket bound still lose labels because minTickGap 48 is a time-series budget charged to band axes - same root cause, left out because the replacement value is a judgement call and happy-dom's zero text metrics make it unmeasurable in unit tests (needs its own browser-verified change); filed unassigned, no label, for PM triage"
]
}
ACCEPT — reviewer of record: PM session session_62849a16-0144-4728-8942-9f60bb3a73f1.
Checked against GitHub, not the report: draft PR #7385 targets main, first line Fixes #7247, no other closing keyword, changed files stay inside the claimed surface (AdvancedChartImpl.tsx + its density test + changeset; no governed path), 30 check-runs green at head 200063b. Spot-read: categorical x axes of ≤5 buckets draw every label (interval 0, rotate/ellipsis), bound derived from the narrowest shipped widget, high-cardinality thinning and the horizontal-bar family pinned as no-regression; browser counts 1→3, 2→5, 2→4 at an 800px console; finding #7386 filed for the >5-bucket minTickGap. Flipping to ready and arming queue merge.
Activity
hotlong commentedon Sep 2, 2026
Claim: PM loop round 3 (dogfood dispatch)
Session:
session_62849a16-0144-4728-8942-9f60bb3a73f1Branch:
claude/issue-7247-bar-axis-label-thinningWorktree:
objectui-issue-7247Domain:
domain:uiFile surface:
packages/plugin-charts/src/**(the categorical x-axis label policy of the vertical bar family and its tests),.changeset/(stop on breach; explain in the report)Container & model: M, model: opus, mode:subagent
Clause-②: no
Serial constraints cleared: #7248 (scatter, same package) is deliberately serialized AFTER this card; no open objectui PR touches plugin-charts (REST pulls list 2026-09-02 17:50); in-flight #7246 (fields) does not touch plugin-charts
Maintainer direct-dispatch channel (2026-08-10 standing authorization); instruction in this session, verbatim: 「P1/P2 都开 issue,packages 500 单独修,服务先留着」 → 「使用 pm-dispatch 派发」. Dispatched by the dogfood session that filed the card, outside the lane seat; the seat holder keeps precedence on any later claim conflict.
Generated by Claude Code
wip(plugin-charts): categorical x-axis label policy scaffolding for #…
hotlong commentedon Sep 2, 2026
Claim: os-dev (developer seat) — recording the dev-side claim on the card. Posted late: this session was cut off by a usage wall mid-implementation and resumed from its transcript, so the marker lands alongside the finished work rather than ahead of it.
Session:
session_62849a16-0144-4728-8942-9f60bb3a73f1Branch:
claude/issue-7247-bar-axis-label-thinningWorktree:
objectui-issue-7247Assignee: untouched (PM owns that field)
Generated by Claude Code
hotlong commentedon Sep 2, 2026
os-dev-report
{ "issue": 7247, "status": "done", "branch": "claude/issue-7247-bar-axis-label-thinning", "pr": "https://github.com/objectstack-ai/objectui/pull/7385", "premise_still_valid": true, "summary": "Premise confirmed on current main and reproduced at the reported widths. Root cause: AdvancedChartImpl's shared xAxisCommonProps applied one tick policy to time and category axes alike (interval 'preserveStartEnd' + minTickGap 48) and keyed it to the VIEWPORT (isMobile), never the widget, so a 174px chart in an 800px console was charged a full desktop inter-label gap; horizontal bars escape only because their category axis is the y axis, which uses recharts' defaults. Fix: a categorical x axis of 5 buckets or fewer now draws every label (interval 0), rotating and ellipsising over-long names; the bound is derived from the narrowest shipped widget (142px plot / rotated labels collide on perpendicular separation bandWidth*sin(35deg) vs a 14px line box => buckets at most 5.8), so nothing is measured at runtime. Above the bound recharts' measured thinning is untouched, which keeps the old 'hundreds of points paint a dense black bar' guard. Change surface declared in the PR body: CHANGED xAxisCommonProps (bar/column, line, area, combo); ALREADY COMPLIANT ChartImpl.tsx (hard-codes interval 0) and the horizontal-bar category axis (both untouched, the latter pinned by a no-regression test); OUT OF SCOPE the scatter axes (#7248, serialized after this card) and the issue's three ride-along polish items. One thing deliberately NOT done here and filed instead: relaxing minTickGap 48 for categorical axes ABOVE the bound (same root cause, but the replacement value is a judgement call and its effect is unmeasurable in happy-dom, which reports zero text metrics).", "tests": "All on final commit 200063bc9 (git rev-parse --short HEAD after the last commit). RED FIRST: the new regression test ran against the unfixed source before the policy existed - 5 of 7 failed, e.g. expected ['Green','Red'] to deeply equal ['Green','Yellow','Red'] and expected ['Low','Urgent'] to deeply equal ['Low','Normal','High','Urgent']; the other 2 (high-cardinality thinning, horizontal-bar family) passed both before and after, which is their purpose. Dependency closure built once (pnpm --filter '@object-ui/plugin-charts^...' build, exit 0) before the red run and not rebuilt between red and green; the subject is imported from source, so both runs read the same tree. GREEN: pnpm exec vitest run packages/plugin-charts/ -> 'Test Files 41 passed (41)' / 'Tests 383 passed (383)'. pnpm --filter @object-ui/plugin-charts type-check -> exit 0 with 'tsc --noEmit' echoed (real run, not a zero-match filter); tsc --listFiles confirms the new test file IS in the type-check file set (1 hit), so its green covers the test too. pnpm --filter @object-ui/plugin-charts lint -> exit 0, '303 problems (0 errors, 303 warnings)', all pre-existing. check-control-bytes 'OK (scanned 6044 tracked text file(s))'; check-vi-mock-specifiers OK; check-vi-mock-inherit OK (this PR adds a vi.mock('recharts')); check-changeset-fixed OK; check-changeset-no-major OK. Exit codes were captured after redirect to a file, never through a pipe. BROWSER before/after, Chromium at an 800px viewport, plot box 174px, counted from the DOM: Projects by Health 3 bars 1 label -> 3; Tasks by Status 5 bars 2 labels -> 5; Tasks by Priority 4 bars 2 labels -> 4; Hours by Status horizontal 5 -> 5 unchanged; 654px report chart 3 -> 3 unchanged. The BEFORE column reproduces the issue's table exactly. That before-pass used the sanctioned mutate-then-restore shape (fix committed first): mutation 'git checkout origin/main -- FILE' confirmed on disk by blob hash 2acc581d3 -> aceb44fcb plus a zero count of the new policy identifier; restore 'git checkout HEAD -- FILE' proven by the on-disk hash matching the HEAD blob exactly and 'git diff HEAD' empty. DECLARED NARROWINGS/DEVIATIONS (all also in the PR body): (1) os-verify-lock.sh found no usable flock on this macOS host and ran in DECLARED UNLOCKED MODE - nothing was serialized against sibling agents; (2) the dispatched command 'pnpm --filter PKG exec vitest run FILE' is REFUSED by this repo's invocation guard (objectui#3378) because a package-cwd run silently executes @object-ui/console's 22 files and reports them green, so every run was started from the repository root, as CI does; (3) lint here is 'turbo run lint' i.e. a per-package 'eslint .', and this diff touches exactly one package, so the package lint run is the whole lint surface rather than a narrowing. Not measured: CI - reported at draft-PR time per contract.", "mcp_calls": "0 - every GitHub read and write in this run went through `gh api` (REST); no MCP GitHub call was made", "open_questions": [], "out_of_scope_findings": [ "filed as #7386: categorical x axes ABOVE the 5-bucket bound still lose labels because minTickGap 48 is a time-series budget charged to band axes - same root cause, left out because the replacement value is a judgement call and happy-dom's zero text metrics make it unmeasurable in unit tests (needs its own browser-verified change); filed unassigned, no label, for PM triage" ] }Generated by Claude Code
hotlong commentedon Sep 2, 2026
ACCEPT — reviewer of record: PM session
session_62849a16-0144-4728-8942-9f60bb3a73f1.Checked against GitHub, not the report: draft PR #7385 targets
main, first lineFixes #7247, no other closing keyword, changed files stay inside the claimed surface (AdvancedChartImpl.tsx + its density test + changeset; no governed path), 30 check-runs green at head 200063b. Spot-read: categorical x axes of ≤5 buckets draw every label (interval 0, rotate/ellipsis), bound derived from the narrowest shipped widget, high-cardinality thinning and the horizontal-bar family pinned as no-regression; browser counts 1→3, 2→5, 2→4 at an 800px console; finding #7386 filed for the >5-bucket minTickGap. Flipping to ready and arming queue merge.PR: #7385
Generated by Claude Code
fix(plugin-charts): draw every categorical x-axis label on short axes (…