| Commit message (Collapse) | Author | Age |
| ... | |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
C-a / opens an inline search bar on the focused pane's status row, on
lib-vt's GhosttySearch (D4's pattern: the scrollback walk, reflow and
alt-screen survival are the library's, the UI and the wiring are ours).
Type to search live; the viewport follows the current match; up/down
(or C-n/C-p) step through them with wraparound; Enter keeps your place,
Escape snaps back. A k-of-n counter reads on the right, or "no matches".
Every match on screen is tinted, and the one the bar is on is tinted
apart -- warm gold by default, painted after the shader pass so the
scrolled wash does not dilute a match you are hunting for. Four themeable
colours (search_fg/bg, search_cur_fg/bg).
Bare / took the key from help, which keeps ?; that needed the legacy
decoder to read the shift out of a punctuation byte the way it already
did for capitals -- without it ? and / decode identically (SLASH, no
mods) and cannot be two keys on a client without the kitty protocol.
tests/test_search.py drives it end to end (39 checks): the bar, the
counter, stepping and wrap, all-match vs current highlighting, Enter/
Escape, new output updating the count, case-insensitivity, the keyboard
belonging to the bar, click-away, and that / and ? no longer collide.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A smooth bounce shimmered: anything placed with sub-cell X=/Y= offsets
had its source rectangle shaved by a different sliver every frame, so
the picture's edges breathed as it moved. dvd.py made it obvious --
the logo's crop wobbled between w=124 and w=130 around a true 142.
The pixels-per-cell a placement reports are its own pixels over its
own grid, floored, and the grid includes the cell the sub-cell offset
spills into, so the figure runs a shade small -- 142px over 19 cells
says 7 when the terminal's cell is 8. That was harmless while the
number was only used proportionally at real cut edges. The pieces
rewrite ran it over every placement: axis_crop() clamped even an
uncut image's source to cols * cell_px - offset, and as the offset
moved the clamp moved with it.
The far edge of a piece that reaches the placement's last cell is
where the source runs out; only an interior cut needs the cell
arithmetic. With that, an uncut placement's source rect passes
through untouched, which is what the old emitter always sent.
Two companions in the same sum. A scaled piece so narrow its share
of the source rounds to nothing now keeps the pixel it is inside,
because w=0 on the wire means "the whole image", which is worse than
wrong. And a cut piece whose source is genuinely empty is cells the
image never reaches, so it is not placed at all -- the uncut
placement stays exempt, since the pane-edge crop has always used 0
for "the rest of it".
The headless check places with X=5: the whole source rect must
survive the offset, where the clamped code sent w=0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Replacing an image's pixels deletes all of its placements on the
client -- kitty drops them when the data is replaced, which is the very
fact the place-once logic leans on. But the invalidation rode a local
in one gfx_place() call, so only the placement said in the same breath
as the transmit was owed again. The image's other placements -- a
second one elsewhere, the other pieces of one a float has cut -- were
judged unchanged against a client that no longer holds them, and
stayed unsaid: a picture the client deleted and we believe it still
has, gone until something moves it.
Masked before place-once by the every-frame re-emission, so this is
the regression that fix bought. The transmit now un-marks every
placement of its image, and each is said again as its gfx_place()
comes around.
The live check drives one image placed twice, then replaces its
pixels and places both again: both must reach the client after the
second transmit, where the old code re-sent one.
|
| |\
| |
| |
| |
| |
| |
| |
| |
| |
| | |
passthrough, found through a mirror
One synchronized message per frame with cells and images together, a
placement said once rather than every frame, z=0 stated so a mirror
knows where the picture goes, a float cutting an image into pieces
instead of erasing it, and overlays cut out of the images under them.
Found running slosh inside shellglass, real on a terminal too.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Overlays are painted over the panes and the compositor needs no more
than that: painted later is on top. A placement is not painted, though
-- the client's terminal draws the image over the cells, in whatever
order they arrived -- so an overlay that is only drawn is an overlay an
image sits on top of. The cheatsheet came up under a picture and was
unreadable through it, in the terminal and in the mirror both.
So a frame now records where its overlays landed, and the graphics pass
takes them out of a placement exactly as it takes out a float: the
modal's box (the cheatsheet and the picker wear the same frame), each
toast, and the splash. The list is rebuilt every compose, so a rect that
is not registered again is one that is no longer on screen.
Only the opaque things are registered. The scrim behind a modal tints
what is behind it rather than hiding it, and an image cannot be tinted
by us at all -- so a picture beside the cheatsheet stays bright while
the text around it is pushed back, which is wrong-looking but a great
deal better than the image disappearing while a modal is up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Occlusion for placements is clipping: the cell compositor gets it free
from paint order, but images are sent after the diff and drawn over it,
so what a float covers has to be taken out of the placement itself. A
clean edge came out as a smaller rectangle; anything else -- a float
across the middle, a float on a corner, a float wholly inside -- was
refused, on the grounds that the remainder is an L and a placement is a
rectangle. The image vanished, all of it, the moment a floating pane
crossed it, on the terminal and in the mirror alike, and came back when
the float moved off.
The remainder is not one rectangle, but it is always a few: the strips
above and below what is covered, and what is left beside it between
them. So rect_subtract() cuts the visible remainder into rectangles and
each is placed in its own right, with the source rectangle the same
arithmetic the pane edge already used -- generalised to a cut on any
side, and to a first piece that keeps the sub-cell offset while the
others start on a cell boundary. Eight pieces is the ceiling (two
floats' worth); past it the old refusal stands.
The pieces of one placement need an id each, and the program's own
placement id is one number that cannot be several, so the model now
hands out the placement ids the client is told and keeps the program's
as the identity it was. `graphics` reports the piece alongside it.
Checked against a float parked over an image: the picture is cut around
the float, the strips tile exactly the image minus what the float
covers, and their source rectangles land where their cells do (a float
inside a 40x10-cell natural-size image gives 320x32 above, 320x32
below, 56x96 left and 72x96 right).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
kitty's default z is 0, which draws an image above the text, and that is
the value slosh has always relied on -- it places after the cell diff
precisely so the picture ends up on top. Saying nothing works because
the terminal supplies the default.
Anything mirroring the stream has no such default to fall back on. It
cannot tell "the emitter wants this over the text" from "the emitter
said nothing", and shellglass's viewer paints an unstated image under
the cells. That is fine until a cell over the picture carries an
explicit background -- and dim_unfocused gives one to every cell of a
pane at once, so the moment focus moved elsewhere the whole image
disappeared from the mirrored copy while the terminal, which took the
default, still showed it.
So the placement states z=0. Nothing changes on a terminal, since that
is the value it was already assuming; the mirror now paints the picture
over the cells and the dim pass runs underneath it, where it belongs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Every frame re-emitted `a=p` for every visible placement. On a terminal
that is invisible: a kitty placement is a standing instruction, not a
per-frame one ("the other commands to erase text must have no effect on
graphics"), so a repaint of the cells under a picture does not disturb
it and saying it again says nothing new.
It is not free anywhere else. A mirror cannot know that the identical
placement is identical: shellglass re-decodes and re-encodes the image
on every one, holds the placement back while that runs, and -- because
an image change rides only in a full frame -- republishes the whole
screen each time. Measured on one image and a pane printing text for
twenty seconds, the mirror published 430 full frames and re-sent the
image list 215 times; 215 of those fulls carried no image at all,
because the placement was still mid-decode. That alternation is what
was visible in the browser: a picture that flickered while anything on
screen moved and vanished when it stopped. The same twenty seconds now
costs one full frame, 412 diffs and one image list.
So a placement is emitted only when it is new or has actually changed,
and the client's state records what it is holding. Three things have to
be right for that to be safe: a dropped frame un-marks the placements it
carried (they are owed again, exactly as transmissions and deletions
already were); a retransmit of the pixels invalidates the placement,
because kitty deletes an image's placements when its data is replaced;
and a full repaint clears the screen, which takes the images with it, so
every placement is owed again -- gfx_repaint(), not gfx_reset(), because
the pixels the terminal was sent survive a clear and a deletion still
owed from a dropped frame is still owed.
The live check drives a picture that does not move while the screen
around it repaints: one placement across 121 frames, where the old
behaviour emitted 120.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A frame went out as two messages: the cell diff, then the kitty
placements that belong on top of it. The client wrote each to the
terminal with its own write, and directly on a tty the two land in one
read often enough that nobody saw the gap. Put anything that re-chunks
the stream in between -- a mirror like shellglass, which reads the pty
8KB at a time and writes each read straight through -- and the gap
becomes a screen refresh of its own: the cells where the picture is,
repainted, with no picture on them, and the placement arriving a moment
later to put it back. Every image on screen blinks once per frame, which
is how it was reported: unstable, as if constantly redrawn.
So the two halves travel as one message now, and the whole frame is
wrapped in synchronized output (DEC private mode 2026), which a terminal
buffers and presents as one update. That covers the case the single
message cannot: a frame larger than one read downstream is still split,
and the markers are what stop the halves being presented separately.
Terminals that do not implement 2026 ignore the private mode, so it goes
out unconditionally -- the same thing tmux does, and the reason a redraw
under tmux never showed this.
The live check asserts what the fix is: no image command reaches the
client outside a synchronized update.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRz3zwZJvBvzsR9k4cj6dd
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
A wide glyph's tail column is never painted or compared, and prev never
learned it either. Overwrite the head later and the terminal blanks the
tail cell -- but if that column's new content matches the stale prev, the
diff skips it, leaving a one-cell hole where the glyph's second half was.
Seen as a missing rule cell in a pane's top border after spawning a new
tab: the old tab's centered title started with a wide glyph, the new
tab's border put the rule back, and the tail column stayed blank.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
The stage-resident logo copy goes: instead the top section itself --
logo and buttons -- is position: sticky below 54rem, full-bleed, with
the tour's stage tucked under it at --hero-h, which the page script
measures (wrapping buttons make it no fixed number; a ResizeObserver
keeps it honest). The tagline yields the room: with the header on
screen at all times, every line it keeps is a line the tour loses.
Desktop is exactly as before -- the floating logo, its observers and
its collision test untouched.
|
| | |
| |
| |
| |
| |
| |
| |
| |
| | |
Below 54rem the fixed corner the float centres itself in is content, not
air, so the float stays hidden there -- but the logo can ride where the
terminal does: a small copy inside the sticky stage, above the player, on
screen at all times. No observer, no collision test; it sticks with the
stage. Same link back to the top, same wash and shine (the shared rules
now name both), same reduced-motion fallback.
|
| |/
|
|
|
|
|
|
|
|
| |
The cast already marks the button in the ptr track ([x, y, 1]); the player
now remembers the cell where it went down and, the moment the pointer
leaves it, swaps the arrow for a four-way move cursor (a clip-path, like
the arrow) centred on the old tip. A still click never triggers it, so
the border-click split keeps its arrow while the gap drag and both
pane-carries in the panes demo read as carrying. Player-side only: every
existing cast gets it without re-rendering.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
An updated GhosttyRenderState retains a full copy of the pane's viewport
(rows, cells, per-row arenas, styles) so renders can read it without the
terminal lock. Measured with bpftrace and a lib-vt micro-benchmark, that
copy is ~140KB for an 80x24 pane -- more than twice the terminal it
mirrors (~57KB) -- and every pane kept one forever, composed or not.
Only the current tab is ever composed, so app_compose now drops the
snapshot of every pane on the other tabs (a pointer test once dropped),
and pane_new no longer creates one at all: pane_compose and
pane_select_word build it on first use. Both already begin with
ghostty_render_state_update(), so a dropped snapshot is rebuilt, never
missed, and the tab switch already forces a full repaint.
Server memory per empty 80x24 terminal drops from 154K to 66K anon
(Superlogical reports 68K on macOS; tmux 4K, zellij 1.2-6.2M). Fifty
background tabs: 8.2M -> 3.8M, reclaimed on close, and cycling through
tabs does not ratchet. What remains is the lib-vt terminal itself.
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
| |
The docs header gains a right-aligned slosh.foo link (absolute, so a
locally rendered build/docs points somewhere real too), and the
README says the website exists, which it never did.
|
| |
|
|
|
|
|
|
|
| |
The pacman.conf section and the install command each get their own
box (one copies config, the other a command; sharing a box made the
copy button copy neither cleanly), and ghostty-terminfo moves from
the aside into the install box as a commented optional line, marked
x86_64-only -- extra does not build it for aarch64, where slosh's
self-installed ~/.terminfo copy is the whole story.
|
| |
|
|
|
|
|
|
| |
optdepends says it at install time, the README and the install tab
say it before. Our package deliberately does not ship the file:
extra/ghostty-terminfo owns that path, and two packages must not.
Without either, slosh's own ~/.terminfo copy covers it. pkgrel=2,
both architectures rebuilt and republished.
|
| |
|
|
|
|
|
|
| |
The demo Makefile compiles pty.c with its own rules and never
learned the new header, so the v0.1.5 tag cannot build its own demo
and ship's website step died there. Same generation recipe as the
top-level Makefile; the tag being immutable, 0.1.5's site publishes
from master.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
| |
The lookup miss now writes the embedded entry to ~/.terminfo and
uses it, so a bare box gets the full TERM with no step at all. An
entry that exists anywhere is respected -- possibly newer, certainly
somebody's -- and a HOME that cannot be written leaves the
xterm-256color fallback standing. --install-terminfo stays as the
by-hand spelling; the writer moved to pty.c where the panes are.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The xterm-256color fallback works everywhere; the real entry is
better, and a tarball user on a bare box had no easy way to get it.
The entry is generated from the vendored tree (contrib/terminfo, so
it describes the terminal core we actually ship), compiled with tic,
embedded as bytes, and written to ~/.terminfo under every name it
answers to, letter and Darwin-hashed layouts both. No root, no
package, no conflict with a ghostty installed later. Proven end to
end in a mount namespace: bare box falls back, installs, and the
next pane says xterm-ghostty.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
TERM=xterm-ghostty was unconditional, but the entry ships with
ghostty and a machine that has never seen ghostty has ncurses'
database and nothing else. There, nano exits the moment it opens
(C-a e flashing a pane) and a shell that cannot look up kbs guesses
at backspace. The entry is now looked up the way curses would --
TERMINFO, ~/.terminfo, TERMINFO_DIRS, the system dirs, letter and
Darwin-hashed layouts -- and when absent, panes get xterm-256color,
which every ncurses database carries. Verified both ways with a
mount namespace hiding the database.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The v0.1.4 arch packages introduced themselves as 0.1.4-dirty: the
tag's tarball has no .git, makepkg builds it inside the release
box's repository, and git describe walked up and answered for the
outer tree -- mid-release, when the paperwork is honestly dirty.
Found at 0.1.2, diagnosed, and then left unfixed by yours truly.
Two fences now. contrib/version only trusts a .git whose toplevel is
its own root (helps every future tarball). The PKGBUILD exports
GIT_CEILING_DIRECTORIES for build and package (helps regardless of
which contrib/version the pinned tarball carries, which is why the
first rebuild still said -dirty: the fix was outside the tarball).
pkgrel=3; both architectures rebuilt, verified 'slosh 0.1.4', and
republished.
|
| |
|
|
|
|
|
| |
Steps 6 and 7 rewrite the formula and PKGBUILD; building the site
from the tag checkout with those uncommitted made git describe say
-dirty, and v0.1.4's demo introduced itself as 0.1.4-dirty+webdemo.
Commit the pins first, build the site from a clean tag second.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three weeks of upstream: 504 commits, 120 touching the terminal core
-- OSC 5522, kitty relative placements, C0 fixes, the wuffs pixel
path. Rebuilt from ghostty's own dist step (zig build dist
-Demit-lib-vt=true), zig floor unchanged at 0.16.0, and slosh's C
usage compiled unmodified.
Patch 0001 is re-implemented, not retired: upstream rewrote the area
into ImageStorage.clearScreen but kept the behaviour we patch out --
a terminal clear still reclaims transmitted-but-unplaced images,
which breaks the upload-once place-per-frame pattern the protocol's
two verbs exist for. The patch now removes just that sweep; the
placement clearing and orphan reaping stay upstream's. Their recent
'preserve data on unmatched delete' fixes the same class of bug, so
sending ours upstream remains the way to actually retire it.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Vendoring by pinned commit stays (D12) -- the package channels build
from source archives where submodules are empty, the arch build is
offline-clean because the core is in the tarball, and upstream has no
stable release channel to depend on anyway. What was missing is the
watching: vendor-check reads the pin from vendor.json, asks the
GitHub API what happened since, and lists the commits touching the
terminal core. First run found 120 relevant commits in three weeks,
including upstream fixing the same class of kitty-graphics bug our
patch 0001 fixes -- which is the argument for finally sending it.
|
| |
|
|
|
|
|
|
|
| |
ship calls arch-repo without the ZIG= prefix the by-hand runs had,
so the leg died on makepkg's dependency check mid-release. arch-repo
now derives ZIG from PATH when pacman has no zig, and exports it.
The formula and PKGBUILD pins ride along: ship's steps 6 and 7 had
already written them when the leg failed, and its preflight rightly
refuses a dirty tree.
|
| | |
|
| |
|
|
|
|
| |
Stamped by build-site from ./VERSION, which the release invariant
keeps equal to the newest tag -- so the page cannot advertise a
version that was never cut, and nothing is bumped by hand.
|
| | |
|
| |
|
|
|
|
| |
The multiplayer patch proved the repo takes patches; say so where
people can see it, and give the not-yet-an-issue conversations
somewhere to happen.
|
| | |
|
| |
|
|
|
|
|
| |
Parentheses for the asides, colons for the explanations, commas and
semicolons for the joins, (none) for the empty table cells. Same
treatment the landing page got, across README, docs/, and both
manpages (\(em included).
|
| |
|
|
|
|
| |
Colons, commas and semicolons where twelve of them stood. People
read an em dash as the house style of a language model, and the
landing page is not the place to argue.
|
| | |
|
| |
|
|
|
|
| |
Every shipped theme names attach_fg explicitly, reusing its own
minbar colour -- the default gray suits the dark themes and nothing
else, and paper's light ground proved it.
|
| |
|
|
|
|
|
| |
The +x+y offset goes -- 'N clients' says the thing that matters. The
inverse-video chip goes with it: the tag now draws in attach_fg /
attach_bg from the theme, defaulting to gray ink on the terminal's
own background. Standing information, not an announcement.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A small tag in each client's own top-right corner when several
terminals share the session: the viewer count, and on a view that is
a crop being panned by somebody else's cursor, the crop's offset into
the shared screen ("2 clients +12+4") -- which is exactly the moment
a screen moving on its own needs explaining. Stamped per client after
projection, because every terminal has its own count of others and
its own offset; the shared status bar is composed once and can be
cropped out of a panned view anyway. attach_indicator false hides
it.
|
| |\
| |
| |
| |
| |
| |
| |
| |
| | |
Several terminals can attach to one session at once: the canonical
screen becomes a model projected into every client's own size, with
per-client diff history, input parsing and kitty graphics state. The
active client (newest attach, then last input) sizes the canvas by
default; multi_attach and size_follows in the config control both
halves of the policy.
|