Zum Inhalt

Keep Only Pinned — Design

Date: 2026-06-18 Status: Approved (design) Scope: A bulk "keep only pinned" action that prunes the 24-slot store down to just the pinned presets, compacted to the front. Follow-up to the preset-slots feature (T9–T11). Engine untouched.

Motivation

After the slot/iCloud sign-off, the user wants to declutter to just the presets they pinned — "keep only the ones I pinned." Today the only bulk action is Reset all → factory; clearing the ~20 non-pinned slots by hand (per-slot Clear context menu) is tedious and non-repeatable.

Behavior

Keep only pinned (compacting): 1. Collect the pinned filled presets in slot order (this is rotation when pins are non-empty), preserving each slot's userOwned flag. 2. Empty all 24 slots; refill slot-00 … slot-(k-1) with the kept presets (each preset's id is rewritten to its new slot id, as fill always does); higher slots stay empty. 3. Pins are preserved, remapped to the new slot ids. So all kept slots are pinned → the ATV rotation (all pinned → all filled) is exactly the kept set. The user can curate a narrower ATV subset afterward by un-pinning (un-pinning never deletes). 4. No-op when nothing is pinned (guarded in the model AND the button is disabled) — otherwise it would wipe every slot.

Selection: the slot ids shift, so the prior selectedID is stale → re-select the first filled slot (slot-00) after compacting.

Thumbnails: clear every slot's local PNG first (slot ids are positional/reused). Factory-derived kept presets resolve their curated bundled art immediately via the stable thumbnail key; user-saved kept presets re-render via the existing backfill on the next library open (brief placeholder). No orphaned PNGs (consistent with resetAllToFactory).

Sync: the new slot layout persists through the backend and propagates via iCloud-KVS to the other devices, whose selection-reconcile + ATV rotation follow automatically.

Components

  • PresetSlots.keepOnlyPinned() (pure, host-tested): the compaction + pin-remap + no-op guard.
  • SlotStore.keepOnlyPinned(): calls the model + persists.
  • AppState.keepOnlyPinned(): clears slot thumbnails, calls the store, re-selects slot-00.
  • Library overflow menu: the two destructive bulk actions consolidate into a ••• menu in the topbar — "Keep only pinned" (disabled when nothing is pinned) and "Reset all Presets" — each behind its existing/own confirmation alert. Replaces the standalone Reset (↺) button to keep the topbar uncluttered.

Testing

  • Host-unit (TDD): PresetSlots.keepOnlyPinned — compacts pinned to the front in order, empties the rest, remaps pins to the new ids, preserves userOwned, and is a no-op when nothing is pinned. AppState.keepOnlyPinned — re-selects slot-00, no-op guard. SlotStore persists.
  • Build-verified: the library overflow menu on iOS/tvOS/macOS.

Non-Goals

  • No per-preset "restore from factory" (Reset all remains the factory path).
  • No thumbnail re-keying on move (backfill re-renders; accepted, consistent with migration).

Self-Review

Slot model + ids reuse the existing slot-NN convention; pins/rotation semantics unchanged (all pinned → all filled). The no-op-when-nothing-pinned guard is explicit (model + UI). Scope is a single small action; no decomposition needed.