<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/docs/shaders.md, branch docs-new</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/docs/shaders.md?h=docs-new</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/docs/shaders.md?h=docs-new'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-09-02T09:55:35Z</updated>
<entry>
<title>docs: big overhaul</title>
<updated>2026-09-02T09:55:35Z</updated>
<author>
<name>Peter Geissler</name>
<email>peter@haxx.in</email>
</author>
<published>2026-09-02T09:55:35Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f'/>
<id>urn:sha1:93e8b6a98de3f9e8f3176199e7974f4bc8a66a1f</id>
<content type='text'>
</content>
</entry>
<entry>
<title>docs: less wordy</title>
<updated>2026-08-29T18:25:39Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-29T18:25:39Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=31c32f9c20e3dd5045fa053fc8e7e606a8139397'/>
<id>urn:sha1:31c32f9c20e3dd5045fa053fc8e7e606a8139397</id>
<content type='text'>
</content>
</entry>
<entry>
<title>README/docs: stop leaning on the aside dash</title>
<updated>2026-08-27T14:11:26Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-27T14:11:26Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=238b1a7eb51c576254537705597f8a039e1d9c86'/>
<id>urn:sha1:238b1a7eb51c576254537705597f8a039e1d9c86</id>
<content type='text'>
Some 230 sentences hung their qualifications between dashes, and at that
density the tic reads as the writing's signature rather than a tool it
occasionally picks up. Each one is now the punctuation the sentence
actually wanted: a colon where the aside defines, parentheses where it
whispers, a comma where it barely pauses, a full stop where two thoughts
were pretending to be one. Definition lists and reference tables take
colons throughout.

Two double hyphens remain on purpose, both literal: the argv separator
in a shell example, and a table cell that means 'takes nothing'.
</content>
</entry>
<entry>
<title>README/docs: spell the dash --, as the rest of the tree does</title>
<updated>2026-08-27T14:04:37Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-27T14:04:37Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=5151e42f22b404cf41dc8b409bcdfcd4b39444ea'/>
<id>urn:sha1:5151e42f22b404cf41dc8b409bcdfcd4b39444ea</id>
<content type='text'>
Every C comment, commit message and generated config in this repo
writes the aside dash as a spaced double hyphen; the Markdown alone
used U+2014, some 220 of them. One spelling, and the ASCII one -- the
prose now matches the code it documents.

Mechanical (every occurrence was the spaced form, checked including
the one inside a code fence, where it was comment text), so the words
themselves are untouched.
</content>
</entry>
<entry>
<title>docs: fact-check against the loader</title>
<updated>2026-08-22T16:30:29Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-22T16:30:29Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=e015e855f19af29d02688b862701999564e4f46c'/>
<id>urn:sha1:e015e855f19af29d02688b862701999564e4f46c</id>
<content type='text'>
splash_ms and the project settings join the knobs table, the pane-state
ranking gains the floating it was missing, contrib/themes' seventh file is
named for what it is, the shader table's numbers are called examples with
the real omitted-value defaults beside them, the index links workspaces,
and the verb table learns splash.
</content>
</entry>
<entry>
<title>slosh: defaults most people can live in, said by shaders</title>
<updated>2026-08-19T11:46:57Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-19T11:46:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=42b3c10995eb67778d9dd3aa2fd80624896acd5f'/>
<id>urn:sha1:42b3c10995eb67778d9dd3aa2fd80624896acd5f</id>
<content type='text'>
The out-of-the-box look, rethought as one piece:

- the compiled-in palette calms down: muted blue #7aa2f7 on the same
  neutral darks, with the colours that mean something kept semantic --
  amber bell, red-orange dead. sl0p stays one include away as the house
  style, and contrib/themes/default.kdl is the starting point written
  down (theme-tour shows it).

- derived state defaults now actually follow the theme: the scrolled
  wash is rebuilt from scroll_bg after the theme block (it used to bake
  the compiled accent), and declaring a state -- in any file of an
  include chain -- still wins. The dump writes derived states as
  comments so C-a e cannot pin what is meant to keep following.

- a bell you can see: the frame blinks three times in theme's bell
  colour then breathes until answered, and a soft diagonal shimmer --
  the bell colour halved towards default_fg, width breathing on a sine
  -- crosses the body for the same 900ms. bell_indicator false silences
  all of it; states { bell { } } replaces it like any state. since
  carries the clock dependency, so an idle session pays nothing.

- scrollback says where it continues: while scrolled, the viewport's
  top and bottom rows melt towards an edge with content past it and
  turn solid at the buffer's end. New expression variables above/below
  (lines hidden past each edge, own dependency bit, cached per scroll
  position), carried in shade_ctx_t -- SLOSH_SHADER_ABI 3 -&gt; 4.

- expressions survive the dump: a program keeps its source and cb_chain
  writes it back, so amount="(y % 2) * 40" no longer dumps as a number.

- headers move to include/, -Iinclude everywhere; the plugin Makefile's
  SLOSH var becomes SLOSH_DIR because slosh exports SLOSH=1 into every
  pane it runs, and building from inside a session read that as a path.

- comment drift culled where features outran the prose, and the
  derivation boilerplate folded into derived_pass()/ctx_scroll().
</content>
</entry>
<entry>
<title>slosh: rename the project from sl0ppty</title>
<updated>2026-08-18T19:26:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-18T19:26:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b9e53cace4eb75263077df8623b011e66f5f2241'/>
<id>urn:sha1:b9e53cace4eb75263077df8623b011e66f5f2241</id>
<content type='text'>
sl0p + pty was error-prone to type and the zero made it worse; slosh
(slop + sh) is one syllable, impossible to typo, and slosh.foo is ours.

Mechanical rename everywhere it appears: binary, env vars (SLOSH,
SLOSH_SESSION, SLOSH_BIN, SLOSH_CONFIG), socket dir (/tmp/slosh-&lt;uid&gt;,
$XDG_RUNTIME_DIR/slosh), config path (~/.config/slosh), header guards,
src/sl0ppty.h -&gt; src/slosh.h, contrib/sl0ppty-dev -&gt; contrib/slosh-dev,
the driving-slosh skill, docs and tests. The sl0ppi fork references and
the sl0p theme stay: one is history, the other is the brand.

The rename surfaced a latent bug in test_osc5577: the hello-reply
answer always appeared on screen twice (ECHOCTL echo plus cat -v), but
a seven-character implementation name happened to wrap the second copy
across the 60-column boundary, so counting substrings in wrapped text
found one and passed for the wrong reason. A five-character name moved
the wrap and exposed it. The loop guard itself is correct -- with echo
off before the request there is exactly one reply -- so the test now
counts on de-wrapped text and tolerates the pane's own echo.
</content>
</entry>
<entry>
<title>sl0ppty: the prompt speaks config, not a dialect of it</title>
<updated>2026-08-16T16:57:44Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-16T16:57:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b4b066c8c4e26191132f99014555d0a1d3232340'/>
<id>urn:sha1:b4b066c8c4e26191132f99014555d0a1d3232340</id>
<content type='text'>
    chrome&gt; include "contrib/chrome/heartbeat.kdl"
      heartbeat.kdl: 1 chrome, 0 content
    chrome&gt; shaders {
    ...&gt;     tint where="chrome" color="#ff5fd7" amount="abs(sin(t / 5))"
    ...&gt;     dim where="content" amount=80
    ...&gt; }
    1 chrome, 1 content

Both of those were errors an hour ago, and the second one was my fault twice
over: `:paste` printed `include "..."` and printing something you cannot type
back is a prompt lying about its own output. Pasting it answered `unexpected
argument for include`, and `load "..."` answered the same about `load`.

So the grammar is now the config's, all of it: an entry, several with `;`, an
entry with its own `where=`, a whole `shaders { }` block over as many lines as it
takes, `include "f.kdl"`, `load f`. There is a test that reads `:paste`'s block
off the screen, clears the pane, types it back line by line and compares the
cells -- round-tripping is the property, not a nice-to-have.

**The text is a document.** What it says replaces both chains, exactly as naming
`shaders { }` in a config replaces the block rather than adding to it, and an
entry's `where=` decides which rect it lands on. That drops a rule I invented last
week -- "one chain, one rect", which refused an entry that named the other one --
because it made the prompt's mode a law rather than a default and made a two-rect
block, the thing `:paste` prints, impossible to paste back. The prompt now keeps
the document and sends all of it, so a line aimed at the frame leaves the contents
alone, and the reply says `1 chrome, 1 content` rather than `ok`: with routing
visible, the counts are the answer worth reading.

`route_entries` is the one place routing is decided now -- config file, preset
file, typed line, same function. `config_parse_chain_doc` takes the wrapper or the
bare entries, because both are things a config contains and therefore both are
things somebody will paste.

Still nothing in the prompt parses KDL. A file goes by path; a pasted block goes
via a temporary file, since an OSC payload cannot carry a newline -- a control byte
ends the sequence, by the spec -- and joining lines with `;` would need exactly the
comment-and-quote scanner I refused to write. The only syntax the script looks at
is counting braces to know whether you have finished typing, and an empty line
gives up on that.

One hint, for the mistake that started this: when the session says `unknown
shader`, the prompt adds `(a file? :load &lt;path&gt;, or include "&lt;path&gt;")`. After the
fact rather than before, so a plugin's shader name is never second-guessed.

Verified: `tests/test_shader_repl.py` is 41 checks -- every input shape, the
multi-line paste and giving up on one, the round trip, the hint -- and
`tests/test_inband_shaders.py` gained the document rule in both directions,
including that a document naming only the frame takes the contents pass off. Full
clean suite green.
</content>
</entry>
<entry>
<title>sl0ppty: `:load` a preset, without a second KDL reader</title>
<updated>2026-08-16T16:40:42Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-16T16:40:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=80b98bbf4c6ffe1a95c389525af30374df21d8eb'/>
<id>urn:sha1:80b98bbf4c6ffe1a95c389525af30374df21d8eb</id>
<content type='text'>
    chrome&gt; :load sine-comet
    sine-comet.kdl: 1 chrome, 0 content
    chrome&gt; :paste
    // sine-comet.kdl
    include "/home/user/dev/sl0ppty/contrib/chrome/sine-comet.kdl"

A path, or the bare name of anything in `chrome/` or `shaders/` beside the
script, from any directory. Entries land wherever their own `where=` says, so one
file can fill both chains and a preset goes where its author put it. Tab completes
filenames after `:load` and vocabulary everywhere else.

**The prompt does not parse the file.** I started writing the scanner -- find the
`shaders {` block, count braces, strip `//` outside quoted strings so that
`color="#ff0033"` and `t / 8` survive -- and stopped, because that is a second
reader of a format that already has one, and the two would disagree exactly where
comments meet strings. Instead the session gets the path and reads it with the
parser it already has: `config_parse_chain_file`, built on the same
`parse_shader_entry` a config file goes through. Anything that is not a top-level
`shaders { }` block is ignored, so a whole `config.kdl` can be handed over and
only its shaders apply.

The alternative was regenerating all 45 presets into something easier to read from
Python. That would have cost them what makes them useful: each one is a config
fragment today -- pasteable, `include`-able, checkable with `sl0ppty --check`, and
loaded as a config by `tests/test_chrome_presets.py`. A format only the prompt can
read would be a fourth thing to keep in step.

`:paste` after a load answers with an `include` line rather than the file's thirty:
the file is on disk, it is already the block, and reprinting it scrolled the pane
you were looking at -- which is how I noticed.

Both chains are built beside the live ones and swapped in together, so a file that
goes wrong half way leaves the pane as it was rather than wearing the half that
parsed. Each compiled expression is matched to the chain whose shader points at it
before either is installed; freeing a program the other chain still uses is the one
bug here that would not appear until the next repaint.

Verified: nine checks in `tests/test_shader_repl.py` -- a preset by bare name, a
file with both rects going to both chains, the three ways to be wrong (no such
file, no `shaders { }` block, no argument), what `:paste` says afterwards, and tab
completion. Then all 45 shipped presets loaded by name in one session: none
refused.
</content>
</entry>
<entry>
<title>sl0ppty: PI is 180</title>
<updated>2026-08-16T14:16:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-16T14:16:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=a540b74dd3bb04d33a69219487cb2db32f2022f1'/>
<id>urn:sha1:a540b74dd3bb04d33a69219487cb2db32f2022f1</id>
<content type='text'>
Angles here are degrees, and pi in an angle language is not a number -- it is a
half turn. So `PI` is 180, `TAU` is 360, and a formula written the radian way
ports across as written:

    sin(TAU * x / cols)      one cycle over the pane's width
    sin(TAU * p * 3 / perim) three lobes fitted exactly to a border
    PI / 2                   a quarter turn
    PI / 6                   thirty degrees, exactly

That last line is the argument. A numeric pi would be 3 in this language, and
`3.14159 / 6` in integers is 0 -- so radians cannot express a sixth of a turn
here at all, while degrees give it as a whole number. The pun is not a
compromise; it is the more precise reading of the two.

Which is why there is no `deg2rad` or `rad2deg` beside them, and asking for one
says `unknown function`. There is one angle unit. A conversion could only lose
the angle (`deg2rad(90)` is 1, and `sin(1)` is nearly nothing) or invent a second
scale for the language to disagree with itself about.

Spelled-out numbers rather than variables: they read no environment, carry no
dependency, and fold into the program like the literals they are. The browser
preview learned the same two, from the same table.

Verified: 12 cases in `tests/expr_test.c`, including that `deg2rad` and
`rad2deg` are refused; the cross-check agrees with the compiler on 700
evaluations, now including `sin(TAU * x / cols)` and friends.

Two smaller things ride along, both from the repl. `PI`/`TAU` tab-complete, and
its drift test grew a third table to check -- it already caught `sin`/`cos`
missing before I did. And it now checks the script *parses* before starting a
pane that runs it: I broke the indentation of one line here, and the failure
arrived as `IndexError: list index out of range` from a pane that died before
its prompt, which is a long way from "line 212".
</content>
</entry>
</feed>
