summaryrefslogtreecommitdiff
path: root/src/CHANGELOG.md
blob: d2641aa907e7a578cd82ce70319c8455f57cea5d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# Changelog

## 0.0.4

- Drive the editor over 9P the way acme is driven. Panes take sam
  addresses (`/re/`, `#n`, `$-1`, `0/re/`), `L:C` and inclusive `L:C-L2:C2`
  ranges on `addr`, with `data` and `xdata`; `Edit` runs the sam command
  language as one undo step. `exec` and `ctl` buffer lines per open, and a
  line without a newline runs with its write. One rule for failure: a
  builtin that fails fails the write and logs one `err`, no `msg`.
- New 9P files: `/layout`, `/tag`, `/tagexec`, `col/<serial>/{tag,ctl,exec}`,
  `pane/<n>/tagexec`, `/recent`, ask/answer records, and a `size` ctl for
  detached sessions (160x50 by default). Listings freeze at their open, so
  a read in several chunks never splices two moments.
- acme's ctl words (`name`, `put`, `clean`, `dirty`, `del`, `delete`,
  `dot=addr`, `addr=dot`, `limit=addr`, `mark`, `nomark`, `show`,
  `cleartag`) are accepted again, done by the files that replace them; a
  pane's ctl reads acme's fields in acme's order. `Put` saves and `Delete`
  closes; acme builtins pardes lacks are refused, never run as shell
  commands. plan9port's `9p write` always truncates, so piping into `body`
  replaces it: append with `>>` through a mount.
- Command panes and a REPL binding; `pty/run` runs one line and answers its
  exit status. Every child starts with default signal dispositions.
- `Recent` lists recently opened files, closed ones included, and persists
  in `$XDG_STATE_HOME/pardes/recent`. A Jumplist row for a closed file says
  `(closed)`. `+Unsaved` lists dirty panes. `ThemeSel` and `FontSel` are now
  `Themes` and `Fonts`; `Exit`'s refusal opens in an output pane.
- `pardes --wait FILE` (`-w`) run inside a pane opens FILE there and returns
  only when its pane is deleted, as plan9port's `E` does: set
  `EDITOR='pardes --wait'` for fish's Ctrl-O, git and the rest. Without it,
  `pardes FILE` still hands the file over and returns at once.
- Only a write logs an `err`: a refused open, create or remove fails with
  Plan 9 words (`permission denied`, `file does not exist`) and logs nothing.
- The tag is a full text buffer: multi-line, with undo.
- Helix keys: the skipped non-conflicting keys, full registers and per-range
  anchors, checked against the latest helix.
- Deleting the last column leaves the window empty but for its tag, as in
  acme. A buffer whose file was deleted on disk counts as dirty.
- 62 themes in 13 families, each as close as possible to its original,
  grouped by family in `Themes`; an `acme` theme taken from plan9port's
  source, with acme's grips, 2 px rules and a 1 px tag rule. Sizes follow
  display density.
- GUI motion: `Motion` flavours (crisp, smooth, bouncy, playful, off), `Lift`,
  a cursor that glides and moves with its pane, smooth scroll, and optional
  Bloom, Vignette, Grain, SelectionGlow, HoverGlow and Shadertoy post passes
  that recompile on save. Still passes let the GUI rest; Crt keeps drawing.
- The terminal: replies to capability queries, malformed escape sequences
  and lone modifier keys never become typed text; function keys, Insert and
  the keypad reach terminal panes; a lone Escape is Escape at once; a focus
  change no longer resends every blank cell.
- Speed: the theme's chrome is worked out once, not per grapheme; typing
  tells `g.` where its edit ended; large body and data writes are linear;
  a pattern that opens with a literal jumps to it. `zig build perf-gate`
  fails any gesture more than 3x its recorded baseline.
- Robustness: over-long paths, non-UTF-8 bytes and odd 9P input are refused,
  never a panic; seeded GUI, core and 9P monkeys (`zig build monkey-9p`)
  replay and shrink what they find.

- A column can be empty, as in acme. `Newcol` makes an empty column and
  gives its tag the keyboard; closing a column's last pane, or dragging it
  away, leaves the column where it was with nothing in it, drawn as its tag
  over blank space. The `+New` placeholder that used to stand in an emptied
  column, and the machinery that swapped it out when something else arrived,
  are gone. Closing the session's last pane now quits.
- New panes go where acme's makenewwindow puts them: into the active column
  (the one last typed or clicked in), filling it when it is empty, else under
  the text of a pane with room to spare, else halving the biggest pane; never
  into a new column. `Placement pardes` brings back the old rules, which open a
  first document in a column of its own.
- A document dragged into the left column no longer closes the untouched
  boot shell there; `BootShell replace` brings that back.
- Column tags are always shown: the `ColumnTags` setting is gone, and an
  init file's `ColumnTags` line is ignored with a message.
- Carrying a pane or a column by its grip shows acme's box cursor in the SDL
  shell and a move cursor on the web.

- `Del` takes a side. `Del k` and `DelAbove` give the closed pane's rows to
  the expanded pane above it, `Del j` and `DelBelow` to the one below. A bare
  `Del` from the keyboard on a pane with panes both above and below asks on
  its notice band, and one key answers; a click, a 9P write or a script
  still closes it at once, rows going up as before.
- A prompt (Save's path, a search, a pipe) is drawn in the tagline's pitch
  like the other notices instead of spread one glyph to a body cell, and in
  the SDL shell every notice chip is ruled off from the body like a tagline.
- Answer 9P on the connection's task instead of the editor's loop. The core
  is single-threaded and `pardes.turn` says whose turn it is with it: the
  editor's by default, given up while it waits for input and while a step of
  it is out in a syscall, and taken by a connection task to answer a request.
  A request that would change a pane while a step is out parks in the engine
  and is retried when the editor rests. So a session can open, read and save
  its own tree through a mount -- which used to hang it outright, and was
  then refused by name -- and the name-based refusal, the in-process routing
  of a mount of oneself and the mailbox that shipped every request to the
  editor's thread are gone.
- A notice wider than its pane keeps its tail rather than its head: the end
  of a message is the file name or the reason.

- Give pty children their own terminal identity. `TERM`, `COLORTERM` and
  `TERM_PROGRAM` are now written by pardes rather than inherited from whatever
  launched it, and the child is exec'd with that environment. A macOS `.app`
  launch supplies no `TERM` at all, so until now `clear` failed, nothing
  painted a colour, and every cursor-addressing program fell back to printing
  lines.
- Answer tty occupancy on macOS. `ttyTaken` had no darwin implementation and
  said `false` for every pane, which made `Pardes.takesCommandLine` permanently
  true: a bare Escape in a raw terminal ran the `Last` builtin instead of
  reaching the child, and `Exec` always believed the pane sat at its prompt.
  libproc answers it from the tty's foreground process group, and the
  occupancy suite now runs on both platforms instead of only Linux.
- Flatten the 9P control tree: `self/` is gone, so `/index`, `/pane/<n>/`,
  `/screen` and `/os` sit at the root. `/new` is one clone file (reading it
  makes a pane and answers its serial), `/ctl` takes `look TEXT` and
  `exec TEXT` in the editor's own command language and answers the serials a
  command touched, `/log` streams pane events, and each pane gains `name` and
  `sel` (replacing `rdsel`/`wrsel`); `cons`, `new/` and the `name`, `put`,
  `del` and `delete` ctl verbs are gone in favour of `name` and `exec`. Stats
  carry real lengths, modes and mtimes. Sources are served under `/src` only
  with `-Dembed-sources=true`. The tree code moved from `src/fs.zig` to
  `src/ninep/`.

## 0.0.3

- Add pinned tree-sitter declaration context, with optional compact tagline
  styling and separators between discontinuous source lines.
- Configure search and LSP result context with `LocationsConfig`. Results use
  aligned, muted context and navigate actual matches with `n` and `N`.
- Follow PDF links through Look, with a link cursor and a choice of destinations
  when a target also names a valid Look location.
- Remember tagline cursors, distinguish filenames and terminal commands with
  theme colors, and keep compact tag text, pointer hits and anchors aligned.
- Reorder columns with aligned, distinctly colored drag grips. Grips leave a
  small gap before command text; GUI pane indicators center using font metrics.
- Cycle pane input modes with `Mode`, retain it in terminal tags, and handle
  keypad Enter in native and attached terminal sessions.
- Preserve selections through jump history, support mouse thumb navigation,
  align click and insertion cursors, and mirror terminal mouse selections.
- Make output location highlighting opt-in and ignore slash-only comment
  markers during general Look navigation.
- Use cloud9 for 9P sessions and transports and add Linux mounted terminal
  sessions with raw terminal key forwarding.
- Detached sessions now use protocol version 6. Update the server and attached
  clients together.

## 0.0.2

- A panic is written down somewhere it survives. Every crash this program has
  ever had went to stderr and nowhere else, and stderr is the one place it
  cannot keep anything: in the TTY shell stderr IS the screen, so the trace
  lands on the grid the terminal is being reset out of and the next `clear`
  takes it; the SDL and AppKit shells have no terminal at all; a `--detach`
  session's goes wherever its launcher left it, which is usually nowhere. So
  the message and the frames behind it are now appended to `crashes` beside the
  `init` file, under one line naming the build that produced them — version,
  commit, UTC timestamp, os-arch and pid — which is exactly the set a bug
  report needs and the set a reporter cannot be asked to reconstruct after the
  fact. `src/crash.zig` runs inside the panic handler, so it takes no lock of
  this program's, allocates nothing of its own, and every failure is swallowed:
  a crash file that could not be written must not become the crash, and stderr
  still gets its own copy either way. The file carries NO STACK TRACE, and that
  is measured rather than chosen: `writeCurrentStackTrace` called from a panic
  handler *before* `defaultPanic` wedges the process at 0% CPU, and
  `captureCurrentStackTrace` — which looks like the safe half of it — takes
  `SelfInfo`'s rwlock exclusively on its first call, so a panic inside the walk
  leaves that lock held and `defaultPanic` waits on it for the life of the
  process. A crash that becomes a hang is worse than the crash. What makes
  `defaultPanic` itself survive that is its private `panic_stage`, reachable
  from nowhere outside `std.debug`, so the frames stay on stderr where they
  already work. ONE record per process, because the nested panic std's trace
  printer raises comes back through the handler: the first version of this
  wrote the real message and then "reached unreachable code" underneath it,
  which is the panic handler's own second `vaxis.recover()` double-closing the
  tty — `recover()` never cleared the global saying there was one. That call is
  guarded now too, in both the panic and the segfault handler, which is a fix
  older than this file. The AppKit shell got a panic handler of its
  own in the process: the macOS build roots at `macos.zig`, so the one in
  `main.zig` had never run there — in the shell with the least useful stderr of
  the four.
- A big screen can attach to a detached session. The wire's grid ceiling is
  512x128 and no frontend ever clamped to it — the hello carried the window
  raw — so a 4K display at a small font, which is already past 128 rows, had
  its geometry refused by the session's decoder as `BadValue`. That path
  answers with `close(.protocol)` and no `refuse` behind it, so the frontend
  reported the one thing a bare hangup can mean: "that session hung up on the
  connect", at a session with all 32 slots free. `client.zig` now asks for the
  largest grid the protocol carries, which is what its own GEOMETRY note
  already promises a frontend gets — the session is drawn at its own size in
  the corner of a bigger window, exactly as when another frontend is the
  smaller one. A zero geometry is dropped rather than clamped, because the
  session grid is the smallest common one and a frontend reporting 1 would
  collapse everybody else: `TIOCGWINSZ` answers 0x0 during a teardown and the
  tty shell forwarded it, so that was the same mute hangup by another route.
  The clamp alone would have replaced one bug with a worse one: `max_cols *
  max_rows` is 65536 and a run's length prefix is a `u16`, so the single grid
  legal at both bounds is the one grid whose full frame — and an attach always
  produces a full frame — cannot be described by one run. It panicked on the
  `@intCast` in a safe build and was illegal behaviour in a fast one. The
  encoder splits the run instead, which `frameBound` had already paid for, and
  the protocol version is bumped to 2: the geometry a v2 frontend now asks for
  is one a v1 daemon panics encoding, so a mixed pair — `zig build` replacing
  the binary under a running session — meets a `Refusal.version` instead of
  losing every pane shell the daemon owns.
- `pardes nosuchfile` opens an editor. It used to return `BadArgs` out of
  `main`, which std prints as `error: BadArgs` with a return trace under it:
  indistinguishable from a crash, for a typo, and it left the human with no
  editor at all. A launch that names nothing now boots one `+Errors` pane
  filling the window — acme's own vocabulary for output that came from the
  program rather than from a word somebody clicked — saying `file or directory
  not found` and the argument AS TYPED, in the directory it was typed in — an
  output pane's directory is where a `Grep` from it walks, where its `Newtty`
  spawns and what its `Save` prefills, so a pane rooted at `""` would have
  pointed all three at `/`. A typo INSIDE pardes is refused by that shell in one
  line instead: the hand-off block that keeps a pardes from stacking a second
  full-screen UI inside a pane of the first says in its own comment that a word
  naming nothing must not get through, and an `+Errors` boot would have made a
  typo the one input that did. A `chdir` that fails on a directory that really
  is one is still `BadArgs`: that is a permission problem, not a typo, and the
  two want different answers.

- The macOS trackpad's two verbs trade places: a two-finger click is Exec and
  a deep press is Look. Exec is what a hand spends a session doing, so it takes
  the gesture that costs nothing, and the verb that goes somewhere is worth one
  deliberate push past the click. Three fingers stay Exec, and that is the
  hardware rather than a gesture left unused: macOS's secondary click is "click
  or tap with two or more fingers", so a three-finger click arrives as
  `rightMouseDown` exactly like a two-finger one, and the resting count is the
  only thing that keeps either of them off Look. `pressureChange` still
  releases the click in flight before the new button goes out, which now steps
  around acme's 1-3 chord (Paste) where it used to step around 1-2 (Cut). Look
  by finger count is gone, so a Mac with "Force Click and haptic feedback" off
  — or a trackpad with no force sensor — reaches it through a real mouse's
  right button, the `Look` builtin, or Enter on the word. `trackpad.snap`
  asserts all of it without a trackpad, because `NSTouch` and the pressure
  stages have no public constructors and `Trackpad` is therefore pure policy a
  script can name finger counts to; the swap left every golden screen
  byte-identical under the other gesture, which is what shows the two paths
  were exchanged and nothing else moved.
- A filtered terminal costs what an unfiltered one does. `Filter`'s second
  stage asked `RGB.contrast` for every cell it painted, and that call ends in
  `std.math.pow` six times over — a libm round trip per cell, per frame, to
  re-derive a ratio against a background that had not moved. The indexed path
  takes a `u8`, so all 256 of its answers are now enumerated once per pass,
  after the two default roles are fixed and before the first cell is read;
  what a cell names is an array index into them. Only truecolour, whose
  16.7M inputs cannot be tabulated, still reduces, and the direct-mapped RGB
  cache is what keeps that cheap. Tracy over the AppKit shell, ReleaseFast,
  190x56: the recolour pass falls 3.09 ms to 0.130 ms and the whole frame
  3.37 ms to 0.299 ms. The luminance table is comptime-evaluated from
  ghostty's own expression and a test pins it to `RGB.luminance` and
  `RGB.contrast` exactly, across every channel value and all 65 536 palette
  pairs, because the filter's decision is a threshold comparison where one
  ULP is a different colour on screen.
- A pardes launched from the Dock finds the same programs a pardes launched
  from a terminal does. LaunchServices hands a bundle launchd's own
  environment, whose `PATH` is `/usr/bin:/bin:/usr/sbin:/sbin`, and every pty
  shell, `|` filter and language server inherited it — so `yazi` in
  `/opt/homebrew/bin` was missing in the app and present in the same build run
  from a shell, which reads as "the Dock build is broken". `host_io.Shell`
  composes `/etc/paths` then `/etc/paths.d/*` in the order `path_helper`
  reads them and adopts the result before the first fork in all four native
  hosts. It appends, so an inherited entry is never demoted and a configured
  `PATH` is left byte-for-byte alone: the rule is that only a `PATH` nobody
  configured gets repaired.
- Language servers other than ZLS start on macOS. The protocol client opened
  its control socket with `SOCK.CLOEXEC`, which Zig defines for Linux and
  which Darwin answers with `EPROTONOSUPPORT` — so `socketpair` failed before
  any fork, every spec in the table reported "no server", and rust-analyzer,
  clangd and gopls were unreachable in every macOS build. It is a plain
  socket plus `fcntl(FD_CLOEXEC)` now, the route `fuse.zig` and `nested.zig`
  already used. Verified end to end against a 200-crate Rust workspace:
  rust-analyzer indexes, and `gd`, `document_symbols` and `hover` answer.
- `SPC l i` reports what the language backends are doing in the macOS app,
  and `|` filters a selection there. Both effects were unimplemented host
  methods — `lsp` and `pipe` were absent from the AppKit vtable, so the core
  answered its own empty answer and the panel rendered blank while a sort
  filter silently did nothing. The snapshot-plus-worker body each shell had
  its own copy of is one module (`src/lsp_host.zig`), and the in-flight pipe
  table is `selection_pipe.Tasks`; all three native shells share both.
  Unsolicited server news ("rust-analyzer indexing 45%") reaches the message
  row because the status sink is registered rather than merely defined.
- Animation runs at the speed it claims. `pardes_animation_tick` advanced one
  scene frame per callback and reported time as `frame_count / 60`, so
  animation time was a count of how often the callback happened rather than
  how much time had passed — and the AppKit chain re-armed `asyncAfter(.now()
  + 0.016)` only AFTER the previous frame's work, making the real period 16 ms
  plus all of it. Motion ran at roughly three quarters of wall clock,
  unevenly. The tick measures elapsed monotonic time and spends it in whole
  16 ms steps, banking the remainder, so a late callback advances two frames
  instead of stretching one; the arithmetic is a pure function with its own
  tests, no display attached. On macOS 14 and later the run is driven by one
  `CADisplayLink` phase-locked to vsync instead of a chain rebuilt per frame.
- A filtered terminal's colours are mapped in two stages, and the second one
  keeps text off the page. `Filter` reduces every colour to its nearest
  canonical xterm key and reads that key out of ghostty-vt's theme-derived
  256-colour projection — a comparison of RGB triples, which knows about hue
  and nothing about the background. The projection is generated FROM the
  default foreground and background, so its cube corners are those two
  anchors, and the nearest key to a truecolour extreme was therefore the
  background itself: `\x1b[38;2;255;255;255m` on acme's `#ffffea` paper
  resolved to `#ffffea`, a WCAG ratio of 1.000, text painted the colour of the
  page under it. Every curated theme owned such a key — 231 on the light one,
  ANSI black on both dark ones, which is a bare `\x1b[30m`. The two default
  roles are now mapped first and named as the anchors they are, and every
  other FOREGROUND has to clear `config.tty_filter_min_contrast` (1.5) against
  the mapped background; one that cannot is not mapped at all, and takes
  whichever anchor is still visible there. Backgrounds are exempt, because a
  background is the page the floor is measured against. Measured across the
  curated three the floor refuses 12, 16 and 7 of 256 keys, where a WCAG
  body-text 4.5 would refuse 61, 154 and 91 and flatten the palette.

## 0.0.1

- Pane taglines sit where they do in the SDL window. The macOS shell had its own
  copy of the band geometry and it only ever centred the shorter band in its
  body-sized row, which is the exact case `gui_topbar_pane_border_px` exists to
  prevent: the topbar's unused half-band and the first pane tag's unused
  half-band meet, and the window background shows through the seam — 4 physical
  pixels at the default 82%, 10 at 50%, 14 at 30% on a 20-pixel cell, so it grew
  as the tagline face shrank. The rule moved into the core and both pixel hosts
  now call it: row zero bottom-aligned, the first pane-tag row top-aligned, the
  two joined by the themed rule, rows between centred, and a `Tagbottom` band on
  the last row flush with the window edge with the sub-cell strip beneath it
  painted in the band's own colour instead of the page's.
- Files reload themselves on macOS. The tty shell, the SDL window and the
  detached daemon all watched with `inotify` and therefore watched nothing off
  Linux: an edit made outside pardes never reached the pane, and a PDF replaced
  on disk kept rendering the old inode. They now share one mark/reconcile
  transaction over two kernels — `inotify`, or a `kqueue` whose queue the hosts
  wait on and poll exactly as they did the old descriptor. A macOS mark is two
  filters, because a kqueue directory filter reports its entries changing and
  never a write to a file already inside it: the parent follows rename-over
  saves, the file catches in-place writes, and the file filter is re-armed once
  a rename-over has moved the inode. That is the same pair the AppKit host's
  DispatchSources already used for the same reason.
- Snapshot captures are deltas against the previous capture in the same script,
  and a scripted click may name a word (`press middle @Del 2`, `@Del#2` for the
  second pane on a row, `@Save-2` for a column beside one) instead of a screen
  column. Adding one builtin word to a tagline used to rewrite 78 goldens
  (3,758 lines) and silently move seven scripts onto the wrong word: `tutor.snap`
  had been clicking `Grep` and asserting a tutor pane, `tagbottomimage.snap`
  clicked 176 columns from the `Del` it claimed to execute. Seven tests regained
  their subject, the corpus dropped from 16,700 lines to 5,722, and the same
  edit now moves 337 lines.
- `zig build snap -- --update` regenerates in parallel at the widest probe
  settings and then verifies every golden it just wrote, instead of capturing
  once, serially, and blessing whatever it saw: 77s to 41s, and a capture that
  does not reproduce is reported rather than committed.
- `zig build unit-test -Dtest-filter=<substring>` runs one test. The whole
  binary is 14s, so a one-line change used to cost the full run; the test
  runner's own `--test-filter` was silently dropped by the build.
- A snapshot script covers the `Save` prompt for the first time: armed on a
  `+New` scratch it draws on the message row, prefilled with the pane's own
  directory, and submitting it turns the scratch into the file it names.

- `pardes --fs` serves acme's control filesystem over Linux FUSE: a directory
  per pane holding `addr`, `body`, `ctl`, `data`, `errors`, `event`, `rdsel`,
  `tag`, `wrsel`, `xdata`, plus `index`, `cons` and `new/` at the root. A
  program that opens those files is an editor extension — no plugin API, no
  interpreter. `--fs=<dir>` names the mount point instead of deriving one under
  `$XDG_RUNTIME_DIR`; every pane shell gets `PARDES_FS` and `PARDES_PANE`.
  See docs/acme-fs.md and examples/acmefs/.
- While a script holds a pane's `event` file open, buttons 2 and 3 in that pane
  are REPORTED to it instead of performed, so the words in that pane's tag are
  the script's commands (acme's extension model). Keyboard Enter/Tab still
  execute, so a scripted pane stays editable.
- Writing an event record back (`origin type q0 q1`) makes pardes perform the
  Look or Exec it names — the same door acme has always had, and the reason
  the mount is 0700.
- Version tracking: the compiled version is embedded from build.zig.zon and shown by Changelog.
- New builtins: Changelog, Newtty, and Joincol.
- Markdown tree-sitter highlighting, including nested fenced code blocks.
- `.patch` and `.diff` files are highlighted.
- Coloring dispatch keys off the buffer title.
- Opening a new file only splits into a new column when both panes would be at least 100 columns wide.
- Terminal ANSI colors render only in tty mode; normal mode is a plain editing
  view, so an edit can never shift a shell row's colour.
- `New` and `Newcol` open an empty scratch buffer instead of a shell or a
  temporary file. Its directory is inherited from the pane it was opened from.
- `Save` on a scratch asks for a path, prefilled with that inherited
  directory, and the buffer becomes an ordinary file once written.
- `Save` reaches every pane that holds text of its own and leads its tagline:
  a terminal ("Save New Newtty Del Filter") and an output buffer like
  `+Search` ("Save New Newtty Del") as well as a file. Images and PDFs keep
  the plain tail — their bytes on disk already are what they are.
- `Save` takes the path as an ARGUMENT (`Save notes.txt`, or a selection
  chorded onto the word) and asks for one only when it was not given, the way
  Find and Grep ask for a pattern. A relative path resolves against the
  pane's own directory, with `.` and `..` normalized; a path that cannot be
  made absolute is refused rather than written next to the process.
- Writing to a path never rewrites the pane: a terminal stays a terminal, an
  output buffer keeps its rows and its place in the n/N ring, and an open file
  keeps the file it has, so `Save <elsewhere>` is a copy and not a rename.
  The scratch `+New` is still the one exception, becoming the file it names.
- A dropped pane's memory outlives its frame, so a pane pointer taken during
  that frame stays valid until the next one repairs it.
- The core owns the event loop. Each platform is now a `Host` of optional
  function pointers; an unimplemented method falls back to an in-process
  default, so a host with no methods at all is still a complete pardes.
- Ctrl-V and Ctrl-Shift-V paste in a tty pane: the default register and the
  system clipboard, typed at the program the way a terminal emulator pastes
  (bracketed under mode 2004). A paste arriving from the desktop reaches the
  shell instead of an edit buffer the pty cannot see.
- The default filesystem is pardes's own source, embedded from an allowlist
  (src/source_manifest.zig): what a host with no file method reads, and what
  the browser has always read. It replaced a build-time generator that embedded
  every tracked .zig file.
- `zig build run-isolated` builds and runs `pardes-isolate`: the same source
  with one comptime option, whose host supplies only the terminal. The
  filesystem is not disabled in it, it is absent — the libc paths compile away.
- Typst highlighting: headings, `*bold*` spans, raw/code blocks and inline raw
  spans, and `#`-prefixed function calls (including the sigil itself).
- Fenced Typst raw blocks with a language tag (e.g. ```` ```zig ````) get that
  language's grammar injected, the same treatment markdown fenced blocks
  already had.
- Markdown bold, italics and inline code spans are highlighted: pardes now
  builds the markdown inline grammar, a second parser off the same dependency,
  and re-parses the block grammar's `inline` nodes with it. Only the block
  grammar was built before, so emphasis had no nodes to match.
- Escape in a PDF pane is the document's own cancel: it drops the mouse
  selection and the search highlight overlay and stays where you were reading.
  It used to run `Last`, which moved focus out of the pane and left every
  following key going somewhere else. `Shift-Esc` is now the hop out — the same
  chord that leaves a raw tty.
- `gj`/`gk` move one VISUAL line, following the automatic breaks of a
  soft-wrapped body and keeping the goal column inside the row. `j`/`k` are
  unchanged: whole file lines. With `Wrap` off a line is one visual row and the
  two pairs are the same motion.