| Commit message (Collapse) | Author | Age |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A notice is an overlay now, which is the one place it is NOT like the
tree-sitter context: it takes no row from the body and moves no text. Each one
is a chip as wide as its own message plus a blank cell either side, anchored
to the pane right edge on the body top rows. The rest of each of those rows is
ordinary body text that still reads and still answers a click -- Notices.left
records where each chip starts, and the hit test refuses only the cells it
covers rather than the whole row.
The width is counted in GRID columns rather than scaled into the tagline face
the chip is drawn in, because the canonical grid is what a terminal client
draws and a chip narrower than its own text there would clip it; the narrower
tagline face simply leaves a little more room inside the chip. The tag layer
takes the geometry the grid pass already chose, so the two cannot disagree and
the GUI skipping the cells a tag layer covers leaves no stub behind.
Pane.body_offset is gone with the reservation it existed for, and bodyTop
takes only a rect. The body layer keeps every row it had, the scrollbar runs
the full body again, and the GUI smooth-scroll extents go back to the pane.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A message, a leader chord and a prompt used to share one row of body text at
the bottom of a pane, wearing the tagline font and nothing else about a
tagline. Now each one is a TagLayer of its own, emitted through the same
renderHeaderLayer the pane and column tags go through, so it gets the tagline
height, the small-font metrics, the band offset and the border for free --
none of which a body-grid row can have by changing its font role. The text is
right aligned. The prompt stays on the canonical grid because it owns a
cursor, and a cursor has to sit on a real cell.
The body starts BELOW the bands rather than under them, the way tree-sitter
context rows already worked. Pane.body_offset is how many rows they took and
Pardes.bodyTop(pane, rect) is the one place that answers "where does the body
begin" -- replacing fifteen copies of `if (tag_bottom) r.y else r.y + BOX_H`
spread across the paint, hit-test, scroll, PDF and image paths, which is what
let the bands and the text under them come adrift. Every notice is painted on
the grid as well, because the grid is what a terminal client draws and a band
it cannot see is a message it never gets; the GUI skips grid cells a tag layer
covers, so nothing is drawn twice.
Three bugs the bands exposed, fixed here:
- a prompt band flush with the right edge put its caret one column past the
pane, which the detached wire refuses -- so every frame was dropped for as
long as the prompt was open. The band now reserves that column.
- a click on a band mapped to Sel row 0, which is the TAG row: clicking
chrome expanded a word out of the tagline and ran it as a builtin.
- a watched file reloading under the editor changed the core without going
through update, so needs_frame was never set and the reload was never
drawn. Pardes.invalidate() is the name for that, and the file and theme
reloads call it.
A session can now drive its own 9P namespace instead of being refused one:
ownMountSuffix answers what a path names inside this editors own tree and
resolve, readLimit and write serve it from memory rather than making the
syscall that never returns. The match is anchored to whole components under
the registrys 9p/pardes/<name>, because a bare /pardes/<name> anywhere in a
string would claim ~/src/pardes/<name>/README -- and, before write learned the
same trick, write the trees bytes over the real file. readFileLimit and
writeFile refuse instead, having no core to answer from.
A toggle setting SETS when given `on` or `off` and only flips when it is bare,
so the report LocationsConfig prints can be fed back as configuration and mean
what it says.
Snapshots: 97/98, from 0/98. The goldens were several commits stale and 17
scripts had stopped running; `config <line>` is a new script command that
appends to the per-script startup config, so a script that clicks body
coordinates pins `Verbose off` instead of counting the rows an announcement
moves. nested-optout is left failing on purpose: two levels of nesting prepend
vaxis F3 codepoints to typed lines, which is a real bug and is written down in
docs/divergences.md with a repro.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Opening a path inside this editor's own 9P tree hung the session outright, and
it is easy to do by accident: a file manager whose $EDITOR is pardes, or a Look
at anything under /mnt/9p/pardes/<me>/. The realpath, the stat and the read all
leave through the mount and come back as 9P requests only this editor's loop
can answer, while that loop is blocked making them. The filesystem then stops
answering anybody, which is what made it look frozen rather than slow.
The answer has to come from the NAME, before any syscall, because the syscall
is the thing that never returns: the listener notes the name it is posted under
and `resolveOs` refuses a path containing `/pardes/<that name>/`, with
`readLimit` refusing it too for anything that gets past resolution. Another
session's mount stays perfectly usable, and `/n/self/...` is the way to reach
your own tree -- the editor serves it from memory without leaving the process.
Reproduced before and after: the 9P write that never returned now returns, the
editor stays responsive, and it spends four CPU ticks doing it.
The transient lines also now look like what they were modelled on. They carried
the context band's bookkeeping -- one list, rows reserved the way sticky
headers reserve them -- but still painted as ordinary body text, so a message
read as a stray line at the bottom of the pane rather than as part of its
chrome. They take the tagline font and the tagline's own colours now, verified
on a running editor: the announcement lands with font_role=tagline.
Two tests the features never had: a builtin announcing itself, reaching the
notice list, being overridden by Msg's own text and silenced by Verbose; and
the own-mount guard, including that a session with no listener refuses nothing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The registry sweep could delete a live socket, anywhere on the filesystem. A
reviewer reproduced it: a socket that is bound but has not reached listen(2)
answers ECONNREFUSED exactly like a dead one -- that window is every server's
startup -- and the sweep then followed the entry's symlink and unlinked
whatever absolute path it named. It now follows a target only into the
directory our own sockets live in and only to a `pardes-9p-*.sock` name, it
re-probes immediately before deleting rather than trusting a probe that is by
then several syscalls old, and a readlink that exactly filled its buffer is
treated as the truncation it is. The test grew a case for an entry whose
target is not ours: the entry goes, the file does not.
Ctrl-V in raw tty mode was a black hole when the yank register was empty --
neither typed nor forwarded -- so vim's visual block, readline's quoted-insert
and every other program's Ctrl-V simply vanished. With nothing to paste the
chord belongs to the program again.
The lone-ESC flush added earlier was dead code. vaxis already returns Escape
for a one-byte 0x1b (`Parser.parseGround` asserts `input.len == 1`), so the
carried byte it waited for can never exist; a reviewer showed a 3 ms gap and a
60 ms gap behaving identically. Removed rather than left to imply a guarantee
it never provided.
A shell whose editor is gone can start one again. Naming a live but
unreachable session made `pardes <file>` exit 1, which let a stale environment
variable lock someone out of their own editor; it falls through to an ordinary
session, as it did before the variable existed.
Also: the macOS ABI check for `pardes_topbar_pane_border_px` had been replaced
by a duplicate of the line above it; `--startup` now fails on a leak the way
every other measurement in that file does, and stops calling its maximum a p95
below twenty samples; the served README and the skill no longer tell you to
write to `data` with `>`, which truncates the whole body before the write
lands; `docs/v9fs.md` described the allocate-on-walk design that was rejected;
and `test/fs.py` keys nesting off `PARDES_PID`, so its forwarding case stops
passing only when the runner happens to be inside a live pardes.
fs-test now reaches its one documented pre-existing failure instead of dying
early. Suite 778/783 with the two known crashes.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A 9P round trip on a local Unix socket cost 9.7 ms against the SDL shell and
0.758 ms against the terminal one. The server was not slow and the wake was
not broken: instrumenting the path showed every request waking the loop early
(wakes=210, woke_early=212, timed_out=38 over 250 ticks) and being answered on
that same pass. The cost was that `pump` answers 9P at one point in a loop
that then renders and presents unconditionally, so a client's next request
landed while the main thread was blocked on the display, and each round trip
therefore cost a whole frame. The frame rate was governing something that has
nothing to do with drawing.
`Pardes.needs_frame` starts true, is set by every event except a tick with
nothing animating and a filesystem request that only reads, and is cleared
once a frame is presented. `pump` returns before render and present when it is
false and nothing is animating. An idle editor answering reads now draws
nothing at all.
9P read_fid, one RPC: gui 9.7 ms -> 0.056 ms (173x)
tty 0.758 ms -> 0.062 ms (12x)
Verified the shells still paint rather than going quiet: the rendered screen
carries the opened file, a write through 9P redraws within the frame, and
`fs-discovery-test` passes over the real wire. Suite unchanged at 778/783 with
the two pre-existing crashes.
Also from the adversarial review of the previous commits:
`pardes --tty FILE` silently discarded the file, and `--tty MISSING` silently
discarded the error pane. main.zig named the boot layout before the positional
was resolved, and naming one short-circuits `Boot.of`. The choice now happens
after the argument is known, and only when there is no file and no missing
word. macos.zig names the same layout, so the app no longer boots a different
one from the terminal and SDL shells.
`pre_close_last_pane_tail` was transcribed from the NEW default rather than the
old one, so the upgrade path it was added for did not exist: a workspace dumped
before the tagline reorder came back with the old default welded on as a custom
tail. It is now the string it claims to be.
A pane two rows tall lost its message and, worse, its prompt and the cursor
with it. The notice cap keeps the LAST notices now, because the prompt is last
and a prompt you cannot see is one you type into blind.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A pane's message, its pending leader chord and a prompt waiting for input all
wanted the same row above the tagline. The prompt won it, the leader drew over
whatever was there on the right, and renderBodyLayer recomputed "is anything
down there" inline to reserve a single row. Three claimants, one row, and two
places deciding.
Pane.Notices is that decision in one place: a short ordered list of the lines
a pane is showing, rebuilt every frame by collectNotices from the state that
owns each one. The body layer reserves exactly notices.len rows, the way it
already reserves rows for sticky context headers, and the paint pass walks the
list and gives each line a row of its own, stacked upward from the tagline.
Nothing stores a second copy of the truth, so a line that goes away is simply
not added next frame and the rest close the gap. One notice lands on exactly
the row the message always had, and the prompt stays nearest the tagline so it
keeps its cursor.
Last also learned what to do when the jumplist is empty. It used to walk the
jumps and, finding nothing, do nothing at all -- which is the ordinary case
for a pane that opened beside this one and was never focused, such as the text
pane the bare tty layout puts under the shell. It now falls back to
neighbourPane: the next pane down the column, wrapping, and any other live
pane failing that. Alternating with the neighbour is what Last is for.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Tcreate that replaced acme's /new was a step away from the idiom dressed
up as a step toward it. A pane is named by a server-assigned serial, so the
create ignored the client's name: `mkdir /pane/foo` succeeded and left you
/pane/12. A mkdir that does not make the directory you named is worse than the
read-with-side-effect it replaced, and it broke in the shell workflow that
motivated the change. `create` is out of the declared features, so Tcreate is
EPERM again; Tremove stays, since `rm` to close a pane is unambiguously right.
/pane/new is now opened, not created: the open makes the pane, the read of
that fid answers its serial, two reads agree, and closing it leaves the pane.
That is /net/tcp/clone's mechanism (kernel/network/ip/devip.c, in ipopen),
not acme's, and the difference is deliberate. acme allocates during the walk
and lands inside the new window, so /dev/new/body works in one step, and it
can afford to list `new` because a Plan 9 directory read carries every entry's
stat and nothing walks. A kernel or FUSE mount walks and stats each name a
listing gave it, so allocate-on-walk would make a pane per `ls -l`. Allocating
on open keeps `new` listed -- a stat is not an open -- at the cost of the
one-step new/body. `new` stays unreachable from an editor path, because that
resolution serves Look hover previews.
The idiom pass behind it, read out of the Plan 9 tree at
~/05-genizah/principia-softwarica rather than recalled:
Rerror carries a string, not an errno (man 5 error: `ename[s]`), and acme
names every refusal. The five refusals pardes shares with acme now say what
they mean; the generic sites keep their bare errno rather than invent strings
acme does not have. body and tag declare DMAPPEND, which they had always
behaved as (acme(4): "always appended; the file offset is ignored"), checked
first against Linux's fs/9p, which never maps the bit. excl stays unset
everywhere, because acme sets DMEXCL on nothing. Blocking reads, per-object
addr scope and the readable pane ctl were already right. Real stat sizes and
qid versions stay: acme reports length 0 and version 0 for everything, and
Linux clients need better.
One bug fell out of it. open reset the addr range, so `echo '#0,#5' >addr;
cat addr` answered `0 0` and `cp addr dot` copied zeros. acme(4) makes the
contract explicit -- "a regular expression may be evaluated by writing it to
addr and reading it back" -- and acme gets away with resetting on the 0-to-1
open only because its clients hold the fid across both. A shell cannot: that
is two opens. The register is cleared by truncating it now.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |\ |
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Pty children are exec'd with their own TERM/COLORTERM/TERM_PROGRAM instead of
inheriting a .app launch's empty environment, and ttyTaken finally answers on
darwin — libproc walks the tty's foreground process group — so Escape reaches
the child and Exec stops believing every pane sits at its prompt. The
occupancy suite runs on both platforms now.
The workspace tag row moves into the native menu bar as a Builtins menu.
-Dworkspace-tag (default off for -Dplatform=macos, on everywhere else) drives
it, and Pardes.topBarHeight replaces the TOPBAR_H constant so the core stops
reserving the row.
The view pins every variable-font axis to the file's own default (Maple Mono
came up Thin otherwise), shapes ligatures, carries per-shape pointer cursors,
and draws the look-hover affordance as refracted glass. Tag rows fill edge to
edge, with the anchor box painted back on top of that fill and its mode glyph
centred on the same square. Theme accents re-saturated across the set.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
The 9P tree stops being a command language wearing a filesystem. /new
created a pane as a side effect of a *read*; it is now Tcreate in /pane,
with Tremove to close, which cloud9's engine has always supported and the
editor never declared: tree.zig now says
`features = .{ .create = true, .remove = true }`. Eleven pane ctl verbs
become files that can be read as well as written -- dot, limit, dirty,
mark, scroll, look, exec -- leaving ctl with `get`, the one verb no file
would say better. Root /ctl splits into a read-only /status and the
/look and /exec files whose write IS the click. stat carries real sizes
where it used to answer 0, and qid versions track a pane's revision, so a
client can poll for change without re-reading the body.
Commit a624a56 moved raw-tty keys to an early-return branch that knew only
Ctrl-B and bare Escape, and in the same edit deleted the paste branch below
it. That cost Shift-Escape (the unconditional way out of tty mode) and both
paste chords: Ctrl-V and Ctrl-Shift-V reached the child as keystrokes, so an
agent CLI running in a pane took Ctrl-V for its image-paste binding and
answered "No image found in clipboard". Both are restored, with tests.
Nested detection was not subtly broken but deleted: 60367d8 removed
nested.zig's process-ancestry walk and left "am I inside pardes" derived from
PARDES_FORWARD_LOOK, which read "0" both for --nested and for "the listener
did not come up". PARDES_PID now answers that question on its own, checked
with kill(pid, 0); PARDES_9P and PARDES_PANE answer how to reach it; the flag
is gone. The posted-9P registry also self-heals now -- a session that aborts
cannot unlink its own socket, so posting sweeps entries whose target refuses
a connection, symlinks only and on a definite ECONNREFUSED only.
Elsewhere: tty scrolling is sticky-bottom, following new output only from
the last row, with typing and entering raw mode snapping back to live; the
boot layouts are a Boot enum instead of a chain of ifs, and the bare tty
startup (Boot.tty, which main.zig names) opens an empty text pane under the
shell while tests keep Boot.tty_shell; builtins announce themselves on the
message row under a Verbose setting that is on by default; Config prints
each setting the way you would type it back, so WindowOpacity 70 rather than
"WindowOpacity: 70%"; LocationsConfig opens its window only when called bare;
every tagline puts the word that closes the thing last, and a column now
outlives its panes -- closing the last one leaves an empty pane, and only
Delcol, newly on the column tagline, takes the column away.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
pardes spoke 9P and could be mounted, but only by naming its socket:
$XDG_RUNTIME_DIR/pardes-9p-<name>.sock sits one directory above the
registry and nothing could find it. Now a listening editor advertises
itself at $XDG_RUNTIME_DIR/9p/pardes/<name>, a symlink to the socket it
already binds. One directory for the program, one entry per editor —
the layout zmx posts its sessions under — so several editors group
rather than crowd the registry root.
The socket does not move: adopting the registry only advertises. Only
the runtime-directory socket posts, so an instance on the
~/.local/state fallback stays out of the user's registry, the way a
private ZMX_DIR does for zmx. Stopping unposts, and only while the
entry is still ours, so a name another editor has since claimed is
never unlinked. The cloud9 pin moves to 9c4d668c for cloud9.post's
path helpers.
Serving is the whole of it. Consuming the registry is 9ns's job: it
mounts the lot at /mnt/9p and an interactive fish already self-wraps in
one, so a pardes started from a terminal reads /mnt/9p/harness/... with
the same code that reads any other path. Two drafts that taught
`resolve` to dial the registry itself were reverted — one duplicated
9ns for no gain, the other reinterpreted relative dials, which are a
feature. `resolve` is byte-identical to what it was, and no dial that
worked changes meaning.
What pardes still does not do, and why, is in docs/cloud9.md: it binds
its own socket rather than posting through cloud9.post, because post
claims flat names only — legalName rejects '/', and claimName derives
its lock directory by stripping "/9p" — so a name inside a subdirectory
cannot go through it. zmx hand-rolls the same symlink for the same
reason. Unifying them means teaching post a group, which is a change to
adversarially-hardened code rather than a rename.
docs/divergences.md records what this bookmark move leaves beside it:
the editor line rruwvuzm (~1300 lines, forked at 01104e7c, still on the
old cloud9 pin), the other bookmarks, and two failures that are not
this change — fs-test's syntax-highlighting assertion, which fails
identically on a clean main, and pardes not starting headless, which is
why this is covered by 9p-io-test rather than by running the editor.
Tests: 11/11 9p-io-test, including a listener that posts on start and
unposts on stop; 31/31 unit-test.
|
| |
|
|
|
|
|
|
|
|
|
| |
The hand-written poll loop for Unix/TCP listeners is replaced by
cloud9.serve.Runner; requests are queued to the editor thread, which answers
them under the connection lock on each frame and retries parked reads as
before. QUIC keeps the poll path (its adapter is fd based). The detached
server no longer loses a wake that lands between frames. The firmware path
keeps driving the engine with push/step. cloud9 re-pinned.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
src/9p.zig shrinks to an 88-line alias: the engine and the backend contract
(Req, Reply/ReplyWith, Op, Status, Attr, E, error strings) now come from
cloud9.fs. The editor's limits become cloud9.fs.Options values; the ESP32-P4
board backend drops its own copies of the contract types. No behaviour
change; fs-bench still allocates nothing per request. cloud9 re-pinned to
the commit that carries the engine.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The served tree loses the self/ level: /index /ctl /new /log /screen
/listeners /pane/<n>/... /os, with /src only in -Dembed-sources=true builds
(default off, on for esp32p4). ctl speaks the editor's own language with two
lowercase verbs, look TEXT and exec TEXT, plus acme's addr verbs; the new/
factory directory becomes one clone file; cons is gone (exec Msg); name and
sel are files; stats report real lengths, modes and mtimes; /log streams
pane new/del/rename/save events. The tree code lives in src/ninep/
(tree, pane, ctl, addr, pty, events, screen, sources); fs.zig keeps host
access, mounts, resolution and find/grep. Same engine and transports.
README (fs-help.txt) and docs rewritten; tests updated and extended.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
| |
The tag's first character moved to TAG_TEXT_INSET when the grip's hit area was
separated from the text by a gap; the assertion kept reading GUTTER, which is
now the gap itself and carries the plain tag background. The test's own point —
that a preview on the tag's first character is paintable at tag column zero,
PREFIX_W being a body concern — is unchanged; only where column zero is moved.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
In raw tty mode Alt-n reached the child as a bare `n`: the ESC prefix was
dropped on the floor, and only on macOS. ghostty's encoder refuses the legacy
alt-esc prefix on a macOS build unless macos_option_as_alt says Option really
is Alt, and KeyEncodeOptions.fromTerminal cannot know that one — it says so in
a comment — so it defaults to false.
False is right for an app that lets macOS translate the chord and hands the
encoder the composed text. Every pardes shell decides the modifier itself and
hands the core the BASE key with the alt bit and no text at all, which
PardesView.keyDown and app.mjs both spell the same way, so by the time the
encoder runs there is nothing left to translate.
The unit suite has been catching this on macOS all along; it is only invisible
on Linux, where that branch does not compile in.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A swipe that was still moving when the fingers lifted stopped dead. The dial
next to it has thrown properly since pardes_rotate_end, and this is that same
curve on the scroll accumulator: velocity sampled between events off the
monotonic clock, weighted toward the newest sample because a flick is decided
by how the hand was moving when it left, and a fling that ramps up from zero
at the floor rather than switching on at it.
A coast stops at the end of a document rather than spinning its remaining
velocity against the edge, which is why spendScroll now reports whether the
pane moved and why paneAt is public. Only a step that delivered a press can
report an edge — the many steps between two rows cross nothing.
Nothing suppresses AppKit's own momentum, and nothing needs to: its momentum
events are ordinary pardes_scroll calls, every one of which cancels the coast
before spending its travel, so on a real trackpad the system takes the gesture
over about a frame after the lift and the tail it ends on is below the floor.
This is the path for devices AppKit does not fling for — and the only one a
script can reach, since NSEvent phases have no public constructor.
momentum.snap asserts both halves, which is why it needs the long scrollback:
a flick with no document left proves nothing. A slow swipe is byte-identical
after the release; a hard one coasts twenty-four rows further on its own, and
a finger back on the pad stops it there.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The builtin was gated on the SDL platform, so the macOS shell never registered
it and the setting had nowhere to land. macOS already had transparency, but
only the binary kind a theme decides — a theme with no background of its own
drops the ground and the blur shows through. WindowOpacity is the graded,
theme-independent version, and the window had no way to hear about it.
pardes_window_opacity reports the percentage and acknowledges the request in
one read, beside pardes_theme_bg: an SDL surface can arrive without an alpha
channel and has to be able to refuse, while an AppKit window always composites
per pixel, so there is nothing here to refuse and no rollback to perform.
The view then paints the rule shaders/ui.frag.glsl states for the SDL shell:
backgrounds — ground, cell and band fills, and the chrome rules over them —
take the alpha, glyph ink never does, and the block cursor is exempt because
it is foreground chrome that happens to be carried in a cell background. That
exemption is why the cursor joins the colour in the background run key; it can
no longer share a fill with the cells beside it.
Two things the harness was missing fall out of testing it: it never adopted
the theme background or the opacity, so a Theme or WindowOpacity in a script
moved the core and never reached a pixel. draw-opacity records both ends of
the alpha range, which is what makes the two-sided contract assertable —
60% reads 153..255, and 0% reads 0..255 with the ink still standing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
The body-layer context rules landed on a private TaglineMetrics.scale, which
never compiled: no macOS host built this shell when they were written. Expose
the physical-pixel thickness the way borderThickness/borderTop already expose
theirs, and paint the rule over the row's last pixel the way the SDL shell
does, so the two shells agree on the pixel rather than sitting one apart.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|