Skip to content

fix(nanoviews): render the branch brought back by a write to the condition - #201

Closed
dangreen wants to merge 1 commit into
mainfrom
fix/nanoviews-swap-write
Closed

fix(nanoviews): render the branch brought back by a write to the condition#201
dangreen wants to merge 1 commit into
mainfrom
fix/nanoviews-swap-write

Conversation

@dangreen

Copy link
Copy Markdown
Member

A branch is allowed to disagree with the condition that selected it. A form that refuses to open until something is allowed, a row that normalises the value it was handed — the write goes back into the very signal the block swaps on, and the block has to end up showing the branch the write asks for.

It did not. The block showed the contradicted branch, and worse: the corrective branch was rendered but never started, so its DOM was in place while its effects never ran and never would.

if_($open)(
  () => {
    /* the branch refuses to be shown */
    effect(() => {
      if (!$allowed()) {
        $open(false)
      }
    })

    return b()('open')
  },
  () => i()($text)
)

$open(true)

Before: <b>open</b>. The write to $open landed while the swapper that reads $open was running, and a running effect cannot be re-queued by its own propagation, so the swap that would undo it never happened.

Two halves

Starting a scope is not the renderer's move. deferScopeBindContext rendered the new content and started it. Now it only renders, and the swap starts what its callback returns:

-    prev = untracked(() => callback(prev, value))
+    prev = untracked(() => startScope(callback(prev, value)))

Before the mount walk the scope's anchor is still lazy and the start is a no-op, so the walk keeps the block's position among its siblings exactly as before. After it, the swapped-in content comes up on the swap's own stack — which is what leaves no scope behind that is rendered but never started.

The echo. That alone settles a write made from the branch's render body, but not one made from a binding the branch started: at that moment the swapper is parked, and nothing re-reads the condition. decide now carries a second subscriber that does nothing but read it:

effect(() => void $condition(), true)

It is idle when the branch writes, so it is free to be notified, and its read settles the condition and re-queues the parked swapper for the corrective swap.

Tests

Three in if.spec.ts, and they check the branch is live, not merely rendered — each asserts an effect of the brought-back branch, which only runs if that branch was started:

  • a write from a binding the selected branch started (fails before this change),
  • a write made while the branch renders (fails before this change),
  • a write made on mount, which brings a branch up before the block was ever shown.

for_ goes through the same swapper and its whole suite is unchanged; no size pin moves anywhere in the chain.

…ition

A branch that writes its own condition - from a binding it started, or from its render body - used to leave the block showing content that was already contradicted: the swap it should have caused ran into a swapper that could not be re-entered, and the corrective branch was rendered but never started.

Starting a swapped-in scope is no longer the renderer's move: the swap starts what it returns, so a correction lands on the swap's own stack. `decide` also carries a second, idle subscriber on the condition, whose read settles it and re-queues the parked swapper.
@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 85.00%. Comparing base (ac6b35f) to head (dbcc7cd).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #201   +/-   ##
=======================================
  Coverage   84.99%   85.00%           
=======================================
  Files         140      140           
  Lines        3159     3160    +1     
  Branches      596      596           
=======================================
+ Hits         2685     2686    +1     
  Misses        338      338           
  Partials      136      136           

☔ 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 closed this Aug 19, 2026
@dangreen
dangreen deleted the fix/nanoviews-swap-write branch August 19, 2026 12:14
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