Repository navigation
fix(components): size an auto-width column from what its cells draw, and stack right-pinned columns (objectui#11682) - #11725
Merged
objectstack-fleet[bot] merged 4 commits intoOct 6, 2026
Conversation
…and stack right-pinned columns The data table estimated an auto-sized column from the stored value's length. A currency cell storing 200000 draws "200,000.00" and truncated; a lookup storing an id draws a name and was too wide, and the columns' sum pushed the scrolling region under the right-pinned Actions column. The table now reads back what each auto-sized column's header and cells drew: the natural width of that content where the page is laid out, its text length where it is not, and the stored value only before the first draw. Two right-pinned columns no longer share right-0: each sticks at the measured width of the pinned cells after it. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…omponents patch Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…fect The latest-function ref was assigned during render, which the react-hooks refs rule flags. The assignment now happens in the same deps-less layout effect that calls it, before the observers' passive effect can. The new test types the registry lookup instead of casting. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…stead of reading its padding Under the table's collapsed borders a cell beside the frozen column's 2px rule, or beside the Actions column's 1px rule, carries half of that rule, and no computed style of its own reports it. Read off the computed padding, the CRM Workbench's Status column came out 1px short and its "Completed" badge truncated. The read now takes each cell's width less its content box's, in the same layout that reads the boxes, and the replayed-geometry pins carry the measured insets. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Oct 6, 2026
This was referenced Oct 6, 2026
objectstack-fleet
Bot
deleted the
claude/issue-11682-auto-width-formatted-text
branch
October 6, 2026 15:57
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11682
Clause-②: no
The data table sized an auto-width column from the length of its STORED value. It now sizes it from what the column's header and cells DREW, read back from the rendered table. Two right-pinned columns also no longer stick to the same edge. All of it lives in
packages/components/src/renderers/complex/data-table.tsx: no producer change, no new column, view or schema key, and no second copy of a formatter.What was measured before building (the order's mechanism assumptions)
Live reading, one time: the showcase backend (objectstack
mainataa09db58,objectstack dev --seed-admin --fresh), the console dev server of this branch's base9ca3cacpointed at it, Chromium 1194, viewport 1440x900, the seeded admin. At 1440px the list's content box is 1184px wide (the sidebar takes 256px).budgetisField.currencyonshowcase_project. A stored150000is drawn150,000.00(no tenant currency is set, so it is a plain grouped number). The estimate readString(200000), which is 6 characters, so the column was 96px with a 72px content box. The widest amount needs 72.61px, so 3 of the 5 Budget cells truncated (scrollWidth 73 against clientWidth 72). The same 3 cells truncated on the CRM Workbench page. Confirmed.cellis a React renderer from@object-ui/fields(CurrencyCellRenderercallsformatCurrencywith the tenant currency and display locale from its own hooks).@object-ui/componentscannot import that package, andcol.typealone carries no currency, scale or storage. The cheapest faithful source is the DOM those renderers drew, so that is what the estimate reads now. The table calls no formatter of its own for it.progressonshowcase_taskistype: 'progress'(notpercent), drawn by the percent cell renderer as a 64px bar beside the number. It is stored as whole points (45), not as0.45as the card says. Neither of the two causes the order named was the cause. The Progress estimate was not too small: its cell was not clipped (88 of 88px). Nor was a reserve missing for the pinned column. The other columns were too WIDE: Assignee got 240px for 171px of drawn text, Title 200px for 120px, Project 168px for 107px (a lookup arrives as an expanded object, andString()of it is 15 characters). The columns summed to 1230px in the 1184px box, so the scrolling region ran 46px under the right-pinned Actions column at scroll 0. That is where the right-aligned percentage sits: every Progress cell was 33px under Actions. The pinned column is not shrunk.What changed
data-auto-width-key. A layout effect reads them back after every commit, before paint. Where the page is laid out, the column gets its widest target's natural width (atmax-content) plus the space the cell keeps around its content. That space is measured (cell width less content box), not read from the computed padding. Under the table's collapsed borders, the column beside the frozen first column, and the one beside Actions, carry half of a neighbour's rule, which no computed style reports. With computed padding, the CRM Workbench's Status column came out 1px short and its "Completed" badge truncated. Where nothing is laid out (happy-dom, a table inside a hidden panel), the drawn text's length is the input to the old estimate. The stored value is read only before the first draw. The 80px floor and the 400px cap stay. A masked column is still sized from its header alone (objectui#10657). An explicitwidth, afitContentcolumn and a dragged width are unchanged.right-0. So a column an author pins right (pinned: 'right', whichObjectGridhonours) beside the auto-pinned Actions column stuck to the same edge and was drawn under it. Measured in a harness at a 700px container: Due Date sat at [880..1000] under Actions at [933..1000]. Each right-pinned header and body cell now sticks at the measured width of the pinned cells after it, the mirror ofmeasuredStickyLefts. After the change, Due Date sits at [538..633] beside Actions at [633..700]. A table with one right pin renders as before.Readings after the change (live, same setup, the tree at
3341132)The scan checks every body cell and its descendants for scrollWidth above clientWidth, and every cell's drawn content for reaching past the left edge of the row's last cell.
Pins: which are arithmetic and which replay geometry
data-table-drawn-width-11682.test.tsx. happy-dom lays nothing out.200,000.00gets 128px (CONTROL: the same column drawing its stored value gets 96px). A lookup drawingMobile Appfor a 16-character id gets 128px (it was 176px). A cell whose text arrives after the table rendered re-sizes the column (256px), through the body observer alone.max-content), with the natural widths measured in Chromium on the two showcase lists. It replays the measured insets (24px, 25px beside the frozen rule, 24.5px beside Actions). A table that does not probe falls back to the text path, and the pins go red. Budget: the content box holds 72.61px. Tasks: the columns plus the measured fixed columns (checkbox,#, Actions) fit 1184px, and every column holds its widest drawn cell and label, Progress's 109.36px included. The probe leaves no inline width behind.right-0. CONTROL: one right pin renders as before.data-table-masked-client-paths-10657.test.tsx: unflagged, the column is now sized from the mask its cells draw, not from the raw value. Flagged, its cells are not read at all, which still changes the width. The old control pinned exactly the stored-value branch this change removes.The live readings above are the geometric half. They are one time and are not committed as a test: the repo's Playwright suite serves a production console build with no backend, so it cannot reach these lists.
Ablation (one leg, from the committed fix)
node ../objectstack/scripts/ablation-replace.mjsreplacedsetDrawnSizes(next);withvoid next;, so the read-back never reaches the estimate and every column falls back to the stored value. The mutation was confirmed on disk (anchor 1 → 0, marker count 1 inside the run, blob9b34e266→08662811). Predicted: the 3 text pins, Budget, Tasks and the masked control red. Observed: exactly those 6 red, 12 green.expected '96px' to be '128px',expected '176px' to be '128px',expected '104px' to be '256px'the content box holds the widest drawn amount: expected 72 to be greater than or equal to 72.61the columns sum to 1229.88px: expected 1229.88 to be less than or equal to 1184the drawn mask, not the raw value, sizes the column: expected '304px' to be '80px'Restored by the tool's own trap. Restore proof: blob after restore
9b34e266equals the HEAD blob, andgit diff HEADis empty.--maxWorkers=1. Three locked attempts timed out in the queue (exit 99, NOT MEASURED). In each, the mutation landed and was restored. The lock was held for more than 30 minutes by another seat's fullpackages/app-shell/suite. The run was these two test files only.Verification (head
3341132)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).pnpm --filter @object-ui/components build(locked, dependency closure built first): exit 0.@object-ui/componentstype-check(both projects;tsconfig.test.jsonincludessrc/**/*.test.tsx): exit 0.@object-ui/componentslint: 0 errors. The three touched files carry the same 35 warnings as the base copies of the two files that already existed. The package as a whole has 972 warnings, none of them errors.check:new-line-citations(0 new),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,changeset:check(fixed group, nomajor),scripts/check-changeset-presence.mjs,scripts/check-governed-queue-guard.mjs --testover the four paths (NOT GOVERNED), and alsocheck: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.pnpm lintand the rest of thecheck:*farm are left to CI.Acceptance notes
#columns now take more of the spare width (Tasks: 28 → 45px and 39 → 45px), because the auto columns are tighter. That is the browser's share-out ofw-full, not a width this table sets.90000.Generated by Claude Code