The anti-slop run (design-dev, 2026-09-28; operator: "start the fix slices") found raw ISO stamps with microseconds and offsets, the poster's IP address, and HH:MM in board rows and <time> tooltips. Operator convention 2026-09-24: a clock the operator reads is 24-hour local time as four digits, no colon. - `clock` filter: ISO (any precision, any offset), epoch, or the board's `YYYY-MM-DD HH:MM` -> `28 Sep 0848` local, year only when not this year's. Never raises; what it cannot read is shown as given. No regex (INV-3). - `byline` filter: a handle is shown, an IP address is not. Stored `by` and `answered_by` are unchanged (u2 still records the client host). - Applied to the marks' answer and memo lines, the inline ask's state tag (so the embed chrome inherits it) and the link board's row time, each in a <time> whose datetime= carries the stored value exactly. - `date_stamp` (the created/updated tooltips) renders `YYYY-MM-DD HHMM`. Folded from the heid bug-hunt (panel 4/4, thread 01M3MGPFKWBX0SJK5HFE0P3AFM): clock converts a number inside its guard (an int past float range raised, Q1); a date or ISO week renders no invented 0000 (Q8); byline also hides addr:port, [v6]:port, addr/prefix and addresses behind invisible characters (Q7); the board row's author is bylined (Q5). Refuted: Q3 (default Jinja Undefined has length 0; the test stays as a StrictUndefined guard). Accepted with reasons: Q4, Q6. Contract: docs/contracts/as_antislop.contract.md S1. Falsifiers: antislop.toml 15/15 proved (S1); all 12 tables 295/295 proved on this tree.
6.4 KiB
contract_version, status, module, purpose, depends_on, language, complexity, touches, assumptions
| contract_version | status | module | purpose | depends_on | language | complexity | touches | assumptions | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.1 | PROPOSED 2026-09-28 by design-dev. The operator ordered the fix slices from the anti-slop run (booth `booth-antislop`, report `~/.local/share/design-dev/research/booth-antislop-2026-09-28.md`) in design-dev's session: "go with your recommendations, push, start the fix slices". Each slice is staged as its own ref (`design-dev/antislop-sN`) for booth-dev's gate: suite, mutation tables and a bug-hunt. | the Booth's rendered surface: filters in booth/app.py, templates, booth/static/embed.js | Fix what the anti-slop run found (the Impeccable detector at 1280 and 390 in light and dark, plus a Vercel Web Interface Guidelines review), one slice at a time, without moving any invariant. |
|
python + jinja | low per slice |
|
|
The anti-slop fix slices
The run found that the Booth is sound on desktop and has a set of problems a viewer feels: clock times that break the house form, layouts that break at phone width, controls you can barely see in the light theme, and keyboard and screen-reader plumbing. The slices below fix them in an order that keeps each ref small enough to gate. Every slice keeps the six invariants (CLAUDE.md), and in particular:
- the server renders every state, and scripts only place it;
- autoescape stays on;
- every ordered surface keeps its stated order;
- blur honesty holds.
S1 — the house clock
The rule (operator convention, 2026-09-24): a clock time the operator reads is 24-hour local time (US Pacific), written as four digits with no colon (0848). Raw ISO stamps, HH:MM, microseconds, offsets and a poster's IP address do not appear in visible text.
-
One filter decides the visible form:
clock.- It takes an ISO-8601 string (with or without microseconds and an offset), an epoch number, or the link board's
YYYY-MM-DD HH:MM. - It returns
D Mon HHMMin local time (for example28 Sep 0848), with the year after the month only when it is not the current year (6 Sep 2025 2335). - A value it cannot read is returned as given, never a guess and never an exception: the Desk and the board render many rows in one response, and one bad stamp must not 500 the page. An empty value returns
"". - Falsifiable: a
clockthat formats%H:%Mfailstest_clock_forms. Aclockthat raises on garbage failstest_clock_never_raises.
- It takes an ISO-8601 string (with or without microseconds and an offset), an epoch number, or the link board's
-
One filter decides who is shown:
byline. It returns the recordedby/answered_byunless it parses as an IP address (v4 or v6), in which case it returns"". The stored value is unchanged; the u2 contract still records the client host.- Falsifiable: a
bylinethat passes IPs through failstest_byline_hides_addresses.
- Falsifiable: a
-
Where the filters apply. Every visible stamp goes through
clockand every byline throughbyline, and each clock sits in a<time>whosedatetimecarries the value exactly as stored:- a pick's answer line and a memo's line (
_marks.html); - the inline ask's state tag (
_ask_inline.html, so the embed chrome inherits it); - the link board's row time (
booth.html). - Falsifiable: the marks page, the lightbox's verdict aside, the embed fragments and the board carry no visible
HH:MM, noT08:48-shaped stamp and no IP:test_rendered_marks_use_the_house_clock,test_board_rows_use_the_house_clock,test_embed_fragment_uses_the_house_clock. Removing the filter from any one of those templates turns its test red.
- a pick's answer line and a memo's line (
-
The date tooltips follow suit.
date_stamp(thetitleof every created/updated<time>) rendersYYYY-MM-DD HHMM.- Falsifiable:
%H:%Mindate_stampfailstest_date_stamp_is_house_form.
- Falsifiable:
-
Folded from the heid bug-hunt (panel 4/4, thread
01M3MGPFKWBX0SJK5HFE0P3AFM):clockconverts a number inside its guard: an int past float range was anOverflowError(Q1).- A date or an ISO week renders its day and no invented
0000(Q8). bylinealso hides an address dressed asaddr:port,[v6]:portoraddr/prefix, or behind invisible characters (Q7).- The board row's author goes through
bylinelike every other surface (Q5). - Falsifiable: the rows marked Q1, Q5, Q7 and Q8 in
antislop.toml.
-
Refuted, with the reason: a malformed answer missing
unanswereddoes not 500 the marks panel (Q3). The Booth's Jinja uses the defaultUndefined, whose|lengthis 0; the no-op fix was reverted when its falsifier stayed green.test_a_malformed_answer_costs_its_line_not_the_pagestays, as a guard against a switch toStrictUndefined. -
Accepted as known risk, with reasons:
- Zone-less mark stamps are read as local by
clockand as UTC by the ordering path (Q4). No writer produces one:now_stampand the legacy import both stamp with.astimezone(). Only a hand-edited file could. - A board time inside the spring-forward gap renders the normalised hour (Q6). No clock can write a local time that does not exist.
- Zone-less mark stamps are read as local by
Out of S1: the CLI keeps writing its board rows as it does today. The board is a multi-writer file other sessions parse, so its storage form is not changed; clock reads both forms.
S2 to S6
These are added to this contract as each slice is staged, in the order of the report's plan:
- S2, legibility;
- S3, phone layouts;
- S4, reading measure;
- S5, interaction and screen readers;
- S6, the operator's rulings (tagline, needs-you stripe, matte brand dot, the Wipe-now stripe on
::before).