Fragment-level render effects (a future direction)
Status: designed, deliberately deferred. This page records the idea so the design isn’t re-derived from scratch when a page finally needs it. It concerns the glue gems (hibiki_rails, hibiki_phlex), not the signal core.
The tax today: one renderable unit per fragment
If you want fine-grained updates over the wire — a write to step
re-sending only the step markup — every independently-updatable region of
a page needs its own renderable unit:
- In the ERB style (
hibiki_rails), that’s one partial per fragment: each effect broadcastspartial: "counter/count"orpartial: "counter/step", and the initial page render reuses the same partials as placeholders. - In the Phlex style (
hibiki_phlex), that’s one component class per fragment:Hibiki::Phlex.render_effectis component-grained by design — the effect’s first run is the dependency-collecting render of the wholeview_template, so every signal read anywhere in the component subscribes that one effect. The only way to get a smaller update unit is to make the fragment be a component with its ownrender_effect.
This is forced, not incidental. Three constraints intersect:
- Update granularity ≡ template granularity. The wire protocol is fragment replacement keyed on the transmitted HTML’s root DOM id. If the server should be able to send just the step markup, something on the server must be able to render just the step markup.
- The same markup renders in two contexts — the initial page paint and every subsequent broadcast — so it must live in one shared, addressable place.
- The template system’s unit of composition is a file (ERB) or a class (Phlex). Whatever “one renderable unit” means, it means one of those.
It’s the same tax every turbo-rails app pays in partials; hibiki didn’t add it. But Phlex opens a door ERB can’t: a class can have methods.
The idea: push the effect boundary inside the component
One class owns the page section; each independently-updatable region is a private method; each method is wrapped in its own effect:
class Counter::Show < Phlex::HTML
def view_template
div(...) do
reactive_fragment :count_display # own effect
reactive_fragment :step_display # own effect
controls # static, no effect
end
end
private
def count_display = p(id: "count") { "count: #{@count.value} · doubled: #{@doubled.value}" }
def step_display = p(id: "step") { "step: #{@step.value}" }
end
(reactive_fragment is a sketch name, not a commitment — the real API
falls out of the first page that needs it.)
The first full render runs each fragment method inside its effect’s first
run, so the signals read in count_display subscribe that effect only.
Afterward, writing step re-runs only step_display — re-rendering just
that method’s markup on the same instance and handing just that fragment’s
HTML to the transport block. Per-fragment display classes (or partials, in
ERB terms) collapse into methods of one class.
The precedent is Solid’s compiled JSX: the template renders once, and each dynamic “hole” becomes its own nested effect. This is that idea at fragment granularity instead of text-node granularity.
Why the machinery is already shaped for it
- The owner tree. Hibiki’s ownership model (Solid’s Owner) already supports effects nested under a parent scope: a channel’s root would own the component’s fragment effects, and disposing the graph disposes all of them with zero new lifecycle code.
- The wire protocol doesn’t change. The client keys replacement on the transmitted HTML’s root DOM id — it can’t tell whether a fragment came from a dedicated component, a partial, or a method. No client changes.
- The scheduler seam composes per effect.
scheduler:passes through to each fragment’s effect, so per-fragment debouncing works for free.
Why it’s deferred
- Isolated fragment re-render on a live Phlex instance.
Hibiki::Phlex::Rerenderablealready reaches into Phlex’s private rendering state to re-render a whole component; rendering one method in isolation — its own buffer, correct escaping context, same instance — reaches deeper still. Phlex 2’s selective-rendering facility may be a substrate, but building on it deepens the already strict Phlex version pin and expands what the contract spec must guard. - Tracking hygiene in the outer render. During the initial full render, the outer pass must not subscribe to signals read inside fragments — otherwise a fragment write re-runs the whole component and nothing is gained. The core has the right primitive (nested observers own their reads), but the initial-render pass — outer render untracked, inner effects tracked — is exactly the kind of tracking-core subtlety this project changes slowly and with specs.
- No page has needed it. A counter with two fragments shrugs off two
small display classes. The tax only hurts on a page with many
independently-updating regions — and that page is also what settles
the API question (explicit
reactive_fragmentvs. something more automatic) with a concrete use case instead of speculation.
Until then, the workaround is cheap and boring: one small component (or
partial) per fragment, one render_effect each.