Skip to content

fix: repin the engine for the grid track sizing hang, and 0.8.4 - #157

Merged
pathscale merged 11 commits into
masterfrom
fix/track-sizing-hang-repin
Aug 16, 2026
Merged

fix: repin the engine for the grid track sizing hang, and 0.8.4#157
pathscale merged 11 commits into
masterfrom
fix/track-sizing-hang-repin

Conversation

@pathscale

Copy link
Copy Markdown
Owner

A table in a transcript could wedge the app permanently. This repins the engine chain onto the fix and bumps to 0.8.4 so a package can ship.

What was happening

ps-taffy's distribute_space_up_to_limits kept a track counted as growable while property + increase < limit, but sized each iteration from limit - property - increase. f32 rounding lets the first be true while the second is exactly 0.0. The increase then came out zero, the increase > 0.0 guard rejected every track, nothing changed, and the next iteration recomputed identical values. A fixed point, so it never recovered.

Two things made it present as badly as it did:

  • Layout runs on the main thread, so it was a beachball rather than a slow frame.
  • It survived SIGTERM. The handler drains the tables from a signal thread and logs SIGTERM received; draining the tables and exiting, but the exit that follows goes through the event loop the layout loop is holding. The store was always flushed; the process just never left.

Reopening did not help, because boot restored the same transcript and wedged again on the same table.

The composer reads scrollHeight on every keystroke, and the comment at Composer.tsx:650 already records that this resolves the whole document. So with any table on screen, one keypress was enough.

Chain

repo rev PR
ps-taffy 4355471d pathscale/ps-taffy#4
ps-blitz b82e649d pathscale/ps-blitz#42
tauri-runtime-blitz 9edbec23 pathscale/tauri-runtime-blitz#19

All ps-blitz pins move together across apps/gui and crates/blitz-bench. Cargo treats one git source at two revisions as two distinct crates, so the shared traits would exist twice. scripts/check-one-rev-per-git-source.sh passes.

Not included

apps/blitz-preview keeps the old pin. It is a separate workspace and its lock cannot be regenerated: it patches blitz-dom to ~/code/blitz-rust, which does not exist on this machine, so cargo update there fails before reaching the pin. That rot predates this change and wants its own fix.

Verification

cargo check --workspace passes in ps-blitz and tauri-runtime-blitz against the new revs. ps-taffy's full suite is green, including 5541 generated layout fixtures, plus a regression test built from the exact column sizes that hung this app. The experimental bundle is building against this branch now, and I will confirm the app opens the offending transcript before this merges.

meh added 11 commits August 16, 2026 13:17
The suite failed 65 tests across 11 files on a clean master. The cause was
not the app code: package.json asked for ^2.2.2 and bun.lock resolved 2.2.2,
but node_modules held 1.3.1, a major version behind.

v1 exports Modal, Toggle and EmptyState where v2 exports Dialog, Switch and
Empty, so three of the twelve components this app imports did not exist at
runtime. CloseConfirm and WelcomeFlow crashed reading a property of an
undefined Dialog, and the other 47 failures were the 5s timeout that follows
when a component never renders and findByRole never resolves.

Move to 2.5.0, published and the version the owner asked for. No source
change was needed, which is the evidence the migration itself was sound:
563 tests pass, 56 of 56 files, and tsc is clean. Suite time drops from
117s to 18s now that nothing waits on a component that cannot mount.
`stable` could not build. The rust gate ran clippy and the tests under
`--all-features`, and this workspace is one where that flag cannot be used:
`blitz-runtime` and `webview-runtime` are mutually exclusive, and `main.rs`
turns enabling both into a compile error naming them. The gate therefore
failed before linting anything, so no delivery bundle could be produced.

Name the runtime that ships instead, with the inspector `stable` already
builds the binary against, which keeps the gate checking the code that
actually goes out.
The git workflow section still said the file is tracked, carries the macOS
link flag and the usvg patch, and that its working copy should equal its
commit. None of that has been true since e8c30f4: the patch had already moved
to the root manifest, that commit moved the linker flag into apps/gui/build.rs,
and the file was deleted. `.cargo/` now holds only the gitignored
local-renderer.toml.

An agent reading the old text goes looking for a tracked file that is not
there, and the natural repair is to recreate it, which is precisely the
arrangement the rest of the section exists to prevent.
…ly it

`flex items-center gap-2` was written out at 17 call sites, the most repeated
layout signature in the app. The library's own recipe maps `gap="sm"` to
`gap-2` and `align="center"` to `items-center`, and its base class is nothing
but `display: flex`, so `<Flex align="center" gap="sm">` emits the same three
classes on the same `div`. The rendering is unchanged by construction.

Only this exact signature. The neighbouring ones are deliberately left alone:
`flex items-center gap-2 pt-0.5` and `flex items-baseline gap-2` carry padding
and alignment the recipe has no parameter for, and inventing one to absorb
them would move a decision out of the call site that reads it.
… colour

Two 2.x-era breakages found by driving the shipping Blitz build.

Paste: the renderer dispatches no `paste` event to JS at all, and answers the
chord itself only for a focused native text input. Anything else reached no
handler in either language, so pasting did nothing. Cmd/Ctrl+V is now served
next to Copy, which was already here for the same reason. It writes through
the field and dispatches `input`, because a controlled Solid field takes its
value from a signal that only that event updates: without it the character
count, the autosize and the submitted text all keep the pre-paste value.
`execCommand("paste")` is deliberately not a fallback; scripted reads are
refused everywhere, so a refusal is the real answer and the field is left be.

The colour wheel: 2.x gives `.radio__control` its own
`background-color: var(--color-base-100)`, and the petal's colour is the
indicator *inside* that control. The fill is painted over it, so all 31 petals
render as identical empty circles. The override list already restated margin,
size, border width and border colour for this control; it never had to restate
the background, because the v1 control had no fill. Measured on the real
renderer: the petals were always the right size in the right circle, which is
why this reads as a colour bug rather than a layout one.
Dragging the glass sliders blanked the window. The cause is the one the
GlassAxis comment already describes: every persisted step is a `set_settings`
round trip costing 5-6ms on the window thread, and enough of them in a row
starve paint. Traced on the owner's live session while they reproduced it,
frames kept advancing (50243 -> 50317) while missed refreshes went 46 -> 73
and fps halved, and the profile log held 7,838 of those writes.

A 180ms debounce was standing in for the event that means "the knob was
released", because the pinned 1.3.1 had none. A debounce is not that event: it
fires whenever the pointer pauses, so a slow drag is still a stream of writes.
2.x carries `onChangeEnd`, and both call sites said in comments that they were
waiting for it, so `onChange` now paints and `onChangeEnd` persists.

The three cost-warning tests drove half an interaction: `keyDown` with no
`keyUp`, `pointerDown` with no `pointerUp`. That passed against a timer, which
needs no release, and could not pass against the real event. They now finish
the gesture, which is also what a user does. The library raises `onChangeEnd`
from `keyup` and `blur`, so holding an arrow key repeats the preview without
repeating the write.
…ll are

Two places said the shipping renderer cannot carry a blur. It can: vello gained
a real backdrop pass, `record_backdrop` with a blur sigma and an expansion
rect, and ce156b0 recorded that landing without revising the section that
contradicts it or the CSS comment repeating the same claim.

What actually keeps glass off is a pair of deliberate switches: the window
config carries no `transparent: true`, so `WINDOW_GLASS_ENABLED` stays false
and the effect view is never attached. They move together or not at all,
because an `NSGlassEffectView` over an opaque window flattens the whole app
under one colour, which is the thing that flag was added to prevent.

Stated as a table of three gates, with the two that are shut named by file and
line, so the next person reads a switch to open rather than a renderer to
rewrite.
Glass is meant to be core, so it should be the library's primitive rather than
a set of axes this app invented. It already is: 2.5.0 ships `styles/glass.ts`,
which derives twenty-five `--glass-*` tokens from `blur`, `refraction` and
`depth`, and its own doc comment credits the consulting.parcle.ai tuning
surface the owner pointed at. The port upstream was already done; what was
missing was a consumer.

So `applyTheme` now calls `applyGlassTokens` and Settings gains those three
sliders, with `GLASS_LIMITS` and `GLASS_DEFAULTS` supplying their ranges and
defaults so nothing is restated here and a retune upstream arrives for free.
The preview re-derives the whole set from the other two axes on every change:
three of the tokens are read by component CSS without a fallback, and an
undefined custom property drops the declaration rather than falling back, so a
partial set is a panel with no background at all.

`.az-glass` is what makes any of it visible. Nothing in this app rendered
`material="glass"`, so tokens alone would have been three controls that move
nothing, which is exactly what the comment they replace warned about. It
carries the same fallbacks, the same no-nested-blur rule and the same
reduced-transparency and unsupported-backdrop paths the library uses, and it
replaces the opaque fill rather than layering over it, because a backdrop
filter behind an opaque background blurs nothing.

The old axes stay. They style AgencyZero's own hue-ladder panel, which is a
different question from how a glass surface refracts, and both now reach the
chrome through one Panel.

Two comments claiming CSS could not drive blur on either shipping renderer are
gone: vello's backdrop pass landed, and `blitz-paint` converts and forwards the
filter, so the chain is whole from stylesheet to scene.
…did not

The document planned both halves and its status section only ever described the
window one. In-app glass is now live through the library's tokens and needed no
window transparency at all, which is worth stating next to the two switches
that are still shut, so the next reader does not open them expecting to be
turning glass on for the first time.
`.az-glass` asked for `--glass-app-surface-opacity` and
`--glass-app-border-opacity`. Those belong to the older parcle vocabulary; the
library's own producer emits twenty-four tokens and neither is among them, so
both reads fell to their fallbacks and the refraction axis moved nothing on a
panel's fill or edge.

The names that carry the same two values are `--glass-background-opacity` and
`--glass-border-opacity`, which is what `Card.css` reads for its own glass
material. Checked the whole block against the producer's output rather than the
two that were wrong: all eight tokens now resolve.
A table in a transcript could wedge the app permanently. ps-taffy's
`distribute_space_up_to_limits` kept a track counted as growable while
`property + increase < limit`, but sized the iteration from
`limit - property - increase`, and f32 rounding lets the first be true while
the second is exactly zero. The increase came out as zero, the guard below it
rejected every track, and nothing changed, so the next iteration recomputed the
same values forever.

Layout runs on the main thread, so this presented as a beachball rather than a
slow frame, and it survived SIGTERM: the handler drains the tables from a
signal thread, but the exit that follows goes through the event loop the loop
was holding. Reopening the app restored the same transcript and wedged again.

The composer reads `scrollHeight` on every keystroke, which resolves the whole
document, so any table on screen made a single keypress enough to trigger it.

Chain: ps-taffy 4355471d, ps-blitz b82e649d, tauri-runtime-blitz 9edbec23.
All the ps-blitz pins move together, since cargo treats one git source at two
revisions as two crates and the shared traits then exist twice.

apps/blitz-preview keeps the old pin. It is a separate workspace whose lock
cannot be regenerated: it patches blitz-dom to ~/code/blitz-rust, which does
not exist, so its pins were already unbuildable before this change.
@pathscale
pathscale merged commit 7a0587e into master Aug 16, 2026
2 checks passed
@pathscale
pathscale deleted the fix/track-sizing-hang-repin branch August 16, 2026 09:42
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