Skip to content

fix(kida): keep a child write in the caller's context - #198

Merged
dangreen merged 1 commit into
mainfrom
fix/kida-child-write-context
Aug 19, 2026
Merged

fix(kida): keep a child write in the caller's context#198
dangreen merged 1 commit into
mainfrom
fix/kida-child-write-context

Conversation

@dangreen

Copy link
Copy Markdown
Member

childOper wrapped its whole write path in untracked — the reads of the parent and the key, and the write back into the parent along with them. Only the reads need it.

The write does not create dependencies, so untracking it looks free. It is not: activeSub is also how the lifecycle queue decides whether the system is quiescent.

function settle(): void {
  if (
    !draining
    && pending.length
    && (
      activeSub === undefined
      || (activeSub.modes & (ScopeMode | LazyMode)) === ScopeMode
    )
    && !batchDepth
    && !flushDepth
  ) {

A write from a subscriber's own body triggers a flush when nothing else holds one open, and flush ends by calling settle. With the write untracked, activeSub is undefined at that moment, so settle concludes that nobody is running and drains the queue on the spot — and the mount listeners the flush woke fire inside the subscriber that wrote, halfway through its body. With the write left in the caller's context, settle sees a running subscriber and defers them to the real boundary.

The gate

const stopWriter = effect(() => {
  log.push('start')

  if ($a() === 1) {
    $a(2)
  }

  log.push('end')
})

where a second effect reaches a mountable signal only once that write has landed:

log
before start, mounted, end
after start, end, mounted

Two gravestones go into agera's signal spec next to it, pinning what a plain signal already guarantees and what the child now stops obscuring: an effect that writes its own dependency is not woken by its own write, whether it writes directly or through untracked.

Size

nanoviews gains 4 B gzip / 3 B brotli on the average-usage bundle — two let slots the write needs outside the untracked block. That is enough to push Average usage (Brotli) past its pin (3998 → 4001 B), so the limit moves 4 kB4.05 kB. Every other pin in the chain stays where it was.

Only the parent and key reads are untracked now. The write goes out in the caller's context, so the flush it triggers is not mistaken for a quiescent moment and the mount listeners it wakes no longer fire inside the very subscriber that wrote.
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.83%. Comparing base (23b3370) to head (5b8eee9).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #198      +/-   ##
==========================================
+ Coverage   84.73%   84.83%   +0.09%     
==========================================
  Files         140      140              
  Lines        3125     3125              
  Branches      586      586              
==========================================
+ Hits         2648     2651       +3     
+ Misses        339      338       -1     
+ Partials      138      136       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dangreen
dangreen merged commit 0816533 into main Aug 19, 2026
10 checks passed
@dangreen
dangreen deleted the fix/kida-child-write-context branch August 19, 2026 08:22
@github-actions github-actions Bot mentioned this pull request Aug 12, 2026
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