Experimental synchronous UI DOM runtime. Designed for simplicity, explicitness, and predictability.
No feature is the feature.
No Virtual DOM. No diffing. No scheduler. No hidden async.
Just real DOM, real mutation, and deterministic structure with zero dependencies.
This library is simple by design, not “easy”.
- There is one way to do things
- Everything is explicit
It is optimized for clarity and long-term maintenance.
- Fully synchronous rendering
- No Virtual DOM, no reconciliation
- Minimal reactivity via
signal/effect - Explicit lifecycle (
mount,unmount) - Structural primitives (
Show,For)
Signals are sources, not values.
const [count, setCount] = signal(0);
setCount((v) => v + 1);
setCount(() => 42);- no equality checks
- no dependency tracking
- mutation is allowed
- every write notifies every subscriber
effect(count, (value) => {
console.log(value);
});Characteristics:
- NOT runs immediately on mount
- only runs on every write to the signal
- only one singal, no dependency arrays
- async callbacks are allowed (fire-and-forget)
Effects are not awaited. Concurrency is explicit and intentional.
Lifecycle is structural, not conceptual.
mount((parent) => {
// runs once, when the root DOM element exists
});
unmount(() => {
// guaranteed cleanup before removal
});- lifecycle is attached to actual DOM nodes
- children unmount before parents
- removal means real
destroy
import { render, signal, for } from "@papack/csr";
const [items, setItems] = signal([
{ uuid: "a", name: "A" },
{ uuid: "b", name: "B" },
]);
render(<App />, { parent: document.body });
function App() {
return (
<ul>
<For each={items}>{(item) => <li>{item.name}</li>}</For>
</ul>
);
}Controls existence, not visibility.
<Show when={visible}>
<User />
</Show>- when
false, the subtree is destroyed - when
true, it is rendered fresh - lifecycle runs correctly on both transitions
For is a keyed structural renderer, not a generic iterator.
Rules:
eachmust be an array- each item must be an object
- each item must have a stable key (
uuid) - no fallbacks, no heuristics
type Item = {
uuid: string;
[key: string]: any;
};What For does:
-
detects:
- additions
- removals
- order changes
-
performs:
- DOM moves (
insertBefore) - rendering only for new keys
destroy()for removed keys
- DOM moves (
What For does not do:
- no content diffing
- no re-rendering existing items
- no prop patching
If an item’s content changes, the item itself must be reactive.
setItems((prev) => {
prev.push({ uuid: "c", name: "C" });
return prev;
});This is correct.
Why:
- signals are active
- updates are not reference-based
Foronly cares about keys and order
Routing is just application state.
const [route, setRoute] = signal("home");Structure is derived explicitly:
effect(route, (r) => {
setIsHome(() => r === "home");
setIsSettings(() => r === "settings");
});<Show when={isHome}>
<Home />
</Show>
<Show when={isSettings}>
<Settings />
</Show>