Commit Graph
7 Commits
Author SHA1 Message Date
vh 72d6c61629 fix(as-S5c): whose key it is, the doc bar, tile sizes, the rail's shadow, reveal names
The last of the anti-slop interaction work (guidelines G6, G7, G14, G15,
G17), plus booth-dev's note from S5b's gate. Every S5b promise holds: no
re-POST, serialized saves, a batch never reloads, focus survives a swap.
- Keys (G6): one rule in base.html's <head>, BoothKeys.theirs(e), called
  first by the grid, the review and compare. A field or a player owns every
  key but Escape (Esc still goes back from a focused player); a control
  owns Space; a focused 1:1 stage that can pan owns the
  arrows and Space (Chromium puts it in the Tab order); Ctrl/Meta/Alt are
  the browser's. The field check lives on as BoothKeys.isEditable. Before:
  an arrow on a focused video left the review, and Enter on any control
  also opened the grid cursor's tile.
- The grid cursor is real focus: the tile it moves to gets tabindex=-1
  (script-set, one tile at a time) and focus, without a scroll; the cursor
  is an item (its data-item), and a doc closed with its ✕ is skipped; focus that
  lands on a tile (S5b's fallback) makes it the cursor; Enter opens the
  review only from the body, the grid or the tile, by its view?f= link;
  n opens a closed doc's fold; Escape clears the cursor
  and releases the tile's focus. The reticle is its focus mark (no second
  ring).
- The doc bar (G7): the controls leave the <summary>. div.doc-bar holds
  details.doc-fold (its summary is the label only) and div.doc-tools beside
  it; the body and notes follow in div.doc-inline, hidden with a closed
  fold by :has(), scripts on or off. A closed doc keeps its tools. Renders
  pixel-identical to today at 1280 and 390, light and dark.
- Tile sizes (G14): a gallery tile's <img> carries width/height, the
  picture as the browser draws it (EXIF 5-8 swap), read from the header
  only (no decode; PNG getexif is skipped unless the header carried it),
  opened O_NOFOLLOW|O_NONBLOCK, cached by the file's identity (ctime
  included, so cp -p over a file is seen), in a separate
  step (items.image_dims over thumbs.drawn_size) so the Desk never pays it.
  Measured before: a link to tile 30 of 40 landed 44px low (3/3); after, on
  its mark. content-visibility:auto, which the report proposed too, is NOT
  added: a swapped-in tile has no remembered size, and a flag far down moved
  the page 2929px (3/3; 0px without it).
- The rail (G15): html:has(.rail){scroll-padding-top} replaces .item's
  scroll-margin-top (the two add), so a control reached by Tab stops below
  the sticky rail too. Measured before: a Tab-focused flag button at 19.6px,
  under the rail's bottom at 47.6px. The scripts-off fallbacks are the old
  rules' numbers (132px, 217px at <=480), now pinned by a test. The height
  script follows the live rail after every in-place save (it watched the
  replaced node, and read 0px after one flag), and the rail's own controls
  cancel the padding (a Tab between stuck group links scrolled 357px).
- Reveal names (G17): no aria-label on any reveal control; the name is the
  words on it, the glyph in an aria-hidden span, the item's name as
  .sr-only text ("reveal a.png" / "hide a.png"). Reveal all drops
  aria-pressed (its words already say the state; r2b rules them) and its
  "on" look reads the .reveal-all class on <html>. No pixel changes.
- booth-dev's note: a refused batch's forms enter `unsent` with the
  refusal's words, and a later save says every standing failure's words
  (each once, in order) instead of "Saved.", and every warning says the
  other standing failures first, so no failure buries another. Test first:
  test_a_batch_refusal_outlives_an_unrelated_save.
- Rows re-anchored to the same failure: r2b "Space on a focused review
  button", r3 "C3 a held modifier" and both "C3 Space on a focused ..."
  (now in BoothKeys), r2c "the stage reveal shows with scripts off", and
  this contract's S3 doc-bar row and five S5b status-line rows.

Folded from the heid contract review (BEINKA, panel 4/4, thread
01M3NZJNX8D3BEYD48M9K3MV3Q): 24 flags, all prose the tests left open; the
contract states the tile/focus/cursor seam with S5b, the helper's union and
scope, the size's source and every path to none, Reveal all's name, the
refusal sentence's lifetime, and the fallback arithmetic (one test added).

Folded from the heid bug-hunt (HRÖSKVA, panel 4/4, thread
01M3P0ZPRSASFSE5K3PR4NTQP6): R1 closed docs and the cursor as an item, R2
the rail's height after a save, R3 no warning buries another, R5 the view?f=
link, R7 ctime in the size cache, R8 Escape from a player, R9 the rail's own
controls, R10 n on a closed doc. Refuted with reasons: R4 (unreachable: refused
picks re-send together), R6 (Chrome takes the same header's size with or
without the attributes; measured), R11 (by design).

From this slice's own falsifier runs: a "one row wide" row that mutated a
flex basis a non-wrapping bar just shrinks (re-aimed at the bar's flex), and
a Reveal-all "on look" read under the clicking pointer, where :hover draws
the same border (the pointer now leaves first; 3/3 proved).

Contract: as_antislop S5c.
Falsifiers: antislop.toml S5c section.
2026-09-29 01:09:15 -07:00
vh 0233ca64fb fix(as-S5b): focus survives a swap, a status line you can see, drafts that ask before they go
The in-place client half of the anti-slop interaction work (guidelines
G1, G2, G4, G13). It still never re-POSTs, still serializes saves, and a
batch still never reloads.
- Focus: the focused element is recorded by identity (its region, its
  key, which same-key element it was) and the fresh one is focused
  without scrolling. If an answered pick's form folds into a closed
  <details>, focus goes to its summary; if nothing is left, to the region
  (tabindex=-1, set by the script). Focus outside the swapped regions
  is not touched.
- One status line per page (_status.html). It floats at the bottom
  centre, above the fixed review stage, so it moves nothing and is in view
  wherever the reader is. Wider than 900px, the letterhead and footer the
  review covers leave the Tab order (visibility:hidden, CSS only).
- The line is never hidden: empty, it takes no space and stays displayed.
  "Saving…" at the press, "Still saving…" on a repeat press, "Saved." when
  the swap lands (cleared after 2s if still the same write), and warnings
  with data-tone="warn". Every write sets or clears the tone. The form in
  flight carries aria-busy until its save settles.
- The client never reloads over a draft: both of its reloads run only
  when every in-place form is clean except the one just sent, unchanged
  since its press, asked again at the reload beat; otherwise it says so
  and stays. A beforeunload guard asks when an in-place form is dirty (its
  own reload does not ask). The embed asks when one of our answers is
  unsent, and skips the pressed form on its own one-form submit.
- Six booth-dev browser tests read the line's hidden state; they read
  its words and tone instead. Two r2_submit_all.toml rows are re-anchored
  to the same failure in the moved code.
Folded from the heid bug-hunt (panel 4/4, thread 01M3MRTNTWEPJHTN4APRR81KH4):
- aria-busy mirrors which forms are in flight on the LIVE page. It is set
  at the press and re-synced whenever a save settles, so it ends on every
  path (a stale tile the swap never replaced included), and a queued form
  replaced by an earlier swap is marked busy again.
- A press inside the reload beat cancels the reload.
- A failure that stayed is said again after an unrelated save, rather
  than buried under "Saved.".
- A 204 followed by a failed page GET is "Saved.", never "could not save".
- An edit made while its save flew is said to be unsaved.
- A focused <summary> has a key.
- The queue settles on rejection.
- The embed's skip covers the one navigation its submit starts; a
  cancelled submit, or one that leaves the page in place, is guarded
  again.
- Pinned: no in-place form holds a control dirty() cannot read, and no
  region nests in another.
Folded from this slice's gate: the status line floats (fixed, bottom
centre, above the review stage) instead of sitting at the top of <main>
or under the viewer's bar. In the flow, every save's "Saving…" moved the
page; booth-dev's test_a_flag_lands_in_place_and_every_region_catches_up
caught a 50px jump. The viewers' grids are back as they were.

Contract: as_antislop S5b (heid contract review and bug-hunt folded).
Falsifiers: antislop.toml S5b sections.
2026-09-28 15:42:37 -07:00
vh 7143fae6c7 fix(as-S5a): fixup from booth-dev's gate — release asks, fail-closed words, ids that cannot collide
From booth-dev's hulda bug-hunt with heid's second voice (BRINGA, thread
01M3MVGQ7QSCCK8WT59TQ4J469):
- The booth page's "kept — release" asks by name, as the Desk's does.
- WORDS has no prototype: data-confirm="__proto__" or "constructor" is an
  unknown word, and asks, instead of throwing before preventDefault.
- The confirm helper moved into <head>: its capture listener exists
  before any form, so a click during load is asked too (the inline
  confirm() it replaced had that property).
- shown() also marks U+2028/U+2029 and the zero-width characters.
- Derived ids take ':' (bk-ask-<id>-<key>:prompt, bk-ask-<id>:title), which
  no id or key can contain; '-prompt' and '-title' collided with valid
  keys. booth-dev's chip test now looks its fragment up by [id=...].
- human_dur says "—" for a value that is not finite, instead of raising.
- The tile's copy of a note drops its id (booth-dev: mark-<id> is the
  panel's article).
- Four guards that asserted source patterns now also hold on computed
  effects: embed rings, rings inside clipping containers, the withdraw ×
  on both axes, and question-level notes fields.

Contract: as_antislop S5a (fixup). Falsifiers: antislop.toml 102/102 with
r2_flow.toml 24/24 proved; the full gate follows.
2026-09-28 13:58:52 -07:00
vh d4f64fd7ec fix(as-S5a): every control named, one h1 and a skip link, rings and hit areas
The markup and CSS half of the anti-slop interaction work. The in-place
client is untouched (that is S5b).
- Glyph-only controls carry a name: the withdraw ×s, downloads, open full
  page, the viewers' ✕, the board's pin, copy and remove, the bench's
  remove, the 1:1 toggle ("1:1, natural pixels"). Film-strip and tray
  frames carry the file's name as sr-only text instead of reading "01".
  A Desk row's wipe names its booth.
- Fields are named by aria-label, not by their placeholder.
- The inline ask's options are a radiogroup labelled by the prompt; a
  single-question fieldset gets an sr-only legend; a titled ask's title
  takes bk-ask-<id>-title (it duplicated the question's id).
- One h1 per page (sr-only on the Desk, review and compare), a skip link
  to <main id="main">, theme-color for light and dark.
- The review tape is one picture (role=img); its segments leave the tab
  order (the film strip holds the same links, named).
- Wipe now uses the Desk's delegated prompt, moved to base.html: it names
  the booth and asks the kept-booth question for a kept booth.
- Embed focus rings of its own; rings drawn inside clipping containers;
  the withdraw × at least 24px, 44px under a coarse pointer;
  touch-action:manipulation; strips contain their overscroll; a long
  slug wraps on a phone.
- A truncated why carries its full text in title; a countdown of 48h or
  more reads in days.
Two r2_flow.toml rows for the confirm helper now name base.html, where
the helper moved (anchors unchanged; the gate found them drifted).

Contract: as_antislop S5a. Falsifiers: antislop.toml 86/86 proved (S1-S6, S5a);
all 12 tables 366/366 proved on this tree.
2026-09-28 13:30:31 -07:00
vh ec807fe41b fix(as-S6): the operator's rulings — sentence tagline, no side stripe, matte dot, stripe on ::before
Operator, 2026-09-28: "go with your recommendations".
- Tagline: 'held for review · wipes in {ttl}h unless kept', mono, muted,
  12px, sentence case ("ephemeral" goes, as agreed with booth-dev).
- 'Needs you' rows lose the 3px side stripe; the '? N OPEN' stamp says it.
  The flagged filmstrip frame keeps its bottom stripe.
- The brand dot is matte (glow = live power; a live bench keeps its glow).
- Wipe now and the armed bulk delete carry the hazard stripe on a 3px
  ::before, so the button's own background is honestly what sits under its
  text; the stripe renders as before.

Contract: as_antislop S6. Falsifiers: antislop.toml 49/49 proved (S1-S4, S6);
all 12 tables 329/329 proved on this tree.
2026-09-28 13:30:31 -07:00
vh 54f3531833 fix(as-S2): legibility — nothing fades, labels 11px, sentences 12px
From the anti-slop run (design-dev, 2026-09-28). Faded is not legible:
opacity divides whatever contrast a line had.

- Review arrows: the chip under the thin chevron is 82% dense, not 60%;
  the glyph now clears 7:1 over a white stage by colour (was 3.84:1), and
  reads at pixel level where the detector sampled a 2.9:1 median.
- Filmstrip numbers, the marks' state stamp and the inline ask's state tag
  are labels at 11px (were 9.5 / 10.5 / 10.5px).
- The Desk's section rules, the board note and the bench note are
  sentences at 12px.
- Retired benches: no opacity; the link and URL take --text-muted.
- Embed chrome: answered-ask details and the "recorded:" line inherit the
  host's text colour at full strength (the embed cannot know the host's
  palette); the notes placeholder inherits it at 75%, not the UA grey.

Folded from the heid bug-hunt: the embed's ask title no longer fades
either (Q9), and the legibility claims are also held on the browser's
COMPUTED style (tests/test_antislop_s2_browser.py): a stylesheet grep
cannot see a later rule in the cascade (font-size:1px, color:transparent,
filter:grayscale, a placeholder at opacity:0); the browser can.

Folded after the first gate run: the flagged tray's number, the tile's
"flagged" stamp and compare's A/B badge were still under the 11px label
floor; they take --size-micro too. Two film-number rows are re-anchored
on the .film-ord selector: the tray's line is now identical to it, and
the runner mutates the first match.

Contract: as_antislop S2. Falsifiers: antislop.toml 34/34 proved (S1+S2);
all 12 tables 314/314 proved on this tree.
2026-09-28 13:30:31 -07:00
vh 09071dcb65 fix(as-S1): the house clock — stamps read 0848, no IPs on the page
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.
2026-09-28 13:30:31 -07:00