aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAge
...
* search: a scrollback search bar on /, matches highlighteduser2026-09-08
| | | | | | | | | | | | | | | | | | | | | | | | 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.
* release: v0.1.6 paperwork -- formula and PKGBUILD pinneduser2026-09-07
|
* release: v0.1.6v0.1.6user2026-09-07
|
* graphics: no cell arithmetic at the far edge of an uncut imageuser2026-09-07
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* graphics: a retransmit invalidates every placement of the imageuser2026-09-07
| | | | | | | | | | | | | | | | | | | | | 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.
* Merge branch 'graphics-fixes' (PR #2 from iksteen): five fixes to the kitty ↵user2026-09-07
|\ | | | | | | | | | | | | | | | | | | 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.
| * app: cut the modals, toasts and splash out of the images under themIngmar Steen2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
| * graphics: place what a float leaves of an image, not nothingIngmar Steen2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
| * graphics: state z=0 on a placement, so a mirror paints it over the textIngmar Steen2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
| * graphics: place an image once, not once per frameIngmar Steen2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
| * server: one synchronized message per frame, cells and images togetherIngmar Steen2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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
* | screen: commit a wide cell's tail to prev, or the diff leaves a holeuser2026-09-03
| | | | | | | | | | | | | | | | | | | | | | 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.
* | web: on one column the hero hovers, not a copy of the logouser2026-09-03
| | | | | | | | | | | | | | | | | | | | | | 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.
* | web: the floating logo, on one-column screens toouser2026-09-03
| | | | | | | | | | | | | | | | | | 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.
* | web/recordings: a move cursor while the pointer dragsuser2026-09-03
|/ | | | | | | | | | 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.
* pane: drop the render snapshot of panes on hidden tabs -- 154K -> 66K eachuser2026-09-03
| | | | | | | | | | | | | | | | | | | | 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.
* web: carduser2026-09-02
|
* README.md: chore fixuser2026-09-02
|
* README.md: choreuser2026-09-02
|
* README.md: choreuser2026-09-02
|
* README.md: remove pointless braguser2026-09-02
|
* docs: a way back to slosh.foo from every page; README links the siteuser2026-09-02
| | | | | | 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.
* web: arch install as three copyable stepsuser2026-09-02
| | | | | | | | | 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.
* arch: point at ghostty-terminfo, the pacman spelling of the entryuser2026-09-02
| | | | | | | | 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.
* webdemo: generate terminfo.h -- pty.c includes it nowuser2026-09-02
| | | | | | | | 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.
* release: v0.1.5 paperwork -- formula and PKGBUILD pinneduser2026-09-02
|
* release: v0.1.5v0.1.5user2026-09-02
|
* pty: install the terminfo entry implicitly when it is missing everywhereuser2026-09-02
| | | | | | | | | 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.
* slosh --install-terminfo: the binary carries its own terminfo entryuser2026-09-02
| | | | | | | | | | | | 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.
* pty: fall back to xterm-256color where xterm-ghostty has no terminfouser2026-09-02
| | | | | | | | | | | | 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.
* version: never answer for somebody else's git; arch: 0.1.4-3user2026-09-02
| | | | | | | | | | | | | | | | 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.
* ship: paperwork before the website, so the demo is not stamped -dirtyuser2026-09-02
| | | | | | | 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.
* release: v0.1.4 paperwork -- formula and PKGBUILD pinneduser2026-09-02
|
* release: v0.1.4v0.1.4user2026-09-02
|
* vendor: libghostty-vt bedb6f2 -> 3c1ef5b3 (2026-09-01)user2026-09-02
| | | | | | | | | | | | | | | | | 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.
* contrib/vendor-check: watching upstream is one command nowuser2026-09-02
| | | | | | | | | | | | 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.
* arch-repo: find zig on PATH itself; release: v0.1.3 paperworkuser2026-09-02
| | | | | | | | | 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.
* release: v0.1.3v0.1.3user2026-09-02
|
* web: the released version in the hero, linking to the releaseuser2026-09-02
| | | | | | 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.
* web: contributing section, shorteruser2026-09-02
|
* web: a contributing section -- the github and the discorduser2026-09-02
| | | | | | 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.
* gen-docs: the page title separator is a middle dotuser2026-09-02
|
* docs: sweep the em dashesuser2026-09-02
| | | | | | | 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).
* web: no em dashes on the landing pageuser2026-09-02
| | | | | | 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.
* web: the sessions step mentions shared attachuser2026-09-02
|
* themes: pin the attach tag to each palette's muted chrome toneuser2026-09-02
| | | | | | 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.
* attach tag: just the count, in theme coloursuser2026-09-02
| | | | | | | 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.
* web: choreuser2026-09-02
|
* multi-attach: say who else is watching, and where you areuser2026-09-02
| | | | | | | | | | | | 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.
* Merge branch 'multiplayer': shared multi-client attachuser2026-09-02
|\ | | | | | | | | | | | | | | | | 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.