summaryrefslogtreecommitdiff
path: root/src/ninep
Commit message (Collapse)AuthorAge
* Jumplist fills its one +Jumps again, as Recent does, never opening a secondGabriel Schneider4 days
| | | | | | | | Each Jumplist opened another +Jumps below the asking pane, so a few clicks stacked stale copies of the list. Recent's refill of its own pane is now shared, and Jumplist uses it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* After a ThemeFile, the root ctl and DumpConfig say ThemeFile and its path, ↵Gabriel Schneider4 days
| | | | | | | | | | | so what they report writes back Both said Theme and the name inside the .zon, which no Theme knows, so writing the report back to /ctl failed with "no theme". While a loaded ThemeFile is the theme in effect they now say the word that loaded it. A Dump carries the line too, so a Restore reloads the file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A builtin for one kind of pane is refused on another with why: Petscii, ↵Gabriel Schneider4 days
| | | | | | | | | | | | | Palette, Ascii, Filter, Togglettymode, the Pdf words, Mode and TreeContext Each of these did nothing, silently, on the wrong kind of pane, so a ctl or exec write of Palette to a text file answered ok as if it had acted. A builtin now declares the one kind it is for (needs), the registry reads it, and runBuiltinFrom refuses the rest before dispatch, as Repl and Edit already refused theirs. Mode and TreeContext, which take more than one kind, say their own refusal. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Save and Save path on a PDF or image pane are refused, "a PDF pane has no ↵Gabriel Schneider4 days
| | | | | | | | | | | | text to save", never a silent success A bare Save on a PDF or an image did nothing, and `Save path` returned without writing, both answered as done. Such a pane shows a file it does not hold as text, so there is nothing of its own to write. Save now refuses it with why ("Save: a PDF pane has no text to save", or an image pane's), and so fails the write that asked. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Joincol is refused, as Newcol is, when the joined column cannot give each ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | pane its tag and 2 rows Joincol moved every pane of the keyboard's column into the next one whether they fit or not. Two columns of two panes in a 12-row window became one column of four, wanting 12 rows where 10 were free, a layout that `size` then refused as it stood. Joincol now tries the join in place first (layout.joinFits: the panes and width moved, minimaFit asked, everything put back, nothing dropped or logged). A join that does not fit is refused, "Joincol: no space: the joined column cannot give each pane its tag and 2 rows". Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fs.md says a body write to a terminal is typed input, never a bracketed pasteGabriel Schneider4 days
| | | | | | | | | | | A write to a terminal's body goes to its child as typed keys: no bracketed-paste marks around it, even when the program set 2004, so a newline in it is Enter and a shell runs each line. fs.md did not say which, and a script cannot guess. It does now, and a test writes to a terminal whose program asked for bracketed paste and checks the bytes sent are only the ones written. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Save path logs a save record naming the path it wroteGabriel Schneider4 days
| | | | | | | | | | | Save of a pane's own file logged `save <serial> <name>`, and so did a scratch's `Save path` (the pane takes that name). A `Save path` that writes a copy of a file pane, or a terminal's scrollback, elsewhere logged nothing, so a follower could not tell the file was written. It now logs `save <serial> <path>` with the path written, once the host has written it (events.noteLogAs). fs.md's log table says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A rename (mv) of a pane's directory or any file of the tree is refused as ↵Gabriel Schneider4 days
| | | | | | | | | | | | | not permitted, EPERM, not an EIO cloud9's engine passes a wstat that changes a name to the backend, and the tree's setattr ignored the name: the rename answered as done, the name did not change, and a mount then gave `mv` EIO. The tree names its own files, so a setattr with a name is now refused, "rename not permitted", which 9ns reads as EPERM. A pane's buffer is still renamed through its name file. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* name refuses a directory, EISDIR, when written, not at the Save that could ↵Gabriel Schneider4 days
| | | | | | | | | | | | only fail `name ~`, `name /` or `name foo/` renamed the pane to a directory, and only its Save failed later. A name ending in `/`, the root, or one naming a directory that is there is now refused at once, with words a mount reads as EISDIR (`name: <path> is a directory, not a file`). The ctl word name and a forwarded `pardes ~` go through the same check. fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A pane refused for want of room spends no pane serial, as a refused Newcol ↵Gabriel Schneider4 days
| | | | | | | | | | | spends no column serial A pane/new, look or exec refused ENOSPC made its pane before placing it. The refusal closed the pane unannounced, but its serial was gone, so the next pane skipped one. When sync removes a refused pane that holds the newest serial, it now puts the counter back. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fs.md says that in a > to a tag cut into several writes, those taken before ↵Gabriel Schneider4 days
| | | | | | | | | | | | | a refused one stand A `>` to a tag that a client sends in several writes is checked a write at a time. When a later one is refused, the earlier accepted ones stay in the tag. Buffering the whole open to apply it or none would cost more than it saves, so fs.md now says what happens: each write stands on its own, and only the refused one changes nothing. A test covers a truncation, an accepted write, then a refused one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* An addr, dot or limit pair out of order or past the text is refused as the ↵Gabriel Schneider4 days
| | | | | | | | | | | | address form is, not clamped `#5,#2` and `#2,#99` were refused (addresses out of order, address out of range), but the pair form `5 2` became 5..5 and `2 99` became 2..end, silently a different range from the one asked for. A pair is now checked the same way: past the text is out of range, and an end before its start is out of order. fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A Restore logs and names the whole path of the dump it reads, not the name ↵Gabriel Schneider4 days
| | | | | | | | | | | | | as typed `Restore x.dump.zon` read the DumpDir's x.dump.zon but logged `restore x.dump.zon`, and `~/d.zon` logged the tilde. A follower could not tell which file was restored. The builtin now resolves the name once (fs.restorePath: `~` expanded, a relative name the DumpDir's when one is there, else the current directory's) and hands that path to the host, which reads it and logs `restore <path>`. Its messages name it too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A tag rewrite counts its text without the newline it drops: echo of exactly ↵Gabriel Schneider4 days
| | | | | | | | | | | | 4096 bytes fits `echo <4096 bytes> > tag` wrote 4097 bytes and was refused "over 4096 bytes", although a rewrite drops that one trailing newline and would have held exactly 4096. The limit now counts a truncating write's text without the newline it drops, for a pane's, a column's and the workspace's tag. An append still counts its newline, which it keeps. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* An empty range written back to event acts on the word a click there expands ↵Gabriel Schneider4 days
| | | | | | | | | | | | to, as acme's expand does `ML960 960` or `MX1 1` written back with no text acted on the empty range between the offsets: a look of nothing, an exec of nothing. A click at a point expands to the word or file name under it, and acme's written-back event does the same. It now expands with look.expandedWord, as a no-drag click does, on its line of the body or tag. fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A focus moved over 9P tells the programs that asked for focus reports, as a ↵Gabriel Schneider4 days
| | | | | | | | | | | | click does A program that set DECSET 1004 heard `CSI O` and `CSI I` when the keyboard moved by a click, a key or the window's focus, because update() checks after each event. A /focus write went through serveFs, which never did, so the program kept believing it had focus until the next input event. serveFs now runs the same check once a request has settled. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A 9P look keeps its leading blanks: an indented line, or blanks alone, is ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | looked for as written Every line written to look or exec went through Messages.next, which trimmed blanks from both ends. A look of ` indented` then found the first bare `indented`, and a diff's blank context line ` ` looked at nothing. A look's line is now its text: only its `\r` and newline go. lookAt keeps the blanks for the word search too; it expands `~` only for a look that starts with one, and the path, address and command readings trim for themselves as before. runSearch keeps a pattern's blanks, since a builtin's argument comes trimmed already. An exec line is trimmed as before. fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A restored pane whose file is gone from disk comes back dirty, and Del asks ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | before throwing its text away A dump keeps a pane's text. Restored after its file was deleted, the pane came back clean: the watch's baseline read of the missing file failed quietly, and Del closed it without a word. Its text was then nowhere at all. Restore now checks the file: a local path with nothing on disk comes back as one deleted on disk, as the watch would have said. It says so once, logs `changed N deleted`, and is dirty, so Del, Exit and Restore ask first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A 9P write scrolls only the view: dot moves as acme's textinsert moves it, ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | never to the end of the write writeData, flushBatch and writeBody showed what they wrote with showOffset, which put the cursor at the write's end. The cursor is dot's head, so a data write or a body append moved dot there, and a `sel` write after it replaced the wrong text. showOffset now scrolls the view to the offset and leaves the cursor, pinned, where it was. Dot shifts only by the rule it already followed, acme's: an insert before dot shifts it, one at its start or inside it grows it, one at its end or after it leaves it. The test covers each case, then a body append followed by a sel write. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A write of a multiple of 4096 bytes holds the partial line it ends with, as ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | one that fills its Twrite does: a burst through a mount no longer runs its cut lines as two commands selfmount's 1000-line burst reported 943 lines, on main too. The log ring was not losing records: a follower opened before the burst heard exactly 943 and no `lost`, and the burst itself exited 1 with `err 1 exec: wrong #args in control message "Msg"`. `seq ... > exec` through the mount arrives as stdio's 4096-byte writes, each shorter than a Twrite. The rule that such a write is whole ran each one's cut last line at once, so both halves of the line ran as commands (hence `exit 127` records). At 12288 bytes the cut left a bare `Msg`, which was refused and failed the write and the rest of the burst. A write of a multiple of 4096 bytes is where a writer's buffer (stdio, a page cache) filled, so it may go on: its unended tail now waits for the next write or the close, like one that fills its Twrite. `printf Save > exec` stays whole at once. fs.md says so, the 9P fuzzer's model of served lines follows it, and a unit test cuts a `Msg` at 4096 bytes. A follower does not lose records silently: a ring that outruns one already reads it `lost N` first (documented, tested). selfmount now follows the log from before the burst, so it checks every line heard once, in order, or a loss said, not a fresh open of a 64 KiB ring. It passed 3 in 3 and joins the gates with the new 9ns. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A path typed with a leading ~ is the home directory, at every entry point ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* pane/new takes rows from a pane in another column before refusing ENOSPC, as ↵Gabriel Schneider4 days
| | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | 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]>
* Save makes the directories its file goes in, always: for the pane's own name ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | | 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]>
* A Newcol refused because the panes' tags would not fit logs its err alone ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | 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 backward search along one long line takes time in the line's length, not ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | 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]>
* The ctl word name refuses a leading blank, as the name file doesGabriel Schneider4 days
| | | | | | | | | | `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 Schneider4 days
| | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | 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]>
* A word at /tagexec or a column's exec that makes, acts on and focuses no ↵Gabriel Schneider4 days
| | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | | | | | 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]>
* Nothing about a pane is logged before its new, and the boot terminal's new ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | | | | | | | | 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 Schneider4 days
| | | | | | | | `>` 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 Schneider4 days
| | | | | | | | 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]>
* Config opens the startup file, DumpConfig the settings report, and the ↵Gabriel Schneider4 days
| | | | | | | | | | | | | | | | | | | | | | | | | | workspace tag reads Dump Themes Config Debug Exit Config (SPC f c) opens init, found as the settings report finds it (opts.startup_config_path: $XDG_CONFIG_HOME/pardes/init or ~/.config/pardes/init, and the macOS and Windows paths config.md gives). A pane that already holds it is gone to instead; a file on disk is looked at; with none, an empty pane is named for it, and Save makes its directory and any above it first (File.make_dirs, fs.makeDirs) before writing. Without a configuration directory it says so and opens nothing. The report Config used to open, the startup path and every live setting, is DumpConfig, in a +DumpConfig pane. No alias keeps the old meaning. The workspace tag's default words put Themes where NextColor was, NextColor staying a builtin, and Config beside it. A tag the user edited is kept as it was: a dump stores only an edited one (topbar_custom), and a restore puts back only that. theme.snap types NextColor onto the workspace tag to click it, and tagnav's walk back to Dump takes five long-word steps. The rest of the re-recorded goldens differ in the workspace words and their widths only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Terminal.zig is terminal.zig: a file of functions and no fields takes a ↵Gabriel Schneider4 days
| | | | | | | | namespace's lowercase name The last of the deferred renames, now that the tty and theme agents have landed: panes.terminal at its importers, the alias lines in Text.zig and File.zig and panes.zig's own uses following. dump.zig's Terminal struct, a dump record, is not this. The served sources list and docs/design.typ name the new file. test/perf.zig's references change, so the three perf baselines take its new harness id with their numbers as recorded. No behaviour changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>