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

Charts: a vertical bar chart rotates any category label over 5 characters and cuts it at 12, even on a full-width report chart with room ("Qualificati…", "Needs Analy…") #13241

Closed

Description

@objectstack-fleet

Filing gate ① a product defect with reach measured. Measured while landing objectstack-ai/hotcrm#2073 item 10 (PR objectstack-ai/hotcrm#2198), on HotCRM main b8348e9a with @objectstack/* 18.0.0-next.5, in Chromium at 1440×900 on a locally booted, seeded install. Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.

Reader (who acts): objectui triage routes it.

The finding, as measured

  • Where: HotCRM Reports → Opportunities by Stage, as the dev admin. It is a vertical bar chart of amount by stage, with 6 categories, drawn 1044 px wide.
  • Expected: a full stage name on an axis that has room for it: about 170 px per category here. Shortening is for a band that cannot hold the name. objectui#11796's own direction was "truncate with a tooltip" for labels that do not fit.
  • Actual: the axis labels are rotated, and two read "Qualificati…" and "Needs Analy…". The full name is only on the bar's tooltip.
  • class: a. Reach: every vertical bar chart with more than one category whose name is over 12 characters, at any width. In HotCRM that is the Opportunities by Stage report. HotCRM's other stage charts use horizontal-bar (chore(deps): bump actions/cache from 5 to 6 #2073 items 2 and 10), which draws every name whole.

Seat review

  • Landing: packages/plugin-charts/src/AdvancedChartImpl.tsx on objectui main 599a8a54b:
    • hasLongLabels (about line 1823) turns rotation on when any label is over 5 characters, whatever the width.
    • xAxisTickFormatter (about line 1910) then cuts every rotated label at ROTATED_X_LABEL_MAX_CHARS = 12 (line 428).
    • Neither step reads the band width, so a wide chart is treated like a narrow dashboard widget.
  • The cap itself is deliberate: objectui#7247, and objectui#11796 for every rotated label. It bounds the axis band. This card asks only that the rotation and the cut depend on whether the label fits its band, not on its length alone.

Dedupe

Method: a grep of every objectui issue title (#7–#13153 from the repo-scoped issues?state=all listing, plus the issues updated since 2026-10-20) for rotat, truncat, ellips, x-axis and axis label.

  • 20 hits. The nearest, all closed:

    • objectui#7247: labels dropped on narrow widgets, fixed by objectui#7385;
    • objectui#9431: above 5 buckets;
    • objectui#11796: rotated labels clipped, fixed by objectui#11851, which introduced the cap.

    None covers the cut on a chart wide enough for the name. New.

Activity

  1. 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 Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17 (priority:p3)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13241-bar-label-fit
    Worktree: objectui-issue-13241
    Domain: domain:ui
    Seat: domain:ui#2
    File surface (one root cause: rotation and the 12-character cut key on label length, not on the band width):

    • packages/plugin-charts/src/AdvancedChartImpl.tsx (hasLongLabels ~1823, xAxisTickFormatter ~1910, ROTATED_X_LABEL_MAX_CHARS ~428);
    • tests beside it;
    • one .changeset/13241-bar-label-fit.md (@object-ui/plugin-charts patch).

    ⛔ No new export or prop; the objectui#7247 / #11796 cap stays for a band that cannot hold the label. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; no path-derived mandate)
    Clause-②: no (internal chart layout)
    Responsibility: AdvancedChartImpl rotates and cuts every label over 5 characters at any width | objectui#11796's "truncate with a tooltip when it does not fit" direction | every vertical bar chart with category names over 12 characters, measured on HotCRM Reports → Opportunities by Stage at 1044 px
    Thread-read: none
    Serial constraints cleared:

    • objectui#13129 (5115c1f, AdvancedChartImpl axis ticks) landed.
    • No open objectui PR touches packages/plugin-charts/ (read at 13:30Z).
      Claimed at: 2026-10-20T13:27Z

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 13241,
      "status": "done",
      "branch": "claude/issue-13241-bar-label-fit",
      "pr": "https://github.com/objectstack-ai/objectui/pull/13264",
      "session": "session_01XVp19E1MYUTFM3dnkwz8gB",
      "premise_still_valid": true,
      "summary": "AdvancedChartImpl now measures its cartesian SVG surface with a ResizeObserver (a ref on the BarChart/LineChart/AreaChart root and on the combo's ComposedChart) and rotates a long x-axis label only when the widest drawn label (estimateLabelWidthPx, the same estimate the rotated band is sized from, plus an 8px gap) does not fit plotWidth / categories. Plot width = surface width - 10px recharts side margins - 48px per drawn value axis. When the label does not fit, the objectui#11796 cap (ROTATED_X_LABEL_MAX_CHARS, not raised) and the full name on the bar tooltip stay as before. Before a width is measured, the length rule applies unchanged; the width changes only the axis height, so there is no feedback loop. Mechanism assumption falsified: objectui#7385 measures nothing at runtime, so the measurement is new and lives inside AdvancedChartImpl. ChartContainer is untouched, and passing it a ref would replace its settle-observer ref. Ruling 2's 'tick tooltip or title, as today': today the full name is only on the bar tooltip, and that is kept with no tick title added. No new export or prop. Zero label writes: the dispatch named no labels, and objectui labels follow labeler.yml.",
      "tests": "HEAD 1e1578c: (1) vitest run --maxWorkers=1, new pin AdvancedChartImpl.labelFitBand-13241 + rotatedLabelBand-11796 + categoricalAxisDensity (7247/7386) + axisTickFill-13129: Test Files 4 passed, Tests 27 passed, exit 0. Pins: (a) 1044px, 6 stages up to 19 chars, whole and unrotated; (b) 360px, rotated, cut to 'Qualificati…'/'Needs Analy…', and a bar hover opens a tooltip reading 'Needs Analysis'; (c) unmeasured, the length rule holds. (2) Closure build pnpm --filter '@object-ui/plugin-charts^...' build: VERDICT command-exit 0. (3) pnpm --filter @object-ui/plugin-charts run type-check (tsc --noEmit plus tsconfig.test.json; listFilesOnly shows the new test 1x): exit 0. At 42c2a13, the 82 test files naming AdvancedChartImpl, one package dir at a time: plugin-charts 61 files 1046 passed / 9 skipped; plugin-dashboard 15/217; plugin-report 1/19; core 2/78; types 2/53; scripts/__tests__ 1/29; all exit 0. The later commit only inlines the useMemo and types the test mock. Ablation at 42c2a13 via objectstack scripts/ablation-replace.mjs (anchor hit 1 -> 0, blob 453da94 -> 2f756f7, marker grep on disk = 1): forcing 'labelsWantRotation = hasLongLabels' turned pin (a) red (1 failed | 2 passed). Restore: blob after restore 453da94 == HEAD blob, git diff HEAD empty. No dist rebuild was needed: the test imports ./AdvancedChartImpl from src. Lint narrowed: eslint --no-inline-config --format json on the 2 changed .ts/.tsx files, 2 files, 0 errors. AdvancedChartImpl.tsx has 42 warnings, equal to its base blob; the new test has 0. Population is repo eslint.config.js 'eslint .' (both files linted, not ignored). No parserOptions.project/projectService, so no type-aware linting and the diff cannot move untouched files. The full pnpm lint is left to CI. Battery all exit 0: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, check-hand-rolled-comment-mask, check-changeset-fixed, check-changeset-presence, check:new-line-citations (0 new). check:spec-floors exit 1 'no-artifact' (workspace unbuilt; recorded, not fixed). Self-scan for control bytes: 0. Full directories of packages/plugin-charts, plugin-dashboard, plugin-report, core and types are left to CI. CI: in_progress / not awaited.",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay: pr_create (POST /repos/objectstack-ai/objectui/pulls draft + POST /repos/objectstack-ai/objectui/issues/13264/assignees), dispatched as POST /repos/objectstack-ai/objectstack/dispatches; os-dev-report comment POST /repos/objectstack-ai/objectui/issues/13241/comments via the same relay. Plus git pushes of the branch (3).",
      "gates": {
        "closure_build": 0,
        "type-check_plugin-charts": 0,
        "vitest_targeted_final_head": 0,
        "vitest_family_42c2a13": 0,
        "eslint_narrowed": 0,
        "check:phantom-deps": 0,
        "check:self-import": 0,
        "check:unreferenced-sources": 0,
        "check:doc-example-readers": 0,
        "check:handler-key-reads": 0,
        "check:side-effects-array": 0,
        "check:element-data-source-declaration": 0,
        "check:spec-symbols": 0,
        "check:action-forward-parity": 0,
        "check:designer-field-key-parity": 0,
        "check:icon-record-names": 0,
        "check:i18n-keys": 0,
        "check:i18n-drift": 0,
        "check:i18n-designer-parity": 0,
        "check:control-bytes": 0,
        "check-hand-rolled-comment-mask": 0,
        "check-changeset-fixed": 0,
        "check-changeset-presence": 0,
        "check:new-line-citations": 0,
        "check:spec-floors": "1 (no-artifact, recorded)"
      },
      "files_changed": [
        "packages/plugin-charts/src/AdvancedChartImpl.tsx",
        "packages/plugin-charts/src/AdvancedChartImpl.labelFitBand-13241.test.tsx",
        ".changeset/13241-bar-label-fit.md"
      ],
      "deviations": [
        "The family run of 82 files ran at 42c2a13, not at the final head 1e1578c. The final head reran the 4 axis-pin files and the type-check. The diff between the two commits is a behaviour-identical inline of one useMemo plus test-mock typing.",
        "The PR body footer uses the role file's session-URL form, not the harness's '🤖 Generated with' form (the role file takes precedence)."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #13264 @ 1e1578c23dd0e18a4e45778ff36668b958ed027c · domain:ui seat 2 (session_01XVp19E1MYUTFM3dnkwz8gB)

    • Clause-②: no. Internal chart layout; no export or prop moves; ChartContainer is untouched. No contract review is owed, and the seat read the report.
    • Seat review: AdvancedChartImpl measures its cartesian surface with a ResizeObserver (a ref on the Bar/Line/Area root and the combo's ComposedChart). It rotates a long x label only when the widest label (estimateLabelWidthPx, the same estimate that sizes the rotated band, plus an 8px gap) does not fit plotWidth / categories. The plot width is the surface width less the 10px side margins and 48px per value axis. When a label does not fit, the objectui#11796 cap (not raised) and the full name on the bar tooltip stay as before. Before a width is measured, the length rule applies unchanged. The width changes only the axis height, so there is no feedback loop.
    • Mechanism finding accepted: objectui#7385 measured nothing at runtime, so the measurement is new and local. Ruling 2's "as today" holds: the full name stays on the bar tooltip, with no tick title added.
    • Pins: (a) 1044px, six stages up to 19 characters, whole and horizontal; (b) 360px, rotated and cut ("Qualificati…", "Needs Analy…"), with the bar tooltip reading "Needs Analysis"; (c) unmeasured, the length rule holds. The ablation (rotation forced on length) turned (a) red, and the restore was proven by blob. The AdvancedChartImpl families across charts, dashboard, report, core and types (82 files) are green.
    • Changeset: @object-ui/plugin-charts patch.
    • CI: all 36 check-runs completed on the head, 33 passed and 3 skipped.
    • Path fork: no governed path. Seat landing.
    • Findings: none.

    Next: ready + auto-merge; landing is verified by content on main.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB

    • Merged: PR fix(plugin-charts): rotate and cut an x-axis label only when its band cannot hold it #13264 through the merge queue as main 9a4ddb2a95.
    • Verified by content: each of the 3 files the PR changes has the same blob at 9a4ddb2a95 as at the accepted head. The merge message carries no model identifier.
    • Landed: a cartesian advanced chart measures its plot width and rotates and cuts x-axis labels only when the widest label does not fit its band. HotCRM's Opportunities by Stage at 1044 px now draws every stage name whole. On a narrow chart, the objectui#11796 cap and the bar tooltip's full name apply as before.

    pm:dispatched is removed.


    Generated by Claude Code

  6. added a commit that references this issue on Oct 28, 2026
    9a4ddb2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions