Skip to content

chore(release): 1.3.1 - #46

Merged
ndlabdev merged 10 commits into
mainfrom
dev
Sep 3, 2026
Merged

ndlabdev merged 10 commits into
mainfrom
dev

Conversation

@ndlabdev

@ndlabdev ndlabdev commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Cut by npm run release. Carries everything on dev, including the version bump. The tag is pushed from main once this merges, and that is what publishes 1.3.1.

ndlabdev and others added 10 commits August 28, 2026 15:03
`hydrate` cast the state slice straight to `FilterModel`, which is a promise
to the compiler and not a check. A condition whose operator belonged to
another kind, a `set` whose `values` was not a list, or a condition missing
the value its operator needs all reached the predicate builders as a shape
they never tested, and threw while the pipeline's `$derived` was reading
them. That read happens inside the body's `{#each grid.nodes}`, so the throw
took the render pass rather than one column. Six of the seven shapes measured
against 1.3.0 brought the grid down, and every path that reaches `hydrate` is
untrusted: share links, `localStorage`, and anything handed back to
`setState`.

`sanitizeFilterModel` now rebuilds the model from the part that can be read
and drops the rest. A column left with no readable condition stops filtering,
which shows more rows rather than none. That is the deliberate call: for a
column behind a value gate it is not strictly failing safe, and a grid that
will not render is worse.

The predicates carry a second layer for a condition arriving some other way,
`applyFilterModel` included: an unknown operator, a missing value, a `set`
whose values are not a list and a kind nothing knows now pass every row
instead of throwing.

Closes #41
`hydrate` checked `Array.isArray` and then cast, which reads as a check and
is not one: a null entry in that array threw on `columnId` while the pipeline
was sorting. The same untrusted path as the filter model, one layer thinner.

`sanitizeSortState` keeps only the entries naming a column and a direction.
An unknown direction, a missing `columnId` and a plain string entry already
degraded quietly; they are now dropped rather than carried.
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.
fix: stop hydrate trusting the snapshot filter and sort state came back in
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.
fix: stop a width the layout cannot draw, and say when two rows share an id
The two tests that assert a typed value reaches the filter model once counted
writes with no reference to how long the typing took, and the count only means
something against that. `GridFilterCell` debounces at 200ms, while the browser
driver round-trips once per key, so on a loaded machine the debounce fires
between keystrokes and one write per key is the correct behaviour, not a
regression. Three release runs on this machine measured 2, 3 and 3 writes for
`365` while the same file passed alone in 5s.

The bound is now what a working debounce can produce: once per window it is
left alone for, plus once at the end. Where the typing fits in one window, on
CI and any unloaded machine, that is exactly the 1 the tests asserted before,
so nothing is given up where the assertion was meaningful. Where it does not,
the test no longer fails the machine instead of the code.

The assertion that the model ends holding the typed value is untouched. That
is the half which catches the bug these tests were written for, the stale
value winning, and it holds at any speed.
@ndlabdev ndlabdev self-assigned this Sep 3, 2026
@ndlabdev
ndlabdev merged commit cdad35d into main Sep 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant