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 Oct 26, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
dashboards: rotated x-axis labels are clipped, and the dashboard inspector Layout fields are squeezed to unreadable #11796
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 「覆盖规则,逐条立卡」.
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.
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 main9990f9e122.
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
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.
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)"
}
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.
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.
Activity
objectstack-fleet commentedon Oct 7, 2026
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 proposesTriage 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 (objectstack879bd38c, objectui179f6fe9); paths re-read on objectuimain9990f9e122.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11796-rotated-labels-layoutWorktree:
objectui-issue-11796Domain:
domain:uiSeat:
domain:ui#3File 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);packages/app-shell/src/views/metadata-admin/SchemaForm.tsx's grid widget, and names the file before editing it. A change toSchemaForm.tsx's shared grid behaviour for every other form is not on the surface;.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 StudioThread-read: 6043042473
Serial constraints cleared:
noneblocking. NeitherAdvancedChartImpl.tsxnor the dashboard inspector is held by an in-flight card. This seat's objectui#11785 (area:studio) holdsStudioDesignSurface.tsx's refusal strips, and seat 1's objectui#11783 (area:studio) holdsObjectFieldInspector.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
objectstack-fleet commentedon Oct 8, 2026
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)"
}
objectstack-fleet commentedon Oct 8, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T01:59Z. PR objectui#11851, headbf2a3ba.PR shape: draft against
main, with59ce048merged in (no rebase). First lineFixes #11796, no other closing keyword.Clause-②: noat line start. The three commits carry only the model-free trailer pair. Assigned toos-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'sdashboardFormdeclares Layoutcolumns: 3, and this inspector isgetDashboardForm's only consumer;patchon both packages).SchemaForm.tsxis 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):
height: 60on a rotated x axis becomesrotatedXAxisHeight(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 noheight, so it keeps recharts' default.stackSectionsForNarrowPanelsets every section tocolumns: 1and clamps a field'scolSpanto 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:
Reverse verification (dev report):
ablation-replace.mjson9957feb, 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 theSchemaFormcontrol green; charging a wide glyph as narrow gives only the CJK pin red; scoping the ellipsis back gives only the above-bound pin red.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-chartsplus the 42-file app-shell set atbf2a3ba: 132 files, 1523 tests passed. Both type-checks exit 0.Gates: CI on
bf2a3ba(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean.mainhas since moved to5aa7f55(objectui#11785 and objectui#11789, neither touching these files), andgit merge-treeof it and this head is clean; the queue tests that merge.Contract review: not required (
Clause-②: no).Findings:
columns: 2through the sameSchemaFormgrid (actionForm,pageForm,viewForm). Not measured in a panel, so it does not pass the filing gate's reach test →Acceptance notesAcceptance notesAcceptance notesLanding: ready + auto-merge through the queue, now.
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T02:19Z. PR objectui#11851 merged through the queue as82500a7at 2026-10-08T02:17Z.5aa7f55, an ancestor oforigin/main.git merge-treeof that parent and the accepted headbf2a3ba(treecd8c345aon both).5aa7f55→82500a7:textAnchor: 'end' as const, height: 60inAdvancedChartImpl.tsx: 1 → 0.rotatedXAxisHeight(inAdvancedChartImpl.tsx: 0 → 2.stackSectionsForNarrowPanel(inDashboardDefaultInspector.tsx: 0 → 2.metadata-admin/SchemaForm.tsxis byte-identical on both sides, so every other form keeps the columns its sections declare.Fixes #11796. It is the only issue closed since the previous landing at 01:53Z.pm:dispatchedis removed.columns: 2sections, the surrogate-pair cut, short CJK labels never rotating, the axis title in the band) have no measured reach.Generated by Claude Code
fix(plugin-charts,app-shell): rotated x-axis labels fit their band; t…
fix(plugin-charts): rotate and cut an x-axis label only when its band…