summaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAge
...
| * | A background job killed while the tty probe walks it is not the tty's owner, ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | however far into exiting it is host_io's "a background job is not the tty's owner" failed now and then, 2 runs in 20 under load, always just after `kill %1`. The probe walks the shell's children and reads each one's exe. A killed background sleep, in the moment it is exiting (its exe link gone, not yet a zombie), has no exe to read and is not a zombie to skip, so the probe called the tty taken. A process outside the foreground process group holds nothing whatever state it is in, so one whose exe cannot be read is now skipped when its group is not the foreground one. Under the same load the test passed 50 runs in a row. The unit-test step's perf gate trips under that load, which is not this test. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | The 9P socket appears already listening: it is listened on under a name of ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | its own and renamed into place A client that waits for the socket file and then connects, as fs.py's session helper and scripts do, was sometimes refused under load. The listener bound the socket at its final name, so the file existed a moment before listen(2), and a connect in that window got ECONNREFUSED. Now it listens under `<name>.<pid>`, is chmodded, then renamed over the final name, so that name only ever names a listening socket. A final name held by a live listener is still refused, and a name with no room for the suffix is bound in place as before. The registry test checks the socket is listening where it appears and that the temporary name is gone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | pardes --wait for a new name waits on the pane it made, by serial, and a ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | pane closed before it looks is done, not a failure fs.py's `pardes --wait 'wait new.txt'` failed two runs in three on the 0.0.8 merge. The stress case repeats the test's steps: 4 of 50 failed under load, each with "pardes: --wait: no pane shows that file". A new name's launch makes its pane, names it, then found it again by path in /index. The script had already seen the name, saved and removed the pane by then, so the lookup missed and --wait exited 1. Now the launch hands --wait the serial pane/new gave it. After a look, finding no pane on the file means that pane is already closed, which is the end --wait waits for: exit 0. The follow with its /index recheck covers a close in between, as before. Under the same load the stress case went 0 of 100. The same load showed a test race too, at fs.py's second Restore. The exec write is answered before the hang-up, but the client's clunk after it can meet the connection already cut. The test now requires the answer and tolerates the reset. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | | A path typed with a leading ~ is the home directory, at every entry point ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | that takes one, through one helper Only DumpDir (dump.zig) and ShaderBuild expanded `~/`, each its own way. So a look at `~/notes.txt`, `name ~/x`, `Save ~/x`, ThemeFile, Restore and a quoted `pardes '~/x'` all took `~` as a directory of that name under the pane's. There is now one rule, filesystem.expandHome. A leading `~`, alone or before `/` or `:`, is $HOME, else the passwd entry's home; `~user` is that user's (getpwnam). As in a shell, it applies even beside a file named `~`, and `./~` names that file. Every entry point runs its typed path through it: - look, B3 and 9P, before any parsing, so `~/x:12`, `~/x:12:3` and `~/x:/re/` all work; - name, and a tag's rename; - Save <path>; - ThemeFile, DumpDir, Restore and ShaderBuild's shader files; - the forwarding launch. Recent rows are not shown with `~` and need nothing. How names are shown is unchanged. Tests: expandHome's cases, and HOME unset (passwd), with HOME passed in rather than set, since setenv moves the environ a spawned child reads. fs.py covers Save into missing directories under `~`, `name ~/x`, a look at `~/x:3`, and a forwarded `pardes '~/x'`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | | Merge: round 26 + codex wheel (the 0.8 candidate)Gabriel Schneider28 hours
|\ \ \ | |/ / |/| / | |/ Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * A wheel over a terminal whose program owns it goes to the program: its mouse ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | report when it tracks the mouse, arrow keys on the alternate screen with alternate scroll, so codex, htop and less scroll codex 0.158 moved its chat onto the alternate screen with any-event SGR mouse tracking (1049, 1000/1002/1003/1006, 1007 off); its sign-in screen keeps 1007 on with tracking off. pardes never forwarded a wheel to a program, only scrolled ghostty's scrollback, which the alternate screen does not have, so the wheel did nothing there. A shell on the main screen still scrolls its scrollback as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A detached session no client watches draws no frames: yes | head -c 50M ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* | pane/new takes rows from a pane in another column before refusing ENOSPC, as ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | acme's placement does A pane/new whose column had no room was refused "no space for a pane in that column", even with another column holding a tall pane that could give rows. acme takes rows wherever a window has them before saying there are none. pane/new now tries the other columns as +Errors already did: an empty one, else its tallest pane's bottom half, last column first. It is refused only when no pane anywhere can give the rows. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Kill with a name and nothing running says "Kill: nothing running" and ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | succeeds, as bare Kill does Bare `Kill` with nothing running is a message, not a failure: a script's Kill has nothing left to do. `Kill make` in the same state failed the write with "no running command has that first word", as though make were the mistake. With nothing running at all, named or not, Kill is now the message. It still fails when commands run but none has the word. The two tests that used `Kill zzz` as a builtin that always fails now use `Unmount zzz`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | An event record written back without its trailing newline is taken, as every ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | other line file's last line is `printf 'MX0 10' > event` was refused as a malformed record, while exec, look and every ctl take a last line with no newline. The event file now does too: a write not ending in a newline is read as though it did, and the write still counts the bytes that came. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A tag write over the limit is refused "no space: over 4096 bytes", so its ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | err record says tag: once The refusal's text began with "tag: ", and the log's err record puts the file's name before the reason. So the record read `err - col/1/tag: tag: no space: over 4096 bytes`. The text is now the reason alone, as the control-character refusal's is, and the record names the file once. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | fs.md gives the msize as 64 KiB, documents the Newcol refusal for tags that ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | would not fit, and says how 16 columns is really reached The docs said msize 8192, but the editor has offered 65536 since the bulk write work. They also told a reader to reach 16 columns without saying it needs a 160-cell window, or that Newcol halves the column it is run from. The "the panes' tags would not fit" refusal was not documented at all. The column paragraph now says all three, and that a refused Newcol logs its err alone. docs/cloud9.md's options line gives the current numbers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Save makes the directories its file goes in, always: for the pane's own name ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | and Save path alike Whether a Save made missing directories depended on how the name had come about. Config's pane did, a name written into a missing directory did (since kkrqxtsp), and `Save path` and every other name did not, failing ENOENT instead. There is now one rule: a Save makes the parent directories of the file it writes, a pane's own or a terminal's scrollback included, and the make_dirs flag is gone. A directory it cannot make still fails the write with why, as fs.py's /nonexistent-pardes-dir case shows. fs.py now also saves into missing directories by Save path and by a written name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A look at file:3:0 is refused as addr 3:0 is: a column counts from 1Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | addr refuses a column 0 with EINVAL, "address out of range: a column counts from 1". A look took `file:3:0` as line 3 with no column, since the parser holds no column as 0, and succeeded. Now a look whose column, or end column, is written as 0 is refused with addr's words and errno. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | DumpConfig's lines that set nothing start with #, and a line starting with # ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | runs as nothing at an exec, a ctl or the init file DumpConfig's information lines ("Platform: tty", "Startup config: ...") began with words. Written back, as the report means to be, or clicked, each ran as a shell command or failed as an unknown control message. Each now starts with `# `. A line starting with `#` is a comment, as in a shell: it runs as nothing, silently, at an exec, at the root's, a pane's or a column's ctl, and in the init file. Documented in fs.md and config.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | winsize refuses fewer than 2 rows, saying so, and its range reads 1x2 to ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | 4096x4096 as the docs say `winsize 20 1` was taken and quietly raised to 2 rows, so the write succeeded with a size other than the one asked for. The range refusal also said "1x1 to 4096x4096", although fs.md says at least 2 rows. One row is now refused with "invalid winsize: at least 2 rows", and the range text starts at 1x2. size already refused anything under 20x6 with a text that names it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A run whose output a redraw moved above its start answers cut, never an ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | empty whole pty/run reads a command's output between its C and D marks. When the end landed at or before the start, it answered `exit 0` with no output, as if nothing was printed. That happens when the shell redraws its prompt over the output, as it can in a window two rows high (winsize 10 2). Now only equal marks mean nothing was printed. An end at the top-left cell or above the start says `cut`, since how much was printed is not known. bash, fish, long lines and 3000-line outputs at winsize 10 2 all came back whole on this build, so the dogfood's loss could not be reproduced directly. The test drives the marks into the inverted case the loss needs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | With no pane at all, the root look opens its file and the root exec runs its ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | command in the session's directory /look and /exec run at the active pane, and with every pane closed they failed ENOENT, "file does not exist", about a file the writer never named. Now an exec runs as the workspace tag's would, in the session's directory: a builtin at the stand-in, a command in a command pane. An empty window's click now runs a command line too, not only session words. A look runs from a scratch made as New makes one, which also makes the column. That scratch is dropped, unannounced, when the look went to another pane, so opening a file leaves only the file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Lspinfo, Lspwhy and the other +Lsp answers share one +Lsp pane per directoryGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | Each query kind found its own output pane by its origin. So Lspinfo and Lspwhy, which both write `<dir>/+Lsp`, opened two panes of the same name in one directory, and so could a code action list or a format diff. The language server's prose now shares one +Lsp per directory, the way the file searches share one +Search, and each answer replaces the last. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Lspwhy explains the recorded last query in its own file and place, wherever ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | it is run Lspwhy kept the last query's kind and argument, but ran from another pane it asked in that pane's file at its cursor. So it narrated a different lookup from the one that failed. When the pane the query was asked in is still open, Lspwhy now asks in it, at the query's offset, whichever pane it is run from. With that pane gone, it falls back to the pane it is run in. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A Newcol refused because the panes' tags would not fit logs its err alone ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | and spends no column serial Newcol tries its column before keeping it: the new column is inserted, the panes' rows are shared out again, and where a pane would be left under its tag and two rows the column is taken back. The trial showed. insertColumn logged `newcol N` and the take-back `delcol N`, so a follower saw a column come and go around the err. Laying the column out also gave it a serial, so the next column made skipped one. The trial is now unannounced, since insert and drop have unlogged forms. A refusal puts the serial counter back where it was, after first giving every existing column its serial so the trial's is the only one spent. Only a column that stays is logged, and it gets the next serial. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A connection holds up to 128 reads, the next refused "too many reads ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | waiting: 128", and a mount beside 40 held event reads still answers Two limits met. pardes parked 32 reads per connection and refused the next with EAGAIN's C string, "Resource temporarily unavailable". 9ns kept 32 tags and 31 workers, each pinned by a held read. So 31 followers through one mount took every worker, and `cat layout` then queued for ever: the whole mount deadlocked. cloud9 (705be665, pushed to sr.ht and pinned here) now refuses in words past the cap. Its 9ns window is 256, so a mount always has workers past what pardes holds. pardes raises its cap to 128. fs.md documents the limit beside held reads. selfmount.py holds 40 event reads through the mount and reads layout beside them. It times out with the 9ns installed before this change and passes with the new one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A tty editor killed with SIGTERM or SIGHUP puts its terminal back before it diesGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | A nested pardes killed in a pane died with the default action. That left the pane's terminal on the alternate screen, in raw mode, with mouse and paste reporting on, so the shell under it was unusable until a reset. The tty frontend now handles both signals: it writes the resets a clean exit writes (keyboard protocol, mouse modes, focus, bracketed paste, SGR, cursor, main screen) and restores the cooked termios, all async-signal-safe. Then it dies of the same signal, since SA_RESETHAND put the default back. fs.py kills a tty session with each signal and checks for the main-screen switch and cooked mode; it fails without the handler. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | pardes FILE in a pane whose session answers never falls back to a nested ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | editor: the refusal is printed, exit 1, and no stray pane is left When the environment named a live session that answered, several failures broke out of forwarding and started a whole editor inside the pane that asked: a refused name, a pane the session has not, a failed look write. Its screen was drawn over the shell. Now, once the session answers (Client.probe), every refusal is printed as `pardes: <file>: <why>`, in the session's own words (the Rerror, now kept by the client), and the launch exits 1, as acme's B does. A new name the session refuses deletes the pane made for it, so no empty +New is left. A missing environment or a session that does not answer still starts a separate editor. fs.py checks a refused name and a stale PARDES_PANE. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | The 9P monkey does not count a file watch's notice as a failed write's msgGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | Seed 121 flagged a refused write to pane 2's dirty file because a `msg 3 <file> deleted on disk (Save writes it again)` arrived in the same window. That notice was from an earlier host rm of pane 3's file, said whenever the watch noticed it. It did not reproduce on replay, since it depends on when the watch fires. The one-failure rule now leaves out the watch's deleted-on-disk and changed-on-disk notices. Any other msg with a failed write still fails the rule. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A backward search along one long line takes time in the line's length, not ↵Gabriel Schneider28 hours
|/ | | | | | | | | | | | | | | its square: ?a? over 117 KB goes from 6.4 s to 0.03 s The 9P fuzzer (seed 124) hung a session for over 5 s on `?a?` in a 117 KB file of one line. A backward search is every forward match up to the limit, so Regex.find ran once per match. Each call looked back from its start to the line's start, and forward to the line's end, over the whole line each time. That is quadratic, and the step budget, which counts only mvzr's steps, never saw it. find now keeps where it last learned a line starts and ends in the same text, and looks back only to there. The literal prefix's look back stops at the known part of the line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Release Pardes 0.7Gabriel Schneider28 hours
|\ | | | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * lapis_plain: lapis on flat grounds, with its framed and shadowed tags and ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | its file-name shadow No dot grid under the text and no stripes behind the focused tag; every colour, the gold frames and vermilion shadows of the plaques, the rules, the scroll column's checker and the title shadow stay lapis's. It follows lapis in the ring and the Themes picker. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * A lapis plaque paints each pixel once: its band's ground, shadow and frame ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | are drawn only where the layers over them leave them showing The four opaque layers of every tag's plaque were whole rectangles stacked on each other. At 4K (scale 2, 120x40, RTX 3050) a lapis draw now takes 491 us of GPU, down from 601 (forge: 463); pixels identical in hidden captures at 1x and 2x. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * Lapis's cells are drawn unblended: a see-through cell over a plaque with no ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | ink loses its quad, not the whole frame its opaque pipeline The cells a plaque shows through are recorded as they are emitted; once the frame's instances are built, those with no ink are dropped, and the frame falls back to the blended pipeline only when one has ink or the page itself is see-through. At 4K (scale 2, 120x40, RTX 3050) a lapis draw took 695 us of GPU against forge's 471; blending every cell for the plaques' sake was 94 us of it (now 601 us). Pixels identical in hidden captures at 1x and 2x. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | fs.md and the 9P skill say what the language server words do over 9PGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | Hover, Rename, Diagnostics, Symbols, Lspinfo and Lspwhy work through exec like any builtin, but neither fs.md nor the SKILL named them. So a script did not know they act at the cursor (set with addr and dot=addr), where their answers land (+Hover, +Search, +Lsp), that the write waits for the answer, or that Lspwhy explains the last query. Each now says so briefly, the SKILL with a short example. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Lspwhy explains the last language server query asked, its kind, argument and ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | place, not always a definition at the cursor Lspwhy said it explains how the last request was answered, but it always replayed a definition query at the current cursor. So after a failed Hover it narrated a gd somewhere else, which told nothing about the failure. Now the core remembers the last query (kind, argument, pane, offset), and Lspwhy sends it as `<kind> <arg>`, at the query's offset when asked from the same pane. The client narrates that kind: lsp.explaining reads it, and the heading names it ("the hover query at byte N"). With nothing asked yet it is a definition, as before. The effect's arg grows to 160 bytes so a 128-byte argument fits with its kind. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | The ctl word name refuses a leading blank, as the name file doesGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | `name /tmp/x` written to a pane's ctl trimmed the extra blank and named the pane /tmp/x. The same name written to the name file was refused with "a blank at its start", which fs.md gives as the rule, so the name a script wrote is the name it gets. The ctl word now hands the name file everything after its one separating blank. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Help and Find from the workspace or a column tag act in the session's ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | directory, not the keyboard pane's New, Tty and commands clicked in a tag no pane owns ran in the session's directory, but Help named its +Help after the pane with the keyboard, and Find walked that pane's directory. So the same click gave different results depending on which pane was focused. Both now take the session's directory from a header, through execDir as the others do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A column's and the workspace's tag read with no trailing newline, as a ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | pane's does A pane tag read back its text alone, but a column tag and /tag appended a newline and gave a stat size one byte longer. So `$(cat tag)` agreed across the three kinds while `cat tag | wc -c` and a byte compare did not. All three now read the text alone, and fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A builtin that takes no argument, written with one to an exec, is refused ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | wrong #args as at a ctl At a ctl, `Config extra` and `DumpConfig extra` were refused with `wrong #args`. Written to an exec, the builtin match missed, since the word takes no argument, and the line ran as a shell command named Config, which did nothing and said nothing. An exec now refuses it with the ctl's words. A setting word keeps its value (`Verbose off`). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | acme's Tab, Indent, Local, Incl and Abort are refused as acme words pardes ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | has not, never run as shell commands The five were missing from the refused list, so at an exec or a ctl a typed `Indent on` fell through to the shell as a command named Indent. pardes has no equivalent for any of them: its tabs and indentation follow the file, commands always run in the pane's directory, and there is no include path or abort. They now get the same "invalid: acme's X is not a pardes builtin" as Zerox or Send. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A pattern anchoring some alternatives with ^ and not others is refused with ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | why, not a bare bad regular expression `^def|x` and `foo$|^zèbre` were refused with a bare "bad regular expression", the same words as a syntax error, so a script could not tell what to change. They now say "an alternation anchors every branch with ^ or none (^a|^b, not ^a|b)", through addr and Edit alike. fs.md states the rule and that a `$` does not count toward it (`foo$|bar` is fine). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | pardes NEWFILE in a pane forwards even when its directory is missing, and ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Save makes the directories fs.md said FILE must already exist, while `pardes new.txt` in fact forwarded, making a pane named for it that Save creates. With a missing directory (`pardes notes/new.txt`) the launch gave up forwarding and started a nested editor inside the pane. Now the name is made absolute as written and forwarded the same way. A name written to a pane's name file that goes into a directory not there has its Save make the directories, as Config's pane does. A Save <path> into a missing directory still fails ENOENT. The Forwarding section now says what a new FILE does. fs.py launches one into a missing directory and saves it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | New and Tty run at /tagexec and a column's exec, and with no pane at allGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | The words New and Tty make a pane in the session's directory, and fs.md already has them run at a tag no pane owns. But /tagexec refused New as a pane's word, since the workspace tag does not hold it. Every header exec also failed ENOENT when no pane was left, and Tty gave up when it had no pane to take a directory from. Now New and Tty are every tag's words. A header exec with no pane runs at the stand-in, as a click in the empty window's tag does, and Tty with no pane asking starts in the session's directory, as New's scratch does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | The Tutor pane is named +Tutor in the session's directory, not /TutorGabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | Every other generated pane is named `<dir>/+<Name>`: +New, +Errors, the outputs. Tutor alone was a bare `/Tutor`, a real path at the root, so a Save would try to write there, and /index listed it as if it were a file. A scan of the other generated panes found no other bare absolute name. The three snapshot goldens that show the Tutor tag were re-recorded by name (tutor, builtins, leader). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A word at /tagexec or a column's exec that makes, acts on and focuses no ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | pane reads back nothing Newcol written to /tagexec read back `1`, the pane with the keyboard, which the word never touched. That was a pane word's fallback (a pane's exec answers the pane it ran at) applied to a tag no pane owns. A word in a column's or the workspace's tag now answers the pane it made, refilled, went to or focused, and otherwise nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A failure naming a long path keeps its reason, and name refuses a component ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | over 255 bytes A Save of a 3000-byte path failed with the record "err 2 ctl: Save…", which lost the reason. The first cause was reportError, which formatted into 256 bytes, so the operation's path filled the buffer before the reason was written. The second was the waiting write's late failure, which took the first 256 bytes of the message row, never its end. Now reportError has room for the longest path. reportFailure also keeps the words fitted as an err is (fitErr: the path gives up its middle, the reason stays), and the late failure of a Save, a Dump, a shell or a ThemeFile takes those words. fs.md already said a name holds up to 255 bytes a component, but a longer one was taken and only failed later at Save. Now it is refused when written. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | DumpConfig prints each setting as the ctl word that sets it, reads ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | unsupported for one the frontend lacks, and includes LocationsConfig The report meant to be feedable back as configuration, but several lines were not. Shell and Font each took three to six lines of "requested", "effective" and "pending". The transition read "Panel transition PanelSlide", with no Panel word. WindowOpacity and TaglineSize carried "(pending)" and "(build-time only)". A GUI-only toggle such as Ligatures was left out on the tty, and LocationsConfig was missing altogether. Now every line up to a blank one is a word and its value: - Shell <path>; - Font <name>:<size>; - one on/off line for each Panel word; - `unsupported` for a setting this frontend does not have; - LocationsConfig as it would be typed. After the blank line comes what is in effect but set by no word, phrased so that none of it starts with a ctl word. The test checks that each line before the blank one starts with a setting word. The GUI scripts that read the font lines (font_size.py, appearance_gallery.py) now read the new ones. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Nothing about a pane is logged before its new, and the boot terminal's new ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | carries the session directory, not / and a rename A pane named in the update that made it (Config's init file, a look renamed on open) logged `rename N <name>` before `new N`, so a follower met a serial it had never seen. The boot shell, started with no directory, was announced as `new 1 /` and renamed once its shell spoke. Now noteLog drops the rename of a pane not yet announced, since its new carries the name, and announces a pane first for any other record about it. A terminal whose shell has not said where it is is named by the session's directory, where it was started, and the shell saying so is no rename. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Column serials go on across a Restore as pane serials do: a restored column ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | never takes a serial the old session handed out A Restore carried next_serial over for panes but started column serials at 1 again. So a restored column could take the serial a deleted column had, and a script holding col/<n> from before the Restore would find a different column there. The column counter is now carried over like the pane one, and restoredcol maps each old serial to a fresh one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | A control character in a write to any ctl file refuses the whole write, and ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | a refused line is quoted with its control bytes shown as blanks fs.md already promised that the whole of a write to /ctl, a pane's ctl and a column's ctl is checked first, as it is for exec and look. But a ctl write ran its lines one by one, so a line holding a control byte gave that line's own, misleading reason ("unknown command"). Worse, the reason quoted the raw byte back into the err record. The check now happens in tree.write before anything runs, with the same EINVAL and reason exec gives. A refusal's quoted line shows control bytes as blanks, so an err record never carries a raw escape. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Pane, column and workspace tags take one set of write checks, and a refused ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | `>` write leaves the tag as it was A column's or the workspace's tag took any length, so a write past 4096 bytes went in and a later Dump failed on it (bad dump tag), and a truncating open wiped any tag before the write after it could be refused. The column and workspace tags now refuse what a pane tag refuses: the 4096-byte limit (ENOSPC, tag: no space: over 4096 bytes) as well as the control characters they already did. A truncation of any of the three is held until the write after it is known to fit, and done with it; a refused write drops it; a close or read with no write after it does it then (so `: > tag` still clears). A test runs a truncating write too long, a control character and a bare truncation at each kind, and Dumps after. docs/fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | An append to a body through 9P costs its own bytes, not five passes over the ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | whole body: 60 KB appends go from 35.7 to 2.1 ms each Round 25 measured bulk body writes at about 8 ms a write. There is no frame wait in it: a profile of open, write, clunk in a loop found the flush of each close walking the whole body five times. dotOf, setDot and showOffset turned the cursor between offsets and rows by counting every newline from the top; setContent found the line index's changed span by comparing old and new byte for byte, and hashed the new text to see whether it was back to the saved one. The rows now come from the file's line index by binary search (checked against the counting at every offset), a splice tells setContent the span it changed, and the text is hashed only when its length is the saved text's. Measured over 9P on a Debug build: 60 KB appends 35.7 to 2.1 ms, 8 KB 5.3 to 0.8 ms, 1 KB 1.3 to 0.9 ms. What is left is two copies of the text an edit (the splice's and the undo snapshot's). The perf gate gains body-appends, 64 appends of 8 KB on the 50k-line file, and its three baselines are recorded again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | The GUI goldens take the workspace tag's new words, Dump Themes Config Debug ↵Gabriel Schneider28 hours
| | | | | | | | | | | | | | | | Exit pqqtzsrz changed the workspace tag's default text and recorded the tty snapshots again, but not test/gui-goldens.txt, so all 17 GUI frames differed on main. Built with the old words, every frame is byte-identical to the recorded ones; recorded again with the new words, from the standard Debug gui build. Co-Authored-By: Claude Opus 5.5 <[email protected]>