summaryrefslogtreecommitdiff
path: root/src/macos.zig
Commit message (Collapse)AuthorAge
* A failed Dump or a Restore of no dump fails its write, before any warningGabriel Schneider32 hours
| | | | | | | | | | | | 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]>
*-. Merge: 9P round 6 + empty columns + render (acme theme, grips, Lift, Motion)Gabriel Schneider32 hours
|\ \
| | * Motion flavours differ by design, and drive notice drops and pane movesGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* | | Kill stops a command pane's whole line again, now it runs with job controlGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 shell that exits under a run answers exit N, and the log says exit before delGabriel Schneider32 hours
|/ / | | | | | | | | | | | | | | | | | | | | | | 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]>
* / A Save the host cannot do fails the 9P write that asked for it, and the log ↵Gabriel Schneider32 hours
|/ | | | | | | | 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]>
*-. Trial merge: 9P + helix + renderGabriel Schneider32 hours
|\ \
| | * Ripple and Glitch are gone; Crt staysGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The user tried them live and wants them removed entirely: the builtins, their levels, config keys and leader paths (tR, tg), their slots in the built-in chain, their shaders (source and prebuilt), EffectCode paths, and on macOS their scene flags (pardes.h), the Metal kernel's coordinate warps and the Swift pointer mapping that mirrored them. Docs say so (decision 8 in docs/render-pipeline.md); config.md documents Crt's levels, Shader and ShaderAnimation instead. Shared files touched: macos.zig (flags). Not touched: pardes.zig, Messages.zig, mouse.zig, gui.zig, detached/*.
| | * Post chain: Shadertoy passes compiled by glslc; Crt, Ripple and Glitch ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | rewritten as Shadertoy files Stage 9 of docs/render-pipeline.md. src/gui/Post.zig runs the chain over the finished frame: each pass reads the one before, ping-pong textures between, the last writes the window; an empty chain is the direct path. The prefix (shaders/post/prefix.glsl) is ghostty's uniform block, name for name and offset for offset (tested against a copy of its Uniforms, the selection colours by their GLSL names), at SDL GPU's bindings, y down. A user's file (Shader <path>) is compiled by glslc with compileGlsl's own flags on a thread of its own, taken in at the next loop step; errors name the file's own lines; a failed compile keeps the last good pipeline; glslc missing is said once. The bundled passes compile with the build, through the same prefix. Crt, Ripple and Glitch are Shadertoy files, rewritten: no barrel (input is identity, tested), everything in linear light and dithered, keyed to iTime. scene_effects, crt.zig and crt.frag.glsl are gone; the core keeps no clock for the chain. ShaderAnimation off|on|always drives redraw level A: the chain alone over the retained frame (34 us CPU a redraw, measured). Shared files touched: pardes.zig (nextWake loses the scene line, disableSceneEffects empties the chain, two tests), builtins.zig (one switch arm), ninep/ctl.zig (two switch arms), macos.zig (flags from the chain), gui.zig. Not touched: Messages.zig, mouse.zig, detached/*, host_io, tty.
| | * A notice's word under the pointer is lit, in its band and the grid; ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | mark_hover is gone Open point 6 of docs/render-pipeline.md: notice words are Look and Exec targets, so the hover affordance headers have is back on notices, painted once in the notice's layer and joined into the grid from it. Deleted: Surface.mark_hover, Cell.hover and render's per-frame reset of it, none of which had been set since stage 5 (macOS loses its glass hover rect; its build is ignored for now). Snapshot goldens unchanged. Shared files touched: macos.zig (the hover bit's removal). Not touched: pardes.zig, Messages.zig, mouse.zig, gui.zig, detached/*.
| | * One Layer for tags, notices, headers and bodies; a taller tag is one layer ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
| | * Step core animation by the shell's clock and sleep to the next wakeGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
| | * Move the frame into draw.zig and the transitions out of layout.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* | | A command's exit is told once its output is in, by the pty's state, not a timerGabriel Schneider32 hours
|/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* | Kill signals a command only while it runs, and only the pane it was meant forGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | 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 command pane's command is over when its process exits, not when its pty closesGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* | A command line runs as its own command pane unless it is clicked at a ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* | A Restore answers its writer before hanging up, and the log records dumps ↵Gabriel Schneider32 hours
|/ | | | | | | | | | | | | | | | 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 asks the host to signal at once and says when a shell has no job to signalGabriel Schneider32 hours
| | | | | | | | | | | 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]>
* A closed terminal's shell is reaped on tty and macOS, killed if it ignores ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | the hangup tty and macOS hung the pty up and called waitpid once without waiting, so a shell still exiting, or one that ignores SIGHUP, stayed a zombie or ran on with nobody reading it; tty also never reaped a shell that exited by itself, and its spawn into an occupied slot closed the old pty without ending the shell. host_io's retireShell says hangup, waits 100 ms on a thread (macOS has no host timer to poll from), then kills and reaps. The gui's own retired list, when full, left the slot holding the old shell and refused the next spawn into that pane; it now hands that shell to retireShell instead. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A terminal pane that closes takes its shell and pty with it, in every front endGabriel Schneider32 hours
| | | | | | | | | | | | | 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]>
* Give a pane's body its own Text holding the cursor, selections, mode and undoGabriel Schneider32 hours
| | | | | | | | | | | 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]>
* Give the pipe its own fields in PipeGabriel Schneider32 hours
| | | | | | | | | | | | | | 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]>
* Split the pipe's editor side out of selection_pipe.zig into Pipe.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | 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]>
* Move saving and loading a whole editor out of pardes.zig into dump.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | 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]>
* Move the mouse out of pardes.zig into mouse.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | Pure move, no behaviour change: handleMouse with dragUpdate, dragRelease, seamIdxAt, reportGesture, gestureRange, dispatchPointerBuiltin, mirrorTtySelection and chordCutPaste, the pointer hit helpers (bodyHitForPane, sameTagCell, tagColumn, sameBodyCell), chromeTarget with ChromeTarget, paneAt, the Drag state type with clampBorderCol/Row, the seam test fixtures and thirteen pointer and drag tests go verbatim to mouse.zig. The methods become free functions taking `p: *Pardes`; their 36 call sites change from `p.handleMouse(..)` to `mouse.handleMouse(p, ..)`, including web.zig's chromeTarget and macos.zig's paneAt. The Mouse event type stays in pardes.zig with the rest of the event vocabulary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Move the message row out of pardes.zig into Messages.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | 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]>
* Move the pipe's editor side out of pardes.zig into selection_pipe.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | 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]>
* Move colour themes out of pardes.zig into colors.zigGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | Pure move, no behaviour change: the Theme type, the curated palettes, fold and the themes ring with its two comptime checks, themeContrast, mix, ChromeTheme, ChromeAnimation and initial_chrome, the Pardes methods that load theme files and switch themes (nextThemeFileGeneration, requestThemeFile, ThemeFileRequest, themeFileRequest, failThemeFile, loadThemeFile, finishThemeInitialization, invalidateThemeDependentRasters, setThemeIndex), and the ten theme tests go verbatim to colors.zig. The methods become free functions taking `p: *Pardes`; their call sites change from `p.setThemeIndex(i)` to `colors.setThemeIndex(p, i)` (37 of them, in pardes.zig, builtins.zig, file_watch.zig, macos.zig and two test files). pardes.zig keeps `pub const Theme/themes/native_theme_count/ ChromeTheme = colors.X;` for the shells that name them, and sync and enterTagEdit become pub because a moved test calls them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Allocate from libc's malloc in every release shell, and check macOS's Debug ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | build too The tty, gui and detached shells allocate from std.start's gpa, which is a DebugAllocator in Debug and, because we link libc, libc's malloc otherwise. The macOS shell hardcoded std.heap.smp_allocator in every mode, so its Debug build checked nothing it allocated outside memory.zig's subsystems. Which allocator a release build should use was measured rather than assumed, in ReleaseFast, with an experimental gui that chose at startup between glibc's malloc and smp_allocator, with and without memory.zig's stack-fallback buffers, plus an A/A pair; ten interleaved rounds, compared round by round. The gui frame-cost harness (idle, scroll and typing in src/pardes.zig, terminal spew, wheel-scrolling docs/design.pdf, first paint) saw every candidate within the A/A pair's noise. Twelve rounds of the allocation-heavy paths themselves, highlighting all of src/pardes.zig (305k tree-sitter allocations) and pardes-pdf-bench's MuPDF renders, split them: smp_allocator was 2 to 6% slower on MuPDF's page-sized rasters (slower in 9 to 11 of 12 rounds), no better on tree-sitter, and the stack-fallback buffers changed nothing either way. So glibc's malloc it is, with the buffers kept. macos.zig now takes a DebugAllocator in Debug, deinitialized in pardes_deinit with leaks logged the way std.start treats the others', and libc's malloc otherwise. main.zig says why init.gpa is kept. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* Center tagline bands, frame anchors evenly, fill to the window edge, and ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | resize columns from their grip Every tag band (workspace, column, pane) is centred in its row, so text sits at one baseline offset. Anchors are inset from the column rule by that same margin, so they are square and the band frames them on the left, top and bottom; the pane mark moves to stay centred. When the window is not a whole number of cells, the bands, rules, spines and scroll thumbs at the right and bottom edges run on through the leftover pixels. A column grip dropped short of another column's place now moves the column's left edge, with a dashed rail preview, sharing setColumnPairWidth with the border drag. A release still on the grip changes nothing, and a pair too narrow for two MINW columns is left alone. PARDES_TEST_PAD adds leftover pixels to a test-mode window and capture.
* Repaint PDF highlights by row, send rasters by shared memory, and animate ↵Gabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Answer 9P on the connection's task, so a session can open its own treeGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Fixes from three adversarial reviews, and a destructive one among themGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The registry sweep could delete a live socket, anywhere on the filesystem. A reviewer reproduced it: a socket that is bound but has not reached listen(2) answers ECONNREFUSED exactly like a dead one -- that window is every server's startup -- and the sweep then followed the entry's symlink and unlinked whatever absolute path it named. It now follows a target only into the directory our own sockets live in and only to a `pardes-9p-*.sock` name, it re-probes immediately before deleting rather than trusting a probe that is by then several syscalls old, and a readlink that exactly filled its buffer is treated as the truncation it is. The test grew a case for an entry whose target is not ours: the entry goes, the file does not. Ctrl-V in raw tty mode was a black hole when the yank register was empty -- neither typed nor forwarded -- so vim's visual block, readline's quoted-insert and every other program's Ctrl-V simply vanished. With nothing to paste the chord belongs to the program again. The lone-ESC flush added earlier was dead code. vaxis already returns Escape for a one-byte 0x1b (`Parser.parseGround` asserts `input.len == 1`), so the carried byte it waited for can never exist; a reviewer showed a 3 ms gap and a 60 ms gap behaving identically. Removed rather than left to imply a guarantee it never provided. A shell whose editor is gone can start one again. Naming a live but unreachable session made `pardes <file>` exit 1, which let a stale environment variable lock someone out of their own editor; it falls through to an ordinary session, as it did before the variable existed. Also: the macOS ABI check for `pardes_topbar_pane_border_px` had been replaced by a duplicate of the line above it; `--startup` now fails on a leak the way every other measurement in that file does, and stops calling its maximum a p95 below twenty samples; the served README and the skill no longer tell you to write to `data` with `>`, which truncates the whole body before the write lands; `docs/v9fs.md` described the allocate-on-walk design that was rejected; and `test/fs.py` keys nesting off `PARDES_PID`, so its forwarding case stops passing only when the runner happens to be inside a live pardes. fs-test now reaches its one documented pre-existing failure instead of dying early. Suite 778/783 with the two known crashes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Draw a frame only when there is one worth drawingGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A 9P round trip on a local Unix socket cost 9.7 ms against the SDL shell and 0.758 ms against the terminal one. The server was not slow and the wake was not broken: instrumenting the path showed every request waking the loop early (wakes=210, woke_early=212, timed_out=38 over 250 ticks) and being answered on that same pass. The cost was that `pump` answers 9P at one point in a loop that then renders and presents unconditionally, so a client's next request landed while the main thread was blocked on the display, and each round trip therefore cost a whole frame. The frame rate was governing something that has nothing to do with drawing. `Pardes.needs_frame` starts true, is set by every event except a tick with nothing animating and a filesystem request that only reads, and is cleared once a frame is presented. `pump` returns before render and present when it is false and nothing is animating. An idle editor answering reads now draws nothing at all. 9P read_fid, one RPC: gui 9.7 ms -> 0.056 ms (173x) tty 0.758 ms -> 0.062 ms (12x) Verified the shells still paint rather than going quiet: the rendered screen carries the opened file, a write through 9P redraws within the frame, and `fs-discovery-test` passes over the real wire. Suite unchanged at 778/783 with the two pre-existing crashes. Also from the adversarial review of the previous commits: `pardes --tty FILE` silently discarded the file, and `--tty MISSING` silently discarded the error pane. main.zig named the boot layout before the positional was resolved, and naming one short-circuits `Boot.of`. The choice now happens after the argument is known, and only when there is no file and no missing word. macos.zig names the same layout, so the app no longer boots a different one from the terminal and SDL shells. `pre_close_last_pane_tail` was transcribed from the NEW default rather than the old one, so the upgrade path it was added for did not exist: a workspace dumped before the tagline reorder came back with the old default welded on as a custom tail. It is now the string it claims to be. A pane two rows tall lost its message and, worse, its prompt and the cursor with it. The notice cap keeps the LAST notices now, because the prompt is last and a prompt you cannot see is one you type into blind. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Make the macOS shell a first-class hostGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | Pty children are exec'd with their own TERM/COLORTERM/TERM_PROGRAM instead of inheriting a .app launch's empty environment, and ttyTaken finally answers on darwin — libproc walks the tty's foreground process group — so Escape reaches the child and Exec stops believing every pane sits at its prompt. The occupancy suite runs on both platforms now. The workspace tag row moves into the native menu bar as a Builtins menu. -Dworkspace-tag (default off for -Dplatform=macos, on everywhere else) drives it, and Pardes.topBarHeight replaces the TOPBAR_H constant so the core stops reserving the row. The view pins every variable-font axis to the file's own default (Maple Mono came up Thin otherwise), shapes ligatures, carries per-shape pointer cursors, and draws the look-hover affordance as refracted glass. Tag rows fill edge to edge, with the anchor box painted back on top of that fill and its mode glyph centred on the same square. Theme accents re-saturated across the set. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Serve Unix and TCP 9P through cloud9.serve's std.Io runnerGabriel Schneider32 hours
| | | | | | | | | | | 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]>
* Add macOS backdrop blur and preserve PDF ink opacityGabriel Schneider32 hours
|
* Align macOS rendering with Linux and establish parity regressionsGabriel Schneider32 hours
|
* Give macOS scrolling momentumGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | A swipe that was still moving when the fingers lifted stopped dead. The dial next to it has thrown properly since pardes_rotate_end, and this is that same curve on the scroll accumulator: velocity sampled between events off the monotonic clock, weighted toward the newest sample because a flick is decided by how the hand was moving when it left, and a fling that ramps up from zero at the floor rather than switching on at it. A coast stops at the end of a document rather than spinning its remaining velocity against the edge, which is why spendScroll now reports whether the pane moved and why paneAt is public. Only a step that delivered a press can report an edge — the many steps between two rows cross nothing. Nothing suppresses AppKit's own momentum, and nothing needs to: its momentum events are ordinary pardes_scroll calls, every one of which cancels the coast before spending its travel, so on a real trackpad the system takes the gesture over about a frame after the lift and the tail it ends on is below the floor. This is the path for devices AppKit does not fling for — and the only one a script can reach, since NSEvent phases have no public constructor. momentum.snap asserts both halves, which is why it needs the long scrollback: a flick with no document left proves nothing. A slow swipe is byte-identical after the release; a hard one coasts twenty-four rows further on its own, and a finger back on the pad stops it there. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Let WindowOpacity through on macOSGabriel Schneider32 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The builtin was gated on the SDL platform, so the macOS shell never registered it and the setting had nowhere to land. macOS already had transparency, but only the binary kind a theme decides — a theme with no background of its own drops the ground and the blur shows through. WindowOpacity is the graded, theme-independent version, and the window had no way to hear about it. pardes_window_opacity reports the percentage and acknowledges the request in one read, beside pardes_theme_bg: an SDL surface can arrive without an alpha channel and has to be able to refuse, while an AppKit window always composites per pixel, so there is nothing here to refuse and no rollback to perform. The view then paints the rule shaders/ui.frag.glsl states for the SDL shell: backgrounds — ground, cell and band fills, and the chrome rules over them — take the alpha, glyph ink never does, and the block cursor is exempt because it is foreground chrome that happens to be carried in a cell background. That exemption is why the cursor joins the colour in the background run key; it can no longer share a fill with the cells beside it. Two things the harness was missing fall out of testing it: it never adopted the theme background or the opacity, so a Theme or WindowOpacity in a script moved the core and never reached a pixel. draw-opacity records both ends of the alpha range, which is what makes the two-sided contract assertable — 60% reads 153..255, and 0% reads 0..255 with the ink still standing. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Separate pane and column grip hit areas from tagline text with a small gapGabriel Schneider2026-09-15
|
* Reorder columns using aligned and distinctly colored tag gripsGabriel Schneider2026-09-15
|
* Separate tag text geometry from physical pane gripsGabriel Schneider2026-09-15
|
* Separate discontinuous pinned source context with thin bordersGabriel Schneider2026-09-15
|
* Add optional compact tagline styling for source contextGabriel Schneider2026-09-15
|
* Follow embedded PDF links through LookGabriel Schneider2026-09-15
|
* Route mouse thumb buttons to jump historyGabriel Schneider2026-09-15
|
* trunk: resume before the Reload experimentGabriel Schneider2026-09-15
| | | | Empty marker on the last pre-Reload change. Keep the Reload experiment on reload (3801914), its first change on reload-start (200a1fc), and the unfinished performance investigation on reload-perf-wip.
* Resolve relative restores in the dump directory and propagate LSP probe errorsGabriel Schneider2026-09-15
|