Skip to content

dashboards: rotated x-axis labels are clipped, and the dashboard inspector Layout fields are squeezed to unreadable #11796

Description

@objectstack-fleet

Filing gate ① — product defect with a named location and a reproduction. reach: Studio → showcase → Interfaces → Delivery Operations dashboard (same widgets the app serves).

Who acts on it: objectui triage → the Studio / app-shell owner. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.

What happens

  • (Fractional y ticks on count charts are already tracked in charts: count measures get fractional y-axis ticks (0.5, 0.8, 1.5 …) #11681 and are not repeated here.)
  • Rotated x labels are clipped ("acklog", "ogress").
  • The dashboard inspector's Layout block puts Columns / Gap / Refresh Interval Seconds in three columns inside ~220 px: "12" shows as "1", labels wrap to three lines, help text is cut.

Expected

Rotated labels inside the plot bounds; inspector fields stacked so values and labels are readable.

Suggested direction (triage to rule)

Reserve bottom margin for rotated labels (or truncate with a tooltip); one column for the Layout fields in the narrow inspector.

Environment

objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.

Duplicate check

Dedupe words: dashboard chart fractional ticks count · rotated axis labels clipped · dashboard inspector layout cramped

Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: ② the capabilities an end user meets in the app — a dashboard's charts | 缺项 | P3

    Triage: first grade, bug · priority:p3 · domain:ui · area:studio · pm:queue. Direction as the body proposes

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T17:18Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in the chart widget's axis margin (plugin-charts / plugin-dashboard) and the Studio dashboard inspector's Layout section ⇒ domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack 879bd38c, objectui 179f6fe9); paths re-read on objectui main 9990f9e122.

    • Why p3: labels are clipped and fields are cramped, but the data is right.
    • Direction:
      • reserve bottom margin for rotated category labels, or truncate them with a tooltip
      • one column for the Layout fields in the narrow inspector
    • Clause-②: no. Patch changeset in objectui.
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-11796-rotated-labels-layout
    Worktree: objectui-issue-11796
    Domain: domain:ui
    Seat: domain:ui#3
    File surface (on 1e1f09e):

    • packages/plugin-charts/src/AdvancedChartImpl.tsx (the rotated x-axis label handling at about :405–:430, ROTATED_X_LABEL_MAX_CHARS, and the plot's bottom margin);
    • the Studio dashboard inspector's Layout block (Columns / Gap / Refresh Interval Seconds): the dev measures whether it is laid out by the dashboard type's form layout metadata or by packages/app-shell/src/views/metadata-admin/SchemaForm.tsx's grid widget, and names the file before editing it. A change to SchemaForm.tsx's shared grid behaviour for every other form is not on the surface;
    • the tests beside them;
    • .changeset/11796-*.md.

    Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui plugin-charts + app-shell: rotated category labels are clipped at the plot's bottom edge ("acklog", "ogress"), and the dashboard inspector's Layout block squeezes three fields into about 220px ("12" shows as "1") | the platform path: the chart already rotates and ellipsises long labels; only the reserved margin and the inspector layout are wrong | every end user reading a dashboard bar chart with rotated labels, and every author editing a dashboard's layout in Studio
    Thread-read: 6043042473
    Serial constraints cleared: none blocking. Neither AdvancedChartImpl.tsx nor the dashboard inspector is held by an in-flight card. This seat's objectui#11785 (area:studio) holds StudioDesignSurface.tsx's refusal strips, and seat 1's objectui#11783 (area:studio) holds ObjectFieldInspector.tsx / FlowReferenceField.tsx; both are disjoint. No open objectui PR touches the files above (read 2026-10-08T00:35Z; open: #11841, #11600, #11069).

    Why Clause-②: no: layout fixes inside two packages. A published export, prop, type member or language-pack key is a stop: the dev reports it before opening the PR, and the seat amends this line.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11796,
    "status": "done",
    "branch": "claude/issue-11796-rotated-labels-layout",
    "pr": "#11851",
    "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 — subagent mode, the parent seat's id (container CLAUDE_CODE_REMOTE_SESSION_ID cse_01CGZy1BGCjdN5cXqL9cnvB8 resolves to it; claim 6049709370 names it)",
    "premise_still_valid": true,
    "summary": "Two fixes, each where its owner lives. (1) plugin-charts AdvancedChartImpl.tsx: a rotated x-axis label is anchored at its end and hangs its start away from the plot, and the axis reserved a fixed height of 60px that counted only the text width (no tickSize offset, no rotated line box). The band is now derived from the longest drawn label (rotatedXAxisHeight: tickSize + tickMargin + 1em cos35 + width sin35, width an upper estimate with CJK/kana/Hangul/full-width glyphs at 1em and others at 0.65em), and the 12-character ellipsis applies to every rotated label so the band stays bounded; non-rotating axes keep recharts' 30px. (2) app-shell DashboardDefaultInspector.tsx (the named file; SchemaForm.tsx untouched): the spec dashboardForm declares its Layout section with columns 3 and header colSpan 3, which SchemaForm honours in the narrow panel; the inspector now stacks every section to one column and clamps colSpan to 1. Zone 2 readings: the chart reserved an XAxis height (not margin.bottom); the clip is the chart SVG's own viewport, not an ancestor overflow (the card grows with content). Measured in Chromium through a temporary uncommitted harness (real DashboardRenderer, runtime and the designer's DashboardPreview wrappers, fake queryDataset with the showcase categories): before, labels reach 61px (In Progress) and 71px (12-char) under a 60px band and, with the legend off, 3 labels lose their starts at the SVG edge; after, bands are 76 and 80 with 0 labels crossing. The inspector reproduced the card exactly at a 240px panel ("12" as "1", three 47px columns) and stacks to one 172px column after. The card's exact "acklog"/"ogress" with the legend ON did not reproduce in this sandbox (tick font is a system stack, Inter here); the mechanism did.",
    "tests": "All through os-verify-lock, VERDICT command-exit 0. Final head bf2a3ba (merge of main 59ce048, which touches none of these files): pnpm exec vitest run packages/plugin-charts/ plus the 42-file app-shell set = Test Files 132 passed (132), Tests 1523 passed / 9 skipped (1532). pnpm --filter @object-ui/plugin-charts type-check and pnpm --filter @object-ui/app-shell type-check after building both dependency closures: TC_CHARTS_OK, TC_SHELL_OK (script names echoed); tsc -p tsconfig.test.json --listFiles lists each new test once. New pins: AdvancedChartImpl.rotatedLabelBand-11796.test.tsx (6) and DashboardDefaultInspector.layoutColumns-11796.test.tsx (3). Ablation on 9957feb via objectstack scripts/ablation-replace.mjs (each anchor hit 1 and landed, blob changed; each restore blob == HEAD and git diff HEAD empty; TREE_EQUALS_HEAD=yes): band back to height 60 = 4 of 6 chart pins red (above-bound and control green, untouched by that leg); stacking removed = 2 inspector pins red, SchemaForm control green; wide glyph charged narrow = only the CJK pin red; ellipsis scoped back to the five-bucket branch = only the above-bound pin red; restored = 9/9 green. Expected direction (turns red) observed. No dist involved: the tests import the source relatively. Lint at bf2a3ba: eslint --format json on the 4 touched files = 4 files, 0 errors; AdvancedChartImpl.tsx 44 warnings = base 44; population = each package's own lint script (eslint .); invariance = eslint.config.js has no projectService / parserOptions.project (type-aware grep exit 1), so the diff cannot move an untouched file's verdict.",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "3 — each through the fleet-write relay (one POST /repos/objectstack-ai/objectstack/dispatches per stroke): pr_create = POST /repos/objectstack-ai/objectui/pulls (objectui#11851, draft, read-back 9631/9631 bytes identical); assign = POST /repos//issues/11851/assignees os-support-ai (label-write.mjs read-back MATCHES); comment = POST /repos//issues/11796/comments (this os-dev-report). Plus git push (not REST). No label writes beyond the assignee; path labels came from labeler.yml.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed — same family as the inspector half: actionForm (Basics, Advanced), pageForm (Basics) and viewForm (Table options) declare columns 2 and reach narrow inspectors through the same SchemaForm grid; not measured in a panel. Closing the class means a container-aware SchemaForm grid, off this card's surface (seat decision). Dedupe words: inspector two columns narrow · SchemaForm section columns container · spec form columns side panel",
    "carrier: 承接者:无 · noted, not filed — xAxis.title is laid out insideBottom of the same band; it moves with the band and was not measured with rotated labels.",
    "carrier: 承接者:无 · noted, not filed — the ellipsis counts UTF-16 code units (label.length / slice), so a cut through an astral character can split a surrogate pair; predates this change, now reached on more axes; not measured.",
    "carrier: 承接者:无 · noted, not filed — hasLongLabels rotates only when a label is longer than 5 code units, so a 3 to 5 glyph CJK label stays horizontal; predates this change; not measured."
    ],
    "gates": [
    {
    "command": "pnpm exec vitest run packages/plugin-charts/ + 42-file app-shell set (os-verify-lock, bf2a3ba)",
    "exit": 0,
    "verdict": "Test Files 132 passed (132) · Tests 1523 passed | 9 skipped (1532) · VERDICT command-exit 0"
    },
    {
    "command": "pnpm --filter @object-ui/plugin-charts type-check (bf2a3ba)",
    "exit": 0,
    "verdict": "TC_CHARTS_OK · VERDICT command-exit 0"
    },
    {
    "command": "pnpm --filter @object-ui/app-shell type-check (bf2a3ba)",
    "exit": 0,
    "verdict": "TC_SHELL_OK · VERDICT command-exit 0"
    },
    {
    "command": "pnpm exec eslint --format json (4 touched files, bf2a3ba)",
    "exit": 0,
    "verdict": "4 files, 0 errors, 46 warnings (44 = base on AdvancedChartImpl.tsx, 2 on the new chart test)"
    },
    {
    "command": "pnpm check:control-bytes",
    "exit": 0,
    "verdict": "check-control-bytes: OK (scanned 7834 tracked text file(s); skipped 85 binary)"
    },
    {
    "command": "pnpm check:test-path-roots",
    "exit": 0,
    "verdict": "check-test-path-roots: OK"
    },
    {
    "command": "pnpm check:changeset-claims",
    "exit": 0,
    "verdict": "Every package declared across those 1 body(ies) is either not negated"
    },
    {
    "command": "pnpm check:pending-changeset-literals",
    "exit": 0,
    "verdict": "No test source names a pending changeset."
    },
    {
    "command": "pnpm check:new-line-citations",
    "exit": 0,
    "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only, exit 0 (the gate prints an arrow there)"
    },
    {
    "command": "pnpm check:vi-mock-specifiers",
    "exit": 0,
    "verdict": "check-vi-mock-specifiers: OK"
    },
    {
    "command": "pnpm check:vi-mock-inherit",
    "exit": 0,
    "verdict": "check-vi-mock-inherit: OK"
    },
    {
    "command": "pnpm check:vi-mock-override-shape",
    "exit": 0,
    "verdict": "check-vi-mock-override-shape: OK"
    },
    {
    "command": "pnpm check:unreferenced-sources",
    "exit": 0,
    "verdict": "Every shipped source file in every covered package is reachable."
    },
    {
    "command": "pnpm check:shell-escape-residue",
    "exit": 0,
    "verdict": "check-shell-escape-residue: OK"
    },
    {
    "command": "pnpm check:spec-symbols",
    "exit": 0,
    "verdict": "spec symbol derivation: 0 untriaged collisions in 0 packages"
    },
    {
    "command": "node scripts/check-changeset-presence.mjs",
    "exit": 0,
    "verdict": "4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"
    },
    {
    "command": "node scripts/check-governed-queue-guard.mjs --test (5 paths)",
    "exit": 0,
    "verdict": "NOT GOVERNED — 5 path(s) checked against 5 governed surface(s); none matched"
    },
    {
    "command": "CI on objectui#11851 head bf2a3ba",
    "exit": null,
    "verdict": "in_progress at report time: 23 completed, 18 in progress, 1 queued, 0 failed (one read, not awaited)"
    },
    {
    "command": "check:i18n-keys / i18n-drift / i18n-dead-keys / i18n-designer-parity / readme-exports",
    "exit": null,
    "verdict": "not applicable: no locale pack, export or README surface changed"
    }
    ],
    "deviations": [
    "Declared narrowing: the full app-shell suite (1082 test files; ledger shows 25-47 min lock holds, past the foreground cap) is left to CI. Local set = every app-shell test naming DashboardDefaultInspector / dashboard-schema / getDashboardForm / register-builtins / default-inspector-registry, plus ResourceEditPage / StudioDesignSurface / inspector tests mounting a dashboard: 42 files, 304 tests (9957feb), green again inside the 132 on bf2a3ba. Positive control DashboardDefaultInspector.test.tsx is in the set.",
    "The real-browser reading used a temporary, uncommitted Vite harness in the worktree (real DashboardRenderer and DashboardDefaultInspector, fake queryDataset) rather than the booted showcase stack: objectstack is neither installed nor built in this container. Both modes measured (runtime, and the designer shape with designMode). The card's exact legend-on cut did not reproduce here; the mechanism did. Harness, driver scripts and the Vite server (PID 18995, own port 5196) are removed.",
    "Bounded in-place widening, named in the PR body: the 12-character ellipsis now also applies to rotated labels above the five-bucket bound (same defect class, same file, mechanical, the existing pinned shape), because a band sized from an unbounded label would be unbounded.",
    "The first commit 54b5b75 carried the wide-glyph range ends as raw U+3000 / U+FF00 / U+FFEF bytes (the editor materialised the escapes); eslint refused the irregular whitespace and 9957feb rewrote them as escape text. The branch history keeps it; the squash merge drops it.",
    "The merge commit message was amended before its first push to add the model-free trailer pair; nothing pushed was rewritten.",
    "PR footer uses the os-dev session-URL form; the harness reminder's two-line attribution (with an emoji) was not used. Session URL is also in the body prose as a code span."
    ],
    "files_changed": [
    "packages/plugin-charts/src/AdvancedChartImpl.tsx",
    "packages/plugin-charts/src/AdvancedChartImpl.rotatedLabelBand-11796.test.tsx",
    "packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.tsx",
    "packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.layoutColumns-11796.test.tsx",
    ".changeset/11796-rotated-labels-layout.md"
    ],
    "line_budget": "not applicable (no skills/** surface)"
    }

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T01:59Z. PR objectui#11851, head bf2a3ba.

    • PR shape: draft against main, with 59ce048 merged in (no rebase). First line Fixes #11796, no other closing keyword. Clause-②: no at line start. The three commits carry only the model-free trailer pair. Assigned to os-support-ai.

    • Scope: 5 files, +381/−28, on the claim's surface (6049709370):

      • packages/plugin-charts/src/AdvancedChartImpl.tsx (the rotated-label band, ROTATED_X_LABEL_MAX_CHARS);
      • packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.tsx, the inspector file the claim asked the dev to measure and name. It is named in the PR body with the reading: @objectstack/spec's dashboardForm declares Layout columns: 3, and this inspector is getDashboardForm's only consumer;
      • two new pin files and the changeset (patch on both packages).

      SchemaForm.tsx is untouched, so every other form keeps its declared columns. Nothing is added to either package entry. No governed path.

    • Diff read (the seat's own):

      • The fixed height: 60 on a rotated x axis becomes rotatedXAxisHeight(labels): tickSize (6) + tickMargin (10) + 1em·cos 35° + widest·sin 35°. The width is an upper estimate (0.65em per glyph, 1em for a wide CJK, kana, Hangul or full-width glyph, at the 12px tick font). With the 12-character cap, a Latin band tops out near 80px. A chart whose labels do not rotate passes no height, so it keeps recharts' default.
      • The 12-character ellipsis now applies to every rotated label, not only at five buckets or fewer. That is the bounded widening the PR body names: it is the constant the claim put on the surface, and an unbounded label would need an unbounded band. The full name stays on the tooltip.
      • stackSectionsForNarrowPanel sets every section to columns: 1 and clamps a field's colSpan to 1, after the server-field graft. A { group } section is left as it came. In this panel that also stacks Basics (columns: 2: name and label), the same defect at a smaller size. The changeset says "every section", which matches.
    • Ruling honoured (triage 6043042473): "reserve bottom margin for rotated category labels, or truncate them with a tooltip": both, the band from the labels plus the cap. "one column for the Layout fields in the narrow inspector": done, scoped to this inspector.

    • Changeset sentences checked against the head:

      • "The axis now reserves the room its longest drawn label needs at the rotation angle … with a wide glyph (Chinese, Japanese, Korean) counted a full em";
      • "Every rotated label is now shortened with an ellipsis past 12 characters, also on axes with more than five categories";
      • "Charts whose labels do not rotate keep the axis height they had";
      • "The inspector now renders every section of the form in one column, in the declared field order. Other forms keep the column layout their sections declare".
    • Reverse verification (dev report):

      • Ablation through ablation-replace.mjs on 9957feb, four legs, each restored with the blob equal to HEAD: the band put back to 60 gives 4 of 6 chart pins red; removing the stacking gives 2 inspector pins red with the SchemaForm control green; charging a wide glyph as narrow gives only the CJK pin red; scoping the ellipsis back gives only the above-bound pin red.
      • Real-browser reading in Chromium (a temporary harness with the real DashboardRenderer, uncommitted and removed): with the legend off, 3 labels lost their starts under the 60px band before, and 0 after (bands 76 and 80). The inspector reproduced "12" as "1" at a 240px panel, and stacks to one 172px column after. The card's exact "acklog" cut with the legend on did not reproduce on this machine's fonts; the mechanism did. Recorded as such in the PR body.
      • plugin-charts plus the 42-file app-shell set at bf2a3ba: 132 files, 1523 tests passed. Both type-checks exit 0.
    • Gates: CI on bf2a3ba (head_sha equals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template, dependabot), 0 failure. Lint and Type Check are success. Mergeable state clean. main has since moved to 5aa7f55 (objectui#11785 and objectui#11789, neither touching these files), and git merge-tree of it and this head is clean; the queue tests that merge.

    • Contract review: not required (Clause-②: no).

    Findings:

    • Other narrow inspectors may feed spec sections with columns: 2 through the same SchemaForm grid (actionForm, pageForm, viewForm). Not measured in a panel, so it does not pass the filing gate's reach test → Acceptance notes
    • The ellipsis counts UTF-16 code units, so a cut can split a surrogate pair. This predates the change and is not measured → Acceptance notes
    • A 3–5 glyph CJK label never rotates (the rotation trigger counts code units), and the x-axis title sits in the same band. Neither is measured → Acceptance notes

    Landing: ready + auto-merge through the queue, now.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T02:19Z. PR objectui#11851 merged through the queue as 82500a7 at 2026-10-08T02:17Z.

    • The merge:
      • One parent, 5aa7f55, an ancestor of origin/main.
      • The landed tree equals git merge-tree of that parent and the accepted head bf2a3ba (tree cd8c345a on both).
      • 5 files, +381/−28.
    • Content check, 5aa7f55 → 82500a7:
      • The fixed band textAnchor: 'end' as const, height: 60 in AdvancedChartImpl.tsx: 1 → 0.
      • rotatedXAxisHeight( in AdvancedChartImpl.tsx: 0 → 2.
      • stackSectionsForNarrowPanel( in DashboardDefaultInspector.tsx: 0 → 2.
      • Control: metadata-admin/SchemaForm.tsx is byte-identical on both sides, so every other form keeps the columns its sections declare.
    • Closures: the PR's only closing keyword was Fixes #11796. It is the only issue closed since the previous landing at 01:53Z. pm:dispatched is removed.
    • Follow-ups: none filed. The four Acceptance notes (other inspectors' columns: 2 sections, the surrogate-pair cut, short CJK labels never rotating, the axis title in the band) have no measured reach.

    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

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething 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