The jobs chip was replacing the whole transcript
This is the blank agent chat, and it was not the transcript rewrite at all.
`hx-target` is inherited. The composer's form carries `hx-target="#thread"`
with `hx-swap="beforeend"`, which is what makes a sent message append a bubble.
The background-jobs chip I added last commit sits inside that form and declared
`hx-swap="outerHTML"` and nothing else -- which reads as "replace yourself" and
resolved, through the form, to "replace #thread with yourself". On load, and
then again every five seconds.
So an agent chat rendered its reply and then went blank, the reader's own prompt
along with it, because the entire transcript had been swapped out for a chip
that renders empty when no jobs are running. Only agent chats, because that is
the only place the chip exists. The server logged nothing, because nothing there
had gone wrong: every page render, every SSE frame and every stored row was
correct throughout, which is why four rounds of looking at the server found
nothing.
Both the chip and the element that loads it now carry `hx-target="this"`, and
`tests/test_chat.py` walks the composer's form and refuses anything that fetches
without saying where its answer goes. Checked against the bug before being kept.
Worth being precise about what made it invisible: the markup was correct. There
is nothing wrong with `hx-swap="outerHTML"` on an element with no target -- it
means "swap yourself" right up until an ancestor disagrees. It is the same
family as the trigger bound where the event does not go, and the same lesson:
assert the resolved property, not the attributes.
My earlier fix in 74a3c0f was a real defect -- an sse-swap container must not
hold another -- but it was not this, and I should have said "best hypothesis"
rather than "found it" when I shipped it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 # 1499 tests, ~92s
|
||||
pytest # 1500 tests, ~92s
|
||||
# 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;
|
||||
@@ -953,6 +953,24 @@ a menuitem is clicked, which is right for an action menu and wrong for a list of
|
||||
switches you want to set several of. That is the whole reason the menu needs no
|
||||
JavaScript at all. The verb is on the checkbox, per the usual rule.
|
||||
|
||||
**`hx-target` is inherited, and the composer's form targets the transcript.**
|
||||
The chip below sits inside `<form class="composer__form" hx-target="#thread"
|
||||
hx-swap="beforeend">` — that target is what makes a sent message append a
|
||||
bubble. htmx resolves `hx-target` by walking up the DOM, so an element in there
|
||||
that fetches and does not name its own target aims at `#thread` too. The jobs
|
||||
chip declared `hx-swap="outerHTML"` and nothing else, which reads as "replace
|
||||
yourself" and meant "replace the whole transcript with yourself" — on load, and
|
||||
then again every five seconds. Every agent chat rendered its reply and then went
|
||||
blank, the reader's own prompt with it, while the server logged nothing at all
|
||||
because nothing had gone wrong there.
|
||||
|
||||
Anything inside that form that fetches must carry `hx-target` (or
|
||||
`hx-swap="none"`). `tests/test_chat.py` walks the form and refuses the rest. Note
|
||||
what the failing version looked like: correct, idiomatic markup whose meaning
|
||||
came from an ancestor — the same family as the trigger bound where the event does
|
||||
not go, and the reason that test asserts the resolved property rather than the
|
||||
attributes.
|
||||
|
||||
**Background jobs have a chip in the composer row and a panel behind it.** A job
|
||||
runs detached for as long as it takes and the only way to see one used to be
|
||||
asking the model to call `job_list` — something that outlives the reply that
|
||||
|
||||
Reference in New Issue
Block a user