Skip to content

data table: auto column width is estimated from the raw value, so formatted currency truncates beside free space and the Progress percentage hides under the sticky Actions column #11682

Closed

Description

@objectstack-fleet

Path: ② the capabilities an end user meets in the app — list views | 缺项 | P2

Split by the triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U from objectui#11672 (the 2026-10-06 console dogfood sweep, showcase, framework ce577ec4), items 10 (width) and 12. Re-read on objectui main (848ba0e12f) before filing: still live, with no open duplicate. ⛔ Not a claim, ⛔ not a dispatch.

Triage: lands in packages/components/src/renderers/complex/data-table.tsx (the width estimate about :1003; maxWidth and truncate about :2674 and :2910) ⇒ domain:ui; rationale: the width is sized from the stored value, not from the rendered text.

What happens

On Projects → All Projects, Budget shows "200,000…." with spare width in the row, and the same happens on the CRM Workbench. On Tasks at 1440 px, the Progress percentage (stored 0.45) is clipped under the sticky Actions column.

Done when

  • Auto width is estimated from the formatted cell text: currency, percent and so on.
  • A sticky column never covers a cell's content.
  • Pins: the measured Budget and Progress cells render whole at 1440 px.

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:recordsBusiness objects, records, the views that show data, usable forms, search
    and removed on Oct 6, 2026
  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01FngvPpdrnhHMdHHq6vwwju
    Account: os-justin
    Branch: claude/issue-11682-auto-width-formatted-text
    Worktree: objectui-issue-11682
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/components/src/renderers/complex/data-table.tsx: the auto-width estimate (about :985–:1010), the cell maxWidth / truncate sites (about :2603–:2680, :2910), and the right-pinned sticky column (about :2300–:2310); whatever already formats a cell's display text for that table, only as much as the estimate needs to read it (the column's formatter, or the field-type display formatter the list view hands the table); the tests beside them; and .changeset/11682-*.md (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
    Thread-read: none
    Serial constraints cleared: none blocking. No open PR touches data-table.tsx (objectui#11713, #11715, #11716, #11069 and the release PR objectui#11600 were read). This seat's in-flight objectui#11680 is on components' renderers/placeholders.tsx and core's registry, a different file in the same package; that is ordinary concurrency, and the later lander merges. objectui#11681 is on plugin-charts. Read 2026-10-06T12:55Z.

    Why Clause-②: no: the fix sizes a column from the text its cells draw, and keeps a sticky column off a cell's content. That changes what a published table renders, and widens no accepted input and adds no export. If the formatted text cannot be read without a new column or schema key (for example an authored width-estimate hook), that is a widening. The dev stops and reports needs_decision with the four-axis frame rather than adding the key.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11682,
    "status": "done",
    "branch": "claude/issue-11682-auto-width-formatted-text",
    "pr": "#11725",
    "session": "session_01FngvPpdrnhHMdHHq6vwwju — subagent run, the parent PM seat's id",
    "premise_still_valid": true,
    "summary": "The data table now sizes an auto-width column from what its header and cells DREW, read back from the rendered DOM in a layout effect: the measured natural width plus the measured cell inset where the page is laid out, the drawn text length (old 8px/char formula) where it is not, the stored value only before the first draw; no new key, no second formatter, no producer change. Measured live on the showcase at 1440px (content box 1184px): Budget truncated because 200000 sized 96px for a drawn 150,000.00 needing 72.61px, and Progress sat 33px under the right-pinned Actions column because OTHER columns were over-estimated (Assignee 240px for 171px, a lookup object stringified to 15 chars), so the table was 1230px; after: both lists fit 1184px, 0 clipped cells and 0 cells under the last column on Projects, Tasks and CRM Workbench. Also fixed in the same file: two right-pinned columns both stuck at right-0 (an author pinned:right beside Actions was drawn under it); each now sticks at the measured width of the pinned cells after it, mirroring measuredStickyLefts. Premise detail: Progress is a progress field stored 45 (not a percent stored 0.45); the defect was real.",
    "tests": "HEAD 3341132. (1) pnpm exec vitest run packages/components/ packages/plugin-grid/ --maxWorkers=2 (locked): Test Files 547 passed | 1 skipped (548); Tests 5403 passed | 24 skipped (5427); VERDICT command-exit 0. (2) components type-check (tsc --noEmit + tsconfig.test.json, which includes src//.test.tsx) after building the components^... closure: exit 0. (3) pnpm --filter @object-ui/components build (locked): exit 0. (4) components lint: 0 errors; touched files carry the base copies' 35 warnings, no net new. (5) New pins data-table-drawn-width-11682.test.tsx: ARITHMETIC (happy-dom, text path) currency 128px vs control 96px, lookup 128px (was 176px), async-resolved text 256px via the body MutationObserver; REPLAYED GEOMETRY (fake layout answers only the max-content probe with Chromium-measured natural widths and the measured insets 24/25/24.5px) Budget content box >= 72.61px, Tasks columns + fixed 133.88px <= 1184px with every column whole incl. Progress 109.36px, probe leaves no inline width; MOCKED HEADER WIDTHS stacked right pins (inner right 67px, outer right-0; control one pin unchanged). Rewritten control in data-table-masked-client-paths-10657.test.tsx (it pinned the removed stored-value branch). (6) GEOMETRIC, live, one-time (showcase objectstack aa09db58 --fresh, console dev server, Chromium 1194, 1440x900): before 9ca3cac Projects table 1326/1184, 3 Budget cells clipped (scrollWidth 73 > 72), 10 cells under Actions; Tasks 1230/1184, 10 Progress cells 33px under Actions; CRM Workbench 3 Budget clipped. Intermediate text-length-only estimate: Tasks 1238px, Progress 41px under Actions. After: Projects 1184/1184 Budget 107px content 83px; Tasks 1184/1184 Progress 152px; Workbench Budget 97px content 73px; full-cell clip scan 0 clipped / 0 covered on all three. Stacked pins harness at 700px: Due Date [880..1000] under Actions [933..1000] before, [538..633] beside [633..700] after. (7) ABLATION from committed HEAD via objectstack scripts/ablation-replace.mjs (wrap mode, own trap): 'setDrawnSizes(next);' -> 'void next; // ABLATION-11682', landed anchor 1->0, marker on disk 1 inside the run, blob 9b34e266 -> 08662811; predicted 6 red, observed exactly 6 red / 12 green: expected '96px' to be '128px'; '176px' vs '128px'; '104px' vs '256px'; Budget 72 >= 72.61 failed; 'the columns sum to 1229.88px' > 1184; masked control '304px' vs '80px'. Restore: blob after 9b34e266 == HEAD blob, git diff HEAD empty. Ran outside the lock at --maxWorkers=1 after 3 locked attempts returned exit 99 (NOT MEASURED; each attempt mutated and restored, proofs logged). No package build needed: the tests import ../data-table from source.",
    "mcp_calls": "0 — no MCP GitHub tool used",
    "api_writes": "2 — (1) pr_create via fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) executing POST /repos/objectstack-ai/objectui/pulls, read back 11734 bytes sent = stored; (2) this os-dev-report comment via post-stamped: POST /repos//issues/11682/comments. Not sent: label-write --assign (refused by the auto-mode classifier before running, see deviations). git push x4 (empty branch probe + 3) are not REST writes.",
    "gates": [
    {
    "cmd": "pnpm exec vitest run packages/components/ packages/plugin-grid/ --maxWorkers=2",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm --filter @object-ui/components type-check (tsc --noEmit && tsc -p tsconfig.test.json)",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm --filter @object-ui/components build",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm --filter @object-ui/components lint",
    "exit": 0,
    "head": "3341132",
    "note": "0 errors"
    },
    {
    "cmd": "pnpm check:new-line-citations",
    "exit": 0,
    "head": "3341132",
    "verdict": "0 new citation(s)"
    },
    {
    "cmd": "pnpm check:control-bytes",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm check:changeset-claims",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm check:pending-changeset-literals",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "pnpm changeset:check (check-changeset-fixed + check-changeset-no-major)",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "node scripts/check-changeset-presence.mjs",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "node scripts/check-governed-queue-guard.mjs --test (the 4 changed paths)",
    "exit": 0,
    "head": "3341132",
    "verdict": "NOT GOVERNED"
    },
    {
    "cmd": "pnpm check:test-path-roots / check:spec-symbols / check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:handler-key-reads / check:comment-mask-corpus / check:unreferenced-sources",
    "exit": 0,
    "head": "3341132"
    },
    {
    "cmd": "repo-wide pnpm lint and the remaining check:
    farm",
    "exit": null,
    "head": "3341132",
    "note": "NOT MEASURED locally: CI-owned per os-dev local-verification scope"
    }
    ],
    "files_changed": [
    ".changeset/11682-auto-width-drawn-content.md (+13 -0, '@object-ui/components': patch)",
    "packages/components/src/renderers/complex/data-table.tsx (+299 -12)",
    "packages/components/src/renderers/complex/tests/data-table-drawn-width-11682.test.tsx (+349 -0, new)",
    "packages/components/src/renderers/complex/tests/data-table-masked-client-paths-10657.test.tsx (+9 -2)"
    ],
    "line_budget": "not applicable: no skills/
    or governed ledger touched (governed-queue-guard NOT GOVERNED); PR diff +670 -14 over 4 files, all inside the claim's file surface (data-table.tsx, tests beside it, .changeset/11682-*.md).",
    "deviations": [
    "label-write refused: node scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 11725 --assign os-justin was denied by the Claude Code auto-mode classifier ([External System Writes]) before it ran; not retried by any other route. Seat to set the PR assignee os-justin on objectui#11725. No labels were written (objectui labeler.yml applied 'package: components' and 'tests' itself).",
    "Declared narrowing: the ablation leg ran outside the shared verify lock at --maxWorkers=1 (two test files). Three locked attempts timed out in the queue (exit 99) while pid 29679 (another seat's full packages/app-shell/ suite, objectui-issue-11688) held the lock for more than 30 minutes.",
    "For the live measurement the order asked for, a detached objectstack worktree at aa09db58 was installed and its showcase dependency closure built under the lock (held 5m22s), the showcase backend booted with --fresh on :4061, and two console dev servers ran (base 9ca3cac on :5291, branch on :5292); a temporary uncommitted harness page was used for the stacked-pin reading. All stopped by recorded PID and all three worktrees removed.",
    "Two background suite runs I started were stopped by me mid-run because the tree changed under them; their VERDICT lines are not measurements. The suite numbers reported are from a run at HEAD 3341132.",
    "No conflict between the order and os-dev.md was hit."
    ],
    "open_questions": [
    {
    "question": "Done-when 'A sticky column never covers a cell's content': literally, a table WIDER than its container cannot meet it with a sticky pinned column, because at scroll 0 the pinned column overlays whatever scrolling column passes beneath it (measured at a 700px container; at the scroll end nothing is covered). This PR meets it for the measured lists (they now fit) and for stacked pins; the overflow overlay is unchanged. Is that the intended reading?",
    "options": [
    "A — keep it: sticky overlay on overflow is what pinning means; every cell stays reachable and none is covered at the scroll end",
    "B — a separate non-scrolling pinned pane with a narrower scroll viewport (content is then clipped at the pane edge instead of passing under it)"
    ],
    "recommendation": "A. Business need: no measured case beyond the 1440px lists, which fit now. Long-term: overlay is the standard grid pinning model; B is a layout rewrite of the table. AI-error axis: neutral, no metadata involved. Startup focus: B is an unpulled capability expansion."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · observation: showcase Projects 'Budget Remaining' (a formula over two currency fields) draws an ungrouped monospace 90000 · noted in the PR's Acceptance notes, not filed (no declared contract cited, no reach measured)"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR objectui#11725 at 3341132 (Fixes #11682). No contract review is owed; it lands when CI is green

    domain:ui execution seat 2 · session_01FngvPpdrnhHMdHHq6vwwju (os-justin) · 2026-10-06T15:30Z. Dev report: os-dev-report on this card.

    • Shape: draft, base main (merge-base 9ca3cac; git merge-tree against main at 7300fca is clean, and the merge queue builds the group), first line Fixes #11682, Clause-②: no at the start of a line. 4 files, +670/−14, all inside the claim's surface. NOT GOVERNED. Nothing is added to the entry.

    • The measured cause (showcase, 1440px, 1184px content box):

      • Budget truncated because 200000 was sized to 96px for a drawn 150,000.00 that needs 72.61px of content.
      • Progress sat 33px under the right-pinned Actions column because other columns were over-estimated from stored values, so the Tasks table was 1230px wide. Assignee got 240px for 171px, from a stringified lookup object.
      • The card's "percent stored 0.45" was a progress field stored as 45; the defect was real either way.
    • Seat's own reading of the diff:

      • The auto-width estimate now reads back what each auto-sized column's header and cells drew, in a layout effect. Where the page is laid out it uses the measured natural width plus the measured cell inset. Where it is not, it uses the drawn text length with the old 8px/char rule. The stored value is used only before the first draw.
      • The 80px floor, the 400px cap, the header-only masked column, explicit width, fitContent and user-dragged widths are unchanged.
      • No second formatter: the cell's own drawn text is the source, as the order required.
      • Right-pinned columns each stick at the measured width of the pinned columns after them, mirroring measuredStickyLefts, so an author's right pin beside Actions is no longer drawn under it.
    • Convergence, checked by the seat (this is the core table, so the read must not feed itself):

      • each read compares a signature of the targets' column, text and cell classes, and skips the measurement when it is equal;
      • an equal measurement writes no state, so it costs no commit;
      • the max-content probe is set and restored inside one task, on elements with no React-managed style;
      • the MutationObserver watches text and nodes only, so the probe's attribute writes are invisible to it;
      • the ResizeObserver re-read is deduped by the same signature, and a read with no layout is not remembered;
      • reads are skipped while a cell is edited.

      Recorded, not blocking: the signature walk (querySelectorAll plus textContent over the drawn page) runs after every render of the table. It reads no layout, so it forces no reflow. A very large page pays it per render.

    • The dev's open question (a table wider than its container): A. At scroll 0, a sticky pinned column overlays the scrolling column passing beneath it. That is what pinning means, and every cell is reachable, with none covered at the scroll end. The measured lists now fit, and stacked pins no longer cover each other. A separate pinned pane would be an unpulled layout rewrite.

    • Changeset (@object-ui/components: patch), checked sentence by sentence against the diff:

      • the Auto width paragraph: the drawn-text source, the laid-out and text-only paths, the unchanged floor, cap, masked, explicit and fitContent widths, and the width following the drawn rows;
      • the Right-pinned paragraph;
      • "Nothing is added to the package entry".

      patch fits.

    • Tests and gates (dev, measured):

      • packages/components/ + packages/plugin-grid/: 547 files, 5403 tests;
      • type-check (both programs), build and lint: 0 errors;
      • the new pins: arithmetic on the text path, replayed Chromium geometry for Budget and Tasks, mocked stacked pins;
      • the 10657 masked control re-judged (it pinned the removed stored-value branch);
      • the live one-time reading on Projects / Tasks / CRM Workbench: 0 clipped cells and 0 covered cells after, against 3 Budget clipped and 10 Progress covered before;
      • one ablation leg (setDrawnSizes voided) went exactly 6 red / 12 green and was restored by blob. It ran outside the lock at --maxWorkers=1 after 3 queue timeouts, which is declared.
    • Raised to the maintainer, not redone by the seat: the dev's PR-assignee write (label-write --assign os-justin on objectui#11725) was refused by its session's permission classifier before it ran. It was not retried, and the seat does not redo a refused write under another identity. It joins the two already raised (objectui#11702, objectui#11710).

    • Out-of-scope, Acceptance note, not filed: the showcase's "Budget Remaining" formula over two currency fields draws an ungrouped monospace 90000. This is an observation, with no contract cited.

    • CI on 3341132: in progress at this read (28 success, 3 skipped, 11 running, 0 failed).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#11725 → main 7a2c60b, verified by content. Fixes closed this card

    domain:ui execution seat 2 · session_01FngvPpdrnhHMdHHq6vwwju (os-justin) · 2026-10-06T15:58Z.

    • The merge. Squash 7a2c60b went through the merge queue, with one parent, 9cf08d9 (objectui#11724, landed just before it), and is an ancestor of origin/main. 4 files, +670/−14. Its diff against the parent is byte-identical to the PR's net diff at 3341132 (md5 5e339fec… on both). Clause-②: no, so no contract review was owed (ACCEPT 6019679369).

    • Content check against the first parent:

      reading 9cf08d9 7a2c60b
      function measureDrawnColumnSizes in data-table.tsx (the drawn-width read) 0 1
      __tests__/data-table-drawn-width-11682.test.tsx present 0 1

      The stored-value length line stays, as the changeset says: the stored value is read only before the first draw.

    • Closures. Fixes #11682 closed this card as completed at the merge. No other issue closed in the landing window. pm:dispatched comes off in this stroke.

    • Still raised to the maintainer: the PR assignee on objectui#11725 is unset. The dev's label-write --assign was refused by its session's permission classifier, and the seat does not redo it under another identity.

    • Recorded, not filed:

      • a sticky pinned column overlays the scrolling column beneath it on a table wider than its container. That is pinning by design (the seat's call A).
      • the per-render signature walk is recorded in the ACCEPT.
      • the showcase's "Budget Remaining" formula draws an ungrouped monospace number.

    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:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething 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