Three things that said one thing and did another

All three shipped in the last two commits, and all three are the same kind of
mistake: an interface that looks right and is not.

The folder settings page could not be scrolled. `.main` is a flex column with
`min-height: 0`, so a `.page` dropped straight into it overflows the viewport
with nothing to scroll -- Save and Back end up below the bottom of the window,
reachable by zooming out or by dragging the prompt textarea up out of the way.
Every other page of this shape already wraps its content in `.admin-scroll`;
this one did not. The two class names that scroll are one rule in admin.css
precisely so this is a wrapper somebody forgot rather than a value they got
wrong, and now it is noted.

The project directory was a text box, on the one screen that asks for an
absolute path on another machine. It is the same button-and-hidden-field the
new-chat screen uses, wired by `[data-dir-field]` in ui.js -- scoped to that
attribute so this and the composer's own handler cannot both answer one click
and open two dialogs. The composer keeps its own because it does more: it
follows the selected profile's default directory until somebody picks their
own, which only means something while a chat is being created. With no
connection chosen it says so rather than opening onto nothing, and Clear is
always there, because browsing somewhere and changing your mind before saving
needs a way back to "no opinion" as much as clearing a saved one does.

And "New chat" did not follow the Chat/Agent switch. The button sits above the
scroll area rather than inside the tree the switch swaps, so it went on saying
"New chat" over a list of agent chats. It moves to its own partial and arrives
out of band, the way the chat title already does. Renaming it to something
neutral would have hidden the bug rather than fixed it, and would have cost the
`?kind=agent` preselection the label is there to explain.

The tests that existed asserted a page load, which re-renders the button
anyway -- which is exactly why nobody saw it. The new ones assert the fragment.
The directory field was driven under a DOM stub first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-04 10:40:01 +02:00
parent fd4db76c64
commit a56ee16ee3
10 changed files with 263 additions and 33 deletions
+22 -2
View File
@@ -20,7 +20,7 @@ lembas info # paths + counts, useful when confused
lembas secret-key # generate LEMBAS_SECRET_KEY
lembas create-admin # create or promote an admin
pytest # 1394 tests, ~85s
pytest # 1399 tests, ~87s
# PLAN.md tracks what is and is not built
ruff check . # lint (line length 100)
python scripts/build_artwork.py # regenerate artwork (SVG + PWA icons;
@@ -838,11 +838,31 @@ folder of contracts into the window would cost the context on every request
forever to answer one question. It therefore needs an existing chat, so it is
absent on the new-chat screen — the same reason project files are.
**A page that uses `.page` needs `.admin-scroll` around it.** `.main` is a flex
column with `min-height: 0`, so content dropped straight into it overflows the
viewport with nothing to scroll — Save ends up below the bottom of the window,
reachable only by zooming out. `settings.html` gets this from `.tabs__body` and
the admin pages from `.admin-scroll`; the folder settings page shipped without
either. The two class names are one rule in `admin.css` for that reason.
**A path is chosen, not typed.** `[data-dir-field]` in `ui.js` is the directory
picker on a form that is not the composer, scoped to that attribute so it and
the composer's own handler cannot both answer one click and open two dialogs.
The composer keeps its own because it does more: it follows the selected
profile's default directory until somebody picks their own, which only means
something while a chat is being created.
**The sidebar shows one kind at a time.** `Chat.kind` distinguishes an agent
chat everywhere except the one place a person looked. The switch is stored on
the account, and three things about it are not the obvious version. It lives
*inside* the fragment it swaps, or the two buttons would go on showing the side
you had just left. `Folder.shown_in` hides a folder the filter emptied and keeps
you had just left — and "New chat", which sits *above* the scroll area rather
than in the tree, comes along out of band
(`partials/_sidebar_actions.html`, rendered with `oob` only by the fragment
route). That one shipped broken: the button went on saying "New chat" over a
list of agent chats. Whether it *worked* was never the question — it said one
thing and did another, which is the shape of failure the switch itself was
arranged to avoid. `Folder.shown_in` hides a folder the filter emptied and keeps
one that was empty to begin with — the second is a container somebody just made,
and hiding it means it can never be found again, let alone filed into. And with
agent chats switched off there is no switch and no filtering at all, rather than