Specimen 00 — walking skeleton
ursprung demo
This document was rendered on the server by a framework that compiled itself: its own parser, its own printer, its own module graph, no JSX runtime & no bundler.
- Status
- Under construction.
- Bundler
- None, at any stage.
- Payload
- Seven records, for eight roots.
Exp 01 — the server → client crossing
A Client component renders exactly like a Server one.
The component below is a Client module, and the server traversal walked straight into it. Nothing in the markup marks the boundary — no wrapper, no comment, no marker. What marks it is the module graph, which named the client copy in a preload link in the head of this document.
Live specimen — greeting.client.tsx
Hello, reader.
This paragraph is a child prop that crossed into a Client component, and it arrived as a Description with a string type, which needs no code to exist.
Exp 02 — resumability and reactivity
The handler is nowhere on the wire.
No event attribute reaches the browser and no function is served. The button carries an Address, and this document carries one payload record naming the module and its props. The first click imports that module, calls it again, walks to the same Address and runs the handler it finds there — on the node the server already rendered.
The count itself is a live position. The handler writes a signal and nothing else: the number between the two Anchors is updated in place, on the very text node the server rendered, and the label around it is never touched.
Live specimen — counter.client.tsx
Exp 03 — a Region is destroyed and rebuilt
Nothing is compared, and the panel comes back new.
The count above is a live position that produces a number, so the Sink writes it into the text node the server rendered. The panel below is a live position that produces a component, which is not that path: the Region is destroyed, its Owner is disposed, and the new value is walked in by the same renderer that produced this document.
So the panel that comes back is a new instance with a new signal, and its own count starts again at zero. Its cleanup runs while the Region is destroyed and writes the tally below it — which is safe because disposal unwatches every Sink in the subtree before it runs a single cleanup.
Live specimen — toggle.client.tsx
Regions destroyed: 0
Exp 04 — derived values and effects
Deriving is free, and an effect never runs on the server.
The Fahrenheit value below is a computed, read by two separate live positions. One write to the Celsius cell moves both of them, the derivation between them runs once, and nothing here declared a dependency: the standard records what the body read.
The two tallies are written by an effect. They were rendered as zero, because an effect is the one thing on this surface that does not run during Server rendering — a live position is evaluated on both sides and a component body runs on both, and this runs only where there is an Owner. The cleanup it returns runs before each re-run.
Live specimen — derived.client.tsx
20 °C is 68 °F.
The same computed, read again: 68
Effect runs: 0 — cleanups: 0
This line was last written by the server.
Exp 05 — a keyed list
Reversing this list moves the rows the server rendered.
List identity lives outside the renderer. The combinator compares keys, and the renderer reorders nodes by reference and never learns what a key is — so nothing here compares two Descriptions, which is the rule every other update path on this page also keeps.
Reverse moves each row rather than rebuilding it, so a row keeps its nodes and everything attached to them. Advance writes one cell, and only the first row's status changes — the callback is handed a signal per row, which is what makes a changed field an update in place. Remove disposes that row's Owner, which runs the cleanup the row registered. Append builds a row that no Address can name, and its button is reached the way the rebuilt panel above is.
Live specimen — list.client.tsx
- 1mainpassed
- 2rendererrunning
- 3for-combinatorqueued
Rows destroyed: 0
Exp 07 — a rebuild that awaits
The fallback you cannot see in this document.
Both readings below are produced by an async component, and neither of them ran while this page was rendered: the live positions held a plain string, so the server never reached the await. That is why this page still arrives in one piece while the next one arrives in three.
Ask for a reading and the two rows part company. The first is wrapped in a fallback, which fills the gap; the second is not, so its Region is simply empty until the reading settles. The old nodes go when the rebuild starts, not when it finishes, and nothing writes a placeholder on its own.
Ask for the slow reading and then the fast one. The fast answer arrives first and stays there: each rebuild carries a generation number, and the slow one comes back to a Region whose number has moved, so it writes nothing at all.
Live specimen — pending.client.tsx
- With a fallback
- Nothing asked yet.
- With none
- Nothing asked yet.
Instances retired: 0
Exp 08 — the Server boundary
One function, called from two places.
The reading below was produced by sounding(), which lives in a .server.ts module. This paragraph called it directly, in this isolate, while the page was rendered. The bench under it imports the same function from a .client.tsx, and there the import is a Server boundary: the edge is cut, a Stub module takes its place in the assets directory, and the call is a POST to /_ursprung/rpc.
Take a sounding and read the last row. The function asks the ambient Request context where it is, and it gets a different answer — the same body, reached two ways. Nothing in the source distinguishes them, which is the property the transform exists to preserve and also the one an author has to hold in mind.
- Rendered on the server
- 3 m, answered from /
The import is the opt-in. Nothing marks sounding() as public and nothing registers it; the second export of that module is not imported by any Client module, so it is not callable from the browser and no Stub names it.
Live specimen — boundary.client.tsx
- Depth
- —
- Metres
- —
- Answered from
- —
- A server throw
- —
- A constant as a value
- —
Exp 09 — the error boundary
A rebuild has three outcomes, and this is the third.
<Pending> spells two of them on one wrapper. fallback is what a Region shows while its content is pending, and failed is what it shows when that content threw. It takes the error rather than a Renderable, because a Renderable may itself be a function and the two would be indistinguishable.
Break everything, and read the rows one at a time. Each error report replaces exactly the position that threw — a rebuild empties its Region before it walks anything, so there was nothing there left to destroy. The row with no boundary above it empties and stays empty, which is the same sentence a pending Region with no fallback already answers to.
The last two rows are the paths the boundary refuses. An effect fires long after its Region settled and a handler fires on a click, so in both cases the reader is looking at working UI and the framework will not replace it. Tick, then throw, then count: the reading and the tally both keep moving, and the console carries both errors under a correlation id.
Live specimen — failure.client.tsx
- A body that throws
- Nothing broken yet.
- An async component that rejects
- Nothing asked yet.
- An expression that throws
- 12 m
- A row that cannot be built
- main@6f2c1ab
- main@0b9d4e7
- The same, with no boundary
- Nothing broken yet.
- A boundary that throws
- Nothing broken yet.
- An effect that throws
- Ticks read by a Sink: 0
- A handler that throws
- Clicks counted: 0
Exp 10 — the static directory
Two namespaces, and the author has the root.
The mark below is a file in this application's static directory, and ursprung copied it into the assets directory as opaque bytes. Every file under that directory is eligible — no allowlist, no globs, dotfiles included — because the directory is the declaration. It is served from the asset store, so the Worker is never invoked for it.
Emitted client modules moved out of the way to make that possible. They live under /_ursprung/, so an author file can no longer collide with one, and the _headers in the same static directory can name that prefix in one line to cache it forever. The author keeps the root, which is forced rather than preferred: /favicon.ico is not ursprung's path to move.
Copied specimen — public/images/origin.svg
Attributes are HTML's own, so this paragraph carries a class.
1 < 2, and the Walk is a synchronous generator.