aboutsummaryrefslogtreecommitdiffstats
path: root/include/app.h (follow)
Commit message (Collapse)AuthorAge
* 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.
* selection: drag past an edge scrolls the viewport under ituser2026-08-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Text taller than the viewport was not selectable in one gesture: the drag stopped dead at the pane's border. Two faults, one visible and one waiting. The visible one: DRAG_SELECT motion subtracted the content origin in uint16_t, so a pointer above or left of the pane underflowed, the grid lookup failed, and the selection froze. The waiting one: the anchor was a pair of *viewport* coordinates re-resolved on every extend, so the moment anything scrolled mid-drag the anchor would have drifted with the viewport, re-anchoring to whatever slid under the press. The anchor is now a tracked grid ref -- the thing lib-vt documents as being for 'long-lived anchors such as selections' -- so it follows its cell across scrolling, pruning and reflow, and only the near end of the selection moves. If a program flooding output prunes the anchored row out of scrollback entirely, the selection keeps what it had; there is no cell left to anchor a new answer to. Motion clamps into the content rect (a pointer past an edge is pointing at the nearest cell), and holding past the top or bottom edge scrolls on a clock rather than on motion, because a resting pointer sends no events and 'keep going' is exactly what resting it there means. app_tick() does the step, app_next_deadline_ms() wakes the poll loop for it -- the same mechanism toasts and hover guides already use -- and both front ends call the tick, so the behaviour exists under the server and the headless harness alike. Rows per step is how far past the edge the pointer sits: one row past creeps, further hurries, so the hand already dragging is also the throttle. The metronome is select_scroll_ms, a knob so the test can configure the clock down instead of sleeping. The test drags to one row above the pane and simply holds: the viewport walks to the top of scrollback (clamping makes the endpoint exact), the copy starts at a line that was never on screen with the press, and still ends at the pressed row -- that last check is the tracked anchor's.
* slosh: M10c, the memory -- floats survive being written downuser2026-08-20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A dumped session quietly re-tiled its floats, which was the one data-loss gap left in D22. dump-layout now records `floating=true x= y= w= h=` -- the *wanted* rect, because intent is what survives a session and the next screen's layout clamps it like any frame -- layout files declare the same (linted: floating is a bool, x/y/w/h mean nothing without it), and a file whose every pane floats self-heals through ensure_tiled: the top float lands, the same rule closing the last tiled pane follows, no lint required. save-workspace inherits all of it from the shared dumper. `float` over the API takes x/y/w/h and then *places* rather than toggles -- a placement is a statement about where, not about whether -- floating the pane first when it is tiled, re-placing when it already floats, with omitted fields meaning "keep". The intent is stored exactly as asked: the write-back rule is for hands, whose drags need the clamp to agree with the eye; a script means what it typed. And kitty graphics respect the stack. The cell compositor gets occlusion free from paint order, but placements are sent after the diff and would paint an image straight over a float covering it. Each pane's placements are now clipped by the float rects above it -- every float for a tiled pane, the higher-raised for a float -- cropping what one clean edge can express (the pane-edge source-rectangle arithmetic aimed at another clipper, including from the top and left, where the source origin moves and the sub-cell offset is consumed) and suppressing what it cannot: a strip across the middle or a corner overlap leaves a shape one placement cannot say, and the image returns whole when the float moves. No z= needed -- clipping is the z-order, since nothing is ever allowed to overlap. D22's "still owed" paragraph is paid and rewritten; M10 is fully checked.
* slosh: floating panes and the drop grammaruser2026-08-20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A pane can float: lifted out of the layout, drawn over the tiled panes and under the modals, freely moved and shaped. The model is a flag and a rect on the leaf, exactly like `minimized` -- the seat in the tree is kept, layout skips it, the siblings absorb its share, and un-floating returns it home. The drawn rect is the wanted one clamped to the tab every frame, never written back, so a terminal squeeze round-trips exactly; an explicit move or resize *does* write back, because the hand is the author of the intent. Floating pops the pane to the centre: the jump is what says it happened. Painted after everything tiled, so the hit list -- searched backwards -- resolves an overlap to the float you can see: the z-order and the click order are the same list. It casts a shadow (float_shadow, one number), and `floating` joins the states table above `unfocused`, so a float is never dimmed as merely-unfocused. The hands: the title moves it, any other border resizes it (a bottom corner on both axes; the grabbed edge pins at walls and floors, never sliding the window), H J K L move a focused float, the =/+ key grows it about its centre, `-` shrinks it (its split was a refusal anyway), and hovering shows which edges a grab would take, from the same derivation the press uses. Equalize moved to `0` to free the key; `C-a f` floats (finder went to `s`, where it reads as search); `C-a F` opens a new floating shell -- the throwaway terminal; the function row f1..f12 became nameable in configs along the way. Guards throughout: the last tiled pane cannot float or minimize away, closing it lands the top float, splits are refused with their own words, floats are not swap targets. Dropping got a grammar: while a pane is carried, the pane under the pointer subdivides into a swap centre and four edge bands meaning "insert me beside this pane, on this side" -- detach_leaf + place_beside, the surgery cross-tab moves already had. Bands are thirds (the first map of quarters showed the swap owning half of each axis), the hovered band fills the half the drop would hand over, the release resolves against the painted frame's hits so it cannot disagree with the fill, and a drop between siblings along the axis is a reorder that always fits -- refusing it was refusing the most ordinary drag there is. Also: rename-pane/rename-tab as palette actions (renaming was a double-click and nothing else, the one verb the palette could not reach), and C-u clears the rename editor like it clears the picker's query. Design log in PLAN-FLOATING-PANES.md; to fold into DESIGN.md as D22/M10-11.
* slosh: layout files are *.layout, not *.layout.kdluser2026-08-19
| | | | | | | | | | | | | | | | | | | | | The old name argued that the extension should say how to read the file. The argument lost to a collision: zellij speaks the same KDL syntax under that same extension with a different schema, and to anyone arriving from there .kdl said *whose* file it is -- naming the wrong owner is worse than not naming the parser. So a layout is x.layout, a project's is slosh.layout, the name says what the file is and --check says how it was read. (It is still KDL underneath; point your editor's highlighting at *.layout.) Nobody's checked-in workspace vanishes on upgrade: the project scan still finds slosh.layout.kdl, never writes it, and where both spellings exist the new one wins, because two files disagreeing needs one answer. The unparseable-file heuristic in --check learned the new suffix -- it matched '.layout.' with a trailing dot, which the new name no longer has. The webdemo overlay moves with it (demo.layout), rebuilt and boot-checked.
* slosh: the logo flies into place -- a particle engine, statelesslyuser2026-08-19
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Every non-space glyph of the logo is a particle that flies to its cell over the first two fifths of splash_ms, then *is* the logo for the rest. There is deliberately no particle state anywhere: a position is a pure function of (target, elapsed, seed), recomputed every frame like the layout and the pane states and for the same reason -- physics that is stored can drift, physics that is derived cannot. The seed is the splash timestamp, so every attach flies differently and one splash is coherent frame to frame; a per-particle hash staggers departures, and the delay rescaling lands every straggler exactly at the end, because an almost-assembled logo reads as a bug. Five motions, picked by the clock and pinned by {"cmd":"splash", "motion":N}: rain (fall in, column by column), scatter (converge from everywhere), slide (rows enter from alternating sides), vortex (spiral in around the centre, orbit shrinking and unwinding), shuffle (the glyphs trade places, then sort themselves). The colour chains keep playing over the box throughout, so particles catch their hue as they cross into it. expr_sin() is the expression language's own sine exported for C-side animation: a motion sketched in the repl and one compiled into the binary cannot disagree about what sin(90) is. The tests learned two things the flights taught: the logo's opening run appears three times per line and assembly is staggered, so a middle occurrence can land first -- "assembled" is the row whose first block glyph is where the match begins -- and windows need margins that survive a parallel suite on a busy box.
* slosh: the splash learns colouruser2026-08-19
| | | | | | | | | | | | | | | | | | | | | | | | One tint is one colour, and every effect wore the theme accent -- six effects that were all the same dull blue. An effect is a *chain* now: phase-shifted tints in loud literal colours, mixing per cell, which is how a fixed-colour pass machinery does rainbows. Passes may light the backdrop's background too, so the box is a scene rather than tinted text, and the logo is drawn white -- the canvas that takes every hue at full saturation, where tinting blue towards pink lands on mud. The six: spectrum (rainbow bands), plasma (two interference patterns in clashing hues), fire (heat up the glyphs, embers in the box), rings (rainbow ripples), glitch (a reveal under two colours of static), aurora (green and violet curtains over cyan). The splash is deliberately the one place that does not defer to the theme: it is branding wearing a demo, not chrome. {"cmd":"splash","fx":N} picks an effect by index -- a test needs a deterministic one, and "show me the aurora again" deserved an answer. The new test counts dominant-channel hue families on the logo rows, because two hundred shades of the same blue is exactly the failure it guards against.
* slosh: greet an attach with the logo, wearing a random shaderuser2026-08-19
| | | | | | | | | | | | | | | | | | | | | | | | logo.txt is the source of truth; the Makefile bakes it into build/logo.h the way version.h is baked. When a client attaches (MSG_HELLO turning a connection into the display), the logo appears centered over everything for splash_ms (900, 0 for never), on a cleared backdrop, in the accent -- and one of six effects runs over its rect through the ordinary shader pass: a diagonal sheen, plasma, rings, sparkle, a left-to-right reveal, a breathe. Which one is picked by the splash's own timestamp, so every attach looks a little different without anybody keeping a counter. Every effect reads the clock, so the frame clock runs only while the splash is up (note_animation, same as any animated chain), and `since` in a splash context is milliseconds since it appeared, so one-shot effects sweep exactly once. Any key or click ends it and still lands where it was aimed: a greeting is not a modal. A screen the box does not fit on gets no greeting rather than a cropped one. Headless and scripted sessions never attach, so the whole fast suite never sees it; {"cmd":"splash"} triggers it on demand, which is what makes it testable from a scripted session -- and lets you replay the greeting whenever you like.
* slosh: defaults most people can live in, said by shadersuser2026-08-19
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 -> 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().