docs(contract): r2 — fold the heid contract panel (round "Lark", 4/4 arms)
Triaged, not adopted wholesale. Folded: - Reviewing refreshes .viewed, as it already did. It is now stated, so the two clocks cannot read as disagreeing. - INV-4 is scoped to pre-R2 request shapes. back=view is the declared exception. - back=view lands on the review only for media items. Anything else falls back to the booth page. - In-place regions: every element whose content can depend on marks is a region, including the rail counts, the filmstrip and the tape. The stage never is. - The script never re-POSTs. A lost response must not duplicate a note or re-date an answer. - The dangling "invariant 5" now points at the Booth's CLAUDE.md invariant 5. - "M" is defined once. Needs-you is picks only. Every key is suppressed in editable fields. - The toggle and the narrow collapse are classified against INV-3. - Every Booth state file is a dotfile, stated. So are "no generated thumbnails" and the audio placeholder. - The requirement wording is tightened, and C7 records the voice and emblem rulings.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
contract_version: "0.1-PROPOSED"
|
contract_version: "0.1-PROPOSED"
|
||||||
status: "PROPOSED 2026-09-23 by design-dev. Ruled by the operator the same day in the `flow` mark on booth-flow-concepts (direction a_b, compare this_arc, voice plain, emblem no), relayed via Miranda → booth-dev, verbatim at docs/rulings/. Compare mode is NOT in this contract: it lands after this one as r3, as a view toggle over the same item record."
|
status: "PROPOSED 2026-09-23 by design-dev. Ruled by the operator the same day in the `flow` mark on booth-flow-concepts (direction a_b; compare MODE to be built in this arc; voice plain; emblem no), relayed via Miranda → booth-dev, verbatim at docs/rulings/. Compare mode is NOT in this contract: it lands after this one as r3, as a view toggle over the same item record."
|
||||||
module: "booth.app + booth.items + templates (the review flow)"
|
module: "booth.app + booth.items + templates (the review flow)"
|
||||||
purpose: "Make the Booth a place where judgment happens rather than a place where files are shown. The operator's bar is 'did anything change when I opened it'. A reskin cannot clear that bar; this contract changes the flow. There are three surfaces and one plumbing change. THE DESK: the index triaged by what needs the operator. THE LIGHTBOX: a booth page with the set on the left and the verdict beside it. THE REVIEW: full size with the judgment on screen, a filmstrip, and seen-tracking. The plumbing is IN-PLACE JUDGMENT: a mark POST that does not reload the page or eject you from full size."
|
purpose: "Make the Booth a place where judgment happens rather than a place where files are shown. The operator's bar is 'did anything change when I opened it'. A reskin cannot clear that bar; this contract changes the flow. There are three surfaces and one plumbing change. THE DESK: the index triaged by what needs the operator. THE LIGHTBOX: a booth page with the set on the left and the verdict beside it. THE REVIEW: full size with the judgment on screen, a filmstrip, and seen-tracking. The plumbing is IN-PLACE JUDGMENT: a mark POST that does not reload the page or eject you from full size."
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -31,7 +31,7 @@ touches:
|
|||||||
- "booth/templates/base.html (layout CSS; the in-place script)"
|
- "booth/templates/base.html (layout CSS; the in-place script)"
|
||||||
- "booth/templates/doc.html (NOT restructured — a doc keeps its reading page; named because it was checked)"
|
- "booth/templates/doc.html (NOT restructured — a doc keeps its reading page; named because it was checked)"
|
||||||
- "booth/static/embed.js (NOT TOUCHED — the verbatim path keeps its author's layout; requirement 6)"
|
- "booth/static/embed.js (NOT TOUCHED — the verbatim path keeps its author's layout; requirement 6)"
|
||||||
- "tests/test_booth.py (FOUR assertions change: the kept-lane pair at L785-786 and L810-811. See 'Assertions that change')"
|
- "tests/test_booth.py (TWO assertions change: L785-786, the kept-lane presence pair. L810-811, the absence pair, survive unchanged. See 'Assertions that change')"
|
||||||
- "tests/test_flow.py (NEW)"
|
- "tests/test_flow.py (NEW)"
|
||||||
assumptions:
|
assumptions:
|
||||||
- "ONE VIEWER. `.seen` records what has been seen at full size, not WHO saw it. ROADMAP parks 'per-viewer state (who has seen what)' on the one-viewer premise; this contract keeps that premise and does not reopen the parked item."
|
- "ONE VIEWER. `.seen` records what has been seen at full size, not WHO saw it. ROADMAP parks 'per-viewer state (who has seen what)' on the one-viewer premise; this contract keeps that premise and does not reopen the parked item."
|
||||||
@@ -52,11 +52,23 @@ open_questions:
|
|||||||
| 1 | show me what needs me | the Desk's *needs you* section |
|
| 1 | show me what needs me | the Desk's *needs you* section |
|
||||||
| 2 | picking winners is the main judgment | the lightbox's flag tray; F in the review |
|
| 2 | picking winners is the main judgment | the lightbox's flag tray; F in the review |
|
||||||
| 3 | flag without losing my place | in-place judgment + `back=view` |
|
| 3 | flag without losing my place | in-place judgment + `back=view` |
|
||||||
| 4 | position is identity | `Item.ordinal`, printed on every tile |
|
| 4 | position is identity (within the set as it is now — not a durable id) | `Item.ordinal`, printed on every tile |
|
||||||
| 5 | the question stays beside the work | the verdict aside (sticky) |
|
| 5 | the question stays beside the work | the verdict aside (sticky) |
|
||||||
| 6 | reports keep their author's layout | verbatim path untouched |
|
| 6 | reports keep their author's layout | verbatim path untouched |
|
||||||
| 7 | listening sets are real | `review_chain` includes audio and video |
|
| 7 | listening sets are real | `review_chain` includes audio and video |
|
||||||
| 8 | lifetime is not an organising principle | the Desk drops the kept/ephemeral lanes |
|
| 8 | lifetime is not an organising principle (it is still SHOWN as a fact on each row; it no longer GROUPS or SORTS) | the Desk drops the kept/ephemeral lanes |
|
||||||
|
|
||||||
|
## Terms used below
|
||||||
|
|
||||||
|
- **Reticle**: the SVOS selection mark in base.html — four corner brackets drawn
|
||||||
|
inside a box. It marks the one current or selected thing and nothing else.
|
||||||
|
- **Tape**: a row of small segments, one per item in the review ring, each
|
||||||
|
showing *seen*, *flagged* or *current*.
|
||||||
|
- **Stage**: the area of the review page where the artifact itself renders.
|
||||||
|
- **All Booth state files are dotfiles.** That covers `.marks.json`
|
||||||
|
(MARKS_FILE), `.viewed`, `.blurred`, `.seen`, `.forever`, `.pins`,
|
||||||
|
`.booth.json` and every `*.lock`. "Non-dot entries" means the posted content
|
||||||
|
and nothing the Booth or the operator wrote.
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
@@ -92,7 +104,19 @@ no route derives it.
|
|||||||
- **`SEEN_FILE = ".seen"`**: one rel per line, same shape as `.blurred`.
|
- **`SEEN_FILE = ".seen"`**: one rel per line, same shape as `.blurred`.
|
||||||
- Written by `record_seen(booth, rel)` from the review route, below the 404s
|
- Written by `record_seen(booth, rel)` from the review route, below the 404s
|
||||||
and gated on the item record — the same gate `record_view` has.
|
and gated on the item record — the same gate `record_view` has.
|
||||||
- Atomic replace, per invariant 5.
|
- Each write rewrites the whole file: the previous set plus `rel`, minus
|
||||||
|
rels no longer in `booth_items`, sorted. It is deduplicated and pruned, so
|
||||||
|
it never grows past the booth's item count.
|
||||||
|
- Atomic replace, per the Booth's CLAUDE.md invariant 5 ("sidecar writes are
|
||||||
|
atomic"), not this contract's INV-5.
|
||||||
|
- Seen is keyed by rel. A file replaced at the same path stays seen; a
|
||||||
|
deleted file drops out at the next write, and every count below
|
||||||
|
intersects with the current `review_chain`.
|
||||||
|
- **The review route ALSO calls `record_view` (existing U4 behaviour,
|
||||||
|
unchanged).** So reviewing a booth at full size refreshes "you looked" for
|
||||||
|
the Desk exactly as opening its grid does. `.viewed` and `.seen` never
|
||||||
|
disagree about whether you looked at the booth; `.seen` only adds WHICH
|
||||||
|
items.
|
||||||
- NEVER RAISES, like `record_view`: failing to record a look costs the
|
- NEVER RAISES, like `record_view`: failing to record a look costs the
|
||||||
marker, not the page.
|
marker, not the page.
|
||||||
- `read_seen(booth) -> set[str]` is lenient, like `read_blurred`.
|
- `read_seen(booth) -> set[str]` is lenient, like `read_blurred`.
|
||||||
@@ -124,8 +148,11 @@ route's forms together with `f=<rel>`. It lands on
|
|||||||
`/b/<name>/view?f=<quote(rel)>#rail`. This fixes the JS-off bounce too:
|
`/b/<name>/view?f=<quote(rel)>#rail`. This fixes the JS-off bounce too:
|
||||||
today's zoom flag form carries no `back`, so it lands on the gallery.
|
today's zoom flag form carries no `back`, so it lands on the gallery.
|
||||||
|
|
||||||
- `f` must name an item in the booth, else the landing falls back to the
|
- `back=view` lands on the review only when `f` names an item in
|
||||||
booth page.
|
`review_chain`, i.e. a media item. For anything else (a doc, a missing rel,
|
||||||
|
an empty `f`) the landing falls back to the booth page, exactly as a form
|
||||||
|
with no `back` does today. `doc.html` carries no forms, so no shipped page
|
||||||
|
sends `back=view` with a doc.
|
||||||
- The URL is built server-side from `name` + `quote(f)`, never echoed, so this
|
- The URL is built server-side from `name` + `quote(f)`, never echoed, so this
|
||||||
is not an open redirect.
|
is not an open redirect.
|
||||||
|
|
||||||
@@ -133,16 +160,30 @@ today's zoom flag form carries no `back`, so it lands on the gallery.
|
|||||||
`data-inplace`.
|
`data-inplace`.
|
||||||
|
|
||||||
1. POST the form with `Accept: application/json`.
|
1. POST the form with `Accept: application/json`.
|
||||||
2. On 204, GET the current URL and replace the regions marked
|
2. On 204, GET the current URL and replace **every** element carrying
|
||||||
`data-region="<id>"` with the same-id regions from the response.
|
`data-region="<id>"` with the same-id element from the response.
|
||||||
3. On anything else, submit the form normally.
|
- The rule is "every region whose content can depend on marks is a
|
||||||
|
region". On the lightbox that means the verdict aside, each tile, and the
|
||||||
|
rail (its filter counts change when you flag). On the review it means the
|
||||||
|
rail, the filmstrip and the tape.
|
||||||
|
- The stage is never a region: replacing it would restart a playing video
|
||||||
|
or audio track.
|
||||||
|
- A region absent from the response is left alone and never deleted.
|
||||||
|
3. **The script never re-POSTs.** A retry after a lost response would re-apply
|
||||||
|
the judgment: a duplicate note, or a re-dated answer.
|
||||||
|
- On a non-204 HTTP response, or a network failure, it writes a fixed
|
||||||
|
message into the page's server-rendered status element
|
||||||
|
(`data-region="status"`, via textContent), then reloads the page with a
|
||||||
|
GET, so what you see is the server's truth.
|
||||||
|
- The one case where a non-JS submit happens is a script that cannot run at
|
||||||
|
all. That is the plain form.
|
||||||
|
|
||||||
**The server renders every state; the script only places it.** This is U3's
|
**The server renders every state; the script only places it.** This is U3's
|
||||||
rule — a second renderer in JavaScript would be the same bug in a new language.
|
rule — a second renderer in JavaScript would be the same bug in a new language.
|
||||||
|
|
||||||
### C4 — the Desk (index.html, app.index, list_booths)
|
### C4 — the Desk (index.html, app.index, list_booths)
|
||||||
|
|
||||||
`list_booths` gains four fields, all read in the one pass it already makes:
|
`list_booths` gains five fields, all read in the one pass it already makes:
|
||||||
|
|
||||||
- **`open_since`**: the `created` of the OLDEST open pick in the booth, or
|
- **`open_since`**: the `created` of the OLDEST open pick in the booth, or
|
||||||
None. Computed via `open_marks`, INV-2.
|
None. Computed via `open_marks`, INV-2.
|
||||||
@@ -151,26 +192,36 @@ rule — a second renderer in JavaScript would be the same bug in a new language
|
|||||||
legacy-import stamp, sort wrong as text.
|
legacy-import stamp, sort wrong as text.
|
||||||
- An unparseable stamp sorts AFTER every parseable one, and name breaks the
|
- An unparseable stamp sorts AFTER every parseable one, and name breaks the
|
||||||
tie.
|
tie.
|
||||||
- **`flags`**: the count of flag marks.
|
- **`flags`**: the count of flag marks, shown on every Desk row that has any.
|
||||||
- **`landed_at`**: the newest mtime among the booth's NON-DOT entries — its
|
- **`landed_at`**: the newest mtime among the booth's NON-DOT entries — its
|
||||||
content. **Deliberately not `_newest_mtime`** (INV-5).
|
content. **Deliberately not `_newest_mtime`** (INV-5).
|
||||||
- **`viewed_at`**: the mtime of `.viewed`, or None.
|
- **`viewed_at`**: the mtime of `.viewed`, or None.
|
||||||
- **`preview`**: up to 4 image items as `(url, blurred)`, first four in item
|
- **`preview`**: up to 4 image items as `(url, blurred)`, first four in item
|
||||||
order. A blurred one renders blurred, the same rule as the cover.
|
order. A blurred one renders blurred, the same rule as the cover.
|
||||||
|
- A booth with no images (an audio set, a report) shows today's kind
|
||||||
|
placeholder instead (`♪ audio`, `▦ page`, `▶ video`, `◆ files`).
|
||||||
|
- These are the original files displayed small with `loading=lazy`. No
|
||||||
|
thumbnail is GENERATED anywhere in R2; see Out of scope.
|
||||||
|
|
||||||
**The index renders three sections, always in this order:**
|
**The index renders three sections, always in this order:**
|
||||||
|
|
||||||
1. **Needs you** — `marks_open > 0`, **or** `hold == "unreadable"`.
|
1. **Needs you** — `marks_open > 0`, **or** `hold == "unreadable"`.
|
||||||
|
- `marks_open` counts `open_marks(...)`, which only ever returns PICKS. A
|
||||||
|
booth whose marks are only flags or notes is the operator's own judgment,
|
||||||
|
not a question to them, so it is NOT here.
|
||||||
- A damaged `.marks.json` holds its booth but is not open by `open_marks`
|
- A damaged `.marks.json` holds its booth but is not open by `open_marks`
|
||||||
(errored picks are not open). Somebody has to fix it, so it must not hide
|
(errored picks are not open). Somebody has to fix it, so it must not hide
|
||||||
in 'everything else'. It renders with the existing "marks unreadable"
|
in 'everything else'. It renders with the existing "marks unreadable"
|
||||||
lifetime line.
|
lifetime line.
|
||||||
- Ordered by `(open_since, name)`, oldest question first. Unreadable booths
|
- Ordered by `(open_since, name)`, oldest question first. Unreadable booths
|
||||||
have no `open_since` and sort after every booth that has one.
|
have no `open_since` and sort after every booth that has one.
|
||||||
2. **New since you looked** — not in (1), and `viewed_at is None` or
|
2. **New since you looked** — `not in_needs_you and (viewed_at is None or
|
||||||
`landed_at > viewed_at`. Ordered by `(-landed_at, name)`, newest first.
|
landed_at > viewed_at)`. Ordered by `(-landed_at, name)`, newest first.
|
||||||
3. **Everything else** — ordered by `(-mtime, name)`, where `mtime` is today's
|
3. **Everything else** — ordered by `(-mtime, name)`, where `mtime` is today's
|
||||||
`_newest_mtime`: last activity first.
|
`_newest_mtime`: last activity first.
|
||||||
|
- Flagging or viewing a booth moves it up this section. That is intended:
|
||||||
|
it is activity. It never moves the booth into (2), because (2) reads
|
||||||
|
`landed_at` (INV-5).
|
||||||
|
|
||||||
The side column holds:
|
The side column holds:
|
||||||
|
|
||||||
@@ -202,7 +253,7 @@ unchanged) remain on every row.
|
|||||||
- **Layout.** Two panes on a gallery booth: the set on the left, the
|
- **Layout.** Two panes on a gallery booth: the set on the left, the
|
||||||
**verdict aside** on the right (`position:sticky`, `data-region="verdict"`).
|
**verdict aside** on the right (`position:sticky`, `data-region="verdict"`).
|
||||||
Under 1000px the aside stacks above the set, with its flags and notes
|
Under 1000px the aside stacks above the set, with its flags and notes
|
||||||
collapsed.
|
collapsed as `<details>`, which needs no script.
|
||||||
- **Board booths are unchanged.** Anything with `links.md` keeps today's
|
- **Board booths are unchanged.** Anything with `links.md` keeps today's
|
||||||
single column.
|
single column.
|
||||||
- **The aside holds, top to bottom:**
|
- **The aside holds, top to bottom:**
|
||||||
@@ -211,7 +262,9 @@ unchanged) remain on every row.
|
|||||||
3. notes;
|
3. notes;
|
||||||
4. the booth-note form.
|
4. the booth-note form.
|
||||||
- **The flag tray is ordered by ORDINAL** — a declared change from the marks
|
- **The flag tray is ordered by ORDINAL** — a declared change from the marks
|
||||||
panel's `(created, id)`. It shows each flagged tile's thumbnail and its #.
|
panel's `(created, id)`. It shows each flagged item's original file
|
||||||
|
displayed small (no generated thumbnail), blurred if the item is blurred,
|
||||||
|
with its #.
|
||||||
The order is total with no tie-break, because rels are unique.
|
The order is total with no tie-break, because rels are unique.
|
||||||
- **The rail stays.** Same element, same `.rail` class (booth.html's cursor
|
- **The rail stays.** Same element, same `.rail` class (booth.html's cursor
|
||||||
and base.html's `--rail-h` script both read it), same filter hrefs, same
|
and base.html's `--rail-h` script both read it), same filter hrefs, same
|
||||||
@@ -230,11 +283,19 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
- **The stage**: the artifact at fit size, with a 1:1 toggle for images ONLY.
|
- **The stage**: the artifact at fit size, with a 1:1 toggle for images ONLY.
|
||||||
- The toggle and its script are rendered and bound only when the stage is
|
- The toggle and its script are rendered and bound only when the stage is
|
||||||
an `<img>`.
|
an `<img>`.
|
||||||
|
- The toggle is a JS-only VIEWING convenience, as it is today: the button
|
||||||
|
starts hidden and the script shows it. With scripts off the image shows at
|
||||||
|
fit size, and no judgment depends on the toggle (INV-3).
|
||||||
- Video and audio get their native controls and no toggle. A toggle that
|
- Video and audio get their native controls and no toggle. A toggle that
|
||||||
renders on audio and silently no-ops (today's script binds
|
renders on audio and silently no-ops (today's script binds
|
||||||
`getElementById('vimg')`) is the failure this names.
|
`getElementById('vimg')`) is the failure this names.
|
||||||
- **The rail** (`data-region="rail"`) holds:
|
- **The rail** (`data-region="rail"`) holds:
|
||||||
- `#NN of M`, and position within the group;
|
- the item's ordinal `#NN` (its number in the whole set, the same number
|
||||||
|
its tile shows);
|
||||||
|
- `K of M`, where K is its position in `review_chain` and M is the length
|
||||||
|
of `review_chain`. The tape's "N of M seen" uses the SAME M, and N counts
|
||||||
|
`.seen` ∩ `review_chain`;
|
||||||
|
- its position within its group, when the booth has groups;
|
||||||
- the caption;
|
- the caption;
|
||||||
- the flag form (`back=view`);
|
- the flag form (`back=view`);
|
||||||
- notes and the add-note form (`back=view`);
|
- notes and the add-note form (`back=view`);
|
||||||
@@ -247,7 +308,9 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
- **The end of the set** is not a separate page. On the last ring item the
|
- **The end of the set** is not a separate page. On the last ring item the
|
||||||
rail adds a summary block: seen count, flag tray, and every open booth-level
|
rail adds a summary block: seen count, flag tray, and every open booth-level
|
||||||
pick answerable in place.
|
pick answerable in place.
|
||||||
- **Keys** (additive; editable targets keep their keys, as today):
|
- **Keys** (additive). **Every** key here, new and old, is ignored while focus
|
||||||
|
is in an `input`, `textarea`, `select` or `contenteditable`, the same
|
||||||
|
`isEditable` guard view.html carries today, so F never fires mid-note:
|
||||||
|
|
||||||
| key | action |
|
| key | action |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -256,6 +319,14 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
| N | focus the note |
|
| N | focus the note |
|
||||||
| Esc | back to the grid, at `#item-<url>` so the grid scrolls to where you were |
|
| Esc | back to the grid, at `#item-<url>` so the grid scrolls to where you were |
|
||||||
|
|
||||||
|
### C7 — copy and brand (the two rulings that are not layout)
|
||||||
|
|
||||||
|
- **Voice: plain and direct** (ruling `voice=plain`). Every NEW string R2
|
||||||
|
introduces says what it means, with no villainy and no jokes. Existing
|
||||||
|
strings are unchanged unless their surface is rewritten.
|
||||||
|
- **No SVS emblem** anywhere in the Booth's chrome (ruling `emblem=no`). The
|
||||||
|
brand dot and the reticle favicon from the SVOS retheme stay.
|
||||||
|
|
||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
- **INV-1 — one resolver.** `ordinal` is set in `booth_items`. No route computes
|
- **INV-1 — one resolver.** `ordinal` is set in `booth_items`. No route computes
|
||||||
@@ -277,11 +348,20 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
|
|
||||||
The notes list keeps `(created, id)`.
|
The notes list keeps `(created, id)`.
|
||||||
- **INV-3 — JS-off parity.** Every judgment, filter and jump works with
|
- **INV-3 — JS-off parity.** Every judgment, filter and jump works with
|
||||||
scripts disabled. The only JS-only affordances are the keys and the in-place
|
scripts disabled. The only JS-only affordances are:
|
||||||
swap.
|
- the keys;
|
||||||
- **INV-4 — 303 byte-identity.** For a request where `wants_json` is False,
|
- the in-place swap;
|
||||||
each mark route's response (status, headers, body) is byte-identical to its
|
- the image 1:1 toggle (a viewing convenience, unchanged from today);
|
||||||
pre-R2 response. This includes the two existing `back` landings.
|
- the existing copy buttons and blur reveal.
|
||||||
|
|
||||||
|
The narrow-screen collapse is `<details>` and needs no script.
|
||||||
|
- **INV-4 — 303 byte-identity, for every request shape that existed before
|
||||||
|
R2.**
|
||||||
|
- For a request where `wants_json` is False and `back` is absent or
|
||||||
|
`marks`, each mark route's response (status, headers, body) is
|
||||||
|
byte-identical to its pre-R2 response.
|
||||||
|
- `back=view` is a NEW request shape with no pre-R2 counterpart. Its landing
|
||||||
|
is specified in C3 and is the one declared exception.
|
||||||
- **INV-5 — two named clocks.**
|
- **INV-5 — two named clocks.**
|
||||||
- `mtime` / `_newest_mtime`: activity. It includes dotfiles and excludes
|
- `mtime` / `_newest_mtime`: activity. It includes dotfiles and excludes
|
||||||
locks, and it feeds lifetime and 'everything else'.
|
locks, and it feeds lifetime and 'everything else'.
|
||||||
@@ -296,7 +376,7 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
escaping path as the grid.
|
escaping path as the grid.
|
||||||
- **INV-8 — blur honesty.** A blurred item stays blurred on every new surface:
|
- **INV-8 — blur honesty.** A blurred item stays blurred on every new surface:
|
||||||
the Desk preview strip, the flag tray, the filmstrip and the review stage.
|
the Desk preview strip, the flag tray, the filmstrip and the review stage.
|
||||||
Reveal stays per-viewer and client-side. Copy keeps admitting it is cosmetic.
|
Reveal stays per-BROWSER and client-side (nothing persisted). Copy keeps admitting it is cosmetic.
|
||||||
|
|
||||||
## Assertions that change (declared before the code, per CLAUDE.md)
|
## Assertions that change (declared before the code, per CLAUDE.md)
|
||||||
|
|
||||||
@@ -304,7 +384,7 @@ This applies to image, video and audio items. Docs keep `doc.html`.
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| test_booth.py L785 | `class="grid kept-grid"` present when a booth is kept | absent; the kept booth appears in its Desk section with the `kept` lifetime line | requirement 8: the lanes sort nothing |
|
| test_booth.py L785 | `class="grid kept-grid"` present when a booth is kept | absent; the kept booth appears in its Desk section with the `kept` lifetime line | requirement 8: the lanes sort nothing |
|
||||||
| test_booth.py L786 | `class="card card-kept"` present | replaced by the row carrying `data-kept="1"` | same |
|
| test_booth.py L786 | `class="card card-kept"` present | replaced by the row carrying `data-kept="1"` | same |
|
||||||
| test_booth.py L810-811 | lane absent when nothing is kept | unchanged in spirit (no lane), and trivially true | same |
|
| test_booth.py L810-811 | lane absent when nothing is kept | these two SURVIVE unchanged (they assert absence and stay true) | — |
|
||||||
|
|
||||||
Every other existing assertion is expected to survive, and one of the TDD
|
Every other existing assertion is expected to survive, and one of the TDD
|
||||||
slices is "the whole suite green before any new test". Named because they were
|
slices is "the whole suite green before any new test". Named because they were
|
||||||
@@ -321,5 +401,6 @@ checked:
|
|||||||
- Thumbnails.
|
- Thumbnails.
|
||||||
- 1–9 answer keys.
|
- 1–9 answer keys.
|
||||||
- Lifetime policy for answered picks.
|
- Lifetime policy for answered picks.
|
||||||
- The verbatim path.
|
- The verbatim path. A verbatim booth's media items remain reachable at
|
||||||
|
`view?f=` by URL, as today, and nothing in the verbatim page links there.
|
||||||
- The link-board page (`/b/links/`) beyond CSS.
|
- The link-board page (`/b/links/`) beyond CSS.
|
||||||
|
|||||||
Reference in New Issue
Block a user