An edge that is not drawn, and a panel that stopped eating the site

`hx-get=""` is not "fetch nothing". htmx looks for the attribute, not for a
value, so the empty one the canvas rendered before a chat existed was a real
request for the empty path -- which the browser resolves against the current
document. Opening the canvas on the new-chat screen fetched the new-chat screen
and swapped the whole site into the panel. The attribute is omitted now, and a
test refuses an empty verb anywhere on the page.

Which panels can exist is the server's answer; which are offered is the
browser's. Both need an agent chat on a chosen connection, and before a chat
exists those are controls in the composer -- so answering with the first profile
offered a terminal on an ordinary chat with nothing selected. They follow
`lembas:agent-target` now, and an open panel whose target goes away is closed
rather than left showing one machine under another's name.

`.tabs__body` is only sometimes the scroller: true where the tabs are a bounded
flex child, false under the admin layout, where the page scrolls instead. So
setting its scrollTop on every tab change had never once run on /admin/prompts,
silently, while the reader was dragged to the bottom of a document that had just
got shorter. The rule names the position now, and the handler finds the
container that actually scrolls.

The two top borders come off. They were what made the misalignment at the bottom
of the shell visible; `--footer-height` stays, because two ends at different
heights are visible without a line to prove it. The top of the shell keeps its
line -- there, everything is `--header-height` and aligns by construction.

And one version. pyproject carried its own copy and had drifted three minors
from the one everything actually reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-06 09:42:06 +02:00
co-authored by Claude Opus 5
parent 4c78215e31
commit bdd7e09753
13 changed files with 281 additions and 39 deletions
+16 -2
View File
@@ -1,8 +1,22 @@
/* Settings and administration screens. */
/* --- Page scaffolding ------------------------------------------------------ */
/* --- Page scaffolding ------------------------------------------------------
One scroll container per screen, and this is the rule that decides which.
`.admin-scroll` is it on every admin page. `.tabs__body` is it only where the
tabs are a *bounded* flex child -- `.main > .tabs` on the settings page --
which is why the selector below says so rather than naming the class alone.
It used to name the class alone, and the result was two scrollers stacked on
/admin/prompts, where `.tabs` sits inside `.admin-scroll > .admin-page`, a
plain block. `flex: 1` and `min-height: 0` mean nothing there, so
`.tabs__body` had `height: auto` and never scrolled while still declaring
`overflow-y: auto` -- and everything written to reset "the scroller" reset
that one, silently, while the reader was lost in the other. Under
`.admin-scroll` the body is now an ordinary block and the page scrolls as one.
*/
.admin-scroll,
.tabs__body {
.main > .tabs > .tabs__body {
flex: 1;
min-height: 0;
overflow-y: auto;
+10 -4
View File
@@ -416,6 +416,11 @@ button, input, textarea, select {
flex: 1;
min-height: 0;
overflow-y: auto;
/* A flick past the end of the list stops there rather than chaining to
whatever is behind it. The shell is `overflow: hidden`, so what chaining
produced was not a scrolled page but a rubber-band into blank background --
which reads as the sidebar having come loose from the layout. */
overscroll-behavior: contain;
padding: 0 var(--sp-2) var(--sp-3);
scrollbar-width: thin;
scrollbar-color: var(--border-strong) transparent;
@@ -423,14 +428,15 @@ button, input, textarea, select {
.sidebar__footer {
flex: none;
border-top: 1px solid var(--border);
padding: var(--sp-2);
display: flex;
flex-direction: column;
gap: var(--sp-1);
/* Meets the composer's top border on the other side of the sidebar edge.
See `--footer-height`; `justify-content` keeps the rows at the bottom when
the reader's permissions leave fewer of them than the token allows for. */
/* Ends level with the composer on the other side of the sidebar edge. See
`--footer-height`; `justify-content` keeps the rows at the bottom when the
reader's permissions leave fewer of them than the token allows for. No top
border: the nav above scrolls, and a drawn edge over scrolling content is
a line the content stops dead at rather than passes under. */
min-height: var(--footer-height);
justify-content: flex-end;
}
+4 -5
View File
@@ -918,12 +918,11 @@
.composer {
flex: none;
padding: var(--sp-3) var(--sp-5) var(--sp-4);
border-top: 1px solid var(--border);
background: var(--bg);
/* Lifted to meet the sidebar footer's top border, so the two read as one
line across the shell rather than as one that has been broken at the
sidebar's edge. See `--footer-height`. A column ending at `flex-end` so the
extra height opens above the box and the input stays where the hand is. */
/* Lifted to end level with the sidebar's footer across the shell. See
`--footer-height`. A column ending at `flex-end` so the extra height opens
above the box and the input stays where the hand is. No top border: the
transcript scrolls under this, and the edge reads better undrawn. */
min-height: var(--footer-height);
display: flex;
flex-direction: column;
+10 -5
View File
@@ -84,11 +84,16 @@
/* What `--header-height` does at the top of the shell, this does at the
bottom. The sidebar's footer and the composer sit either side of the same
vertical line, and both were content-sized -- so the two top borders met
the sidebar's edge at different heights and read as one line that had been
broken. Neither could be made to match the other by accident: the footer's
height depends on which entries the reader's permissions allow, and the
composer's on how much they have typed.
vertical line and both are content-sized, so without this they end at
different heights -- and neither can be made to match the other by
accident: the footer's height depends on which entries the reader's
permissions allow, and the composer's on how much they have typed.
This began as the fix for a broken *line*: both carried a top border, and
the two met the sidebar's edge at different heights. The borders are gone
now -- an edge that content scrolls under reads better undrawn than drawn
and aligned -- and the token stays, because the two ends of the shell
sitting at different heights is visible without any border to prove it.
A calc of the pieces the footer is actually built from -- four rows at
`--control-h`, the gaps between them, and its own padding -- so it stays