<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/src/ninep/screen.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-01T03:12:17Z</updated>
<entry>
<title>Terminal.zig is terminal.zig: a file of functions and no fields takes a namespace's lowercase name</title>
<updated>2026-10-01T03:12:17Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-29T23:56:14Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=04b047fb366ea2c595708fb62467f690fce4b9ec'/>
<id>urn:sha1:04b047fb366ea2c595708fb62467f690fce4b9ec</id>
<content type='text'>
The last of the deferred renames, now that the tty and theme agents have landed: panes.terminal at its importers, the alias lines in Text.zig and File.zig and panes.zig's own uses following. dump.zig's Terminal struct, a dump record, is not this. The served sources list and docs/design.typ name the new file. test/perf.zig's references change, so the three perf baselines take its new harness id with their numbers as recorded. No behaviour changes.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>A terminal's body reads as logical lines, the last one ending in a newline</title>
<updated>2026-10-01T03:12:15Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-29T03:45:33Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=e563be42a5b3e9243e2c01d34cf8e94d2047b753'/>
<id>urn:sha1:e563be42a5b3e9243e2c01d34cf8e94d2047b753</id>
<content type='text'>
A terminal's body came back as the screen's rows, a long line cut where the
terminal wrapped it and the last line with no newline. The body (and a
terminal's Save, and the length its ctl reports) now joins wrapped rows by
ghostty's wrap flags, as pty/run's output does, and ends the last line with
a newline.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Motion flavours differ by design, and drive notice drops and pane moves</title>
<updated>2026-10-01T03:12:15Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T23:31:56Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=c5c2baf21bba0fb8f66e7d2ee096818d6bd5a8e0'/>
<id>urn:sha1:c5c2baf21bba0fb8f66e7d2ee096818d6bd5a8e0</id>
<content type='text'>
The flavours looked alike because Lift, the only motion that read them,
moves a shadow a few pixels: overshoot and wind-up were sub-pixel. By design
now: crisp leaves at full speed and never passes its mark; smooth (the
default) eases in and out in twice crisp's time, a slide arriving in 208 ms
(inside §8.1's 220); bouncy passes its mark by ~30% and settles twice;
playful winds up, passes by ~40%, and stretches and squashes, text included
(the user's choice). Small motions are exaggerated by the flavour's gain.

Motion gains launch, gain, span and norm, and ease(u)/speed(u) for moves of
fixed length. The notice drop follows ease() over its fall (alpha never
behind its position) and stays inside its pane's body past its row. Panel
tracks carry the flavour in Track.motion (the old padding byte; macOS header
name follows); opening and moving slides, zooms and rises follow it over
span x their frames; landed, a pane is exactly its target; closing keeps
its own exit. A pane past its mark never takes a neighbour's click; a
rising one stays in its box.

Tests: each flavour's lift trajectory distinct; flavour character; exact
landing bit for bit; a stretched pane's drawn cells map back; the overshoot
hit rule; the vertical clip at peak; legacy tests pinned to crisp. GUI
golden 18-mid-transition changes (the retuned smooth slide at frame 6).

Shared files: src/animation.zig, src/Presentation.zig, src/Messages.zig,
src/macos.zig, src/config.zig, src/pardes.zig tests, src/ninep/screen.zig
test, docs/effects.md, docs/config.md.
</content>
</entry>
<entry>
<title>Move the frame into draw.zig and the transitions out of layout.zig</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T15:57:47Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=30c2c8a154e770b3920bcfd7d553825fe9182bf4'/>
<id>urn:sha1:30c2c8a154e770b3920bcfd7d553825fe9182bf4</id>
<content type='text'>
Pure moves, no behaviour change. draw.zig holds the whole core frame in
order: render, the pane, tag, header and notice painters it calls, and the
character-effect composition (Pardes.render stays a declaration alias).
Presentation.zig is the panel presentation state as a file struct, and
animation.zig the easing curves, transition kinds, tracks and boxes, the
character effects' sources and the generic displayed-value transition, all
of which lived in layout.zig. layout.zig keeps only layout.
</content>
</entry>
<entry>
<title>Inline the one-line helpers in src/ninep at their call sites</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-28T12:55:21Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=f5c48e98b80ad6517c756e8fd15c976d4a2d1894'/>
<id>urn:sha1:f5c48e98b80ad6517c756e8fd15c976d4a2d1894</id>
<content type='text'>
reads, paneFileNamed, topFileNamed, serialNamed, attrReply, makePane,
events.pending, mtimeOf, versionOf, dirOf, cellOf, shiftBy, flagOf,
stageIndexLine, ctlLen, listenersLen, activeSerial, fontName and
Serials.has each wrapped a line or two used once or twice; the logic now
reads where it is used. Helpers that do several things or are shared
widely (bodyOf, fileOf, dirtyOf, nameOf, kindOf, clip, readRange,
readFlag, pty's answer and waitingRun, nextSerialAfter) stay.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Keep what each open holds in one table of open records</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-27T23:20:27Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=7dc3891ed574d6e96548f592b09b0f8abc87ae2a'/>
<id>urn:sha1:7dc3891ed574d6e96548f592b09b0f8abc87ae2a</id>
<content type='text'>
Snapshots (and /log's cursor), runs, and the reader_handle constants for
event and pty/data each reused the open handle and each validated
handle and node on its own. Now p.fs.opens is one table of 64 records,
each the node it was opened on and a tagged union of what it holds, like
lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one
lookup (openOf), one release, ENFILE when full. A held read lives in
its open's record, so it goes with the release. Opens that hold nothing
answer handle 0 and take no record.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Make pty/run and /log streams, and take refused lines back cleanly</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-27T21:20:06Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=594970f18dee92308fd1554663cc408cda4d102c'/>
<id>urn:sha1:594970f18dee92308fd1554663cc408cda4d102c</id>
<content type='text'>
Review fixes to pty/run and /log:

- Both are streams with a per-open cursor. A shell's exec 3&lt;&gt;file shares
  one offset between write and read, so cat &lt;&amp;3 after echo make &gt;&amp;3 asked
  for offset 5 and got "0" instead of "exit 0"; log after follow lost its
  first bytes the same way.
- A run is accepted only in pardes's own tagged input phase, so a nested
  shell's prompt (ssh, a shell with its own integration) is never taken for
  this shell's.
- A line the shell refused is taken back so the next finds the prompt
  clear: bash's continuation prompt (now tagged k=c) gets Ctrl-C and the
  answer waits for the fresh prompt; a bash syntax error (no C, but a D) is
  answered at once; fish's kept line gets Ctrl-U (a Ctrl-C sent while fish
  redraws is lost). fish's right prompt no longer reads as typed input.
- bash marks a command's start from PS0 on bash 4.4+, not a DEBUG trap, so
  a user's own DEBUG trap (bash-preexec, atuin) survives. bash's own job
  notices now show as in plain bash (ttyfollow golden).
- 32 run slots; a new line on an open starts a fresh answer.

Checked live in bash, fish, and fish with a right prompt.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Wake reads waiting on /log, event and pty/data; one reader per consuming file; pty/ctl reads back</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-27T20:52:31Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=f4412d6dfdb1e2bd8549a7edba785ee242fd38f2'/>
<id>urn:sha1:f4412d6dfdb1e2bd8549a7edba785ee242fd38f2</id>
<content type='text'>
A read parked on a queue was retried only when some unrelated write had
to wait, so a follower of /log (and a reader of event or pty/data) slept
until then. Pushing a record now marks the turn parked, and giving the
turn up wakes them (measured: stuck past 3 s before, 0 s after).

event and pty/data consume what they read, so a second open for reading
is refused with rio's "file in use" (EBUSY through 9ns); writers still
get in, and pty/data queues output only for an actual reader. pty/ctl
reads back "winsize C R", in the words it takes.

/log fixes from review: a record longer than a read comes in pieces (a
shell read loop failed on long lines), every repeated message is logged,
a record bigger than the ring is cut to fit instead of emptying it, the
ring is reserved at boot so recording never allocates, and panes present
at boot are recorded first.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Record /log whether or not anyone reads it, with the editor's messages in it</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-27T20:41:55Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=542dd489149e33915d19878a277e23b3a9c0b070'/>
<id>urn:sha1:542dd489149e33915d19878a277e23b3a9c0b070</id>
<content type='text'>
/log is now one ring (64 KiB, 4 KiB on the board) that records new, del,
rename and save, and a msg line for everything the editor says. An open
freezes it, so cat log shows what happened lately and ends; writing
follow to that same open makes reads past it wait for newer records, and
a follower the ring outran reads lost N first.

The message log keeps the pane's serial, not its reusable slot.

A test now fails when the tree serves a file /README never mentions; it
caught pty/status, now documented along with typing through pty/data.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Ask from the keyboard which neighbour a closed pane's rows go to</title>
<updated>2026-10-01T03:12:14Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-24T13:06:08Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=3f6ad1bc30570348297ae4aa6d1fd676de229a09'/>
<id>urn:sha1:3f6ad1bc30570348297ae4aa6d1fd676de229a09</id>
<content type='text'>
Del takes a side: `Del k` gives the closed pane's rows to the nearest
expanded pane above it, `Del j` to the one below, each falling back to the
other side when it has none. DelAbove and DelBelow are those two lines, with
no path of their own under SPC.

A bare Del started from a key -- SPC d, Enter on the tag word, a row run from
an output buffer -- on a pane with expanded panes both above and below asks
instead of guessing. The question is a prompt like Save's or a search's
(Pane.Prompt.del_side), so it is painted on the pane's notice band by the
same path, and the next key answers it before any mode sees it: k or Up,
j or Down, anything else keeps the pane, as does a click. Only a key press
sets Pardes.can_ask, so a click, a 9P ctl or event write, a startup line, a
restore and a shell exiting all close the pane at once, the rows going
where layout.absorbVWeight has always sent them. A collapsed pane is not
asked about (it has only a tag row to give), and collapsed neighbours are
passed over (layout.expandedNeighbor, which Collapse now uses too).

removePane and absorbVWeight take the recipient; every other caller passes
null. Three scripts that closed a middle pane with SPC d answer k, which
is where the rows went before, and their goldens are unchanged. delask.snap
covers the question, Esc, j, a clicked DelBelow and a clicked Del.

Co-Authored-By: Claude Opus 5.5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
