fix(kida): keep onMount subscribed when a signal remounts within the unmount delay - #229
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #229 +/- ##
==========================================
+ Coverage 82.73% 82.75% +0.01%
==========================================
Files 98 98
Lines 2468 2465 -3
Branches 522 521 -1
==========================================
- Hits 2042 2040 -2
Misses 315 315
+ Partials 111 110 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dangreen
force-pushed
the
fix/kida-on-mount-remount
branch
from
September 3, 2026 14:28
d6be732 to
9e6c589
Compare
…e unmount delay `onMount` scheduled the delayed teardown on unmount and never cancelled it, so a signal that remounted inside `STORE_UNMOUNT_DELAY` lost its subscription a second later while it was still mounted, and stayed deaf from then on: the mount level is delivered on a change, so no further mount fires until a full unmount and mount cycle happens. Everything built on `onMount` is affected, `paced` among them, and React StrictMode reaches it on every double-invoked subscribe. The pending timer now holds the state the `active` flag used to: a mount with a teardown scheduled cancels it instead of starting a second listener, a mount without one starts the listener, and the teardown clears the timer. Levels alternate per listener, so those two cases cover every delivery, and the result is 40 bytes smaller than the code before the fix.
dangreen
force-pushed
the
fix/kida-on-mount-remount
branch
from
September 3, 2026 14:38
9e6c589 to
055f950
Compare
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.
The bug
onMountarms asetTimeouton unmount and never cancels it. A signal that remounts insideSTORE_UNMOUNT_DELAYtherefore loses its subscription one second later, while it is mounted and being read, and it never gets it back:evaluatedelivers the mount level on a change, so no further mount fires until a full unmount and mount cycle happens.Everything built on
onMountinherits it.pacedkeeps its source subscription there, so a paced signal goes quiet after a quick route remount. React StrictMode reaches the same state on every double-invokedsubscribe.Why the existing tests missed it
should debounce unmount callbackfollows every remount with an immediate unmount, so when the first timer finally fires the signal really is unmounted and the teardown is legitimate. It pins the debounce, not the cancellation.should dispatch onMount eventrunsvi.runAllTimers()between transitions, so a timer never survives a remount.The new case is the one that was missing: mount, unmount, remount after 100 ms, advance past
STORE_UNMOUNT_DELAY, assert the unmount listener was never called. It fails onmainwithexpected "vi.fn()" to not be called at all, but actually been called 1 times.The fix
The pending timer now holds the state
activeused to. A mount with a teardown scheduled cancels it instead of starting a second listener; a mount without one starts the listener; the teardown clears the timer. Per listener the levels alternate strictly, sinceevaluateonly delivers an unchanged level to listeners past thelcfcursor and the unmount branch requireschanged, so those two cases cover every delivery and the flag is redundant.Size
Dropping the flag pays for the cancellation: measured on kida's bundle, raw 21562 → 21522 B, gzip 5067 → 5061 B against
main. No.size-limit.jsonchanges needed.Verification
Unit tests green in kida (70), store (55), router (129) and query (163).
oxlintandtsc --noEmitclean for kida.test:sizerun sequentially across all 13 packages with no exceedances.