<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/next-steps.txt, branch main</title>
<subtitle>Pardes</subtitle>
<id>https://git.0x4200.cafe/pardes.git/atom?h=main</id>
<link rel='self' href='https://git.0x4200.cafe/pardes.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/'/>
<updated>2026-10-01T03:12:17Z</updated>
<entry>
<title>The reference is fs.md's per-file semantics, errors and limits in Typst, with the settings table, and says what the pane ctl's Left, Right, Up and Down do</title>
<updated>2026-10-01T03:12:17Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-10-01T02:04:26Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=5e72bfc34cc97d12be5185930135b55c2eb001b4'/>
<id>urn:sha1:5e72bfc34cc97d12be5185930135b55c2eb001b4</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Refactor panes and filesystem; replace FUSE with 9P</title>
<updated>2026-09-07T16:59:12Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-06T21:11:36Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=60367d8fe23f6af98ec28e3cf6c2094dfe332df0'/>
<id>urn:sha1:60367d8fe23f6af98ec28e3cf6c2094dfe332df0</id>
<content type='text'>
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.

Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
</content>
</entry>
<entry>
<title>build: pardes builds without the ESP32-P4 toolchain checkout beside it</title>
<updated>2026-08-28T01:09:08Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-27T19:42:15Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=98b62629795e24de19f535f1072b8a25dcf9f018'/>
<id>urn:sha1:98b62629795e24de19f535f1072b8a25dcf9f018</id>
<content type='text'>
`build.zig.zon` named `../05-zig-p4` as a path dependency, and `build.zig`
`@import`ed it inside `if (-Desp32p4-firmware)`. But `@import` in a build script
is resolved when the SCRIPT is compiled, not when the branch that needs it is
taken -- so naming the package at all meant anyone without that sibling checkout
could not build pardes AT ALL. Not the firmware: the terminal shell, the SDL
shell, the tests. `zig build` failed with

    build.zig:1073: error: no module named 'zig_p4' available within module 'root.@build'

from a line inside an `if` that was false.

Neither escape hatch works for a PATH dependency, and both were tried rather
than assumed. `.lazy = true` is about FETCHING; a path dep whose directory is
absent is generated as a package with no `build.zig` rather than one marked
unavailable, so `b.lazyImport` -- which exists for exactly this and is what the
standard library says is to `@import` what `lazyDependency` is to `dependency`
-- reaches a `@compileError` instead of returning null. Making it a fetched
dependency instead is not available either: the toolchain has no remote.

So the duplicate goes. That build tree's firmware block linked an image the
toolchain repository already knows how to link -- its own build.zig has
`-Dpardes`, `-Dapp=&lt;root&gt;` and `-Dpardes-obj=&lt;path&gt;`, and its comments record
having learned this same lesson from the other direction, where nesting pardes's
~30-package graph under it broke every build there. The object is the seam: it
crosses by PATH and never by package, and each repository builds what it owns
the pieces of.

  zig build -Dplatform=esp32p4                    # here, no toolchain needed
  zig build -Dpardes                              # there, the console image
  zig build -Dpardes -Dapp=&lt;pardes&gt;/src/esp32p4_9p.zig   # there, the 9P image

For the second and third to work with no module map, `src/board9p.zig` and the
9P firmware root now reach the codec by PATH instead of through a named `ninep`
module that only pardes's own build.zig knew to inject -- which is also why the
root moved from `src/esp32p4/nine.zig` up to `src/esp32p4_9p.zig`, beside
`src/esp32p4.zig`: a path import may not escape its module's own directory. Both
files are now self-contained, and `zig test src/board9p.zig` works with no flags.

`-Desp32p4-port`, `-Desp32p4-prof` and `-Desp32p4-cpu-mhz` go with the block. An
option this build cannot honour is worse than no option, because it accepts the
flag and then ignores it; all three are spelled the same way in the toolchain.

Verified by moving ../05-zig-p4 out of the way: `zig build`, `zig build
-Dplatform=esp32p4` and `zig build unit-test` all pass without it. With it back,
the toolchain still links both images -- console 812,720 B, 9P 88,096 B.

next-steps.txt gains the six features the 9P chain shipped.
</content>
</entry>
<entry>
<title>An edited row keeps its colours, four copies of forkShell become one, and Esc stops recentring</title>
<updated>2026-08-27T12:47:39Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-26T21:58:37Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=29ac9be75fdcafbd7d05c15aa9eb8490d74caa98'/>
<id>urn:sha1:29ac9be75fdcafbd7d05c15aa9eb8490d74caa98</id>
<content type='text'>
## A terminal row's ANSI colours survive being edited

The loudest colour bug this editor had: one keystroke anywhere in a coloured shell row turned EVERY
column of it grey. `EditAnchors` anchored a buffer line only when it was BYTE-IDENTICAL to the shell
row it stood over, so a single differing byte dropped the whole row's colour projection. Worst shape
is invisible: append past the pane's right edge, where the text is clipped, and the row looks the
same and only its colour goes.

Anchoring is byte-level now. An edit leaves the row's own bytes at both ends, and being the same
bytes they keep the same colours; only what was typed has no cell under it, so only that takes none.
Live, on real `fastfetch`: a 32-column blue run split into 6 + 26 around one typed character.

Three defects underneath it, all found by machinery rather than by reading:

* A JOIN removes a buffer line while the buffer's covered span grows, so `lines == covered` and both
  aligned guesses — Nth line over the Nth covered row, and the same counted from the bottom —
  resolved to the SAME wrong row. Every untouched row below a join went plain. Anchoring is now a
  streaming monotone matching: one shell-row cursor that only ever moves forward, advanced once per
  buffer line, linear in the buffer where the version before it was quadratic.
* An EMPTY line is not evidence. Splitting a row makes one, it equals every blank row in the span,
  and left free to look ahead it claimed the blank row below the last output and took every coloured
  row in between out of reach of the lines that owned them.
* Reflow under a scrolled viewport. `PageList.getTopLeft(.viewport)` returns the viewport pin
  verbatim, x and all, while `PageList.pin` forces x to 0 — so after a reflow remapped a tracked pin
  into the middle of a row, the text pass dumped row 0 from that column while the colour pass paired
  the fragment with the row's FIRST cells. Row 0 wore its left half's colours until the pane snapped
  back to live output. `bodyText` dumps from column zero now, which is also what ghostty's own
  renderer draws.

Also here: DECSCNM (reverse video) was silently dropped whenever `tty_filter` was off, because the
raw path resolved a `.none` colour by role and never consulted the mode.

The test that found the first two is the one worth keeping: random editing against an ABSOLUTE
oracle — every row's own text names the colour it must have — because the differential oracle it
replaced was blind by construction. It skipped the edited row, which is the row the user is
complaining about.

## Esc returns to a pane without moving its view

Esc in body normal mode runs `Last`, "the pane you were in before this one", and that went through
`focusPaneLine`, which recentred a file on the target line unconditionally. So returning to a buffer
repainted the whole screen to show a line that was already on it.

`focusPaneLine` takes a landing now: `.center` for the three callers going somewhere you have not
been (a look target, a path a pane already holds, `@pN:LINE:COL`), `.keep` for Esc. `.keep` leaves
the view alone and lets `ensureCursorVisible` — which already existed and already scrolls by the
minimum into the `scroll_off` band — be the only thing that may move anything.

Not `line = 0`, which `focusPaneLine` already understands as "focus and touch nothing": a background
pane's view can move while you are away, because the wheel scrolls the pane under the POINTER and a
resize reveals no cursor, so the recorded cursor plus a minimal nudge is what actually gets you back.

Ctrl-o and Ctrl-i keep centring, and the asymmetry is structural rather than arbitrary: `Last` only
ever CROSSES panes, so the pane it lands on already holds the view you left it with, while `jumpBy`
can land in the SAME pane, where a long in-file jump would arrive on the very top or bottom row with
`scroll_off` lines of context on one side. Helix splits the same pair the same way — its jumplist
centres, its buffer switch does not.

One deliberate consequence: under `.keep` a PDF's page is not restored AT ALL, because a page reveal
IS that pane's view and a reveal of the page you are already on still snaps `document_scroll_y` to
that page's start, discarding where you had read to. When something moved the pane while you were
away — the wheel again — Esc leaves it where the wheel left it, and Ctrl-o is how you reach the
recorded page.

## host_io.zig: the machine-local half of a host, once

`host.zig` is the seam. The part of the answer that is identical on every host with an operating
system under it — fork a pane's shell, put bytes on a disk — was written FOUR times: in tty.zig,
gui.zig, macos.zig and detached/server.zig. What those copies had in common says what they were for:
all four were missing FD_CLOEXEC on the pty master, so in every shell pardes has shipped, a program
in one pane could read another pane's terminal.

One copy now, and the wire got smaller for it: `ServerMsg.spawn` is gone. A frontend never asked the
server to fork anything — the server has an operating system under it and forks through `host_io`
like every other host — and `decodeClient` lost the scratch buffer that message needed.
</content>
</entry>
<entry>
<title>host: the core owns the event loop; every platform becomes a vtable of optional methods</title>
<updated>2026-08-25T12:42:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-24T13:56:05Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=c3d0b84b7961ae26d2d654e7120821cc2d83d20d'/>
<id>urn:sha1:c3d0b84b7961ae26d2d654e7120821cc2d83d20d</id>
<content type='text'>
</content>
</entry>
<entry>
<title>add the transient message row</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-05T19:28:03Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=79a8980e46d8ed1f9ef7e3f4e44db7ee58e9f88e'/>
<id>urn:sha1:79a8980e46d8ed1f9ef7e3f4e44db7ee58e9f88e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>soft-wrap long lines behind a toggle</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-05T18:57:48Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=1891a51ed2b64eec3964d3af3807be90d08a14c2'/>
<id>urn:sha1:1891a51ed2b64eec3964d3af3807be90d08a14c2</id>
<content type='text'>
</content>
</entry>
<entry>
<title>boot a file argument alone</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-05T18:32:57Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=68d208e1a0ff37eaef4a2a97b04797a1a6df277c'/>
<id>urn:sha1:68d208e1a0ff37eaef4a2a97b04797a1a6df277c</id>
<content type='text'>
</content>
</entry>
<entry>
<title>theme the selection colors</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-05T18:05:34Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=6eaa81710fee8ee2191a0b967b069bb01952be2e'/>
<id>urn:sha1:6eaa81710fee8ee2191a0b967b069bb01952be2e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>queue the next four features</title>
<updated>2026-08-10T12:17:07Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-05T18:03:04Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=98199e573d678f6b29a5c122836ec393856d7bd8'/>
<id>urn:sha1:98199e573d678f6b29a5c122836ec393856d7bd8</id>
<content type='text'>
</content>
</entry>
</feed>
