feat(r2): C1 ordinals and C2 the review ring and .seen
- Item.ordinal: the 1-based position in booth_items over the items that render. It is appended, and set in the resolver. Tiles print it padded to the whole set's width, and a filter never renumbers. - review_chain: the item order filtered to media. It replaces image_chain as the zoom route's ring, so a set of pictures and sound steps through both. image_chain stays importable. - .seen: which media items were looked at full size, written by the review route under record_view's gate. It is rewritten whole: deduplicated, pruned to live items, sorted. The temp file is created with O_EXCL and swapped in with os.replace, so a planted symlink is replaced, never written through. It never raises. Nine new tests. The contiguity and symlink tests are mutation-checked. 669 passed.
This commit is contained in:
@@ -95,6 +95,27 @@ class Item:
|
||||
blurred: bool
|
||||
doc: str | None
|
||||
size: int
|
||||
# R2 C1: the 1-based position in `booth_items` order over ALL items — the
|
||||
# number the operator means by "the third one". Set in the resolver loop
|
||||
# and nowhere else (INV-1). APPENDED, never inserted: a mid-dataclass field
|
||||
# is a positional-construction break.
|
||||
ordinal: int
|
||||
|
||||
|
||||
# R2 C2: which items have been looked at full size. UI state, not judgment —
|
||||
# never exposed to sessions, holds nothing. One viewer: this records WHAT was
|
||||
# seen, never who saw it.
|
||||
SEEN_FILE = ".seen"
|
||||
|
||||
|
||||
def read_seen(booth: Path) -> set[str]:
|
||||
"""Rels seen at full size. Missing or unreadable file -> empty set; a
|
||||
damaged marker costs the tape its memory, never the page."""
|
||||
try:
|
||||
text = (booth / SEEN_FILE).read_text()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return set()
|
||||
return {ln.strip() for ln in text.splitlines() if ln.strip()}
|
||||
|
||||
|
||||
def read_blurred(booth: Path) -> set[str]:
|
||||
@@ -273,6 +294,10 @@ def booth_items(booth: Path) -> list[Item]:
|
||||
blurred=rel in blurred,
|
||||
doc=doc_kind(p.name),
|
||||
size=size,
|
||||
# Counted over items that RENDER: a caption sidecar or a name
|
||||
# the quote() guard skipped takes no number, so the numbers
|
||||
# stay contiguous over what the operator can see.
|
||||
ordinal=len(items) + 1,
|
||||
)
|
||||
)
|
||||
return items
|
||||
@@ -287,6 +312,18 @@ def image_chain(items: Sequence[Item]) -> list[str]:
|
||||
return [it.rel for it in items if it.kind == "image"]
|
||||
|
||||
|
||||
# R2 C2: what the review route steps through. ONE LINE: the item order
|
||||
# filtered to media. It is a declared change to the zoom-ring rule, which was
|
||||
# images only: a listening set is reviewed the same way a picture set is.
|
||||
REVIEW_KINDS = ("image", "video", "audio")
|
||||
|
||||
|
||||
def review_chain(items: Sequence[Item]) -> list[str]:
|
||||
"""The rels of the media items, in item order — the review's prev/next ring,
|
||||
its filmstrip and its tape."""
|
||||
return [it.rel for it in items if it.kind in REVIEW_KINDS]
|
||||
|
||||
|
||||
def find_item(items: Sequence[Item], rel: str) -> Item | None:
|
||||
"""The record for one rel, or None — the zoom/doc route's entry point."""
|
||||
for it in items:
|
||||
|
||||
Reference in New Issue
Block a user