<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/web/demo, branch master</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/web/demo?h=master</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/web/demo?h=master'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-22T14:00:10Z</updated>
<entry>
<title>webdemo: the picker in the machine, and a guest that proves its themes</title>
<updated>2026-09-22T14:00:10Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T14:00:10Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b8e931b548e5e71fd001b9b5e9bbfce8574c7bb6'/>
<id>urn:sha1:b8e931b548e5e71fd001b9b5e9bbfce8574c7bb6</id>
<content type='text'>
The demo image was cut before any of this existed: an old guest binary with
no `C-a t` in it, and a config whose theme was a pasted palette. Rebuilt --
guest binary, root filesystem, site -- so the thing a visitor lands in is the
thing the front page is now claiming.

The overlay already kept every shipped theme in ~/.config/slosh/themes (they
are symlinks into contrib/, copied by content because a symlink in an ext2
image would dangle), which turns out to be exactly the directory the picker
looks in first -- and in the guest it is the *only* one, since /usr/bin/slosh
has no share/slosh beside it. So `boot-check` now asks the guest itself:
`{"cmd":"theme"}` over --script, expecting the worn theme and the shipped list
back. It failed honestly the first time, for the right reason (no HOME in a
phase that boots straight to /bin/sh, so no config, so no themes/ beside it),
which is the kind of failure a check is for.

The welcome float grew a fourth line -- `C-a t`, every theme, worn as you
browse -- and a row to hold it; the motd and the config's own comments say the
same. A visitor who reads three lines and presses one key should land on the
feature the page just sold them.
</content>
</entry>
<entry>
<title>theme: a name and a directory, not sixty colours in your config</title>
<updated>2026-09-22T10:42:15Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T10:42:15Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=faa3fb6ee575a900abc574b0f9ed5ff291761837'/>
<id>urn:sha1:faa3fb6ee575a900abc574b0f9ed5ff291761837</id>
<content type='text'>
Wearing a theme meant pasting its `theme { }` block into config.kdl, or
including a path to a file you had copied there yourself. Both make the
palette a literal in your config, and a literal is the one thing you cannot
change cheaply: not from a script, not from a running session, not by a list
of what is available, and never again from upstream -- the colours you pasted
are the colours that theme had the day you pasted them.

So a theme is a file in a directory now, and a config names it:

    theme_name "phosphor"
    theme { frame_focus "#00ff88" }   // ...but that one colour is mine

theme_name is applied after the includes and before everything the file says
itself, which is the rule includes already follow and for the same reason: the
loader reads a document by asking it for names rather than walking it in order,
so "here" is not a position it could honour, and "what you asked for is the
base, what you wrote is yours" is the rule worth having.

Two directories, yours first: `themes/` beside the root config -- beside the
file that was loaded, so a config somewhere else (SLOSH_CONFIG, or one handed
to --check) takes its themes with it -- and then &lt;prefix&gt;/share/slosh/themes,
compiled in from PREFIX. install(1) has been putting themes there all along
and nothing could read them; now a shipped theme is nameable without being
copied, and a file of yours with the same name shadows it, which is how a
root-owned theme gets edited.

A name, not a path. The directory is what makes a theme switchable, listable
and shadowable, and a path silently does none of those, so `theme_name
"../x.kdl"` is refused with a line pointing at `include`, which still works
and is still the answer for a file somewhere a directory of names cannot
reach. A theme that is not there is one complaint and no more (D9): the rest
of the config applies, because losing your keybindings over a mistyped colour
scheme would be the wrong trade. A theme file is read by the same loader, so
it is watched like any other: saving one reloads the session wearing it, and
the derivations (tab_status_fg, tab_idle_bg) run over the theme's own colours
exactly as before.

Switching without editing anything, over the socket a session already has:

    slosh -s main cmd theme                                # amber default* ...
    slosh -s main cmd '{"cmd":"theme","name":"amber"}'      # now
    slosh -s main cmd '{"cmd":"theme","name":"amber","save":true}'

The session holds the name over later reloads, so saving your config for an
unrelated reason does not put the old colours back; `save` writes the
theme_name line into the file, replacing the one already there and leaving
every other line and comment alone -- dumping the whole config back over
itself would reformat a hand-written file and drop its comments, which is not
a fair price for changing a colour scheme. A name is tried on a throwaway
config first, so a typo costs nothing. An empty name gives the answer back to
the file.

--dump-config stops writing colours: it writes the theme's name and the
colours a config overrode by hand, so a dumped config keeps following its
theme instead of freezing it. --dump-theme is the other half and is new: the
resolved palette as a theme file, which is how you start one from the session
in front of you rather than from an empty file. It is also what keeps
contrib/themes/default.kdl honest -- that file is now the annotated reference
(the commented palette moved there out of config.kdl, where it was 119 lines
of colour in the middle of the prose), and a test compares it against the
compiled-in one.

While in there: tests/test_themes.py's "every theme sets every colour" guard
had been grepping for `&amp;c-&gt;`, the spelling before the colour table moved to
offsetof, so it had been finding zero colours and passing for months. Fixed,
with the derived and opt-in colours exempted for the reasons they are derived
and opt-in, and with a check that the guard found a table at all.
</content>
</entry>
<entry>
<title>build: a vendor bump rebuilds libghostty-vt, everywhere it is consumed</title>
<updated>2026-09-10T09:19:54Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-10T09:19:54Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=16620e7fd9a6780756dc956466c3579e5bb2f384'/>
<id>urn:sha1:16620e7fd9a6780756dc956466c3579e5bb2f384</id>
<content type='text'>
Three stale-build landmines, all found by shipping v0.1.7 -- the first
release where new code (GhosttySearch) actually needed the vendor bump
of 2026-09-01:

- $(VT_LIB) had no prerequisites, so an existing .a *was* the build:
  the macOS builder compiled pane.c against zig-out headers from before
  the bump. The vendor manifest is now the prerequisite; a re-vendor
  rewrites it, so a bump rebuilds the lib and its installed headers.

- web/demo had the same guard and also never passed -Dversion-string:
  upstream's build.zig ran git describe in our repo, and on the tag
  checkout web/publish builds from, a slosh tag is a fatal version
  mismatch. Both fixed; both had hidden behind a stale build/vt.

- The builder's zig-pkg (excluded from the sync, and left half-empty by
  a once-interrupted push) is an input, not a cache: it is now synced
  separately and additively, so a bump's new packages arrive and a
  partial local tree cannot maim the builder's copy.
</content>
</entry>
<entry>
<title>webdemo: generate terminfo.h -- pty.c includes it now</title>
<updated>2026-09-02T14:33:20Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T14:33:20Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=6c1afdc57aa892ed274cc4b9a54bb40e39a20ea6'/>
<id>urn:sha1:6c1afdc57aa892ed274cc4b9a54bb40e39a20ea6</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>webdemo: version from contrib/version, published by ship</title>
<updated>2026-09-02T12:47:30Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T12:47:30Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=3578eefea5b53bce2c1dbbc7f104f3cf85c8aa5b'/>
<id>urn:sha1:3578eefea5b53bce2c1dbbc7f104f3cf85c8aa5b</id>
<content type='text'>
The demo's Makefile said VERSION := 0.1.0 by hand, so the in-browser
slosh announced a release two tags old no matter what was built into
it. It now derives from contrib/version like every other artifact,
suffixed +webdemo so the guest build names itself. And ship gains a
website step: the site (demo included) is rebuilt from the tag
checkout and published as part of a release, so the demo cannot fall
behind again.
</content>
</entry>
<entry>
<title>web: cleanup</title>
<updated>2026-09-02T10:51:03Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-02T10:51:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b2a4e2a5189a140436ecd8f0eb4cefcbaabdd6e4'/>
<id>urn:sha1:b2a4e2a5189a140436ecd8f0eb4cefcbaabdd6e4</id>
<content type='text'>
</content>
</entry>
<entry>
<title>webdemo: no attach splash -- the welcome float already greets</title>
<updated>2026-08-30T14:28:57Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-30T14:28:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=9022512f573ad515f08cd9b42b1f0f3948ee75fb'/>
<id>urn:sha1:9022512f573ad515f08cd9b42b1f0f3948ee75fb</id>
<content type='text'>
Two logos in quick succession -- the splash's, then the welcome
float's -- reads as a glitch, not a greeting, technically correct or
not. splash_ms 0 in the guest config; the comment leaves the knob
visible for anyone editing their way through the playground.
</content>
</entry>
<entry>
<title>web: the demo machine, bootable in the front page</title>
<updated>2026-08-30T14:28:57Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-30T14:28:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=a5ce3d56f9ba17acf073b9ca051e658bfd262cae'/>
<id>urn:sha1:a5ce3d56f9ba17acf073b9ca051e658bfd262cae</id>
<content type='text'>
A try section between the tour and the install box: a modest poster
with one button, and the machine only when the button is pushed -- the
front page keeps costing two files, which is the decision the README
recorded when the demo got its own page, still being honoured.

The click grows the stage to most of a viewport *before* the iframe
goes in (the guest reads its geometry from the room exactly once, at
boot), then glides the scroll to land the machine centred. An iframe,
not the demo's scripts inlined: the emulator's emscripten glue owns
its globals by name, and isolation is what keeps this page and that
blob out of each other's namespaces. ?embed=1 makes the demo page
drop its own header and footer inside the frame; standalone it is
unchanged, and phones navigate to it rather than embedding -- wasm
under a soft keyboard in an iframe helps nobody.

The floating logo learns the tour's edges properly while at it: the
try section arriving closes the show (measured with a -45% rootMargin,
because the tour's exit margin lets the section ride high while the
last step is still centred), and a section scrolled past counts as
arrived, not gone.
</content>
</entry>
<entry>
<title>webdemo: fit with font size, not scale -- a transform misplaces clicks</title>
<updated>2026-08-30T13:38:14Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-30T13:38:14Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=6e25c746e7cf3184fd30cae4d4c10299315717ce'/>
<id>urn:sha1:6e25c746e7cf3184fd30cae4d4c10299315717ce</id>
<content type='text'>
xterm turns a mouse event into a cell with getBoundingClientRect() and
its own unscaled cell metrics, so the scale() that stretched the fixed
grid into the stage put every click on the wrong cell, off by the
scale factor. Desktop kept the factor near 1 -- cols and rows are
derived from the same room the scale fills -- so it passed for
working; a phone is forced onto the 80-column floor, the factor lands
near a half, and a tap selects a pane the finger never touched.

Refit the font size instead: walk it until the grid fills the room
(iterated, cell metrics are not linear in font size), centre the
leftover with translate, which moves rect and clicks together. The
grid stays what the guest was told at boot; only the glyphs move. A
webfont resolving late changes the metrics under the fit, so refit
again when document.fonts settles.
</content>
</entry>
<entry>
<title>webdemo: land in the playground, not just the demo</title>
<updated>2026-08-30T13:23:02Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-30T13:23:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=749dac89798ab1ec8a4869ce92f4bd02f67d7774'/>
<id>urn:sha1:749dac89798ab1ec8a4869ce92f4bd02f67d7774</id>
<content type='text'>
The overlay grows into a first session worth having:

- The config is a playground now -- every theme the repo ships rides
  along as an include one word away, and the settings worth flipping
  (compact, shaders, keys, dim_unfocused) sit commented in the file,
  each one uncomment-and-save from happening on screen.
- C-a e opens vim, not busybox vi: editor "vim" in the config, since
  the image always carried the real vim and its kdl syntax without
  ever launching them. shortmess in the vimrc keeps the file-info
  message from wedging a narrow pane into a hit-enter prompt.
- The config starts open, highlighted, in a pane of its own: the
  discovery C-a e would make, made before anyone has to make it.
- A welcome float greets over everything, the slosh logo on top,
  centred by the layout engine at whatever size the browser gave the
  terminal (the previous commit is its enabling act). It teaches its
  own dismissal -- C-a x, or the X it wears -- and it never exits on
  its own, so keep_dead has no corpse to keep.
</content>
</entry>
</feed>
