fix: stop a width the layout cannot draw, and say when two rows share an id - #45
Merged
Merged
Conversation
A `NaN` or `Infinity` width reached the CSS custom property as `NaNpx`. A custom property accepts that, but `grid-template-columns: var(...)` then resolves to an invalid value, the declaration is dropped at computed-value time, and every column folds into a single track with the cells stacked down the page. Nothing threw and nothing was logged: the grid simply looked broken, and `widthOverrides` still read as a plausible record while the layout was already gone. Two ways in, both closed. The snapshot boundary now reads values as carefully as it already read keys, and `setWidth`/`setWidths` refuse a width that is not finite, where `clamp` and `Math.round` had been carrying `NaN` straight through. An app computing a width from an empty input field reached the second without going near a snapshot. `setState` also stopped throwing on a corrupt `columns` slice: an `order` that was not an array reached `.filter` inside the caller's own call. Only string ids order the columns now, only real booleans hide one or fold a group, and a slice that is not an object is read as nothing at all. The fields are typed as unknown while they are read, because naming the type there would be the same promise that let the broken snapshot in. The regression test asserts the computed `grid-template-columns` in the browser, not the override record. Backing the fix out turns it red with one track where there should be two. Closes #43
The row index keeps the last row for a repeated id, so an edit addressed to the row the user opened was written to a different one, and a selection stood for two rows at once. Nothing reported it. The id belongs to the app and the grid cannot mend it, but failing at it silently, in the data, is the worst of the available behaviours. A development build now names the ids that collided. Production pays one integer comparison for the whole data set, `index.size` against `nodes.length`; working out which ids repeated costs a second pass and only a build that will print it pays for that. This is the library's only `console` statement, and the lint rule that forbids them is disabled on exactly that line. An error would take an app down over data the grid can still draw, and there is no logger to route it to. Closes #44
Closing the width setters and the snapshot boundary closed the routes the issue named, not the failure itself. Three more reach the same line without passing either: a container measured as `NaN`, which makes every flex column `NaNpx` at once, and a column definition written with `width: NaN` or `flex: NaN`. They all converge on `buildColumnCssVars`, so the check belongs there. A value that is not finite falls back to the column's minimum, and an unusable minimum or flex weight falls back to a width that can be drawn. A track of the wrong size is a much smaller failure than a grid that will not lay out at all. The earlier guards stay. They keep unusable values out of the model rather than papering over them at the end, and they let `setWidth` return an honest answer about the width a column still has.
Both branches added under `## [Unreleased]`, which is the one place two open fix branches always collide. Resolved by keeping both blocks: changelog entries are additive, and taking either side would have dropped the other branch's lines. Ordered by when they landed, so the filter and sort entries that reached `dev` first lead.
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.
Summary
Two defects reached the same place from different directions, so they travel together.
A column width that the layout cannot draw destroyed the grid, silently. A
NaNorInfinitywidth reached the CSS custom property asNaNpx. A custom property accepts that, butgrid-template-columns: var(...)then resolves to an invalid value, the declaration is dropped at computed-value time, every column folds into a single track and the cells stack down the page. Nothing threw and nothing was logged, andwidthOverridesstill read as a plausible record while the layout was already gone.setStateseparately threw on a corruptcolumnsslice: anorderthat was not an array reached.filterinside the caller's own call.Two rows sharing an id failed silently in the data: the row index keeps the last row for a repeated id, so an edit addressed to the row the user opened was written to a different one.
Closes #43
Closes #44
Changes
Where a number becomes CSS.
buildColumnCssVarsandcolumnTrackSizerefuse a non-finite value and fall back to the column's minimum. This is the gate that closes the failure rather than a list of routes, and while writing it three more routes turned up that the issue had not named: a container measured asNaN, which makes every flex columnNaNpxat once, and a definition written withwidth: NaNorflex: NaN. None of them pass through a setter or a snapshot.Where an unusable value enters the model.
setWidthandsetWidthsrefuse a width that is not finite, whereclampandMath.roundhad been carryingNaNstraight through.setWidthreturns the width the column still has. These stay alongside the gate above: they keep junk out of the model rather than papering over it at the end.Where a snapshot is read.
resolveColumnSnapshotnow reads values as carefully as it already read keys. Only string ids order the columns, only finite numbers set a width, only real booleans hide a column or fold a group, and acolumnsslice that is not an object is read as nothing. The fields are typed as unknown while they are read, because naming the type there would be the same promise that let the broken snapshot in.Where rows are indexed.
nodesByIdcomparesindex.sizeagainstnodes.length, and a development build names the ids that collided. Production pays that one integer comparison for the whole data set; working out which ids repeated costs a second pass and only a build that will print it pays for that.Behaviour changes
setWidthreturns the current width instead ofNaNwhen handed one. It still returns0for a column that is not there.console.warnin development, the library's only console statement.no-consoleis disabled on exactly that line: an error would take an app down over data the grid can still draw, and there is no logger to route it to.Checklist
pnpm check: 1503 files, 0 errors, 0 warningspnpm lint: cleanpnpm test: 106 files, 1323 passed, 13 skippedpnpm bench: ran clean, andbudgets.test.tspasses inside the suite. The added work is one comparison per data set and one finite check per track, not per row per framegrid-template-columns, not the override record. Backing the fix out turns it red with one track where there should be two, which is how the collapse was confirmed in the first placeNote
This branch and #42 both add to
## [Unreleased]inCHANGELOG.md, so whichever merges second needs a short conflict resolved there.