diff --git a/docs/ADR-INDEX.md b/docs/ADR-INDEX.md index a54c6cb3..5b4ca45b 100644 --- a/docs/ADR-INDEX.md +++ b/docs/ADR-INDEX.md @@ -3,7 +3,7 @@ Generated by `scripts/adr_index.py` from the records in `docs/decisions/` — do not edit; run `make adr-index` after adding or changing one, and CI fails on a stale copy (ADR-0127 point 6). -140 current records (140 accepted, +142 current records (142 accepted, 0 proposed), 10 retired. A record whose status carries a note has had a point superseded by a later one; the note says which. ## Apple clients @@ -43,6 +43,8 @@ Generated by `scripts/adr_index.py` from the records in `docs/decisions/` — do | [0139](decisions/ADR-0139-the-apple-clients-record-where-a-queue-was-started-from.md) | The Apple Clients Record Where a Queue Was Started From | accepted | | [0146](decisions/ADR-0146-the-site-leads-with-the-mac.md) | The Site Leads with the Mac | accepted | | [0148](decisions/ADR-0148-familiar-server-has-a-stable-download-link.md) | Familiar Server Has a Stable Download Link | accepted | +| [0151](decisions/ADR-0151-the-phone-makes-and-edits-smart-playlists.md) | The Phone Makes and Edits Smart Playlists | accepted | +| [0152](decisions/ADR-0152-on-the-phone-search-belongs-to-the-screen.md) | On the Phone, Search Belongs to the Screen | accepted | ## Playback diff --git a/docs/decisions/ADR-0013-the-mac-is-a-management-surface-too.md b/docs/decisions/ADR-0013-the-mac-is-a-management-surface-too.md index bd604ff9..8f3c996a 100644 --- a/docs/decisions/ADR-0013-the-mac-is-a-management-surface-too.md +++ b/docs/decisions/ADR-0013-the-mac-is-a-management-surface-too.md @@ -1,7 +1,8 @@ # ADR-0013: The Mac Is a Management Surface Too Status: accepted — points 1 and 5 superseded by -[ADR-0050](ADR-0050-the-web-app-is-a-management-surface.md) +[ADR-0050](ADR-0050-the-web-app-is-a-management-surface.md); point 2 superseded for playlists by +[ADR-0151](ADR-0151-the-phone-makes-and-edits-smart-playlists.md) Point 1 said "Both clients keep everything… which costs nothing to keep". That was measured and found false; ADR-0050 records what it cost. Point 5 kept "a complete player at a computer, not an admin diff --git a/docs/decisions/ADR-0061-the-phones-tabs-are-home-search-and-library.md b/docs/decisions/ADR-0061-the-phones-tabs-are-home-search-and-library.md index 41da003b..d9486629 100644 --- a/docs/decisions/ADR-0061-the-phones-tabs-are-home-search-and-library.md +++ b/docs/decisions/ADR-0061-the-phones-tabs-are-home-search-and-library.md @@ -1,6 +1,6 @@ # ADR-0061: The Phone's Tabs Are Home, Search and Library -Status: accepted +Status: accepted; points 1 and 2 superseded by [ADR-0152](ADR-0152-on-the-phone-search-belongs-to-the-screen.md) Date: 2026-08-17 diff --git a/docs/decisions/ADR-0151-the-phone-makes-and-edits-smart-playlists.md b/docs/decisions/ADR-0151-the-phone-makes-and-edits-smart-playlists.md new file mode 100644 index 00000000..d142c352 --- /dev/null +++ b/docs/decisions/ADR-0151-the-phone-makes-and-edits-smart-playlists.md @@ -0,0 +1,87 @@ +# ADR-0151: The Phone Makes and Edits Smart Playlists + +Status: accepted + +Date: 2026-10-07 + +Implementation: +- Accepted 2026-10-07. + +Supersedes point 2 of [ADR-0013](ADR-0013-the-mac-is-a-management-surface-too.md), for playlists +only. + +## Context + +ADR-0013 point 2 says **"iOS stays the listening path. The phone is not a management client"**, and +point 3 puts smart playlist CRUD on the Mac. `familiar-apple` still cites both: +`SmartPlaylistsListView.swift` keeps creating, refreshing and deleting `#if os(macOS)`, and its +empty state on the phone reads "They're made on the Mac or in the web app, and fill themselves." + +**The phone stopped being only a listening path on 2026-08-15, and nothing recorded it.** +`familiar-apple` #119 ("Add visible iOS playlist editing", `2bb5b2d`) gave the phone, in the iOS +branch of `BrowseViews.swift` (lines 722–797 at the time of writing): + +- **Edit Rules** on a smart playlist's screen, opening the same `SmartPlaylistEditor` the Mac uses; +- **Edit Contents**, **Rename** and **Delete** on an ordinary playlist. + +No ADR was written, so the record and the app have disagreed for seven weeks. That is the defect +ADR-0061 point 5 names: "the record and the app disagreed for weeks and neither one knew." + +The prompt for this ADR is the owner, travelling with only the phone, wanting to **make** a smart +playlist and finding no way to. The phone can now change any rule of a smart playlist that exists, +and cannot write the first one. That line follows from no principle: it is where #119 stopped. + +ADR-0013 point 2 says managing the library from a phone "is still a second-class experience by +design", and gives no reason specific to playlists. Point 3 lists smart playlist CRUD beside pending +review, proposed changes and mixtapes, and point 4 keeps settings, imports and backup on the web. +Pending review, proposed changes, settings, imports and backup all change what the server holds or +how it runs. A smart playlist changes neither: it is a saved question about the library. It is a description +of **what to listen to**, and the test ADR-0018 and ADR-0019 used to bring Discover to the phone, +whether it is a way of finding something to play, is one it passes. The editor is already built +from the server's own field list, so writing a rule on the phone needs no new screen, only a way in. + +## Decision + +1. **Playlists are not management.** On the phone, as on the Mac, a listener can create, edit, + rename and delete ordinary playlists and smart playlists. This supersedes ADR-0013 point 2 for + playlists and for nothing else. + +2. **The phone gets "New Smart Playlist"**, opening the existing `SmartPlaylistEditor` with no + playlist, the way the Mac's `+` does. It sits in the toolbar of the smart playlists list, and the + empty state names it rather than sending the listener to another device. + +3. **Refresh and Delete join Edit Rules** on a smart playlist's screen on the phone, under the same + actions menu, so a smart playlist has the same three operations an ordinary one already has + there. + +4. **#119's editing is recorded as decided here, not as an exception.** Nothing it added is removed. + +5. **Everything else ADR-0013 point 2 kept off the phone stays off it**: settings, pending review, + proposed changes, mixtapes, imports, backup, and every other surface whose subject is the server + rather than what to play next. + +## Alternatives Considered + +- **Leave ADR-0013 point 2 as it is and remove #119's editing from the phone.** Restores agreement + between the record and the app at the cost of the feature. Rejected: the owner uses it, and the + reason given for the original line, that a phone is a poor place to manage a server, does not + describe a playlist. +- **Record #119 and still keep creation on the Mac.** Smallest change, and creating is where a new + rule is most likely to be written badly on a small keyboard. Rejected: the editor that edits a rule + is the one that would create it, the phone already writes every field of one, and "you can change + any rule but not write the first" is the gap that prompted this ADR. +- **Create on the phone through a simpler, phone-only editor**, such as a handful of preset rules. + Easier to use with a thumb. Rejected for now: a second editor is a second thing to keep in step + with the server's field list, which `SmartPlaylistEditor` already reads, and the full editor + already works on the phone through Edit Rules. + +## Consequences + +- **Positive:** the record matches the app again, and a smart playlist can be made where the idea + for it happens. +- **Positive:** the phone and the Mac offer the same playlist operations, so nothing about a + playlist has to be explained per device. +- **Tradeoff:** ADR-0013's simple rule, "the phone is not a management client", becomes a rule with + an exception. Point 5 states the boundary so it does not drift further one feature at a time. +- **Follow-up:** `SmartPlaylistsListView`'s header comment and empty state, and the `LibraryView` + comment on `smartPlaylistsStore`, describe the old split and change with the code. diff --git a/docs/decisions/ADR-0152-on-the-phone-search-belongs-to-the-screen.md b/docs/decisions/ADR-0152-on-the-phone-search-belongs-to-the-screen.md new file mode 100644 index 00000000..5f2c1c18 --- /dev/null +++ b/docs/decisions/ADR-0152-on-the-phone-search-belongs-to-the-screen.md @@ -0,0 +1,75 @@ +# ADR-0152: On the Phone, Search Belongs to the Screen + +Status: accepted + +Date: 2026-10-07 + +Implementation: +- Accepted 2026-10-07. + +Supersedes points 1 and 2 of [ADR-0061](ADR-0061-the-phones-tabs-are-home-search-and-library.md). + +## Context + +ADR-0061 ratified three tabs, **Home, Search and Library**, on the grounds that "searching is a root +action, not a destination". The owner, using the phone while travelling on 2026-10-07, found two +things wrong with how that turned out: + +- **The Search tab only searches tracks.** `phoneSearchRoot` (`familiar-apple` + `App/Shared/LibraryView.swift:1059`) is the tracks list with `.searchable(prompt: "Search + tracks")`. Looking for an artist there finds the artist's tracks, never the artist. +- **The phone already searches artists and albums, and nobody could find it.** The Library tab's + section screens carry a field scoped to what they show — "Search artists", "Search albums", + "Search tracks", "Filter playlists" (`searchPrompt`, `LibraryView.swift:2040`) — applied at + `LibraryView.swift:735`. On this SDK the system puts that field at the **bottom** of the screen + (noted at `LibraryView.swift:1548`), under the transport and beside a tab bar that has a + magnifying glass of its own. A listener who wants to search reaches for that tab, and the tab + searches something else. + +So the phone has two searches with different scopes, and the visible one is the narrower one. +The owner's ask is the scoped one, made visible: **on the Artists screen you search artists, on +Albums you search albums.** + +ADR-0061 point 5 requires an ADR for a navigation change on either client. Removing a tab is one. + +## Decision + +1. **Two tabs: Home and Library.** The Search tab is removed. This supersedes ADR-0061 points 1 + and 2. Its points 3 and 4 stand: the Library tab is `LibraryRootList`, unchanged. + +2. **Every list screen's search field sits at the top**, under the title, always visible + (`.searchable(placement: .navigationBarDrawer(displayMode: .always))`), and searches what that + screen lists: Tracks searches tracks, Artists artists, Albums albums, Playlists and Smart + Playlists filter by name. The scopes and prompts are the ones the screens already have; only the + placement changes. + +3. **Screens with nothing to search get no field**, as now: Home, Discover and Ambient + (`isSearchable`, `LibraryView.swift:738`). + +4. **The Mac is untouched.** Its sidebar and toolbar search already follow the selected section. + +## Alternatives Considered + +- **Keep the Search tab and make it search everything**: artists, albums and tracks in one list of + results, the shape the Music app uses. It keeps ADR-0061's "root action" and needs no navigation + change. Rejected for now: no endpoint answers all three (the only `/search` route, in + `admin_artists.py`, finds artists for the admin tools), so it would be three requests merged on + the device, ranked by nothing, and it would still leave a second, differently scoped field on + every section screen. It could come back as its own ADR, and nothing here prevents it. +- **Keep the Search tab as it is and only move the section fields to the top.** The smallest change. + Rejected: the phone would then show two magnifying glasses with different scopes on the same + screen, which is the confusion that prompted this. +- **Make the Search tab open the Library section in use with its field focused.** One search, + reached from the tab bar. Rejected: a tab that navigates another tab is a surprise, and on Home it + would have to pick a section arbitrarily. + +## Consequences + +- **Positive:** one search per screen, where the eye goes, and its scope is the screen's title. +- **Positive:** artists and albums are searchable from where they are browsed, with no new endpoint. +- **Tradeoff:** searching for a track takes one more tap than today: Library, then Tracks. + ADR-0061 argued that cost was worth a tab; this ADR argues the scoped field is worth more. +- **Tradeoff:** a field that is always visible takes a row of every list screen. +- **Follow-up:** the placement must be checked on an iOS 26 device, not only a simulator on an older + runtime: iOS 26 moved search into the tab bar, and a simulator on 18.5 has hidden a bug that only + existed on 26 before.