summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-24 10:56:05 -0300
committerGabriel Schneider <[email protected]>2026-08-25 09:42:07 -0300
commitc3d0b84b7961ae26d2d654e7120821cc2d83d20d (patch)
tree0b9a060a8ff0ee7d83c8df5ca797a99e0d66c53e /docs
parent70bde600793ea70bd68832018a154671c6bf1512 (diff)
downloadpardes-c3d0b84b7961ae26d2d654e7120821cc2d83d20d.tar.gz
pardes-c3d0b84b7961ae26d2d654e7120821cc2d83d20d.zip
host: the core owns the event loop; every platform becomes a vtable of optional methods
Diffstat (limited to 'docs')
-rw-r--r--docs/design.typ2
-rw-r--r--docs/lsp.md6
-rw-r--r--docs/web.md67
3 files changed, 50 insertions, 25 deletions
diff --git a/docs/design.typ b/docs/design.typ
index 0d8f03dd..3e387f7a 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -104,7 +104,7 @@ serial and carry only phase, effect, frame, and from/to cell boxes. Canonical
layout is committed immediately; tracks are finite presentation data.)
*Effect* (out, queued): `spawn{pane, cwd}`, `write{pane, bytes}`,
-`resize_pty{pane, cols, rows}`, `open_link`, `new_file{pane, serial}`,
+`resize_pty{pane, cols, rows}`, `open_link`,
`save_file{pane}`, `write_dump`, `set_clipboard`, `read_clipboard`, `lsp`,
`pipe`, `watch`, `quit`.
The core never performs IO for any of these; it asks — and four of the asks have an
diff --git a/docs/lsp.md b/docs/lsp.md
index 0125f958..54ffd764 100644
--- a/docs/lsp.md
+++ b/docs/lsp.md
@@ -33,9 +33,9 @@ about thirty lines per shell: `tty.zig` uses `io.concurrent` + the vaxis loop
queue, `gui.zig` uses a detached thread + the mutex queue it already had. The
web shell compiles in no backend at all (`zls_backend` is off for wasm), which
makes `lsp.supports` empty, which makes `lspRequest` return before it emits —
-so on the web the effect is never even raised. `web.zig` carries a prong for it
-and exports `pardes_lsp_response` anyway; both are unreachable in the shipped
-configuration and wait for a host that links a backend.
+so on the web the effect is never even raised. `web.zig` leaves the host's
+`lsp` method null and exports nothing for a response; a host that links a
+backend would add both.
Three rules make it safe:
diff --git a/docs/web.md b/docs/web.md
index 793c79bc..0f8c068f 100644
--- a/docs/web.md
+++ b/docs/web.md
@@ -1,10 +1,11 @@
# DOM web backend
The web backend keeps the existing Pardes core and replaces the old
-Emscripten/SDL/WebGL shell. Zig produces a `wasm32-freestanding` module with no
-imports. Vanilla JavaScript owns `requestAnimationFrame`, browser input, window
-sizing, and effect/IO dispatch; HTML and CSS render selectable, accessible text.
-There is no canvas.
+Emscripten/SDL/WebGL shell. Zig produces a `wasm32-freestanding` module whose
+only imports are the four browser capabilities listed below — no libc, no WASI,
+no runtime. JavaScript owns `requestAnimationFrame`, browser input and window
+sizing; the CORE owns the loop and JavaScript calls one iteration of it per
+frame. HTML and CSS render selectable, accessible text. There is no canvas.
The default web build links Tree-sitter's C runtime and the Zig grammar inside
that same import-free module. A small freestanding compatibility layer supplies
@@ -12,11 +13,20 @@ the C ABI locally, so source panes retain the core's Tree-sitter highlighting
without Emscripten, WASI, or browser libc imports. Other grammar tiers remain
selectable with `-Dtree-sitter=minimal` or `-Dtree-sitter=full`.
-`src/web.zig` is the narrow WASM ABI. Events enter through exported functions,
-and `pardes_frame` writes the canonical `Surface` into a packed 20-byte cell
-array. `src/web/app.mjs` reads that array in one linear pass and patches stable
-DOM nodes. Core effects become browser operations (links, downloads, clipboard)
-or `pardes-io` custom events for a process-capable embedding host.
+`src/web.zig` is the narrow WASM ABI. Events enter through exported functions
+that hand them straight to `Pardes.update`. `pardes_tick` is one
+`Pardes.pump` — queued input, effects, render, present — and the host's
+`present` packs the canonical `Surface` into a 20-byte cell array that
+`pardes_frame`/`_ptr`/`_cols`/`_rows` describe. `src/web/app.mjs` reads that
+array in one linear pass and patches stable DOM nodes.
+
+Animated time is JavaScript's alone. `pardes_animation_tick` spends one fixed
+60 Hz step and is the only entry that advances a transition, exactly as the
+AppKit shell keeps its display clock apart from its pumps: if every input pump
+also advanced a fade, a burst of keys would collapse ten frames into one.
+`pardes_tick` never advances animated time itself, and no longer has to undo
+anything to avoid it: `Pardes.pump` does not post a tick for its caller, since
+only the host knows when a real frame interval has passed.
Cell attribute bit 7 carries the core's tagline font role. The DOM keeps every
cell at the body grid's fixed width and height, but scales and centres the
@@ -65,22 +75,37 @@ and no host filesystem, so there is no startup config directory, init file,
ThemeFile, or DumpThemes; LOOK resolves
against the build-generated source archive rather than disk, and no language
BACKEND is compiled in (`zls_backend` is off for wasm — which also means
-`lsp.supports` is empty, so the core never even raises a language query there;
-`web.zig`'s prong for it is waiting for a host that links one).
+`lsp.supports` is empty, so the core never even raises a language query there,
+and the host leaves `lsp` null).
Replay terminals retain the same core shape but use Zig's failing IO value;
this keeps the module freestanding without instantiating POSIX threaded IO that
the browser can never call.
-The EFFECTS themselves are all still emitted, each as a numbered code across
-the ABI: `spawn` 1, `write` 2, `resize_pty` 3, `open_link` 4, `save_file` 5,
-`write_dump` 6, `set_clipboard` 7, `lsp` 8, `watch` 9, `quit` 10, `new_file`
-11, `pipe` 12, `read_clipboard` 13. Inbound, `pardes_output`,
-`pardes_set_cwd`, `pardes_eof`, `pardes_paste` and `pardes_lsp_response`
-exist to answer them. A plain page ignores most of those and gets a replay
-viewer; a
-process-capable host (the `pardes-io` events above) answers them and gets
-terminals. The core does not know the difference — that is the point of the
-seam.
+`src/web.zig` fills in six methods of `Host.VTable` and leaves the rest null,
+and a null method is answered by the core itself rather than forwarded to a
+page that could not honour it. The six are `present`, `write_file`,
+`write_dump`, `set_clipboard`, `read_clipboard` and `open_link`; the last five
+are backed by four imports from the module `pardes` that JavaScript supplies at
+instantiation, because a written file and a written dump are both a download:
+
+| import | effect behind it |
+|---|---|
+| `host_open_link(url, len)` | `open_link` — `window.open` |
+| `host_set_clipboard(text, len)` | `set_clipboard` — `navigator.clipboard.writeText` |
+| `host_read_clipboard()` | `read_clipboard` — answered later, or never, by `pardes_paste` |
+| `host_download(path, path_len, bytes, len)` | `save_file`, `save_text` and `write_dump`: a file a page writes is a download |
+
+What the core answers instead of asking: `spawn`, `pty_write` and `pty_resize`
+land in the per-instance fallback, so a pane with no child is SILENT rather
+than synthetic; `tty_taken` is never taken; any save the page declines resolves
+against the core's virtual filesystem, which is also where every read comes
+from — the core reads files itself and never asks a host; `watch_file` records
+the request; `lsp` answers an empty row set at once (`zls_backend` is off for
+wasm, so the core never raises a query there anyway) and `pipe` answers
+failure; `wait_input` is null because wasm must never block, and `theme_file`
+and `dump_themes` are not reachable without a config directory. `quit` sets the
+core's flag, which `pardes_should_quit` reports so the page can stop its frame
+loop.
Two dependency-free test layers cover the boundary: