feat(u3): a verbatim report declares the seam, the Booth mounts into it
A booth that ships its own index.html was served through ten regular
expressions applied to markup the Booth did not write: six in
wrap_verbatim_html hunting for somewhere to hang a favicon and a chip, four
in booth/inline.py substituting rendered ask markup into the author's own
tags. Both worked. Both were the most fragile thing in the service, on the
path the operator uses most.
The whole class is replaced by a declared seam. A report carries one line —
<script src="/_booth/embed.js" defer></script> — and the chrome mounts
through DOM APIs. What the server does to author HTML is now, in full:
return html if declares_embed(html) else html + EMBED_SCRIPT_TAG
Two substring tests and a concatenation. Both of the old wrapper's hard
constraints stop existing rather than being satisfied more carefully:
nothing can displace a leading doctype into quirks mode and nothing can push
the charset meta out of its detection window, because nothing in front of
them ever moves. A page that declares the seam is served exactly as written.
Fragments are still rendered by the _ask_inline.html macros and handed over
GET /b/<name>/embed.json; embed.js places them and decides nothing. Openness
comes from open_marks, order from (created, id), questions in declaration
order. A single-question pick normalizes to key None, so the payload carries
questions as a list rather than an object — keying by name would serialize
that as the string "null".
Placement is an anchor fill, not a replacement: el.insertAdjacentHTML(
'beforeend'), so an author's wrapper and its contents survive. The regex it
replaces was eating the opening tag of dfa-concepts' styled .ask blocks and
orphaning their headings, live, unreported.
data-booth-mark is canonical; data-booth-ask stays a kept alias because two
live reports use it. The comment placeholders are dropped — no users.
Declared cost: the verbatim path now needs JavaScript. The never-invisible
guarantee holds through the index badge and /b/<name>/marks, both of which
render server-side.
Deleted: booth/inline.py entire, wrap_verbatim_html and its six patterns,
_BACK_CHIP, asks_chip, inject_asks, FAVICON_LINK, the styles() macro.
Tests 410 -> 434. tests/test_embed_browser.py drives a real Chromium: the
placement algorithm and the form= binding of a scattered multi-question form
cannot be observed any other way, and that binding was measured rather than
assumed (N=3 per condition, with a form-first positive control and a
points-at-nothing negative control).
Contract: docs/contracts/u3_declared_embed_seam.contract.md, with the
in-session seam review and the cold contract panel both recorded. Two of the
panel's findings were code fixes: a vacuous INV-3 falsifier that a renamed
regex walked straight through, and a bare-substring seam detection that read
a report merely quoting the path as declaring it and silently served it with
no chrome.
This commit is contained in:
@@ -85,6 +85,12 @@ doing so, never resolved the caption — the operator's "zoomed images lose
|
||||
their annotations" bug. It was not a rendering bug; it was three readers of one
|
||||
truth.
|
||||
|
||||
**U3 extended this to the verbatim path.** A booth's own `index.html` now gets
|
||||
its chrome from `/_booth/embed.js`, which *places* server-rendered fragments and
|
||||
never builds one. The fragments come from the same `_ask_inline.html` macros the
|
||||
gallery page uses, handed over `/b/<name>/embed.json`. A second renderer in
|
||||
JavaScript would be the same bug in a new language.
|
||||
|
||||
### 4. Re-export, don't move-and-break
|
||||
|
||||
Names that moved from `app.py` to `items.py` (`classify`, `doc_kind`,
|
||||
@@ -122,9 +128,14 @@ the operator's judgment being quietly misfiled.
|
||||
|
||||
Current rules: items `sorted(rel)`; the zoom ring is that order filtered to
|
||||
images; captions resolve over a sorted scan; marks `(created, id)`; legacy
|
||||
import `(mtime, name)`; link rows pinned-then-newest. `ROADMAP.md` carries the
|
||||
table and the two places still undecided (U7 sections and compare pairing, U6
|
||||
bench listing).
|
||||
import `(mtime, name)`; link rows pinned-then-newest; a verbatim report's embed
|
||||
anchors in document order, its tail in payload order, its questions in
|
||||
declaration order. `ROADMAP.md` carries the table and the two places still
|
||||
undecided (U7 sections and compare pairing, U6 bench listing).
|
||||
|
||||
U3's rows are the first that bind **across a language boundary** — decided in
|
||||
Python, honoured in JavaScript. A string assertion cannot see that, which is
|
||||
why `tests/test_embed_browser.py` exists.
|
||||
|
||||
When you add an ordered surface, state its rule in the docstring. If you cannot
|
||||
state it in one line, it does not have one.
|
||||
@@ -179,8 +190,15 @@ one caused an outage.
|
||||
is that template work needs a restart to see, and that price is the point.
|
||||
`test_templates_do_not_hot_reload_from_disk` holds the line.
|
||||
|
||||
**So: after ANY edit here — Python or template — the live service is stale until
|
||||
you restart it.** If you are touching this repo while the operator may be using
|
||||
3. **`booth/static/embed.js` is the third thing that would have hot-reloaded,
|
||||
and it does not.** U3 gave the service a static asset living in the
|
||||
deployment root; it is read ONCE in `create_app` and served from memory with
|
||||
an ETag over its content, for exactly the reason above. Same rule, same test
|
||||
shape (`test_embed_js_does_not_hot_reload_from_disk`). Anything else this
|
||||
repo learns to serve from disk inherits the rule — read it at startup.
|
||||
|
||||
**So: after ANY edit here — Python, template or static asset — the live service
|
||||
is stale until you restart it.** If you are touching this repo while the operator may be using
|
||||
the service, either restart promptly or expect him to be looking at the old
|
||||
version. Never leave the tree in a state where a restart would 500.
|
||||
|
||||
@@ -197,6 +215,15 @@ curl -s localhost:8090/healthz # the live service (systemd --user)
|
||||
systemctl --user restart booth.service # after a code change, to see it live
|
||||
```
|
||||
|
||||
`tests/test_embed_browser.py` drives a real Chromium against a real uvicorn on
|
||||
an ephemeral port — the only place U3's placement and `form=` binding can be
|
||||
observed at all. Browsers are NOT downloaded per project; they live box-wide in
|
||||
`/opt/ms-playwright`. The file **skips rather than fails** when playwright or a
|
||||
usable browser is missing, so the suite stays green anywhere. If those tests
|
||||
start skipping on this box, the pinned `playwright>=1.60,<1.63` in
|
||||
`pyproject.toml` has drifted past the shared store — read the comment there
|
||||
before raising the bound.
|
||||
|
||||
`booth.service` is a user unit installed to `~/.config/systemd/user/`. The repo
|
||||
copy is the source; edits there need a `daemon-reload`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user