aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAge
* docs: choredocs-newPeter Geissler2026-09-02
|
* docs: deprecate boring design narrative, more trimming and cullingPeter Geissler2026-09-02
|
* docs: big overhaulPeter Geissler2026-09-02
|
* web: the hero button says try it, like the section it goes touser2026-08-30
|
* web: the whole step is the road, not just its titleuser2026-08-30
| | | | | | | | | | Clicking anywhere in a dimmed article glides it to the centre and so, through the observer, switches the recording. Three guards keep the click meaning navigation: the active step does not react (clicks there are for reading and selecting, not going where you already are), a selection is not a click, and a link is a link. The pointer cursor and the title's breath of pink now belong to dimmed steps only -- the active one is just prose.
* web: the tour's doorway steps stop grabbinguser2026-08-30
| | | | | | | | | Proximity snapping traps at the edges: between steps, adjacent fields hand you from magnet to magnet, but at the first and last there is no next field -- a wheel tick toward the hero or past the final demo ends inside the boundary step's radius and is pulled straight back, forever. The interior steps keep their snap-align; the doorways go without, because a doorway that grabs is worse than a step that does not snap.
* web: the tour magnets, politelyuser2026-08-30
| | | | | | | | | | | | scroll-snap-type: y proximity on the root, snap-align: center on the steps and nothing else: a scroll that ends near a step is settled onto its centre -- the exact spot the tour activates it, so the rest position and the lit step agree -- and momentum, half-reads and every snapless section are left alone, by spec. Proximity, never mandatory: the mandatory kind on a page that also has prose is a trap, and the JS kind is worse. Off in the one-column layout, where the sticky stage covers the upper viewport and a centred step would sit half beneath it.
* web: a step's title is a road to the stepuser2026-08-30
| | | | | | | | Clicking an article's h3 glides it to the middle of the viewport -- the exact spot where the tour's observer activates it, so the click also switches the recording, through the same door scrolling uses rather than a second mechanism. The pointer cursor and a breath of pink on hover say clickable.
* web: the floating logo yields to the buttons and the rising terminaluser2026-08-30
| | | | | | | | | | | | | | | Two overlaps the section observers missed. The show opened on the big logo leaving, but the hero's buttons sit below it, so there was a band of scroll with the little logo arriving on top of install/try/docs -- observe the whole hero instead. And at the tour's exit the stage unpins and is dragged up through the logo's air before the try section crosses its line -- no section boundary says so, but two rects do: a rAF-throttled collision check against the pinned player's rect, and whenever the terminal would touch the logo, the logo yields. Verified by sweeping fifteen scroll positions: shown through the whole tour, hidden over the buttons, the rising terminal, the demo box and the install tabs, with zero overlapping frames.
* web: the config cast zeroes the padding before going compactuser2026-08-30
| | | | | | | | padding 1 2 was still in force when compact arrived, and the dense school reads best with the panes flush against its dividers. The cast now takes the padding back first -- cc rewrites the line to padding 0 on camera, one more save applying live -- so the compact beat lands on the layout it was designed around.
* web: the config cast edits in colouruser2026-08-30
| | | | | | | | | | vim -u NONE kept the render machine's dotfiles out of the cast, but NONE also switches off filetype detection and syntax -- so the config demo was editing kdl in monochrome, underselling vim and the file both. The demo now authors its own six-line vimrc instead: syntax on, habamax (the webdemo's own look), shortmess for the column. The cast depends on vim's runtime and nothing personal, which is what NONE was protecting in the first place.
* web: the casts play at 1.75x -- the owner has watched them enoughuser2026-08-30
|
* web: the config cast turns the layout before it editsuser2026-08-30
| | | | | | | | | | | C-a e arrived as a strip along the bottom, and a 66-line config in a twelve-row strip reads like a keyhole. A quarter turn (C-a Space) right after it opens makes the editor a column with the height the file deserves -- and shows off the rotate while at it. The column brought back vim's hit-enter stall (the file-info message wraps at 49 columns), so the recording's EDITOR gains the same shortmess the webdemo's vimrc already carries.
* web: one knob to hurry every cast on the pageuser2026-08-30
| | | | | | | | | window.SLOSHCAST_SPEED, multiplied into each mount's own data-speed: the front page sets 1.25 and every recording plays a quarter faster than authored, the scripts under demos/ untouched -- their pacing is theirs, and how much of it a visitor's attention span gets is the page's call. The clock keeps counting cast-time, so the timestamps mean the same thing at any speed.
* web: the panes cast shows the drop bands, not just the swapuser2026-08-30
| | | | | | | | | | | | | | | | The title drag had one beat -- drop in the middle, trade places -- which undersells the feature: carried over a pane, the target subdivides into a centre and four edge bands, a band meaning "in beside it, on this side", filling the half it would hand over while every other candidate greys out. The cast now lingers over the centre, steers into a band, and drops -- and the drop restacks the whole tab, a rearrangement no swap could produce, chosen so the release visibly changes the layout (the first cut dropped a pane beside the neighbour it already had, and proved nothing). The band is aimed by its entry in the compositor's hit list, like every coordinate in these demos: the rect the program promised is the rect the pointer hits. The panes article's bullet follows suit.
* web: the demo poster keeps to the content columnuser2026-08-30
| | | | | | The tour-width breakout belongs to the machine, not to the offer of one: unbooted, the panel now sits flush with the prose and the install box; booted, it still takes the tour's width and most of a viewport.
* web: power off is a button, not a cheeky linkuser2026-08-30
| | | | | | A real ghost button centred under the running machine, in the hero buttons' own dress -- ghost on purpose: booting stays the loud verb, stopping the quiet one. The caption drops to a side note beside it.
* web: the demo machine can be powered offuser2026-08-30
| | | | | | | | | | | | A caption under the running machine offers the off switch, dressed as a link inside a sentence. Dropping the iframe out of the DOM tears the whole VM down -- emulator, kernel, disk cache, the lot -- the stage shrinks back into the poster (the original nodes come back from a stash, listeners and all, so the boot button is immediately live again), and the scroll follows both ways. Reload always did this; the button does it without taking the rest of the page with it.
* webdemo: no attach splash -- the welcome float already greetsuser2026-08-30
| | | | | | | 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.
* web: the demo machine, bootable in the front pageuser2026-08-30
| | | | | | | | | | | | | | | | | | | | | | | 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.
* web: the logo cycles hyper, and casts a glow to matchuser2026-08-30
| | | | | | | | | | | | | Fewer colours at once, more per second: the gradient slims to three stops -- pink, orchid, blue -- and the hue rotation takes over the variety, a full wheel in 4s (5s in the reduced-motion lane, colour in place, still nothing travelling). The slide drops to 5s with it. And a drop shadow, inside the hue keyframes because the filter property is the animation's to own -- a static filter would lose to it -- and listed before the hue-rotate so the glow is rotated with the letters and always matches their current colour. Soft and low: on a near-black page a shadow can only read as a faint underglow.
* web: reduced motion stills the logo's slide, not its colouruser2026-08-30
| | | | | | | | | | | Both of the owner's browsers showed the cycle frozen, and the culprit was neither of the browsers: the OS asks for reduced motion, both pass it on, and the media rule here answered by killing every animation. The preference is about motion -- patterns that travel, the vestibular kind -- not about colour, so the position slide goes and the in-place hue rotation stays, at half speed. Nothing moves, everything still breathes, and a visitor who asked the OS for stillness gets exactly the stillness they asked for.
* web: the logo's colour cycle gets range, speed, and a Safari laneuser2026-08-30
| | | | | | | | | | | | | | More of the wheel -- the gradient now walks pink, orchid, blue, cyan, mint and back, all colours the demo's own terminal palette already speaks -- and a faster walk, 8s to the position slide's loop. And a second motion: WebKit caches a text-clipped paint and never repaints it for a background-position animation, so on Safari the wash sat frozen, pink on the left, cyan on the right, which rather defeats a colour cycle. A hue rotation runs alongside (12s to the full wheel): filters apply post-paint on the compositor, so every engine animates it over the cached clip. Chrome and Firefox get both motions, WebKit gets the rotation alone -- either way, nothing stays pink.
* web: the floating logo cycles its colour, slowlyuser2026-08-30
| | | | | | | | | A gradient of the pink's neighbours -- orchid one side, a lighter pink the other -- clipped to the ink and slid along it, one tile every fourteen seconds: the logo's own little colour pass, in the spirit of the shaders it advertises. Horizontal on purpose, because only a 90deg gradient meets its own edge seamlessly when the loop wraps; animated only while shown, and not at all under prefers-reduced-motion.
* web: centre the floating logo in the air above the terminaluser2026-08-30
| | | | | | | | | | | | | A fixed top worked at one viewport height and overlapped the pinned terminal at others: the terminal's top edge is (viewport - the stage's lift padding) / 2 - half the player, which no constant tracks. Measure it instead, centre the logo in what remains, and hide it outright when a short viewport leaves no air -- hiding beats overlapping. Re-placed on resize and by a ResizeObserver on the player mounts, not on the pin: the players are zero-height until their casts arrive, and the pin is a 100vh box that never resizes, so watching it would freeze the placement at the pre-load height.
* webdemo: fit with font size, not scale -- a transform misplaces clicksuser2026-08-30
| | | | | | | | | | | | | | | | | 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.
* webdemo: land in the playground, not just the demouser2026-08-30
| | | | | | | | | | | | | | | | | | | | 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.
* layout: a float sized but not positioned is centred, per axisuser2026-08-30
| | | | | | | | | | | | | | | | docs/layouts.md always said an absent coordinate takes the centred default; the loader said 0, and a layout float with only w= h= landed in the top-left corner. Now an axis the file does not name is FLOAT_CENTRE, a sentinel no real coordinate can reach, and float_clamp answers it with the middle -- every frame, at whatever size the terminal is by then, until an explicit move or resize writes a real number over it. The webdemo's welcome float is the customer: it must sit centred on a terminal whose size the layout file cannot know. The dump writes a still-centred axis by omission, because the number it happened to land on would pin to this screen's size the one intent that deliberately has none. Every mutating verb already writes back from the drawn rect, so the sentinel survives nothing it should not.
* web: a seventh cast -- floats, and the cheatsheetuser2026-08-30
| | | | | | | | | | | | C-a F drops a throwaway shell over a working layout; a monitor runs in it, moves with the keys that move boundaries, grows about its centre. Then C-a ?, the cheatsheet built from the bindings actually in force, and exit closes the float with the layout exactly as it was. The monitor is an htop impression with authored numbers, not htop: a recording that renders differently every run is a bug here, byte-for- byte is the contract. Phosphor, because every cast in the tour records under its own theme so the next step reads as a scene change.
* web: close the logo's pinstripe gaps, undress its hoveruser2026-08-30
| | | | | | | | | | | Block glyphs' ink can round a hair narrower than the monospace advance, so the ascii logos showed hairline gaps between characters that should read as solid ink; -0.05em of letter-spacing tucks each glyph into the last, on the floating logo and the hero's alike. The floating logo also stops dressing as a link on hover: the global a:hover underline through seven rows of block glyphs was a mess, and the colour flash no better. It still is one -- the pointer says so.
* web: a whole lot of html/css wranglinguser2026-08-30
|
* web: sloshcastuser2026-08-30
|
* graphics: a frame the client never got must be said againuser2026-08-29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A pane image is re-emitted to the client as decoded RGBA, which for one screenshot is ~4MB of base64 in a single indivisible message -- and conn_send silently dropped anything that would overflow the 4MB outbox, while push_graphics ignored the refusal and the model advanced anyway. Everything stateful in that dropped frame was lost for good: the image was marked sent (so its placements drew nothing, forever), and the deletions rode out with it (so a placement the client had was never revoked). Reported as screenshots in a recovered pi transcript parked at a fixed spot on the screen, immune to scrolling and pane swaps -- those were placements whose deletion left with a dropped frame. The 1000x800 one could never be delivered at all: its transmission alone was bigger than the cap. So the frame is now transactional. Deletions queue and are repeated every flush until gfx_commit() confirms the client heard them; transmissions recorded in a frame are unwound by an undelivered commit so they are sent again. A flush nobody commits counts as undelivered at the next gfx_begin(), which is what the `graphics` control command is: bytes rendered for a CLI, not the client. The server reports the verdict and keeps the paint pending until it lands. The outbox cap now bounds the backlog rather than backlog-plus-message, so one big transmission on a drained queue always fits -- and that surfaced a second way to lose: a queued image legitimately holds the backlog over the cap until the client swallows it, and push_frame read that as a dead client and closed the connection. The session auto-detached every time a screenshot crossed the wire. A full outbox is a client that is *behind*, not gone: push_frame now marks the screen for a full repaint (the dropped diff means the client's cells are unknown) and retries as POLLOUT drains the queue; a client that truly left still shows up as a write error there. The headless suite pins the owed-until-committed contract; the new tests/live_graphics.py drives the real socket with a bigger-than-the-cap image and a reader that stalls, and would have caught both bugs: the flood must not detach, the transmission must arrive whole, and a placement scrolled away must be deleted -- then come back on scroll without a second copy of the pixels.
* docs: less wordyuser2026-08-29
|
* arch: sign the packages and the database, and say whose keyuser2026-08-29
| | | | | | | | | | | | | | | | | | | | An ed25519 key made for exactly this -- slosh package signing <slosh@slosh.foo>, 0F67C909938CFFA8A76B59FD8D69B55A0E9056B9 -- signs every package and both indexes. The public half travels two ways that would both have to lie: contrib/arch/slosh.asc in git, and /arch/slosh.asc on the website; the READMEs walk through pacman-key --add and --lsign-key, and the [slosh] stanza loses its SigLevel override because pacman's default demands signatures and now gets them. contrib/arch-repo pins the fingerprint, refuses to run without the secret key (an unsigned update would make every enrolled pacman refuse the repo), and now regenerates the index from the directory every time: repo-add skips an entry whose version it already has, so after a same-version rebuild an incremental add left the db describing the previous bytes -- a checksum mismatch for every client. An index rebuilt from disk cannot disagree with disk. Verified end to end with a scratch pacman keyring: enrolled key syncs and downloads under Required DatabaseRequired, unenrolled key is refused at the database.
* arch: a self-hosted pacman repository, since the AUR is shutuser2026-08-29
| | | | | | | | | | | | | AUR signups are closed while the AUR deals with supply chain attacks, so the package is published from our own roof -- which is the better offer anyway: a pacman repository serves built packages where the AUR hands every user a rebuild. contrib/aur/ becomes contrib/arch/ (the .SRCINFO goes; it is AUR metadata, regenerated in a minute when signups reopen), and contrib/arch-repo builds the package and maintains the slosh.db / slosh.files indexes in dist/arch/<carch>/, which rsyncs to /opt/slosh/arch/ on the box. The README and the front page carry the [slosh] stanza; SigLevel is spelled out because the packages are not signed yet and pacman defaults to demanding it.
* aur: a PKGBUILD, building offline from the cgit snapshotuser2026-08-28
| | | | | | | | | | | | contrib/aur/ is the upstream copy of the AUR package; the AUR repo gets these files verbatim on a release, and contrib/release now says so in its checklist. The source is the cgit snapshot of the tag, which carries the vendored libghostty-vt -- and every zig dependency in its build.zig.zon is lazy and unused by the VT-only build (verified against a cold ZIG_GLOBAL_CACHE_DIR), so the build fetches nothing and is clean in an offline chroot. prepare() writes pkgver into VERSION because the v0.1.1 snapshot predates the release script that keeps the floor bumped; for later tags the line writes what is already there.
* release: one command to bump, commit and taguser2026-08-28
| | | | | | | | | | | | | | contrib/release X.Y.Z moves ./VERSION, commits and cuts the annotated tag together, so the floor and the tag cannot disagree by construction. It refuses a dirty tree, a detached HEAD, a malformed or non-increasing version, and an existing tag; notes passed as the second argument land in the tag message. It prints the push and `make release` commands instead of running them, because publishing should be deliberate. contrib/release --check verifies the invariant (VERSION equals the newest tag) on its own: `make release` runs it before building anything, and the new pre-push hook runs the same comparison against the commit a v* tag points at, so a tag made by hand is caught before it escapes the machine.
* VERSION: 0.1.1, the bump v0.1.1 should have carrieduser2026-08-28
| | | | | | | The floor contrib/version falls back to without git -- a cgit snapshot, a Homebrew source build -- still said 0.1.0 after v0.1.1 was tagged, so those builds reported a version that was already released. The tag and the file are two steps, and nothing made them one; contrib/release now does.
* gitignore: DESIGN.md and .fastfeedback are local, not the projectuser2026-08-28
| | | | | | | | DESIGN.md was a scratch design doc and .fastfeedback a per-repo tool cache; neither belongs in the tree. Both stay on disk, ignored. Everything that pointed at DESIGN.md now points at docs/design.md, and the decision counts quoted from it (which had already drifted: the README said twenty-one, the docs said twenty-two) are gone with it.
* web: the Windows port, on the front page and out of install.shuser2026-08-28
| | | | | | | | The install section says what Windows gets (one slosh.exe, ConPTY panes, AF_UNIX sessions) and points at the docs; the front-page doc list gains the windows page. install.sh states in its header that Windows is not its job, and refuses under MSYS/MinGW/Cygwin with a pointer instead of building a binary that is the wrong shape for the machine.
* windows release: a zip in dist/, built beside the linux tarballsuser2026-08-28
| | | | | | | | | make -f Makefile.windows dist stages slosh.exe as slosh-<version>-windows-<arch>.zip -- a zip, not a tarball, because Windows is the machine that will open it -- carrying the same licensing paperwork as the linux tarballs, with docs/windows.md riding along instead of the manpages. `make release` now builds both architectures via release-windows, so the windows zips land under the same SHA256SUMS as everything else.
* windows watcher: rebuild the watch set instead of freezing itPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | watch_config() rebuilds the set of watched directories after every reload, because a config that just changed may include different files than it did a second ago. The inotify and kqueue branches unwatch and re-add. The Windows branch did neither: unwatching was a comment saying there was nothing to do, and sl_watch_add() appended to an array the pump thread had already finished reading. The pump captured `int n = w->ndirs` once, at the top, and armed one ReadDirectoryChangesW per directory it found. Everything added after that was stored and never watched. So an include added to a running session loaded fine and then sat there: editing it reloaded nothing, while editing the main config still worked, which is the shape of bug that gets reported as "sometimes". It was worse than deterministic. `w->ndirs` was written by the server thread and read by the pump with nothing between them, so whether a directory made it in depended on which won the start-up race -- _beginthreadex returns before the new thread runs, so the includes present at startup usually did, and the ones added later never could. And because nothing was ever closed, each reload re-added the same directories: handles accumulated until the cap of 8, after which sl_watch_add() failed quietly and no new directory could be watched at all for the life of the session. So the pump owns the handles and the server owns the wish. An overlapped read can only be cancelled by the thread that issued it, which settles where the boundary goes: the server writes a directory list under a lock, bumps a generation and pokes an event; the pump notices the generation moved, disarms what it had and arms what is wanted now. Cancelling waits for the cancellation to land before the slot is reused, because the OVERLAPPED and its buffer live in that slot. Duplicate directories are dropped on the way in -- a config and the theme beside it are usually in the same one -- and the compare is case-insensitive, as the filesystem is. sl_watch_close() also stopped every watcher in the process rather than the one it was given, and freed nothing. It now finds its own, joins the thread, and releases what that leaves behind. Verified on the Windows box, with a fully backslashed SLOSH_CONFIG: editing the config reloads; adding an include and then editing it reloads; repointing the include at an unrelated directory reloads; editing the file the include used to name does *not*, which is the rebuild working rather than an append; and after twelve reloads in a row it is still watching, where the old cap would have given up after eight.
* path: unit tests for the two spellingsPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | The functions the last two commits fixed are only interesting on the platform the suite does not run on, which is how they came to be wrong in the first place. So the Windows cases are asserted under #ifdef and the POSIX ones assert the opposite: that a drive letter is an ordinary name here, that a backslash is an ordinary character, that neither rule leaked across. That second half is the one worth having. Both fixes are guarded, and a guard is exactly the kind of thing a later edit removes without meaning to -- `\` splitting a POSIX path would invent a directory out of a file called `a\b`, and nothing else in the suite would notice. Pure functions, so it joins the C unit tests rather than driving a session. Run on the Windows box as well, cross-compiled: 28 checks there, 20 here, the difference being the cases each platform has.
* path: a directory ends at either separator on WindowsPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two places split a path by hand, both with strrchr(path, '/'), both falling back to "." when there was none: include_path() in config.c and watch_config() in server.c. On Windows "none" is the ordinary case. `SLOSH_CONFIG=C:\Users\you\slosh\config.kdl` is how a person writes it, and a path that has been near a native API comes back all backslashes whatever it started as. So an include resolved against "." -- `cannot open ./themes/t.kdl` -- and, worse and quietly, the config watcher watched "." too. Not the config's directory: the current directory of whatever started the session. Hot reload did not fail, it watched somewhere else and waited. Saving your config did nothing at all, with no error to go looking for. The default path escaped this by accident. slosh builds it as `C:\Users\you/.config/slosh/config.kdl` -- mixed, because a literal joined to $HOME always is -- and that one embedded '/' is what strrchr found. It worked for the path slosh writes and not for the one a user does. Both callers now use path_dir(), which learns to stop at a backslash too, and server.c takes path_base() for the other half rather than keeping its own copy of the arithmetic. Windows-only: a backslash is a legal character in a POSIX file name, and splitting on it there would invent a directory out of a file called `a\b`. path_dir() also keeps the separator when what is left would be a bare drive, because `C:\config.kdl` lives in `C:\` and `C:` names the current directory on drive C, which is a different place. Verified on the Windows box with a fully backslashed SLOSH_CONFIG: --check passes, the include loads, and editing the config reloads the running session. Watching a second directory is still unreliable; that is the watcher's own bug and the next commit.
* path: an absolute path does not always start with a slashPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | | | `include "C:/themes/t.kdl"` on Windows did not fail to find the file. It found a different one, or rather looked for it: absoluteness was tested as `path[0] == '/'`, a drive letter is not that, so the path was read as relative and joined to the including file's directory. The result was cannot open C:/Users/you/cfgA/C:/Users/you/cfgB/t.kdl which at least says what happened. A layout's `cwd="C:/srv/api"` took the same turn through path_resolve() and said nothing at all -- the pane started in the wrong directory, which is the failure this file's own header comment is about. Windows spells absolute three ways and all three now count: rooted on the current drive (`\dir`), rooted on a named one (`C:\dir`, `C:/dir`), and a UNC share. A drive letter has to be followed by a separator to qualify, because `C:file` is relative to the current directory *of that drive* -- a real thing, and not one we can resolve for anyone. Windows-only, deliberately. `C:` is an ordinary file name on POSIX and a path beginning with one is relative there, so reading it as a drive would break the platform that works in order to fix the one that does not.
* ctrl_d_exits: Ctrl-D at a prompt, where the tty cannot do itPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Windows has no VEOF. The console driver does not know the character, so ^D arrives at cmd.exe and PowerShell as an ordinary keystroke and they ignore it -- it even echoes, which is how you find out: the line reads `echo hello^D`. The one key that closes a pane on every other platform does nothing at all, and closing a pane becomes a word you have to type. That you can type `exit` instead is the tell: the intent was never ambiguous, only unspoken. So slosh speaks it. What goes to the pane is the word, not a signal and not a close from outside -- the shell runs its own cleanup, returns its own status, and keep_dead decides what is left behind, all three of which reaching past it would skip. It is also what the user would have typed, so there is nothing new to explain. The knob is what makes it honest, because a wrong guess costs a pane. It fires only with the shell itself in the foreground (a program that is running may want the EOF for itself -- `cat` reading to end is the whole job), only on the primary screen (in an editor or a pager ^D is half a page down, and the alternate screen is the pane stating that rather than us matching process names), and only on an empty line (POSIX sends EOF there; what it does not do is discard what you typed). The line is an estimate. The shell's editor is on the far side of a pty, so pane_send_key counts what it forwarded since the last Enter. Editing it does not model -- a recalled history entry, ^U, completion -- leaves the count too high, which forgets to exit. That is the direction to be wrong in; the other one throws away a half-typed command. Default true on Windows, false on POSIX, where the line discipline already does this and does it properly: ^D there is also EOF for `cat` and every REPL, none of which is ours to intercept. A knob rather than an #ifdef so the guards are the same code everywhere and can be tested where the test suite runs -- test_ctrl_d.py turns it on and drives all four decisions, plus the case that does exit. Telling trapped from forwarded needs a program that reports what it received: on POSIX a forwarded ^D also ends an idle shell, and looking for the word `exit` cannot separate them either, because bash prints `exit` itself on EOF. Verified on the Windows box as well, where it is the default: ^D at an idle cmd.exe prompt takes the pane (and with it the session, having no panes left), `more` in the foreground keeps its own EOF, and a half-typed line survives with the line still on screen.
* windows build: re-stamp version.h when the version changesPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | | The rule had version.h as an order-only prerequisite of its output directory, and nothing else. Order-only means "make this first if it is missing", so the file was written once and then never again: the version is derived from `git describe`, not from a file, so no timestamp on disk ever moves when it goes stale, and make had no reason to look. Every Windows binary after the first therefore carried whatever the version had been the day the build directory was created. The one on the test box reported 0.1.0-2-gba48db9 -- a commit that no longer exists, since the rebase rewrote it -- while being built from a tree six commits later. `--version` was not just stale, it was pointing at history that had been thrown away, which is worse than saying nothing. So FORCE, and write through a temp file that replaces the real one only when the text differs -- the same shape the main Makefile already uses for build/version.h, and for the same reason. `make` after a commit rebuilds; `make` twice in a row still does nothing. Makefile.macos does not generate a version.h of its own -- it takes the main Makefile's -- so it never had this.
* docs: the Windows build is a cross-compile, and only thatPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | The docs offered a native build as an equal alternative -- "the same command builds natively on the Windows box itself, with zig on PATH and a make" -- and it does not work. Makefile.windows needs sed, cat, mkdir -p and shell grouping, none of which cmd.exe has; and on ARM64 `zig cc` in zig 0.16.0 dies with an access violation on a one-line C file, so the compiler cannot run there at all. The zip that does it matches the published shasum, so this is upstream rather than a bad download. Nobody following those two sentences reached a binary. So they are gone, along with the "# host arch, native" usage line in the makefile and the "cross-compiles too" aside in the two install sections, which both implied that building on the box was the normal path and the cross-compile a bonus. It is the other way round, and now the pages say so once, in the same words each time. `builds and runs natively` on the doc's first line loses `builds`: what is native about slosh.exe is the binary, not where it was made.
* windows: fall back to `edit`, not to a notepad windowPeter Geissler2026-08-28
| | | | | | | | | | | | | | | | | | | | | | | `C-a e` on a Windows with no editor installed opened notepad, which is a GUI program: the pane it was spawned in drew nothing, so the split sat there empty while the config opened in a window somewhere else. The toast said as much, but a pane you have to be told to ignore is still the wrong answer. Windows 11 24H2 ships Microsoft Edit as `edit`, and it is a console program -- it fills the pane like nvim or nano would, so the fallback now has the same shape as the thing it is falling back from, and a first-time user gets a usable editor without installing one. It goes last of the console editors rather than first. Having fetched nvim is a statement of preference; having been shipped `edit` says nothing about what you want, so anything you chose yourself outranks it. notepad stays behind it: `edit` is not on Windows 10, or on 11 before 24H2, and something that opens is better than nothing when the alternative is an empty pane. sl_which() already tries .exe, so it is found as installed; the search still only runs when neither EDITOR nor the config asked for anything.