The map to specs/. Each module spec is the source of truth for its area — this page is the
map, not the territory.
For what is still unfinished and in what order to close it, see
gap-closure-plan.md (Apple TV tracked separately in specs/1000-apple-tv/).
Specs are organized as one durable spec per module, mirroring the Swift packages. There is no chronological feature numbering anymore; a single concern lives in exactly one spec.
-
Hundreds-block per topic. Each module owns a
Nxxblock:100,200, …700. -
Sub-spec room.
N10,N20, … inside a block are reserved for sub-specs (a deferred or spun-off capability of that topic). Example:110-shared-album-linkis a sub-spec of the100data-access topic. -
Requirement IDs carry the full spec number:
FR-<specnum>-NNandSC-<specnum>-NN(e.g.FR-700-03,SC-200-05). This keeps a sub-spec's IDs from colliding with its parent block and makes any ID self-locating. -
Deferred capabilities are not deleted: each topic spec ends with a
Roadmap / Deferredsection, and substantial future work gets a reserved sub-spec number (and, where it already has a real outline, its ownStatus: Deferredspec). -
Supporting processes get a separated four-digit block. The
100–1200blocks are product modules that mirror Swift packages. Some durable concerns are not modules at all — they govern how the product is presented rather than what it does. Those live in a clearly separated 9000 block, far above the module range so the two can never be confused, and they use the sameN/N10/N20sub-spec convention scaled to four digits:9000-design-languageis the topic,9010-store-presentationits sub-spec. The requirement-ID rule is unchanged — they readFR-9000-NN/SC-9000-NNandFR-9010-NN/SC-9010-NN, so an ID still locates its own spec. Such a spec'sPackagecolumn names the surfaces it governs, not a package it owns.
When a genuinely new module appears, give it the next free hundreds-block. A new capability of an
existing module becomes a sub-spec (N10, N20, …) or amends the module spec directly.
| # | Spec | Package | Purpose | Status |
|---|---|---|---|---|
| 100 | immich-client | ImmichClient | REST data access: albums, album assets, preview/original/thumbnail image data, errors. | Active |
| 110 | shared-album-link | ImmichClient | (sub-spec of 100) Play a shared/public Immich link (+ optional password) as a source. | Active |
| 120 | source-library | ImmichClient | (sub-spec of 100) Save several switchable sources (albums + shared links); one active. | Active |
| 130 | immich-api-v3 | ImmichClient | (sub-spec of 100) v3-only API baseline: album assets via metadata search, shared-link assets via /me, shared-link password in body, outdated-server (v<3) notice. Drops v2. |
Active |
| 200 | connection-onboarding | OnboardingKit | First-run setup, in-place connection editing, and the Settings-screen structure. | Active |
| 210 | shared-link-onboarding | OnboardingKit | (sub-spec of 200) Choice-first onboarding, shared-link-only setup (no API key), iOS Share Sheet acceptance, resolve-first/password-when-needed, one searchable/subscrollable album picker shared by onboarding + Settings. | Active |
| 220 | onboarding-welcome | OnboardingKit | (sub-spec of 200) Welcome-screen overhaul: iCloud album at the top, camera QR scan for shared links, and three friction-ordered options with light decoration; reuses the 900 photoLibrary source. Camera end-to-end is a device gate. | Merged to main (2026-07-18); camera QR end-to-end (SC-220-07) still a device gate |
| 300 | slideshow | SlideshowKit | Fullscreen playback engine + Liquid Glass UI: chrome, gestures, album browser, info. | Active |
| 310 | slideshow-resilience | SlideshowKit | (sub-spec of 300) Auto-retry with backoff + periodic source refresh — unattended frame survival. | Active |
| 320 | disk-image-cache | SlideshowKit | (sub-spec of 300) Byte-capped disk cache + remembered source list — whole-album offline survival incl. relaunch; budget in Settings (500 MB default). | Active |
| 400 | power-manager | PowerKit | Keep the display awake and dim brightness while the slideshow runs in the foreground. | Active |
| 410 | brightness-mode | PowerKit + app target | (sub-spec of 400) Automatic (iOS decides, OwnFrame never writes) by default; Fixed holds a remembered level for a frame whose light sensor is covered; a free night window keeps nights very dim; Home Assistant/Shortcuts set session-only overrides, last event wins. | Built 2026-09-28 — device checks open (SC-410-06/07) |
| 500 | display-options | ThemeKit | User-configurable order/duration/transition/Ken Burns/fit/quality/clock, applied live. | Active |
| 510 | clock-overlay | ThemeKit + app target | (sub-spec of 500) Rendered clock overlay: Digits/Pill/Analog styles, six places + Random, Room/Cozy sizes, yields while chrome shows; off by default. tvOS rendering rides 1000 (FR-1000-10 pixel-shift — not started). | Implemented (iOS/iPadOS, 2026-07-18) |
| 600 | broker-setup | BrokerSetupKit | Enter and persist MQTT broker credentials (Keychain) so 700 has something to connect to. | Active |
| 700 | ha-control | HAControlKit | Remote control via MQTT/HA: availability + pause/play + brightness + album (730 deferred). Amended 2026-07-21: frame identity (US3, FR-700-16…22, SC-700-11…14) — identity must survive reinstall and is split from a user-editable frame name; documents a live-verified defect against FR-700-06. | Active — identity amendment implemented + merged to main (2026-07-21, PR #19); issue #15 closed |
| 710 | ha-full-control | HAControlKit | (sub-spec of 700) Read/set every display setting, next/previous, current-photo image + metadata, and diagnostics over MQTT. | Active |
| 800 | app-intents | app target (+ AppIntentsKit) | Shortcuts/Siri/personal-automation control via App Intents — second front-end to 700's command surface. | Merged to main (2026-07-18); device gate T029 (SC-800-02/03) pending |
| 900 | photo-library-source | PhotoLibraryKit (new) | Apple Photos / iCloud albums (incl. Shared Albums) as a source, behind a backend-neutral source protocol. Amended 2026-07-16: full-access gate, shared-album quality ceiling, iOS 27 rebuild risk. | Merged to main (2026-07-18); device/beta gates pending |
| 1000 | apple-tv | tvOS app target (new) | Apple TV port: same packages/engine on tvOS 17+, purgeable-storage discipline, config sync (non-secrets via KVS, secrets E2E via CloudKit encrypted fields — constitution III v1.1.0), remote-first chrome, HA device parity. | Merged to main (2026-07-18): all four user stories implemented + sim-verified. US1 (frame plays, real demo-link end-to-end), US2 (onboarding + real-source routing + KVS prefill/restore + secret hydration seam), US3 (purge-tolerance), US4 (HA adapter + coordinator with distinct identity + broker onboarding). All packages tvOS + new ConfigSyncKit; software-dim, remote chrome, FR-1000-07 bypass removed; iPad companion publishes full payload on launch/foreground; ThemeSettings Codable. Ken Burns redesigned here (2026-07-18 micro-judder fix): shared scoped-animation KenBurnsMotionModifier + DecodedImageStore decode-ahead — motion contract unchanged, swap decode-stalls eliminated (see 1000 tasks.md Status). Remaining (device-gated): real MQTT/CloudKit, tvOS clock + FR-1000-10 pixel-shift, real-hardware gates (SC-1000-02/05/06/08 + CloudKit-on-tvOS proof + 24h soak). |
| 1100 | purchase-gate | app targets (package decided at plan time) | Purchase gate: free core stays whole (all sources + core playback + basic transitions); a single one-time Supporter Unlock grants every gated capability at once (ambience — Ken Burns + clock, never-publicly-shipped rule — plus HA/MQTT + App Intents); no tiers, no bundle; offline entitlement caching for unattended frames; Family Sharing + universal purchase (incl. tvOS); never-claw-back; gated build must be the first public release (v1.0 b8 stays unreleased). No price points in-repo by design. | Merged to main (2026-07-20, PR #14; tiers collapsed into the single Supporter Unlock 2026-07-23, PR #40): entitlement model + all US1–US6 gates/UI + US5 broker degradation + the real StoreKit adapter + the tvOS unlock surface green (measured 2026-07-25: PurchaseKit 106 host, full iOS suite 163/0/5; the 5 skips = ASC-screenshot + live-smoke + 3 device-rig items). tvOS surface Apple-TV-simulator screenshot-verified. Remaining: T042 only — the manual ASC day (create the IAPs, sandbox purchase/restore/Family-Sharing/universal checks, release sequencing FR-1100-17), blocked on ASC access. |
| 1200 | observed-fixes | OnboardingKit + app UI, SlideshowKit, HAControlKit | Work-order bundle of three fixes observed on the running frame: Album-tab no-server guidance instead of a dead-end load error (FR-210-30), Ken Burns honors the Fit setting instead of forcing Fill (FR-500-20), battery + charging as free read-only HA telemetry (FR-710-23). Defines no new durable FR-1200 IDs. | Merged to main via PR #39 (2026-07-22); live MQTT/HA + perceived-motion checks ride the device day |
| 9000 | design-language | cross-cutting: both app targets + every String Catalog (no package of its own) | Supporting process, not a module. The governing design language for app UI and store copy, so the two cannot drift: always-dark appearance, the SF Pro type scale, the surface palette and the single declared accent, the content-column/inset-grouped/photographic-row layout rules, and the DE/EN vocabulary that retires "API key", "server address", "instance" and "Add a source" from the first-run path. Written after the design canvas of 2026-09-01 as the record of what it converged on, in the same relationship quiet-glass has to 500/510. Owns no screen: the UI overhaul applies it and amends 210 (album picker) and 220 (welcome). |
Active — specced 2026-09-01; accent landed (AccentColor declares Messing #E3A857, FR-9000-14, 7eba377), one name per source and dark app-wide landed (73ff5f0); empty/error-state layouts still open (see its Roadmap) |
100 ImmichClient ──> 200 Connection & Onboarding ──> 300 Slideshow (engine + UI)
│ │
│ 400 PowerManager <──────┤ (foreground brightness / idle)
│ 500 Display Options <───┘ (order/duration/transition/clock)
│
110 Shared Album Link (source kind) ──┐
120 Source Library (switchable list) ─┴─> surfaced in 200 (onboarding/Settings) + 700 (select)
600 Broker Setup ──> 700 HA Control (MQTT remote control)
├─ active: pause/play, brightness, source-select (120)
├─ active: 710 full control (settings, photo, diagnostics)
└─ reserved: 730 sleep/wake
310 Slideshow Resilience ──> 300 (auto-retry + periodic refresh; implemented 2026-07-09)
320 Disk Image Cache ─────> 300 + 310 (offline photo survival; implemented 2026-07-09)
510 Clock Overlay ────────> 500 (styles/places/sizes) + 300 (renders in the ambient layer)
+ 710 (HA-wired); tvOS rendering owned by 1000
800 App Intents ──> 700's command surface (Shortcuts/Siri/automations, no MQTT; merged 2026-07-18)
900 Photo Library Source ──> 120 source library (new kind) + a source-neutral data
protocol that 100 and PhotoKit both implement (merged 2026-07-18)
1000 Apple TV ──> second app target reusing every package: 210 onboarding semantics,
320 purge tolerance (as the normal case), 400 seam (software dim),
600/700/710 parity as a distinct HA device (merged 2026-07-18;
Photos source on tvOS still roadmap with 900)
- 200 owns the Settings-screen surface; it surfaces the 600 (Broker) and 500 (Display) sections and the 400 (brightness) control without re-specifying their behavior.
- 210 evolves 200's onboarding (choice-first entry, shared-link-only path, Share Sheet, the searchable album picker) and reuses 120's source library + shared-link secret store and 100/110's shared-link resolution; it adds no new backend behavior.
- 300 consumes 500 (option values), 400 (brightness), 100/110 (sources) and delegates reset to 200 — it does not redefine them.
- 9000 / 9010 sit outside the module graph and own no behaviour.
9000constrains how every screen above reads and looks (appearance, type, layout, vocabulary);9010applies that same language to the store page. When applying9000turns up a behaviour change, it is specced in the owning module — for the current UI overhaul,210(album picker) and220(welcome screen).
Core path: 100 → 200 → 300, then 400 / 500 as the slideshow's foreground-power and
display layers, then 600 → 700 → 710 for the Home Assistant remote-control path. 110 feeds 120's
source kinds — read it alongside 120.
Before touching any user-visible surface — a screen, a string, or a store asset — read 9000 first, and 9010 as well if the work touches the store page. They are short, they are binding on everything above, and they are the reason a reviewer can cite a requirement instead of taste.
Recorded in each owning topic's Roadmap / Deferred section. 110/120/710 shipped and are
Active above. 310, 320 and 510 are implemented. 800, 900, 220 and 1000 are no
longer deferred — all four are implemented (see their table rows); what remains for each is
real-hardware verification, which shares a single device day (docs/manual-verification.md).
900 and 1000 interlock: 1000 reuses 900's source protocol, and the Photos source on tvOS
is prototype-gated in both roadmaps.
Reserved sub-specs / future sources:
730HA sleep/wake driven by an HA presence signal (pairs with the 400 sleep/wake roadmap item).- Multi-source pooling (merge into one stream) and Memories as sources (topic 100 roadmap) —
120covers switching between sources, not pooling.
Specified but not yet built (carried over from old "extended/added" notes, deferred during the overhaul so every Active requirement maps to real, tested code):
Disk image cache + size limit + Clear-cache action— promoted to sub-spec 320-disk-image-cache, implemented 2026-07-09.Auto-retry with backoff/periodic source refresh— promoted to sub-spec 310-slideshow-resilience, implemented 2026-07-09.Rendered clock overlay— design agreed 2026-07-18 (500, FR-500-12/17/18/19: Digits/Pill/Analog, six places + Random, Room/Cozy sizes, yields while chrome shows); promoted to sub-spec 510-clock-overlay, implemented on iOS/iPadOS 2026-07-18. Still open: tvOS rendering + the FR-1000-10 pixel-shift contract, both owned by1000.- Settings/onboarding source management — now built:
120owns the source library and210delivers the choice-first onboarding, shared-link-only setup, iOS Share Sheet acceptance, and the shared searchable album picker (onboarding + Settings). Remaining 210 work is polish + a device Share-Sheet pass, not new surface.