feat(desk): the Desk row, booth dates, and the theme toggle (r2b merge 2: D1 + D1b + D3)
Operator rulings, 2026-09-23. D1, the Desk row: - Kept vs ephemeral reads at a glance: an always-visible lifetime pill in the right column (sage ★ kept, amber held, ◷ counting down). - The facts line is facts only. - zip / keep|release / wipe are one cluster, with zip out of the middle. Where a real hover exists it floats over the preview strip (covering pictures, never information), appears on hover or keyboard focus, and takes no room. Anywhere else (touch, any coarse pointer) it is the row's last line, visible, with 32px controls. × hides too (the operator answered yes). D1b: "created 12 Sep" (filesystem birth time; nothing when unknown) and "updated 5d ago" (the content clock), as <time> facts on the row and in the booth header, from one macro and one clock per page. D3, the theme toggle: System · Light · Dark in the top bar. - Stored in localStorage and applied in <head> before any stylesheet. - System removes data-theme, so the OS query follows the OS live, with no listener. - The token sheet is re-vendored at the same SVOS SHA with a scoping-only transform (155 declarations, the same set, both directions), so forced themes win over the OS and high contrast follows the theme in effect. - The ask chrome inside verbatim pages follows the choice through data-bk-theme on our own fragments, live across tabs. The host page's <html> is never touched. Declared test changes: - two row tests replaced; - the wipe-dialog test hovers first; - four r2_flow rows retired, with successors in r2b.toml (45/45). 785 passed.
This commit is contained in:
@@ -11,6 +11,11 @@
|
||||
# covers them together); and `flagged_targets`' `error is None`, since
|
||||
# hydration already strips the target from a damaged mark.
|
||||
#
|
||||
# RETIRED (r2b D1, 2026-09-23): four rows proving the facts-line row controls
|
||||
# (visible at rest, compact, on the facts line) — the operator ruled those
|
||||
# controls hover-revealed over the preview strip, and their tests were replaced
|
||||
# as declared in r2b's contract. Their successors are in r2b.toml.
|
||||
#
|
||||
# The serialization row is only a falsifier because its test HOLDS the first
|
||||
# refresh in the client: localhost alone never lost the race, and the first
|
||||
# draft of that test stayed green with serialization deleted.
|
||||
@@ -159,26 +164,6 @@ new = '''
|
||||
except FileNotFoundError:
|
||||
continue'''
|
||||
|
||||
[[mutation]]
|
||||
label = "Desk row controls hidden until hover again (opacity 0 keeps their box)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_a_rows_keep_release_and_wipe_take_no_room_of_their_own"
|
||||
old = '''
|
||||
.desk-facts form{display:inline;margin:0}'''
|
||||
new = '''
|
||||
.desk-facts form{display:inline;margin:0;opacity:0}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "Desk row renders its side column with no badge in it (a reserved gap)"
|
||||
file = "booth/templates/index.html"
|
||||
test = "tests/test_flow_browser.py::test_a_rows_keep_release_and_wipe_take_no_room_of_their_own"
|
||||
old = '''
|
||||
{% if b.marks_open or b.hold == "unreadable" or section == 'new' or b.uploaded %}
|
||||
<div class="desk-side">'''
|
||||
new = '''
|
||||
{% if true %}
|
||||
<div class="desk-side">'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the stacked Desk column is a bare 1fr (content sets its minimum)"
|
||||
file = "booth/templates/base.html"
|
||||
@@ -206,24 +191,6 @@ old = '''
|
||||
new = '''
|
||||
.desk-main{flex:1 1 auto}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "a coarse pointer gets the compact ~21px controls"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_on_a_touch_screen_the_row_controls_keep_their_tap_floor"
|
||||
old = '''
|
||||
.desk-facts form button{min-height:32px;min-width:32px;padding:0 10px;margin-left:6px;vertical-align:middle}'''
|
||||
new = '''
|
||||
.desk-facts form button{padding:0 10px;margin-left:6px;vertical-align:middle}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the zip link may break between its glyph and its word"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_on_a_touch_screen_the_row_controls_keep_their_tap_floor"
|
||||
old = '''
|
||||
.desk-facts .dl-link{white-space:nowrap}'''
|
||||
new = '''
|
||||
.desk-facts .dl-link{}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "the wipe dialog shows the agent-made name raw (bidi, newline)"
|
||||
file = "booth/templates/index.html"
|
||||
|
||||
@@ -282,3 +282,165 @@ old = '''
|
||||
b = pw.chromium.launch(args=OFFLINE)'''
|
||||
new = '''
|
||||
b = pw.chromium.launch()'''
|
||||
|
||||
# ---- merge 2: D1 the Desk row, D1b dates, D3 the theme toggle
|
||||
# Successors to the four r2_flow rows retired for D1. One branch is proved by
|
||||
# reading, not here: a true touch LAPTOP (fine pointer + a coarse one) cannot be
|
||||
# emulated — Chromium's touch emulation makes the primary pointer coarse.
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 the cluster is visible at rest"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
||||
old = '''
|
||||
opacity:0;pointer-events:none;transition:opacity var(--dur-1) var(--ease-out)}'''
|
||||
new = '''
|
||||
opacity:1;pointer-events:none;transition:opacity var(--dur-1) var(--ease-out)}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 hover shows the cluster but leaves it unclickable"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
||||
old = '''
|
||||
.desk-row:hover .desk-acts,.desk-row:focus-within .desk-acts{opacity:1;pointer-events:auto}'''
|
||||
new = '''
|
||||
.desk-row:hover .desk-acts,.desk-row:focus-within .desk-acts{opacity:1}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 the cluster stays in flow where a hover exists (it takes room)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_row_controls_take_no_room_where_a_hover_exists"
|
||||
old = '''
|
||||
.desk-acts{position:absolute;top:5px;right:5px;flex:none;gap:4px;padding:3px;'''
|
||||
new = '''
|
||||
.desk-acts{position:static;top:5px;right:5px;flex:none;gap:4px;padding:3px;'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 hover-only everywhere (no controls at all on touch)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_on_touch_the_row_controls_are_visible_in_flow_and_at_least_28px"
|
||||
old = '''
|
||||
.desk-acts{order:99;flex:1 0 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px}'''
|
||||
new = '''
|
||||
.desk-acts{order:99;flex:1 0 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px;opacity:0}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 touch controls fall below the 28px floor"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_on_touch_the_row_controls_are_visible_in_flow_and_at_least_28px"
|
||||
old = '''
|
||||
min-height:32px;min-width:32px;padding:0 10px;font-family:var(--font-mono);font-size:var(--size-caption);'''
|
||||
new = '''
|
||||
min-height:18px;min-width:18px;padding:0 10px;font-family:var(--font-mono);font-size:var(--size-caption);'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 zip back in the middle"
|
||||
file = "booth/templates/index.html"
|
||||
test = "tests/test_flow_browser.py::test_the_row_controls_run_zip_keep_or_release_then_wipe"
|
||||
old = '''
|
||||
<a class="dl-link" href="/b/{{ b.name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>
|
||||
{%- if b.kept %}
|
||||
<form class="release"'''
|
||||
new = '''
|
||||
{%- if b.kept %}
|
||||
<a class="dl-link" href="/b/{{ b.name_url }}/?download=1" title="download this booth as a zip">⬇ zip</a>
|
||||
<form class="release"'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1 a held booth's pill reads as counting down"
|
||||
file = "booth/templates/index.html"
|
||||
test = "tests/test_flow.py::test_the_lifetime_pill_class_is_kept_held_or_counting"
|
||||
old = '''('life-held' if b.hold in ('open', 'unreadable') else 'life-count')'''
|
||||
new = ''''life-count\''''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1b an unknown birth time renders a guess"
|
||||
file = "booth/templates/_dates.html"
|
||||
test = "tests/test_flow.py::test_created_and_updated_are_dated_facts_and_none_says_nothing"
|
||||
old = '''
|
||||
{%- if created_at %} · <time class="d-made" datetime="{{ created_at|iso }}" title="created {{ created_at|stamp }}">created {{ created_at|day(now) }}</time>{% endif -%}'''
|
||||
new = '''
|
||||
{%- set created_at = created_at or landed_at %}{%- if created_at %} · <time class="d-made" datetime="{{ created_at|iso }}" title="created {{ created_at|stamp }}">created {{ created_at|day(now) }}</time>{% endif -%}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D1b updated is measured from the wrong clock"
|
||||
file = "booth/templates/_dates.html"
|
||||
test = "tests/test_flow.py::test_created_and_updated_are_dated_facts_and_none_says_nothing"
|
||||
old = '''updated {{ (now - landed_at)|ago }}'''
|
||||
new = '''updated {{ (now - created_at)|ago }}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 a stored theme is not applied at load (a reload forgets it)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
||||
old = '''
|
||||
if (t === 'light' || t === 'dark') d.setAttribute('data-theme', t);'''
|
||||
new = '''
|
||||
if (false) d.setAttribute('data-theme', t);'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 System snapshots the OS instead of following it live"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
||||
old = '''
|
||||
if (c === 'system') d.removeAttribute('data-theme'); else d.setAttribute('data-theme', c);'''
|
||||
new = '''
|
||||
if (c === 'system') d.setAttribute('data-theme', matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'); else d.setAttribute('data-theme', c);'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 forced light is not in the sheet"
|
||||
file = "booth/templates/_svos_tokens.css"
|
||||
test = "tests/test_flow_browser.py::test_the_theme_toggle_forces_light_and_dark_and_system_follows_the_os_live"
|
||||
old = '''
|
||||
:root[data-theme="light"] {'''
|
||||
new = '''
|
||||
:root[data-theme="light-OFF"] {'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 the toggle shows without JS (display beats [hidden])"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_theme_toggle_never_shows_without_js_and_a_storage_failure_still_applies"
|
||||
old = '''
|
||||
.theme[hidden]{display:none}'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 a storage write that throws swallows the choice"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow_browser.py::test_the_theme_toggle_never_shows_without_js_and_a_storage_failure_still_applies"
|
||||
old = '''
|
||||
var c = b.getAttribute('data-theme-choice');
|
||||
if (c === 'system')'''
|
||||
new = '''
|
||||
var c = b.getAttribute('data-theme-choice');
|
||||
localStorage.setItem('booth.theme', c);
|
||||
if (c === 'system')'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 the stored theme is read after the stylesheet (a flash)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_flow.py::test_a_forced_theme_is_applied_before_first_paint"
|
||||
old = '''
|
||||
var t = localStorage.getItem('booth.theme');'''
|
||||
new = '''
|
||||
var t = localStorage.getItem('booth.' + 'theme');'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 the ask chrome in a verbatim page ignores the choice"
|
||||
file = "booth/static/embed.js"
|
||||
test = "tests/test_flow_browser.py::test_a_forced_theme_reaches_the_ask_chrome_inside_a_verbatim_page"
|
||||
old = '''
|
||||
bkTheme();
|
||||
reassociate();'''
|
||||
new = '''
|
||||
reassociate();'''
|
||||
|
||||
[[mutation]]
|
||||
label = "D3 the ask chrome does not follow a choice made in another tab"
|
||||
file = "booth/static/embed.js"
|
||||
test = "tests/test_flow_browser.py::test_a_forced_theme_reaches_the_ask_chrome_inside_a_verbatim_page"
|
||||
old = '''
|
||||
if (e.key === "booth.theme" || e.key === null) bkTheme();'''
|
||||
new = '''
|
||||
if (false) bkTheme();'''
|
||||
|
||||
Reference in New Issue
Block a user