RatMinimal web framework

Location transparency

The same pure-computation call produces the same result regardless of which host runs it. clamp(x, 0, 10), json.stringify(items), and date.format(t, layout) mean the same thing in a > server handler, a server-side render, a reactive patching the DOM, or a button's on_click[...] attribute. The substrate routes the call to whichever host is fastest for the site, but the result of computing it never changes.

Same call, three compile paths

clamp in server render and a handler

The interpolated 3 below evaluates server-side on first paint, then re-evaluates in the browser whenever p_count changes. The same clamp(...) call in the on_click handler runs entirely in the browser, with no server round-trip, and logs the same value to the console.

> page
p_count: 3

<p> [clamp(p_count, 0, 5)]
<button on_click[p_count++]> +1
<button on_click[log[clamp(p_count, 0, 5)]]> log clamp
Result

clamp(p_count, 0, 5) = 3

Namespaces transfer too

json.stringify works identically server and client

Namespace members follow the same rule. The interpolation renders the stringified person on first paint; the handler stringifies the live person object at click time and logs it. Same call, two execution sites, one semantic.

> page
person: {first: 'Grace', last: 'Hopper'}

<p> [json.stringify(person)]
<button on_click[log[json.stringify(person)]]> log JSON
Result

{"first":"Grace","last":"Hopper"}

What qualifies as pure

Value in, value out: no I/O, no provider commitment

The line is sharp on purpose. Pure computation gets a JS twin and runs wherever the call sits: strings, math, collections, regex, date, json, encoding, plus log / debug / error. Effects (databases, services, Tail, HTTP, the filesystem) commit to a host because that's where the I/O lives. Calling them from the wrong tier round-trips through dispatch instead of running locally.

# Pure - runs in place wherever you write it:
<p> [round(p_count * 1.5)]
<button on_click[log[round(p_count * 1.5)]]> log

# Effectful - round-trips to where the host lives:
<button on_click[main_db.users.add({name: 'Ada'})]> add user
<button on_click[auth.login(name, pw)]>            login

User-defined functions still round-trip

Not a leak: it is the correct routing

Your own > name declarations live on the server and need access to server state, so calling them from a handler issues an RPC. That's not a violation of the rule: they're not pure-computation primitives, they're application logic with a deployment target. The rule only commits the substrate's own builtins to dual-tier execution.

> add[a, b] >> a + b           # user-defined → round-trips
> clamp_to_5[x] >> clamp(x, 0, 5)  # also round-trips

<button on_click[log[add(1, 2)]]>          # one RPC per click
<button on_click[log[clamp(1 + 2, 0, 5)]]>  # zero RPCs

Why this matters for authoring

Learn the model once, write the call where it reads best

Location transparency lets you write a function by what it does, not by where it'll run. Moving a piece of logic from a reactive client-side path to a server-tier handler, or back, is a performance decision, not a semantic one. The author's mental model stays the same: this transforms input to output, and the tier is a deployment knob.

See also Tail (Python) · Inverse Tail (JS) · State tiers