<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/test/fs_namespace.zig, 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-01T13:46:01Z</updated>
<entry>
<title>A dump no longer embeds a picture or a PDF that is on disk: Restore reads it from there, as a clean file's text is</title>
<updated>2026-10-01T13:46:01Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-10-01T13:36:27Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=860796aa8832bb9bf32b2770bc0584b962bb51b7'/>
<id>urn:sha1:860796aa8832bb9bf32b2770bc0584b962bb51b7</id>
<content type='text'>
Every Dump carried each image's and PDF's bytes in base64 (a 900 KB PDF
made a 1.3 MB dump), and a Restore showed that copy however the file had
changed. One on disk now goes by its path alone; one not on disk (no
file behind it) still has its bytes kept.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Restore reads a clean file from disk, and an unsaved one whose file changed since the Dump is said changed, its first Save asking before overwriting it</title>
<updated>2026-10-01T11:53:11Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-10-01T10:56:48Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=6c82d06cd2a15d724b8e82e1128fa15dc9e35d05'/>
<id>urn:sha1:6c82d06cd2a15d724b8e82e1128fa15dc9e35d05</id>
<content type='text'>
A Restore put back the dump's copy of every file pane and armed the
watch with the disk as it now was, so a file changed on disk after the
Dump came back as its old text, marked clean, and the next Save wrote
the old text over the new without a word.

A clean file pane's text is now not in the dump: Restore reads its file
(an older dump's copy is read past too, and used only when the file is
gone). An unsaved pane keeps its text once, base64, the plain copy beside
it dropped, with the hash of the file it was read against
(`disk_hash`); a Restore that finds another there says the file changed
on disk, and Save asks once before overwriting it, as for a change seen
while it was open. file_watch.zig's own tests were never collected (no
test root imported it): main.zig's test block does now, and the restored
watch test says what a clean restore now reads.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Mount dials its peer at once and fails if none answers; a peer out of reach is said as `dial failed: &lt;reason&gt;`</title>
<updated>2026-10-01T03:12:16Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-29T10:27:13Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=22fb8b6147f7fb72407af80c36d5e99e4664fea2'/>
<id>urn:sha1:22fb8b6147f7fb72407af80c36d5e99e4664fea2</id>
<content type='text'>
Mount took any well-formed dial and nothing was found out until the
first look through it, which then said `look: look: dial`. Mount now
probes the peer (version, attach, its root) with the turn out, and fails
its write, mounting nothing, when none answers; the dial errors read as
words, `no answer`, `timed out`, `hung up`, and a look through a peer
gone since names its path: `look: /n/peer/f: dial failed: no answer`.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>A Restore answers its writer before hanging up, and the log records dumps and restores</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T15:44:57Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=6fc9f416938b59b665b1fddc052402770a21cc3f'/>
<id>urn:sha1:6fc9f416938b59b665b1fddc052402770a21cc3f</id>
<content type='text'>
A client that wrote Restore saw its connection cut with no answer, and could
not tell a Restore from a crash. The listener now lets the writer's answer out
before the cut, and cuts only the old editor's connections, refusing their
requests meanwhile; a client that dials during it is the new editor's and
stays. Dump logs 'dump &lt;path&gt;' and the restored editor's log 'restore &lt;path&gt;'.
Keeping connections across a Restore was weighed and left: the fids name the
old editor's panes and opens, so it would mean carrying serials and open
records into the new one, where acme's Load only adds windows. tty's Restore
also closed its shells' ptys without reaping them; it retires them now.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Give the pipe its own fields in Pipe</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T01:46:26Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=71a9f04342493325c10b1b9bc122a88d41143782'/>
<id>urn:sha1:71a9f04342493325c10b1b9bc122a88d41143782</id>
<content type='text'>
Not a pure move: state moves. Pardes's `pipe_seq` and `pipe_wait` become
Pipe.zig's own fields `seq` and `wait`, and Pardes embeds one as
`pipe: Pipe = .{}`. pipeRequest reads only the request in flight, so it now
takes `pipe: *const Pipe` and the four shells call
`core.pipe.pipeRequest(id)` (their Pipe imports go away again); the other
pipe functions still need the panes and keep `p: *Pardes`, writing
`p.pipe.seq/wait`. Field reads in dump.zig, the shells' tests and two test
files follow (`core.pipe_wait` becomes `core.pipe.wait`). No behaviour
change.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Move saving and loading a whole editor out of pardes.zig into dump.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T01:34:56Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=7ba54de4bd498c2a56ea3d0d43c88356759f7b1f'/>
<id>urn:sha1:7ba54de4bd498c2a56ea3d0d43c88356759f7b1f</id>
<content type='text'>
Pure move, no behaviour change (acme keeps dump and load in rows.c):
dumpState, restore, initFromDump and initDump go verbatim to the end of
dump.zig, after the dump format they read and write. Inside dump.zig the
moved code's `dump.` prefix drops, so `Pane` there is the dump record; the
one editor pane it names is spelled `pardes.panes.Pane`, and its other
`panes.X` references become `pardes.panes.X` because dump.zig's own tests
use `panes` as a local name.

The methods become free functions taking `p: *Pardes`: `p.dumpState()`
becomes `dump.dumpState(p)`, `core.restore(bytes)` becomes
`dump.restore(core, bytes)` and `Pardes.initFromDump(..)` becomes
`dump.initFromDump(..)`, in pardes.zig, the shells, layout.zig,
Terminal.zig, builtins.zig and the tests (38 receiver rewrites plus the
initFromDump calls). The tag-tail restore helpers stay with the tag code.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Move looking out of pardes.zig into look.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T01:27:42Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=82551f77833a09b313bd3e58371582abd1aedcc4'/>
<id>urn:sha1:82551f77833a09b313bd3e58371582abd1aedcc4</id>
<content type='text'>
Pure move, no behaviour change (acme keeps this in look.c): expanding the
word under a click (ExpandedWord, expandedWord, expandedSel, cursorWordSel),
the n/N walk (LookFrom, lookEdge, lookStand, lookWalkPanes, lookPast,
wholeRowSpan, lookSpanIn, lookWalk, landLookSpot, noteLookSource,
armLookWalk), search results (Search, SearchStart, submitSearch,
lookFirstHit, runSearch, searchStep, jumpResult), the look-hover preview
(LookHoverWait, LookHoverPreview, FileWordSpan, PdfWordPreview,
invalidateLookHover, cancelLookHover, lookHoverPane, noteLookHover,
refreshLookHoverFromRaw, advanceLookHover) and lookAt with its targets
(focusPaneLine, selectSpan, openPaneTarget, focusPaneByPath,
clearNavigationSelection, resolveLookTarget, locationText,
canonicalLookLocation, pdfLinkLocation, followPdfLink), with five tests, go
verbatim to the end of look.zig after its word and target resolution.

The methods become free functions taking `p: *Pardes`; their 143 call sites
change from `p.lookAt(..)` to `look.lookAt(p, ..)` (tests reach them as
`pardes.look.x`). Inside look.zig the moved code's `look.` prefix drops.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Answer 9P on the connection's task, so a session can open its own tree</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-22T14:15:46Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=31cb659ded4cf50af5903fc107f8c868ee3c7311'/>
<id>urn:sha1:31cb659ded4cf50af5903fc107f8c868ee3c7311</id>
<content type='text'>
The editor's loop was the only thing that could answer a 9P request, which
made the editor's own syscalls through a mount of its own tree -- a Look at
/mnt/9p/pardes/&lt;me&gt;/anything under a `9ns --mntgen` view, a Save into it --
requests only the blocked loop could serve. The name-based refusal that
followed (ownMountSuffix) and the in-process routing of a mount of oneself
(Client.sameSession) were patches over that, and both are gone, with the
mailbox that shipped every request to the editor's thread.

One rule replaces them, `pardes.turn`: the core is single-threaded, the
editor's thread has the turn by default and gives it up in two kinds of gap
-- while it waits for input and while a step of it is out in a host syscall
-- and a cloud9 connection task takes it in those gaps to answer. `out`
counts the steps that are out, from any thread: while one is, the core reads
consistently but that step still holds pointers into it, so a request that
would change a pane (a write, a truncation, an rmdir) is parked in the
engine and retried when the turn is next given up with nothing out, and the
editor's own wake waits for the count to reach zero. It is never a write of
its own that a step waits on out there -- writes come from a shell
performing a save between steps -- so a parked request is never the
syscall's own, and making a pane or rendering a screen need not park:
every yield sits before its step's mutation, so the layout and the surface
are whole under it. A changing request that queued effects is answered
once the editor has performed them (`echo Save &gt; exec` returns with the
file written, as acme's `put` does), and it settles the way a step does,
because without that a /log reader waited for the user's next keystroke.

Every host syscall on a user path has to give the turn up, not fs.zig's
alone: the first end-to-end run hung in `inotify_add_watch` performing the
new pane's watch effect. PDFs and images are read whole at open, so no
draw goes out into the host. The core's allocator takes its fixed buffer
through the lock-free interface, since a connection task allocates while
the editor's thread is out in a syscall that allocates too. A Restore puts
the replacement in first and releases every task waiting on the old core.

cloud9 (pinned at eb1a104) parks an open, a truncating wstat, a clunk and a
remove on `again`, not only reads and writes, and answers a parked job
whose fid was clunked without asking the backend.

Verified: test/selfmount.py runs the editor under `9ns --mntgen` and
Looks at, reads and Saves its own tree through the mount; a unit test pins
that a change parks while the editor is out mid-step and lands when it
rests, while a read is answered in the window. 9P over the Unix socket
against a tty session, same machine, Debug builds: a read of /index 278us
-&gt; 61us, a truncating body write 1184us -&gt; 609us, exec Save 718us -&gt; 583us;
the gesture benchmark is unchanged (geometric mean 0.997 over 53 cells).

Also from the reviews: a notice chip over an image or PDF pane was painted
out by the picture drawn after the cells, so pictures give up the rows; in
the GUI a tree-sitter context band painted over the chip, so body layers
are emitted first; a message is one row of printable text, its 256-byte
cut never leaves half a glyph, and one wider than its pane keeps its tail
(the file name, the reason) rather than its head.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Plan 9 idiom for the control filesystem, and the regressions a624a56 left</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-21T23:07:43Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=9070942b29bd10dddcdecdb0e88ba0fb40608467'/>
<id>urn:sha1:9070942b29bd10dddcdecdb0e88ba0fb40608467</id>
<content type='text'>
The 9P tree stops being a command language wearing a filesystem. /new
created a pane as a side effect of a *read*; it is now Tcreate in /pane,
with Tremove to close, which cloud9's engine has always supported and the
editor never declared: tree.zig now says
`features = .{ .create = true, .remove = true }`. Eleven pane ctl verbs
become files that can be read as well as written -- dot, limit, dirty,
mark, scroll, look, exec -- leaving ctl with `get`, the one verb no file
would say better. Root /ctl splits into a read-only /status and the
/look and /exec files whose write IS the click. stat carries real sizes
where it used to answer 0, and qid versions track a pane's revision, so a
client can poll for change without re-reading the body.

Commit a624a56 moved raw-tty keys to an early-return branch that knew only
Ctrl-B and bare Escape, and in the same edit deleted the paste branch below
it. That cost Shift-Escape (the unconditional way out of tty mode) and both
paste chords: Ctrl-V and Ctrl-Shift-V reached the child as keystrokes, so an
agent CLI running in a pane took Ctrl-V for its image-paste binding and
answered "No image found in clipboard". Both are restored, with tests.

Nested detection was not subtly broken but deleted: 60367d8 removed
nested.zig's process-ancestry walk and left "am I inside pardes" derived from
PARDES_FORWARD_LOOK, which read "0" both for --nested and for "the listener
did not come up". PARDES_PID now answers that question on its own, checked
with kill(pid, 0); PARDES_9P and PARDES_PANE answer how to reach it; the flag
is gone. The posted-9P registry also self-heals now -- a session that aborts
cannot unlink its own socket, so posting sweeps entries whose target refuses
a connection, symlinks only and on a definite ECONNREFUSED only.

Elsewhere: tty scrolling is sticky-bottom, following new output only from
the last row, with typing and entering raw mode snapping back to live; the
boot layouts are a Boot enum instead of a chain of ifs, and the bare tty
startup (Boot.tty, which main.zig names) opens an empty text pane under the
shell while tests keep Boot.tty_shell; builtins announce themselves on the
message row under a Verbose setting that is on by default; Config prints
each setting the way you would type it back, so WindowOpacity 70 rather than
"WindowOpacity: 70%"; LocationsConfig opens its window only when called bare;
every tagline puts the word that closes the thing last, and a column now
outlives its panes -- closing the last one leaves an empty pane, and only
Delcol, newly on the column tagline, takes the column away.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Flatten the 9P control tree and move it out of fs.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-20T03:36:50Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=c1990b3e6e196ad41379aa432bf3ccca8a65d9f0'/>
<id>urn:sha1:c1990b3e6e196ad41379aa432bf3ccca8a65d9f0</id>
<content type='text'>
The served tree loses the self/ level: /index /ctl /new /log /screen
/listeners /pane/&lt;n&gt;/... /os, with /src only in -Dembed-sources=true builds
(default off, on for esp32p4). ctl speaks the editor's own language with two
lowercase verbs, look TEXT and exec TEXT, plus acme's addr verbs; the new/
factory directory becomes one clone file; cons is gone (exec Msg); name and
sel are files; stats report real lengths, modes and mtimes; /log streams
pane new/del/rename/save events. The tree code lives in src/ninep/
(tree, pane, ctl, addr, pty, events, screen, sources); fs.zig keeps host
access, mounts, resolution and find/grep. Same engine and transports.
README (fs-help.txt) and docs rewritten; tests updated and extended.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
