summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/config.md15
-rw-r--r--docs/design.typ5
-rw-r--r--docs/fs.md56
-rw-r--r--docs/macos.md21
-rw-r--r--docs/open-questions.md19
-rw-r--r--docs/render-pipeline.md61
-rw-r--r--docs/web.md2
7 files changed, 124 insertions, 55 deletions
diff --git a/docs/config.md b/docs/config.md
index 9c51f329..16fd59e5 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -513,18 +513,19 @@ immediately and remains the one authoritative geometry. DOM web intentionally
does not expose these builtins: its renderer is selectable HTML/CSS and has no
canvas or shader stage.
-The scene effects are independent switches and can be combined:
+The scene effect is `Crt`, at a level from 0 (off) to 3; `on` is 2:
```text
Crt
-Ripple
-Glitch
+Crt 3
```
-They share one full-scene shader pass in SDL and macOS. With all three off the
-pass is bypassed. CRT works in linear light with restrained scanlines, mask,
-bloom, curvature, and noise rather than remapping the theme to a strong fixed
-palette; Ripple and Glitch primarily perturb sample coordinates.
+The SDL GUI runs it as the bundled pass of its post chain, which also takes
+Shadertoy files written for ghostty (`Shader ~/crt.glsl`, `Shader off`), and
+`ShaderAnimation off|on|always` says when the chain animates on its own. With
+the chain empty the pass is bypassed. CRT works in linear light with restrained
+scanlines, mask, bloom and vignette, and no curvature, so clicks land where
+they are drawn.
`EffectCode PanelAscii` or `EffectCode Crt` lists the current backend's
build-embedded source paths under `/virtual`. Look opens each full file;
diff --git a/docs/design.typ b/docs/design.typ
index 4765fabd..a6bf56a3 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -913,8 +913,9 @@ while its geometry moves and placed again on the last active sample.
SDL supplies old/new glyph data, diff flags, final/presented boxes, and effect
parameters to the glyph and native-image shaders. Pixel attachments bypass ASCII
because they have no character byte. macOS passes the same records across its
-plain C ABI and composites old/new panel images in Metal and Core Image. Scene
-`Crt`, `Ripple` and `Glitch` bits share one full-window pass in each native GUI.
+plain C ABI and composites old/new panel images in Metal and Core Image. The
+scene `Crt` is one full-window pass in each native GUI; the SDL GUI's post
+chain also runs Shadertoy files (`Shader`).
DOM web is a separate platform, not a shader GUI: retaining selectable HTML and
CSS is more important than duplicating the renderer in canvas, so it exposes
diff --git a/docs/fs.md b/docs/fs.md
index e93704a6..86f44501 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -62,8 +62,10 @@ private namespace:
A `9ns --unix` mount lives in the private namespace of the command it runs,
and nothing outside that command sees it. The mount everyone on the machine
shares is the registry one, `9ns --mntgen` (default `/mnt/9p`): every running
-editor posts itself there, so `/mnt/9p/pardes/<pid>/` is that editor's tree
-for any process. A new pane made through `pane/new` is a scratch named
+editor posts itself there, so `$NINE_MOUNT/pardes/<pid>/` is that editor's tree
+for any process. 9ns exports `$NINE_MOUNT` to everything it starts, so a
+script checks that variable to know the mount is there, and takes `<pid>` from
+the name of `$PARDES_9P` (`pardes-9p-<pid>.sock`). A new pane made through `pane/new` is a scratch named
`<dir>/+New` until it is given a name, and closing a column's last pane
leaves such a `+New` in its place (`Delcol` closes the column).
@@ -110,12 +112,15 @@ which quits the editor as acme's does (it refuses once, naming each pane with
unsaved text, `<name>: Modified (Exit again to discard)`, and a second
`Exit` with nothing edited since quits, throwing that text away; a scratch
under 100 bytes is not asked about; `Restore`, which replaces every pane,
-asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` in
+asks the same first -- `Dump` writes `pardes-<date>-<time>.zon` (UTC) in
`DumpDir` (default `$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes`)
and logs `dump <path>`, and `Restore` with no path takes the last one; a
Restore puts a new editor under every client, so the write of it is
answered and then every connection is hung up, their fids naming the old
-editor's panes: dial again, and the new log names the restored panes and
+editor's panes (through a 9ns mount the write usually fails with
+ECONNRESET all the same: 9ns fails a request whose reply is already in once
+the hang-up breaks its next send, so trust the log): dial again, and the new
+log names the restored panes and
`restore <path>`. The answer has 200 ms to leave before the cut, so a slow
client may see only the cut; the log's `restore <path>` is what says the
Restore happened. Keeping connections across it would mean carrying serials
@@ -130,7 +135,8 @@ start mark (C) to its end mark (D), where Kill sends its foreground job SIGTERM
"kill" note, which ends a process -- and never signals the shell itself;
in a shell running without job control (`set +m`) the job shares the
shell's group, so there is none to signal: Kill says `Kill: no job to
-signal`, and a write of it to `ctl` fails with that; and only the
+signal`, and a write of it to `ctl` fails with that; with nothing running
+it says `Kill: nothing running`; and only the
foreground job, so of `sleep 30; echo done` the `echo` still runs once the
`sleep` is stopped --
and reads every setting there is, one a line, in the words a write of it
@@ -184,7 +190,7 @@ the keyboard no pane does: `/focus` reads empty and every pane's `ctl` says
`notcurrent`. A write gives the keyboard and nothing else: a folded pane
stays folded (unfold it with `Collapse` on its ctl), as rio keeps `current`
apart from `unhide`. A serial no pane has fails with `no such
-window`; anything but a number, with `ill-formed control message`.
+pane`; anything but a number, with `ill-formed control message`.
A pane is made by **opening** `/pane/new`, and closed by Tremove on
`/pane/<n>` (`rmdir`), which is the only remove the tree serves; Tcreate is
@@ -236,7 +242,8 @@ same tree without leaving the process.
or anything else, a command line. Written at a terminal at its prompt it
is typed into that shell. From anywhere else -- a file, a scratch, a tag,
a terminal whose tty a program holds -- it runs as a command pane: a
- terminal whose child is `$SHELL -c` the line in the pane's directory,
+ terminal whose child is the root ctl's `Shell` (fish unless set) run
+ with `-c` and the line, in the pane's directory,
which shows its output and then `exit N` (its tag reads `<dir> (<line>)
running`, then `exit N`), and stays. The command is over when its process
exits, as in acme, not when its terminal closes: a job it left in the
@@ -247,17 +254,21 @@ same tree without leaving the process.
leaves any alternate screen and turns off the modes a program left on
(mouse reports, bracketed paste, a hidden cursor); a command that clears
the screen and its scrollback (`clear`, ED3) erases the history above it. A command pane's own exec starts
- the next command there too. The log says `run <serial> <word>` and `exit
+ the next command there too. The log says `run <serial> <line>` and `exit
<serial> <N|?>`; `exec` reads back the command pane's serial; Kill ends
its whole process group; a line is at most 1 KB. A misspelled word is a
command that says so and ends `exit 127`. `echo Tty > pane/<n>/ctl` makes
an interactive terminal in that pane's directory.
A terminal can be bound as a language's REPL: `Repl python` in its tag or
-on its `ctl` (the language names are the syntax table's; `Repl -` unbinds,
+on its `ctl` (the language names are the syntax table's, or a code fence's
+alias such as `py`, in any case; `Repl -` unbinds,
`Repl` bare says the binding, `Repl python` again changes nothing). Its tag
-shows its id, `python-a`, `python-b` for the next, a freed letter reused.
-Then an exec made by a gesture on the body of a file in that language -- a
+shows its id, `python-a`, `python-b` for the next, a freed letter reused,
+and so does the end of its `ctl` line, after `current`/`notcurrent`.
+A builtin's word still runs first, bound or not: `Del` clicked in the file
+closes its pane. Then any other exec made by a gesture on the body of a file
+in that language -- a
middle click, the execute key, on a selection or a single word, even `make`
in a comment -- or on the REPL's own body is typed into the REPL instead of
run: bracketed paste when its program asked for it (DECSET 2004), else line
@@ -265,14 +276,20 @@ by line, where a blank line inside a Python block ends the block (said
once), then Enter. The message row says `→ python-a` in the tag's name tint
and the log `send <from> <to> <id>`. With several REPLs bound for the
language the pane asks which, on its notice band, one key answering and Esc
-sending nowhere; nothing is remembered. A command pane whose command is done
-is no REPL any more. Still commands, whatever is bound: a word in a tag (so
+sending nowhere; nothing is remembered. A REPL takes text only while its
+program has the terminal: a command pane's until its command is done (the
+binding goes with it, and a done one cannot be bound), an interactive
+terminal's while a program other than its shell holds the tty -- after
+Ctrl-D the shell would run the text, so nothing is sent and the pane says
+so. Still commands, whatever is bound: a word in a tag (so
the tag is how to run `make` from that file), `Exec <text>` run by name
(typed, a 2-1 chord onto `Exec`, a `ctl` line) and a command word @`cmd` in
the text, looked at or clicked (`# @`pytest -x`` in a script). A 9P `exec`
is no gesture and is never sent: a script writes to the REPL pane's
-`pty/data`. The REPL gets the text wherever it is -- at a `pdb` or `input()`
-prompt too. Bindings are not dumped, so a Restore leaves none.
+`pty/data`, multi-line code as a bracketed paste (`\e[200~<code>\e[201~`
+then `\r`), since line by line a blank line ends a Python block and Python
+3.14's REPL auto-indents each line typed into it. The REPL gets the text
+wherever it is -- at a `pdb` or `input()` prompt too. Bindings are not dumped, so a Restore leaves none.
The root's pair clicks at the active pane and `/pane/<n>/look` and
`/pane/<n>/exec` at that pane. Blank lines are skipped, and every other line
@@ -293,7 +310,10 @@ pane's own `look` or `exec`, or to the root's while that pane has the
keyboard, a click with no place in the text: an `F` record at `0 0` carrying
the line. So a client holding `event` that wants a command run gets its own
exec back as a record: it runs it through `ctl`, or writes the record back.
-Writing a record back performs the action. acme takes back only `<origin>
+Writing a record back performs the action, as the click would have (acme's
+xfideventwrite): a body `X` goes to a REPL bound for the text as a middle
+click does, where an `F` record, a line written to `exec`, runs as the
+command it was. acme takes back only `<origin>
<action><q0> <q1>`, the text of that range; pardes takes the record whole as
it was read too, and for an empty range acts on its text, which is how such
a line is done. A click in a file's body carries the offsets of the text it
@@ -490,7 +510,9 @@ for ever: cancel the read (interrupt it, or close the open) to give up;
`error not run` when the shell refused
the line without running it (a fish syntax error; the line is taken back off
the prompt); `error shell gone` when the pane closed or its shell was
-replaced; `error no prompt marks` for a shell pardes could not instrument.
+replaced; `error no prompt marks` for a shell pardes could not instrument;
+`error command done; not a shell` (or `error a command runs here, not a
+shell`) on a command pane, whose child is its command.
It relies on the OSC 133 marks pardes injects into bash and fish, tagged
`aid=pardes` so fish's own marks and a nested shell's are ignored. A second
line on an open whose command still runs fails the write. `exec zsh` or a
diff --git a/docs/macos.md b/docs/macos.md
index e104d3a6..5166e2d5 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -102,8 +102,8 @@ asking for a thin insert caret instead of a block. `Surface.images` crosses
separately as `pardes_frame_images` / `pardes_frame_image_list` — see "Pixel
attachments" below.
-`pardes_scene` is the matching full-window snapshot: a bit each for CRT,
-ripple, and glitch, plus the 60 Hz time/frame that animates them. It is returned
+`pardes_scene` is the matching full-window snapshot: a bit for CRT, plus the
+60 Hz time/frame that animates it. It is returned
by value, so the renderer never holds a pointer into live core state. A zero
flags word is also the fast-path contract: draw the CoreText frame directly.
@@ -662,8 +662,7 @@ longer leaves a dark titlebar over a cream grid.
## Scene effects
-`Crt`, `Ripple`, and `Glitch` are three switches over one postprocess, not three
-stacked filters. Their canonical macOS source is `shaders/crt.ci.metal`, which
+`Crt` is one full-window postprocess. Its canonical macOS source is `shaders/crt.ci.metal`, which
the build installs as `pardes.app/Contents/Resources/crt.ci.metal`. The Swift
shell loads that exact asset with `CIKernel.kernels(withMetalString:)` and runs
it through a `CIContext` created from the system Metal device. The source is
@@ -673,14 +672,12 @@ The ordinary CoreText/attachment/cursor pass is one function. With any scene
bit or panel track it targets a retained, backing-scale bitmap; kernels then
sample the complete frame into the flipped view. PDFs, image panes, taglines,
rules, and the caret therefore receive the same effect. With every bit off and
-no panel track the bitmap and Core Image context are bypassed entirely. Ripple
-and glitch alter sample coordinates only, preserving theme colors; CRT works
+no panel track the bitmap and Core Image context are bypassed entirely. CRT works
in linear light with restrained bloom, scan/mask, vignette, hum, and noise
instead of applying a broad color remap.
The scene kernel alone receives a `clampedToExtent` image and the final output
-is cropped back to the original finite extent. Coordinate tears and chroma
-samples therefore clamp to the edge exactly like SDL's scene sampler instead
+is cropped back to the original finite extent. Chroma samples therefore clamp to the edge exactly like SDL's scene sampler instead
of acquiring transparent-black seams from Core Image's finite source image.
The Zig side owns the clock. `pardes_animation_tick` increments its wrapped
@@ -689,8 +686,8 @@ from that integer. Input bursts cannot accelerate the shader.
Pointer input follows the same destination-to-source transform as the last
presented scene frame before it is divided by the cell metrics. The view keeps
-that exact `pardes_scene_s` snapshot and mirrors the Metal barrel, ripple and
-glitch sampling arithmetic in `ScenePostprocessor.sourcePoint`; pixels outside
+that exact `pardes_scene_s` snapshot and mirrors the Metal barrel arithmetic
+in `ScenePostprocessor.sourcePoint`; pixels outside
the CRT tube have no cell. The resulting displayed-grid cell then reaches the
core, whose panel-track mapping resolves it to canonical pane content.
@@ -716,8 +713,8 @@ while `pardesComposedInCore` effects only clip the already-composed core cells;
pixel attachments have no character value and pass through unchanged.
Because the input is the finished bitmap rather than a glyph-only
batch, backgrounds, glyphs, taglines, rules, the caret, PDF pages, and image
-panes move and dissolve together. Scene CRT/ripple/glitch runs once after the
-panel composition.
+panes move and dissolve together. The scene CRT runs once after the panel
+composition.
With no scene bit and no panel track the retained bitmap, Core Image context,
and Metal passes are bypassed. `EffectCode Panel*` links to this Metal
diff --git a/docs/open-questions.md b/docs/open-questions.md
index a2ce2086..d4b5699c 100644
--- a/docs/open-questions.md
+++ b/docs/open-questions.md
@@ -11,7 +11,8 @@ Raised 2026-09-27 in the review of pardes against acme (Plan 9 source at
**Decided 2026-09-28: a command pane.** Clicked or written at an interactive
terminal at its prompt, the line is typed into that shell, whose state the
clicker can see. From anywhere else it runs as its own terminal pane whose
-child is `$SHELL -c` the line in the pane's directory: full emulation
+child is the root ctl's `Shell` run with `-c` and the line, in the pane's
+directory: full emulation
(colours, `less`, `vim`, `sudo`'s prompt work), the exit status from waiting
on the child (`exit N`, `exit 127` for a misspelling, no prompt marks
needed), Kill signalling its process group, `run`/`exit` records in the log,
@@ -57,3 +58,19 @@ behaviour inside a chosen terminal: a line in, `exit N` and its output out.
The planned root `ctl` (session builtins) and pane `ctl` (pane builtins)
refuse unknown words, whatever is decided here; the question is only what
`exec` and the middle-click do with them.
+
+## Where does an exec from a code file go?
+
+**Decided 2026-09-28: to a REPL bound for its language, when one is.**
+`Repl python` binds a terminal; then an exec made by a gesture (a middle
+click, the execute key, a selection or a single word) on the body of a file
+in that language, or on the REPL's own body, is typed into the REPL
+instead of run. What stays a command whatever is bound: a builtin's word,
+a word in a tag, `Exec <text>` run by name, a command word @`cmd` in the
+text, and a 9P `exec` write (a script writes the REPL's `pty/data`). An
+event record written back is done as the click it was, REPL and all. With
+several bound the pane asks which, one key answering, and remembers
+nothing. A REPL takes text only while its program has the terminal, and a
+done command pane cannot be bound. Chosen over a per-file or per-directory
+binding, which would need a place to keep and show it; bindings are not
+dumped. `docs/fs.md` has the behaviour.
diff --git a/docs/render-pipeline.md b/docs/render-pipeline.md
index 2a679b8d..7c94fbea 100644
--- a/docs/render-pipeline.md
+++ b/docs/render-pipeline.md
@@ -491,21 +491,31 @@ Mirror ghostty 1.3.2 (`zig-pkg/ghostty-*/src/renderer/shadertoy.zig`,
≈3 cells, at the corners of a 4K screen, where the grips and rails are.
`crt.zig` is deleted. If the user insists on barrel in the bundled CRT, keep
`crt.zig`'s inverse keyed to "bundled crt active".
-- **Compile**: GLSL → SPIR-V at runtime with glslang (ghostty's `pkg/glslang`,
- the same vendored package in zig-pkg, GUI build only, behind `-Dshadertoy`).
- Compiled on load and on file change (file_watch), never on the frame path;
- the last good pipeline stays on error; the error is a pane message. glslang is
- C++ with its own allocator: a recorded exception to the "Zig allocator hooks
- in all C deps" policy.
+- **Compile** (decision 3): a user's file is compiled by spawning `glslc` with
+ the build's own flags, prefix and file on its stdin, SPIR-V on its stdout
+ (no file of ours anywhere; std's spawn allocates before fork), on a thread of its own when the file joins the chain, never on the
+ frame path; the result is taken in at the next loop step. The prefix ends in
+ `#line 1`, so glslc's errors count the file's own lines, and they name the
+ file, not `<stdin>`. Level A redraws once a refresh of the window's display. A failed compile keeps the file's last good
+ pipeline and says glslc's first line as a message; glslc missing is said
+ once, and only files stay off (the bundled passes are compiled with the
+ build, through the same prefix). No file watching: `Shader <path>` twice
+ (out, then in) compiles it again.
- **Animation mode**: `ShaderAnimation off|on|always` (ghostty's
`custom-shader-animation`): off = redraw only when content changes; on =
continuous while the window is focused; always = continuous. Continuous means
the shell redraws its retained Surface (3.1), never a core tick or render.
-- **Existing crt/ripple/glitch** become bundled Shadertoy files in
- `shaders/post/` selected by the same builtins; `scene_effects` and
- `crt.frag.glsl`/`crt.zig` are deleted. The bundled CRT is rewritten to
- quality (6.1); ripple and glitch are dropped unless the feel review keeps a
- rewritten version.
+- **Existing crt** becomes a bundled Shadertoy file in `shaders/post/` under
+ the same builtin; `scene_effects` and `crt.frag.glsl`/`crt.zig` are deleted.
+ It takes a level, `Crt 0..3` (0 off, `on` the default 2, `off` and bare
+ still work), passed as `pardesLevel`: 2 is the polished rewrite, 3 at least
+ as strong as the old CRT (pixels changed by more than 8 in a channel against
+ a plain frame: 18.3% vs 17.4%). Ripple and Glitch were rewritten too, then
+ removed at the user's word after a live look (decision 8). The chain is
+ `Crt` and `Shader <path>` in the order they were put in
+ (`Shader off` empties it of files), `ShaderAnimation off|on|always`
+ (default on). The core keeps no clock for it: level A redraws are the
+ shell's.
- macOS (out of scope) would need spirv-cross → MSL, as ghostty does.
Region filters (e.g. "tint/blur one pane") are NOT built. If one is ever needed:
@@ -803,15 +813,15 @@ stripped. No visual change until stage 9 unless stated.
|---|---|---|
| 0 | GUI capture goldens: a handful of scenes rendered hidden in capture mode, PPM hashes checked in (same machine/driver) | the safety net the GUI stages need; machine- and driver-specific, so a LOCAL gate only, never CI |
| 1 | pure moves: render → `src/draw.zig`; Presentation/animation out of layout.zig | no behaviour |
-| 2a | clock plumbing: `now_ns` into pump/events, `nextWake`, `advance`, deadlines for linger/hover; all shells pass now and sleep to the wake; six tick drivers deleted; ack moves into pump; capturePrevious only when transitions are on. Animations still compute `frame = floor(elapsed / 16 ms)`, so every curve is identical | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. Unit tests step an injected clock by exactly 16 ms, so their numbers do not change. The snapshot harness runs the binary with `--test-clock` (each core-time step is exactly 16.67 ms per tick request, as today), so goldens see the same sequence and `stable` (test/snapshot.zig ~861) is unaffected by wall-time pacing |
+| 2a | clock plumbing: `Host.now` (the shell's monotonic ns) and `Pardes.advance(now)`, which runs one `.tick` per whole 16 ms frame since core time last stood still; `nextWake()` lists every core animation and answers the next frame while anything moves, the wait's end while something only waits (message linger, look-hover delay), null when idle; the pump sleeps to it and draws only stepped frames. No shell posts `.tick` any more (tty's timer thread only wakes the wait; GUI's AnimationClock, web's JS tick bank and the detached server's and the board's ticks are gone; grid mode moves its virtual clock straight to the next wake). `acknowledgePanelPresentation` stays in the shells (revisit at stage 8: grid mode acks `&.{}` on purpose). capturePrevious only when transitions are on. `.tick` stays as the one-frame step `advance` and the unit tests use, so every curve and every asserted number is unchanged | pacing is corrected (144 Hz no longer ~25% slow, ssh no longer drifts): a bug fix allowed in this phase. PARDES_TEST_CLOCK (set by the snapshot harness): tty and the detached server answer a virtual clock that a timed-out wait moves exactly to the core's next wake, so goldens replay the same frame sequence on any machine load. Review fixes: tty's wake timer is interruptible (a newer, shorter request cuts short an older sleep); a wait (linger, hover delay) is jumped to its end in one step, never counted against the 240-frame catch-up cap; an overshoot under 1.5 ms after a step is let go, so 60 and 120 Hz take exactly one step per display frame. Known exceptions: the GUI still polls every 16 ms when idle (SDL events, gamepad, fs tick, smooth scroll depend on it; revisit at stage 8); a minimized GUI renders every stepped frame of a running animation (stage 8) |
| 2b | (on `fx`, feel-reviewed) Tween/Spring curves, settle rules, OKLab crossfades, new durations from §8.1 | a look change, kept apart from 2a so either can be bisected |
| 3 | paint functions take `s: *Surface`; delete the swap hack | mechanical, wide |
| 4 | PLACE: Region list built once; renderPane/tags/notices read their rects from it | wait for tag→Text; the refactor is already replacing BOX_H with `pane.tag_rows` and `p.tagTop/bodyTop(pane, r)` — PLACE absorbs those into the region rects |
| 5 | JOIN: paint tags/notices/headers once into layers, copy into grid; the grid wins where the copies disagree, and each disagreement is listed for a later decision | goldens are the oracle; wide-grapheme re-clip at the edge (§3.2) |
| 6 | body layer joined the same way (paint once, copy visible rows) | riskiest join; A/B the old double paint in a temporary test with tag_bottom on and off, then delete it |
-| 7 | Layer merge (TagLayer + BodyLayer), wire v8, web accessors | after the other agent lands; touches mouse hit paths; breaks the macOS shell's layer ABI (accepted: macOS build ignored for now) |
-| 8 | GUI draws from regions: role tiers with track groups in tiers 2-3, page cover, hard-edged snapped decor for rules/rails/grips (pane chrome in the pane's tier), per-instance clip; delete inference functions, `transient_on` and `mark_hover` (breaks macOS glass hover; accepted, macOS ignored for now) | stage-0 PPM goldens byte-identical (possible only because hard decor is snapped and not anti-aliased); pane chrome now also shows during transitions, which is the one allowed visible delta, listed |
-| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) |
+| 7 | Layer merge (TagLayer + BodyLayer), wire v8, web accessors | after the other agent lands; touches mouse hit paths; breaks the macOS shell's layer ABI (accepted: macOS build ignored for now); done: one `Layer` (src/Layer.zig) with `rows` (0 = no layer) and a `cursor{x, y}`; a tag of N rows is ONE layer of N grid rows (`tagHit` answers the row as `line`, `bodyHit` keeps its meaning), so the per-line layer bases are gone. Wire v8 ships rows, cursor y and the region list in the one bump; v7 and v9 peers are refused in both directions (tests). web: `tag_layer_value` 11 = rows, 12 = cursor y, and app.mjs lays every row. macOS: its Zig side compiles against `Layer`, but pardes.h still sees one row per tag layer (a taller tag shows its first row there) |
+| 8 | GUI draws from regions: role tiers with track groups in tiers 2-3, page cover, hard-edged snapped decor for rules/rails/grips (pane chrome in the pane's tier), per-instance clip; delete inference functions, `transient_on` and `mark_hover` (breaks macOS glass hover; accepted, macOS ignored for now) | stage-0 PPM goldens byte-identical (possible only because hard decor is snapped and not anti-aliased); pane chrome now also shows during transitions, which is the one allowed visible delta, listed Done: the frame is drawn in groups (makeGroups): tier 0, one group per track in paint order (tiers 2 and 3), tier 4 as two groups (the notices; then the guides and the debug box, which the core paints over them), tier 5 (bar cursors), each drawing cells, images, decor. Decor is every rule, rail, thumb, grip mark, spine, the workspace and column rules, the bottom band, notice rules and bar cursors: whole-pixel rects drawn by `decor.frag` over `ui.vert` as cell instances (colour in the ground, coverage in fg.r, blended like the overlay), so each carries its track's transition and clip; a closing pane's comes from the last frame's regions (`Surface.previous_regions`). Only `solid` exists yet: `decor.vert` and the other kinds (§5.1) come with the first effect that needs them. Deleted: taglineBaseRgb, topbarPaneBorderHeight, bottomTaglinePresent, frameChromeBg, cellBackgroundIs and the rail inference, paneGripCell's scan, transient_on, PaintPlan; one cover map (coverFrame: layer, grip and offset, focus, anchor, floating) is marked from layers and regions once a frame. New regions: `column` (spines, anchors, the focused column's tint), `guide`, `debug`; `Surface.chrome` is the palette, on the wire in v8 (not yet shipped, so no bump), and an attached GUI draws the same chrome (test). Per-instance clip (`CellInstance.clip_*`, 104 → 120 bytes an instance, ~15% more upload a frame) replaces the vertical transition's scissor. While any pane moves, opens or closes, notices stay in their own panes' groups, under whatever slides over them; tier 4 holds them only when nothing moves. Goldens: 01-12 byte-identical; 13-17 differ only in pixels past the grid (with a picture on screen the image pass left the scissor at the grid's size, cutting every rule end, rail foot and band that runs into the leftover pixels; they now run to the edge as in every scene without one); 16-debug shows the debug box (it was drawn from the grid under the source's context-row layer, so the GUI never showed it there); 18-mid-transition is new (virtual clock, PanelSlide Newcol with the picture, frame 6 of 12: chrome moves with its pane). Other deltas, outside the goldens: a guide over a tag or a context-row body now shows; with WindowOpacity < 100 a layer's bar cursor is ink like the grid's; an attached GUI gains the focus tint, notice rules, spines and the theme's page and caret colours. Deferrals fixed: the GUI sleeps when idle (SDL and queue events wake it; a smooth scroll, a gamepad, the test feed, a shell's kill deadline, a present to retry still poll), and a minimized or occluded window sleeps through animation. Tracy, `gui frame build`, 200x60, terminal output plus scrolling, ReleaseFast, two interleaved runs of ~180 frames: before 992/1015 µs median, after 989/987 µs |
+| 9 | post chain: glslang, Shadertoy prefix, ping-pong, ShaderAnimation, redraw levels A/B (§5.5); bundled CRT without barrel; delete scene_effects/crt.zig/crt.frag | first visual change (the CRT look) Done: src/gui/Post.zig, shaders/post/ (prefix, crt, ripple, glitch), post.vert. glslc (decision 3) with compileGlsl's flags, off the frame path; ghostty's Uniforms matched offset for offset by a test against a copy, and the prefix's block checked name by name; ghostty's test_shadertoy_crt and _focus compile, _invalid fails with glslc's words. Level A measured (Tracy, 200x60, Crt, idle 10 s): 215 chain-only redraws at 34 µs median CPU each, 6 core frames (957 µs) in the same time; the core's clock stays idle. Input is identity (test: the corner click with Crt on). The three bundled passes were rewritten: Crt without barrel or tube edge, scanlines and mask that average to one, dithered vignette; Ripple as rings in pixels, eased in, lit on their slopes in linear light, dithered; Glitch as short eased bursts of torn bands with an RGB split, keyed to iTime. Before/after stills for the user's judgement. |
| 10+ | `fx` bookmark: G1–G3 → feel review → G4 bundled → G5 lapis theme → P2s; tty T1–T2 → feel review → removals of audited effects (after user decision) → T3–T5 | each effect its own change, default off |
Tests to add: fixed-clock animation tests (Tween/Spring closed form, retarget
@@ -842,6 +852,26 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach.
4. The macOS shell breaks at stages 7 and 8 (Layer ABI, mark_hover). Accepted,
since the macOS build is ignored for now.
5. Stage-0 GPU goldens gate locally only.
+6. Revisit at the first visual stage (from stage 5): the notice layer lost
+ its hover word to keep the grid's behaviour. Notice words are Look/Exec
+ targets, so bring the hover affordance back in BOTH the grid and the
+ layer then. Done right after stage 8: a notice's word under the pointer
+ is lit as a header's is, in the layer and so in the grid's copy joined
+ from it. The same change deleted `mark_hover`, the `Cell.hover` bit and
+ the per-frame reset of it (never set since stage 5; macOS loses its
+ glass hover rect, accepted with open point 4).
+7. Revisit at stage 8 (from stage 6): a body without context rows has no
+ layer (paint once, straight onto the grid). If the GUI is to read every
+ body from a layer, give every body one then and measure the copy. The
+ context-row path also still builds the body's text twice (a pre-pass
+ with body_rows 0 decides the context rows, renderBody builds it again at
+ the layer's rows); the paint is single. Measure both there. Decided at
+ stage 8, measured (Tracy, 200x60, TreeContext on a scrolled source,
+ ReleaseFast): the pre-pass text build is 5 µs median and the layer's
+ copies 10 µs per context-row body per frame. Kept as they are: nothing in
+ the GUI reads a layerless body from anything but the grid, which is
+ exact, so a layer for every body would be ~10 µs a body a frame for no
+ pixel.
## 15. Questions for the user
@@ -873,3 +903,4 @@ goldens; ssh/tty byte budgets; wire version bump breaks mixed-version attach.
5. The animation pacing fix goes into the no-visual-change phase.
6. Cursor blink is on by default.
7. The bundled CRT has no barrel distortion, so clicks stay exact.
+8. (After trying them live) Ripple and Glitch are removed entirely; Crt stays.
diff --git a/docs/web.md b/docs/web.md
index 3a68f958..bbbed8a3 100644
--- a/docs/web.md
+++ b/docs/web.md
@@ -42,7 +42,7 @@ shells, and two comptime capability flags say so once each. It exposes no
`PanelAscii`, `PanelVertical`, `PanelEdges`, `PanelFall`, `PanelWave`,
`PanelCurtain`, `PanelScramble`, `PanelType`) because
`capabilities.panel_transitions` is `pardes.hosted`, and no
-`Crt`/`Ripple`/`Glitch` because `capabilities.scene_shaders` is
+`Crt` or `Shader` because `capabilities.scene_shaders` is
`platform == .gui or platform == .macos` (`builtins.capabilities`; the words
themselves carry those availabilities in `config.Runtime.settings`).
Applying either faithfully would require a second canvas renderer and give up