| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
| |
size or a nominal 8x16 before one attached
forkShell sized a new pty in pixels (ptyWinsize), but the detached
server's resize set xpixel and ypixel to 0, and a pane is resized as it
is placed, so a program in a detached session with no frontend (yazi)
saw a pty with no pixels and drew no images. CSI 14/16/18 t were
already answered at the nominal 8x16 cell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
already running" and exits 1, with no error trace
The second session built its whole core before its bind failed, then
returned an error that main printed with a Zig error-return trace. The
name is now checked first: a live session's socket is said, and the
process exits 1 having made nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
browser and says so in a msg
With no frontend to send it to, the link was kept and nothing said,
so a script could not tell the look had landed or why nothing opened.
It still opens nothing on the session's own desktop, and now leaves
"look: <url>: no frontend attached to open it in a browser", logged
as a msg.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
name no session has is "no such session"
A detached session's core has no frontend loop to take an Attach, so
the word was asked and never answered: a ctl write of it succeeded
and nothing happened. The core now knows it is a detached session's,
and Attach there says why: bare names no session but itself, a name
with no session behind it is said so, and one that is a session is
told to be attached from a frontend.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
through pty/run goes from over 30 s to 11 s
Profiling the 50 MB run showed 70% of its time in draw.render,
recolorAnsi alone 43%, walking cells through PageList.pin. The detached
session drew a frame after every pty read, with no client attached to
see it. A host now says whether anyone watches (Host.VTable.watched). The
detached server answers whether a client is attached, and while none is
the core keeps the frame owed instead of drawing it. A /screen read draws
its own, and a client attaching gets one (hello sets needs_frame).
Debug, 80x24: 10 MB went from 7.36 s to 2.10 s, and 50 MB from over the
30 s client timeout (about 37 s by rate) to 11.08 s. What is left is the
terminal's own parsing and scrollback growth. A session a frontend shows
still draws after every read. Pacing those frames is the next step if
that case matters.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
host_io's, once, where the tty, the gui and the detached session each had a copy
writeFile, writeDump and dumpThemes were byte-for-byte the same in tty.zig, gui.zig and detached/server.zig but for how each reaches its core, and the gui's and the session's reapers the same but for a pid <= 0 against == 0 (a pid is never negative there). Each vtable entry is now a line calling host_io's. ttyTaken, fgName, killJob and ptyResize stay each shell's: they are already one call to host_io each, and what differs is how a shell reaches its pty (an optional with a file, an optional with an fd, a bounds-checked array). macos.zig keeps its copies (no macOS build here to check it). No behaviour changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
the post chain's builds) takes a type's name
The naming the split agreed on: a file with fields is a type, TitleCase, as Messages.zig and Layer.zig are. Its importers bind it as ShaderBuild; comments and docs/render-pipeline.md name the new file. No behaviour changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
is a real refusal or says why it cannot fire
A sweep for round 23's crash: a run's answer (pty/run) was bufPrint'd into 48 bytes with catch unreachable, so a foreground program's long name (macOS gives up to 32 bytes) panicked; it now cuts at the room, keeping its newline, in 96 bytes. The rest were numbers into buffers sized for them, a braille codepoint, pthread calls on the queue's own mutex, and pdf_view's resolved outline entries: each now carries a one-line comment saying why it cannot fire.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
| |
Each host closed the running shell before it forked the new one, so an exec whose shell failed left the pane with none and later runs answered error shell gone. A shell not there is now refused up front, and every host starts the new shell first, replacing the old only once the close-on-exec pipe says it ran; a failure there is only said (restartFailed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |\
| |
| |
| | |
cursor + faithful themes
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
chain
shader_build.zig takes the Shadertoy compile out of the GUI's Post: the
process that holds the core compiles each chain file behind the prefix on a
thread, watches its directory (file_watch's new shader slots), and on a
change reads and hashes each file, compiling only one whose bytes moved. A
failed compile keeps the last good SPIR-V and is said once; the same bytes
are never compiled or reported twice. A file is read with std, not fs.zig's
readFile, whose turn hand-off a worker thread does not hold.
A detached session compiles for its frontends and sends them the chain
(wire post: each pass's scene and level or the file's SPIR-V, and
ShaderAnimation) on attach and on every change, so an attached GUI runs the
same passes, levels and animation as a local one while still reading no disk
and running no program. The attached GUI describes its frames to the chain
from the session's chrome and redraws an animating chain on its own.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
spoken, so it logs no rename to the session's and back
Each host read /proc/<pid>/cwd right after forking the shell and at every
frame after; before the shell's chdir that says this process's directory,
a rename to it, then the real one a frame later, a rename back. A shell
given a directory is named by it already (newShell) and is no longer
asked at fork; and until it has printed anything, an answer naming this
process's directory is not taken.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |/
|
|
|
|
|
|
|
|
|
|
|
| |
started answers runs
The host could not start the shell (its chdir failed), and the pane stayed
with no pty: a run written to it waited for a prompt for ever. Tty now
refuses a directory that is not there, `Tty: <dir>: no such directory`,
before making a pane; and a terminal whose shell the host could not start
(Pardes.shellFailed, from each host's spawn) answers any run with
`error shell gone`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
| |
At 80x24 a script's third or fourth pane/new was refused for want of
rows. `size` still sets any other size, and a frontend attaching its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
busy said only that something ran. Where the host can tell (Linux, the
tty's foreground group's /proc comm, a new fg_name host call), it says
busy: <program> is running; elsewhere, busy as before. The GUI's host
gains the one small callback (render agent's file: gui.zig, fgName).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
A --detach session nobody is attached to stayed 80x24, too small for an
agent's panes, and nothing could change it. The root ctl takes size <cols>
<rows> while no frontend is attached (refused while one owns the size), and
a client that attaches later still sets its own (the server compares with
the core's size). Documented with the 80x24 default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A Dump into a DumpDir it could not write, or a Restore of a file that is
not a dump, answered a ctl write with rc 0 and only a message, and the
Restore warned about unsaved panes before it ever looked at the file. The
hosts' dump write now fails the waiting 9P write with EIO (the late failure
a Save uses), naming the path and the reason in the log, and Restore parses
the file before its unsaved-panes refusal, failing with `not a pardes
dump`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
With the line run under job control (the change before), the job running
has a group of its own, and Kill's SIGTERM to the tty's foreground group
stopped only that job: of `sleep 30; touch x` the touch still ran. For a
command pane the hosts now signal the shell's group as well, so the whole
line stops, as it did; a line typed at a prompt still loses only its
foreground job. The test forks a real pty for both halves: a background job
outliving its command and the pty's hangup, and a killed line not running on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A dogfood agent ran `exit 3` through pty/run: the reader got ENOENT, and
the log went straight to del. The hosts now read a shell's exit status at
its pty's end as they do a command's; a run waiting on the line answers
`exit 3` with what it printed, the log says `exit <serial> 3` before
the pane's `del`, and an open run still stats after its pane is gone
(cat fstats its input). Pins cloud9 f35b7ed, whose stat of an open fid names
its open. Writes to a gone pane's pty/data, held open or not, fail ENOENT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
names the path and why
The write answered ok and the pane stayed dirty, with `save: AccessDenied` on its message row: a script saw success. The failure now says `Save <path>: <why>`, and a 9P write that waited on the save fails with EIO and that text, logged as an err record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |\ \ |
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
Stage 8 of docs/render-pipeline.md. The frame is drawn in groups: tier 0,
one per track in paint order (tiers 2 and 3), tier 4 (notices, then guides
and the debug box), tier 5 (bar cursors), each cells, images, decor. Decor
(rules, rails, thumbs, grip marks, spines, the workspace and column rules,
the bottom band, notice rules, bar cursors) is whole-pixel rects through
the new decor.frag over ui.vert, so it carries its track's transition and
clip. A closing pane's chrome comes from Surface.previous_regions.
Deleted: taglineBaseRgb, topbarPaneBorderHeight, bottomTaglinePresent,
frameChromeBg, cellBackgroundIs and the rail inference, paneGripCell's
scan, transient_on, PaintPlan. One cover map (coverFrame) is marked from
the layers and regions once a frame. New regions column, guide and debug;
Surface.chrome is the palette, carried in wire v8 (not shipped yet), so an
attached GUI draws the same chrome (test). A per-instance clip replaces the
vertical transition's scissor.
Goldens: 01-12 byte-identical. 13-17 differ only past the grid: the image
pass left its scissor at the grid's size, cutting rule ends, rail feet and
the bottom band in the leftover pixels whenever a picture was on screen.
16-debug now shows the debug box, which a context-row layer had hidden.
18-mid-transition is new: virtual clock (PARDES_TEST_CLOCK in the GUI),
PanelSlide Newcol with the picture, frame 6 of 12.
Also: the GUI sleeps when idle instead of polling every 16 ms, and a
minimized or occluded window sleeps through animation. Tracy 'gui frame
build' at 200x60: 992/1015 us median before, 989/987 us after.
Shared files touched: pardes.zig (one export), detached/client.zig,
detached/server.zig, detached/wire.zig (all additive), gui.zig. Not touched:
Messages.zig, mouse.zig, tagline.zig, colors.zig, tty.zig, host_io.zig,
dump.zig, exec.zig, panes.zig.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
of rows; wire v8
src/Layer.zig merges TagLayer and BodyLayer. `rows` (0 = no layer) and a
cursor at {x, y}. A tag of N rows is ONE layer of N grid rows: tagHit answers
the row as `line`, bodyHit keeps its meaning, and the per-line layer bases
(TAG_LINE_LAYER_BASE, HEADER_LINE_LAYER_BASE) are gone, not aliased.
Wire v8, the one bump: tag layers carry rows and cursor y, and the frame
carries the placed region list. v7 and v9 peers are refused in both
directions (server test over both, a new frontend test over both).
web: tag_layer_value 11 = rows, 12 = cursor y; app.mjs lays every row.
macOS: the Zig side compiles against Layer; pardes.h still sees one row
per tag layer (accepted, the macOS shell is ignored for now).
No visual change: snapshot goldens and the 17 GUI goldens byte-identical.
Shared files touched: pardes.zig, Messages.zig, gui.zig, macos.zig,
detached/client.zig, detached/server.zig, detached/wire.zig (plus web.zig,
app.mjs, edit.zig, look.zig). Not touched: mouse.zig, tagline.zig,
colors.zig, tty.zig, dump.zig, exec.zig, host_io.zig, panes.zig.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
A shell now answers Host.now, its monotonic clock in ns, and the pump
advances core animation to it: one .tick per whole 16 ms frame since core
time last stood still. The same animation therefore takes the same time at
60, 120 and 144 Hz, over ssh and after a slow frame (the GUI's re-armed
clock ran them ~25% slow at 144 Hz; the tty's post-frame sleep drifted).
nextWake() lists every core animation in one place: a frame from now while
anything moves, the end of the wait while something only waits (a message
lingering, the look-hover delay), null when idle. Shells sleep exactly that
long, and a wait is jumped to its end in one step, so an 800 ms linger
costs one frame instead of fifty. An overshoot under 1.5 ms after a step is
let go: at 60 and 120 Hz every display frame takes exactly one step (a 16 ms
frame against a 16.67 ms vsync would otherwise double-step every 24th).
The six tick drivers are gone: tty's timer thread only times the wait,
interruptibly (a newer, shorter request cuts short a sleep still timing a
longer one); the GUI's
AnimationClock, web's JS tick bank (pardes_tick now takes rAF's timestamp),
the detached server's and the board's ticks; grid mode steps a virtual
clock straight to each wake. PARDES_TEST_CLOCK, set by the snapshot
harness, gives tty and the detached server the same virtual clock.
Every stepped frame is drawn. The old pump never drew the last frame of a
fade (a .tick asks for no frame, and the fade was over by the check), so a
theme switch stopped at 9/10 of the way until the next input: theme.golden,
themesel.golden and the GUI's acme-light scene move to the theme's true
colours, and nothing else changes. The frozen previous grid is captured only
while a panel transition is chosen, not on every idle frame.
|
| | | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
The watcher woke the host a second time 60 ms after the exit by its own clock
while the grace was counted from the reap: a host busy for 10 ms missed it,
and the tag said running for ever and Kill did nothing; and under load exit 0
could land before the last output. As decided, no timer: the exit is told
once it is reaped and the pty says nothing is left (poll: no POLLIN, and no
POLLHUP, which means the end of file is on its way behind the output), else
at that end of file, checked after each chunk of output. The tty host checks
inside its step so the frame shows it. The four hosts' copies are one
host_io.takeExits/commandEof, which close a told command's pty at its end of
file (the fd and the GUI's reader leaked when the exit came first). A
finished command pane whose pty a job it left still holds is not reused, so
that job is not hung up; and the reset before a reuse is SGR 0, not DECSTR,
which ghostty's stream does not implement.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |/ /
| |
| |
| |
| |
| |
| |
| |
| |
| | |
The signals' default action ended the process where it stood, leaving
pardes-9p-*.sock and pardes-detached-*.sock for the next session to trip on.
The detached session now takes them to mean quit: the handler sets a flag and
wakes the loop, which leaves as Exit does, through the teardown that unlinks
both. fs.py sends each of the three and checks the sockets are gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A signal effect carried only a slot, so a Kill followed in the same step by
the pane's closing and its slot's reuse signalled the new pane's child; and a
command pane whose child had exited and been reaped kept a process group id
another process could take. The effect now carries the pane's serial, checked
as it is performed, and a host signals no command whose exit it has recorded;
the core already sends Kill only to a command it has not been told is done.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A job left in the background (sleep 100 &) held the pty open, so the pane
stayed running and the child a zombie until the job ended; a command that
closed its terminal and ran on got its end of file at once, the host waited
100 ms for an exit, reported exit ? and hung it up. Now each command's child
is watched on a thread (waitid with WNOWAIT, so its pid stays its own until
the host reaps it), and the host tells the core the exit from that: after the
pty's end of file, so the output before the exit is in, or 50 ms after the
exit without one, a job holding the pty. The pty stays open until both, so a
command that let go of its terminal is never hung up by it. All four front
ends; a host that cannot start the watcher reads the exit at end of file as
before. Tests: host_io's for both cases, and cmdexit.snap end to end.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
shell's prompt
A middle click, an exec write or a tag word that no builtin knows was typed
into some terminal for the pane's directory, sharing whatever state that
shell was in and answering nothing, so a misspelling vanished into a shell.
Now only a line clicked at an interactive terminal's prompt is typed there.
From anywhere else it runs as a command pane: a terminal whose child is
$SHELL -c the line in the pane's directory, full emulation, which shows its
output and then exit N from the host's reaping of the child, and stays. A
finished command pane is the next command's for its directory, which runs
below what it showed after a '% line' line (acme appends to +Errors and never
clears it, util.c:213); a running one gets a second pane. Kill ends a command
pane's whole process group, the log records run and exit, exec reads back the
command pane's serial, and a line is at most 1 KB, read off the pane as the
host forks rather than carried in every spawn effect. ttyForDir's search for
a free shell is gone. The goldens of chordcut, cmdword and layout-open change
where a file's exec now opens a command pane, and ttytaken is rewritten to
exec from the terminal itself; docs/open-questions.md records the decision.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
request is served
serveFs expired them, but a read is answered while a step is out in a
syscall, because reads change nothing, and this one changed pane messages
and needs_frame from under the step. The detached session's loop now expires
them between steps, and while nobody is attached and a message is up it
looks again every 100 ms rather than waiting for something to wake it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A message lingers until input dismisses it, and a detached session with nobody
attached has no input: its messages, and /screen, kept what was said long ago.
The session now marks the core unattended while no client is attached, and
the core drops a pane's messages once the newest has been up MessageLinger on
the clock, checked as it is stepped or served. A pane's older lines go with
its newest rather than each on its own clock; the render pipeline's clock is
the place for that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
and restores
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 <path>' and the restored editor's log 'restore <path>'.
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Kill queued a signal effect for later, so the job it saw running by its marks
could have ended, and another started, before the signal went; and with job
control off the job shares the shell's process group, so the host skipped it
and Kill reported nothing. Kill now calls the host's kill_job while it holds the
turn, and when the only job is the shell's own group says 'Kill: no job to
signal', which also fails a write of Kill to ctl.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The detached server closed a pane's pty only when the shell was respawned,
hit EOF or the session shut down, so rmdir, Del or Delcol on a terminal
left its shell running with nobody to read it; the tty front end and
macOS did the same. Retiring a terminal pane now emits a close_pty
effect, which each host that runs shells answers by hanging the pty up
and ending the shell (the detached server's and the GUI's existing
retire-and-reap path, and a close plus SIGHUP in the tty and macOS
shells).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
acme keeps what edits a text in its Text (dat.h:171-190) and the window
holds a body and a tag of that type. The cursor, the selections, the modal
state and the edit-buffer undo move off Pane into Text.zig, Pane holds
them as its body, and the edit and normal-mode operations take the Text
they edit. Nothing changes in behaviour; this is the step that lets the
tag become a second Text.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: the editor half of `|` that the earlier
change put at the end of selection_pipe.zig (PendingPipe, pipeMarker,
submitPipe, pipeRequest, pipeFailed, pipeCut, pipeOutput, pipeResponse,
and the eight pipe tests with nextPipeEffect) now lives in its own
Pipe.zig, so the pipe's editor state can become Pipe's own fields next.
selection_pipe.zig goes back to exactly what it was before this series: the
native runner and the boundary values the shells hand to their workers.
The moved code names the runner's types as `selection_pipe.X` again, as it
did in pardes.zig; callers change from `selection_pipe.submitPipe(p, ..)`
to `Pipe.submitPipe(p, ..)` (pardes.zig and the four shells).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: the Message stamp helpers, LoggedMessage,
setStatus, setMessage, showMessage, dismissMessage, dismissLine,
advanceMessages, advanceLine, messagesAnimating, MessageMotion,
messageFrames, messageMotion, noticeText, noticeLife, blendRgb,
logMessage, messageLog, reportError, the notice painters (leaderText,
noticeCols, Printed, printRight), collectNotices, and the six message
tests go verbatim to Messages.zig.
The methods become free functions taking `p: *Pardes`. setStatus,
setMessage and reportError are called from ~170 places as `p.setMessage(..)`,
so Pardes keeps three declaration aliases (`pub const setMessage =
Messages.setMessage;`) and those call sites stay as they are; every other
call changes from `p.x(..)` to `Messages.x(p, ..)` (46 of them). The five
shells' `pardes.Pardes.Message` become `pardes.Messages.Message`. The
message ring's fields stay on Pardes for now; moving them into Messages is
a separate change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Pure move, no behaviour change: submitPipe, pipeRequest, pipeFailed,
pipeCut, pipeOutput and pipeResponse, the PendingPipe they share,
pipeMarker, and the eight pipe tests with their nextPipeEffect helper go
verbatim to the end of selection_pipe.zig, so the whole of `|` (runner,
boundary values, prompt, request and atomic edit) is one file. Inside that
file the `selection_pipe.` prefix drops; the file doc now says it holds both
halves.
The methods become free functions taking `p: *Pardes`: the four shells'
`core.pipeRequest(id)` become `selection_pipe.pipeRequest(core, id)`, and
pardes.zig's two calls change the same way. pushUndo becomes pub because
the pipe's edit calls it; PendingPipe.deinit becomes pub for Pardes.deinit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
messages
PDF highlights (hover preview, search, selection) are baked into page rasters,
and any change re-rendered the whole page with MuPDF; the TTY then re-sent it
as base64 (4.7 MB a page), the GUI as a new texture. Worse, a pointer motion
over a PDF invalidated the page even when no preview was shown, so every
motion paid that. Now:
- A raster whose baked highlight set equals the wanted one is left alone.
- A highlighted page keeps its clean rows (before highlights and tint); a
change repaints only the rows of quads that differ, running MuPDF's
highlight pass (pardes_pdf_paint_highlights) over those clean rows and
tinting them: the operations a full render performs, so the pixels are
identical. MuPDF band renders are NOT bit-identical to a whole page (edge
rows, resampled images), so they are never used to patch; the comment
claiming otherwise is corrected.
- ImagePlace.patch hands shells the changed rows; the GUI uploads just those
rows into the texture it holds.
- The TTY probes kitty shared memory (t=s) with an id vaxis never reaches and
sends rasters as a /dev/shm name when the terminal reads it; direct base64
otherwise (ssh).
- Shells that take row patches (GUI, TTY with shm) repaint a selection while
it is dragged instead of only on release.
Latency elsewhere:
- TTY: an animating frame no longer sleeps 16 ms blind; a tick thread posts
into the input queue, so input inside the frame is handled at once.
- TTY and GUI: queued pointer motions coalesce to the last.
- GUI: a skipped swapchain image re-arms the frame (3 retries); animations
still tick while nothing presents.
- Editing: the line index is carried across an edit instead of rebuilt from
a scan of the whole file per keystroke.
Messages fall into their row (ease-in; the GUI slides the band out from under
the tagline, a terminal fades it), stay until the next input as before, linger
MessageLinger ms (default 800), and dissolve (ease-out). MessageAnimation
toggles it; both are settings, in Config and startup files. The snapshot
harness pins the old behaviour. The detached server now ticks animations.
A restored terminal comes back live: the old screen and scrollback (dumped
as clean VT by ghostty's formatter, replayed at the new size; older dumps
fall back to their rendered text), a dim
"restored history" marker, then a new shell in the directory it was in.
Right-click on a line number in a file pane looks at that line (a sticky
context header's number included).
Measured with an external pty driver (TTY), an in-process fence trace
(GUI, PARDES_TEST_LATENCY), and test/pdf_pointer_bench.zig (pixel identity
against the baseline and a whole-page oracle); balanced A/A/B rounds, paired
per-round statistics.
Messages stack: each event gets its own row and its own fall, linger and
dissolve; a line keeps its row until it leaves and a new one fills the first
free row. Announcements and statuses are replaced in place, not stacked.
MessageFall, MessageDissolve and DumpDir are settings Config reports.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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/<me>/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 > 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
-> 61us, a truncating body write 1184us -> 609us, exec Save 718us -> 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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
The hand-written poll loop for Unix/TCP listeners is replaced by
cloud9.serve.Runner; requests are queued to the editor thread, which answers
them under the connection lock on each frame and retries parked reads as
before. QUIC keeps the poll path (its adapter is fd based). The detached
server no longer loses a wake that lands between frames. The firmware path
keeps driving the engine with push/step. cloud9 re-pinned.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A review of what this program does when the environment says no. The finding
that reframes it: there were almost NO panics on ordinary paths — the rule
already held — but there was a great deal of silence, and one case worse than
any panic.
SILENT DATA LOSS ON SAVE. `saveFile` marked the pane saved the moment it
QUEUED the effect, before any host had tried; `host_io.writeFd` returned void,
so a short or failed write was indistinguishable from a complete one; and
`writeFileBytes` returned true regardless. A save to a read-only file, or into
a directory removed under the pane, therefore cleared the tag's ` *` and posted
nothing — and `Del` makes no dirty check, so the next click threw the edits
away with the screen saying they were safe. On a full disk it was worse: the
file is already `O_TRUNC`'d when `write` fails, so the message row said `saved`
over a file that had just been emptied.
Now: `writeFd` reports, `writeFileBytes` returns WHY (`PermissionDenied`,
`NoSpaceLeft`, `ReadOnlyFilesystem`, …) including a failed `close`, which is
where write-back filesystems report at all; the core marks the pane saved
around `perform` rather than at emit, which is also where the bytes are read;
and a host that could not write calls `Pardes.saveFailed`, which puts the
reason on the message row and takes the clean mark back. That is a CALL and
not a return value because host.zig enforces, at comptime, that a `push_`
method reaching every host in a fan-out cannot have one answer — the first
attempt at this changed the signature and the compiler was right to refuse it.
TWO PANICS ON AN ORDINARY KEYSTROKE, in look.zig's number scans. `v = v * 10 +
d` over caller-supplied digits, reached from `parsePathLine` and the `@pN` scan
— which every Look, every right-click and every n/N motion runs on whatever
word is under the pointer. A hash in a log, a CSV column, any output shaped
`foo:99999999999999999999`, and the editor died with "integer overflow". Both
saturate now, the same way acmefs.zig's address parser already did; a saturated
line is refused by `file_pane.open`'s `line <= total` and a saturated pane id
by `focusPaneLine`'s `id < MAX_PANES`, so nothing addressable changes.
A BOOT FILE THAT WILL NOT OPEN joins the missing-name case in the `+Errors`
pane instead of taking the launch down: `pardes /root` resolves as a `.file`,
could not be read, and left `error: PermissionDenied` and a return trace.
`look.readFile` now says which errno it was, so the pane can say "permission
denied" rather than a word from the source code.
The tag-marker test drained no effects and passed anyway, which is exactly the
defect; it drains now.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016Q4RATpafkwahrovHQLKRf
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
bugs go with them
Nine read-only scouts compared every host-side concern across `src/macos.zig`,
`src/tty/tty.zig`, `src/gui/gui.zig` and `src/detached/server.zig`. What they
found was not a style problem: each duplicated body had drifted, and in every
case the drift WAS a bug the users of that shell could see. So the fixes and
the deduplication are the same change.
**One PATH, adopted before the first fork.** LaunchServices hands a bundle
launchd's environment, whose `PATH` is `/usr/bin:/bin:/usr/sbin:/sbin`. Every
pty shell, `|` filter and language server the app forked inherited it, so
`yazi` in `/opt/homebrew/bin` was absent from a Dock launch and present in the
identical binary run from a terminal — the "it worked briefly" window was
simply the sessions started from a shell. `shell_bin.adoptSystemPath` composes
`/etc/paths` then `/etc/paths.d/*` in the order `path_helper` reads them,
deduplicating on first occurrence, and runs once at startup in all four native
hosts. It APPENDS: an entry already present keeps its position, so running it
over a real session cannot demote a mise shim behind `/usr/bin` and silently
change which `node` runs. A `PATH` that was configured is left byte-for-byte
alone; only one nobody configured is repaired. `prepareForFork` folds that
adoption together with the prompt-rc staging and the `BASH_SILENCE_DEPRECATION_WARNING`
setenv the five hand-copied prefork sites had between them — `server.zig` had
none of it, which is why every detached pane opened with Apple's zsh banner.
**The LSP protocol client never worked on macOS.** It opened its control
socket with `libc.SOCK.CLOEXEC`; Zig defines that constant for Linux and
Darwin answers `socketpair` with `EPROTONOSUPPORT`, so the call failed before
any fork, `ensure` returned `error.NoServer`, and every row in the spec table
— rust-analyzer, clangd, gopls — was unreachable in every macOS build. The
in-process ZLS backend kept answering, which is what made it read as "only Zig
is supported". It is a plain socket plus `fcntl(FD_CLOEXEC)` now, the route
`fuse.zig:943` and `nested.zig:95` already took for the same reason. The
snapshot suite that covered this path had never run natively on a Mac: the
harness targets defaulted to x86_64-linux.
**One LSP host worker.** `src/lsp_host.zig` is the snapshot, the worker body
and the job lifetime that `tty.zig` and `gui.zig` carried verbatim — `gui.zig`
said so in a comment — and that `macos.zig` did not carry at all: `lsp` and
`pipe` were absent from its `Host.VTable`, so the core answered its own empty
answer, `SPC l i` rendered a blank panel and a `|` filter silently did
nothing. All three shells share the module, and the AppKit host implements
both effects. Its status sink is now REGISTERED as well as defined, so
unsolicited server news reaches the message row instead of nowhere.
**The animation clock measures time.** `pardes_animation_tick` advanced one
scene frame per callback and published `frame_count / 60`, so scene time was a
count of callbacks rather than elapsed seconds — and `AppDelegate` re-armed
`asyncAfter(.now() + 0.016)` only after the previous frame's work had
finished, making the true period 16 ms plus all of it. Motion ran at about
three quarters of wall clock and unevenly. The tick now spends measured
monotonic time in whole `frame_ns` steps and banks the remainder, so a late
callback advances two frames instead of stretching one; `spendTickTime` is
that arithmetic as a pure function with its own tests and no display attached.
On macOS 14+ the animating run is one `CADisplayLink` phase-locked to vsync
rather than a chain rebuilt after every frame; macOS 13 keeps the old chain.
**Three more single definitions.** `panel_animation.paintOrder` is the
moving-then-opening-then-closing composite order as a rule the core applies
once in `Pardes.render` — `macos.zig` was re-sorting an already-sorted list.
`selection_pipe.Tasks` is the bounded in-flight pipe table `tty.zig` and
`gui.zig` each declared. `boxContains` was a fourth copy of the half-open cell
test and is now an alias of `Box.contains`.
**A filtered terminal stops asking libm per cell.** `Filter`'s legibility
stage called `RGB.contrast` for every painted cell, and that ends in
`std.math.pow` up to six times, re-deriving a ratio against a background that
had not moved; the existing memo cache covered the palette reduction beside it
and never this. The indexed path's input is a `u8`, so all 256 answers are
enumerated once per pass — after the default roles are fixed, before the first
cell is read — and what a cell names becomes an array index. Only truecolour
still reduces. ReleaseFast, 190x56, Tracy: recolour 3.09 ms -> 0.130 ms,
frame 3.37 ms -> 0.299 ms. The comptime luminance table is pinned to
`RGB.luminance` and `RGB.contrast` by exact-equality test over every channel
value and all 65 536 palette pairs, because the decision is a threshold
comparison where one ULP is a different colour. A `filterInit` Tracy zone
records the part that is still per-pass: 2.9 us warm against a 117 us pass,
which is the measurement that says not to cache it across frames.
Released as 0.0.2. `build.zig.zon` carries the version into `pardes --version`
and into the `Changelog` pane through `@embedFile`, so the entries above open a
`## 0.0.2` section and `## 0.0.1` closes with the tagline work of the parent
commit.
Two bugs here were mine, caught by review rather than by me: a double free in
the macOS pipe drain arm (`Msg.free` already owns the response) that segfaulted
the app on the first `|`, and a proposed `getRowAndCell` optimisation that
targeted 2 of 43 draw samples while the contrast math beside it took 12 — and
would not have compiled. The profile that justified it was a Debug build, which
`build.zig:1160` already documents as ~5x slower than release.
Native and -Dplatform=macos suites: 0 failures. All targets build with Tracy on
and off; the shipped release binary contains no `___tracy_emit_zone_begin`.
App reinstalled, signature verified, dmg regenerated, launched with 0 crash
reports; installed binaries verified byte-identical to a fresh build.
|