Skip to content

feat(nanoviews): expose swap_, the block if_ and switch_ are built on - #203

Merged
dangreen merged 1 commit into
mainfrom
feat/nanoviews-swap-block
Aug 19, 2026
Merged

feat(nanoviews): expose swap_, the block if_ and switch_ are built on#203
dangreen merged 1 commit into
mainfrom
feat/nanoviews-swap-block

Conversation

@dangreen

@dangreen dangreen commented Aug 19, 2026

Copy link
Copy Markdown
Member

if_ and switch_ were both built on an internal block that renders whatever a decider returns for a value, and rebuilds it when the value changes. That block is useful on its own, so it becomes public as swap_.

tbody()(
  swap_($tab, tab => (tab === 'list' ? List() : Grid()))
)

Why not dynamic

This is the job other libraries hand to <Dynamic component={X} {...props}/> or <component :is>, and the todo entry that started this called it exactly that. But those exist because in JSX and in templates the tag position is not an expression — you cannot write "call this function with these props", so a component has to be smuggled in as a prop and the rest spread over it. There is no tag position here: a component is a plain function, and Comp(props) already is what <Dynamic> is for. A wrapper with that signature would re-implement function application, and it would be weaker — with a decider the props are built per value, inside the closure, which one props object cannot do.

So swap_ is the general block and Dynamic is one line of it. Naming it after the narrow case would have promised a signature the library does not need.

Why swap_

The name carries the thing consumers get wrong. A binding updates content in place; this block replaces it — the old subtree is destroyed and everything it held goes with it. decide said a decision happens and stayed silent about that, which is the half people learn by losing state.

The trailing _ follows the flow vocabulary — if_, for_, switch_, case_, each_ — where it marks the DSL rather than dodging a keyword.

What moved

  • internals/flow/decide.tsinternals/flow/swap.ts; reactiveDecideswap, and the parameters lose the conditional vocabulary ($condition/decider$value/render) now that the block is not only about conditions.
  • The value-or-accessor entry moves out to the public flow/swap.ts as swap_; if_ and switch_ call it.
  • A spec for the public function: a static value renders once without subscribing, and a changed value gets a new node rather than a reused one — the property the name is chosen for.

Cost: 10 B gzip on the all-publics bundle for the new export. Average usage is unchanged; nothing that does not import swap_ pays for it.

One correction rides along, in the specs of the directory this touches: the flow specs called their group logic, which has never matched the directory.

@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.29%. Comparing base (18127c1) to head (6440225).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #203   +/-   ##
=======================================
  Coverage   85.29%   85.29%           
=======================================
  Files         140      141    +1     
  Lines        3168     3168           
  Branches      593      593           
=======================================
  Hits         2702     2702           
  Misses        335      335           
  Partials      131      131           

☔ 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 force-pushed the feat/nanoviews-swap-block branch from c89e882 to fa22dfe Compare August 19, 2026 21:14
…ilt on

`swap_($value, value => Child)` renders a child decided by a value and builds it anew whenever that value changes. It is what other libraries reach for `<Dynamic>` or `<component :is>` to do, except the decider returns arbitrary children instead of a component - there is no tag position to fill here, so rendering a component chosen at runtime is just calling it.

The name says the part consumers get wrong: unlike a binding, which updates content in place, the block replaces it, and everything the old content held goes with it. `if_` and `switch_` are specialisations and now call it too. Internally `reactiveDecide` becomes `swap` - one name for one thing.

The flow specs also stop naming their group `logic`, which never matched the directory.
@dangreen
dangreen force-pushed the feat/nanoviews-swap-block branch from fa22dfe to 6440225 Compare August 19, 2026 21:16
@dangreen
dangreen merged commit 9fc639f into main Aug 19, 2026
10 checks passed
@dangreen
dangreen deleted the feat/nanoviews-swap-block branch August 19, 2026 21:20
@github-actions github-actions Bot mentioned this pull request Aug 19, 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