40 — Sort / Filter / Search (envisioning)
Envisioning doc — needs confirmation. Governed by
00-ROADMAP.md. Created 2026-06-26 (owner-raised). The ask: "we need to implement a Sort / Filter / Search option in the whole app and per view… following the best envision possible." Triggered by the Artists roster default (roster-grouping should be opt-in + saved, not automatic). Finding: a filter foundation already exists (LensBar+SavedViews) — this generalizes it into a consistent per-view view-config and adds the missing Sort and Search axes. Slice 0 already shipped (2026-06-26).
What triggered this
Roster grouping on the Artists grid was automatic (any artist with a roster → sectioned layout), with no way to turn it off. The owner wants it off by default and an opt-in toggle that's saved. Generalizing: every list view (Artists, Albums, Tracks, Shared, Teams, Recents, a Project's tracklist) should offer consistent Sort / Filter / Search with persisted per-view preferences — calm, predictable, the same shape everywhere.
Finding — the foundation (verified 2026-06-26)
There's real plumbing, but it's narrow:
LensBar+LensSelection— a filter bar (role · roster · tag) wired into Artists only.SavedViewsSheet— doc-26 server-side saved lenses (named, shareable filter sets) — exists.- Gaps: no Sort anywhere; no Search anywhere; filtering isn't generalized beyond Artists; and display choices (group-by) were hardcoded automatic, not user preferences. No per-view display-pref persistence existed before slice 0.
So Filter has a head start (LensBar) and shareable Saved Views exist (doc-26). The work is: (1) generalize the filter bar, (2) add Sort + Search, (3) give every view a persisted view-config.
The model — one "view-config" per list, three axes + two persistence grains
Each list view gets a small view-config: Filter (subset) · Sort (order) · Search (find).
| Axis | Today | Plan |
|---|---|---|
| Filter | LensBar (Artists only) | Generalize the chip bar; per-view facets (role / roster / tag / status / type) |
| Sort | — | Per-view sort keys: name · recently opened · updated · status · plays (doc-38) |
| Search | — | Per-view text search (debounced, client-side first); wire the backend search module for global/scale |
Persistence — two grains, deliberately split:
- Display preferences (group-by, sort key, ascending) → local per-device
@AppStorage, keyed per view. Calm, instant, no backend. The roster toggle (slice 0) is the first instance of this pattern. - Saved views (a named, shareable filter set) → server-side, reuse doc-26 SavedViews. This is the collaborative artifact ("the Mixing lens"); a display pref is not.
The control — each axis to its idiomatic iOS home (calm, doc-20/22)
Not one mega-control. Map each axis to where iOS users expect it, unified visually:
- Sort + display prefs → a toolbar Menu (
line.3.horizontal.decrease.circle) — exactly the affordance slice 0 introduced for "Group by roster." Native, quiet, out of the way until needed. - Filter → the inline
LensBarchips — high-frequency, glanceable, already built. - Search →
.searchable— the system search field (pull-to-reveal), per view.
This keeps the default screen calm (doc-20 one-surface, doc-31 professional calm): nothing appears until the user reaches for it.
Files & attachments are a filter/search dimension too (owner-raised 2026-07-05)
The asset cloud (doc-45) is now real at every altitude, and its files carry attributes (doc-43 §3 — file is a first-class attribute node). So the same three axes apply to files, not just to the artist/project/track catalog:
- Filter files by kind (audio · video · image · attachment), attachment folder (Lyrics/Stems/Contracts/custom), format/MIME, size, review status (doc-29), and any custom file attribute from the doc-43 catalog (e.g. "stem instrument", "contract status").
- Search files by name — and, at scale, by the backend
searchmodule extended to theFilecollection (today search is Artists-only; files join the same generalization). - Sort files by name · size · recently added · review status.
- Where it lives: the Files zone in every Portal (doc-45/49) gets the same
view-configgrammar (a filter chip row + search) once a node's cloud is non-trivial; the roll-up view (doc-45 §step 3) is where filtering pays off most — "show only Stems across this whole project", "every unsigned contract in the studio drive". A Saved View here = "the Contracts lens" (doc-26 SavedViews), exactly like a catalog lens. - Sequencing: rides the same generalization as the catalog lens bar — start client-side over the node's already-fetched attachments (cheap), wire the backend
search/aggregate when clouds grow (pairs with the doc-45 pagination follow-up). Envision now; build alongside the doc-43 catalog slice.
Roster UI/UX — confirmed against the design system (answers the owner's "do we have the philosophy?")
Yes — the philosophy is documented; the roster work now aligns to it:
- Margin fixed (shipped). Roster section names had negative leading padding (flush to the screen edge); removed → they sit at the standard content margin (doc-22
SnSpacing), consistent with the tiles. - Roster is now opt-in. Default grid is flat/calm (doc-20); roster grouping is a saved choice, not imposed.
- Aesthetic home: roster headers (color dot · name · count) follow doc-22; the Roster Portal (doc-36) is the richer future surface for roster identity + its roll-up. No new design language needed — this is doc-22 + doc-26 + doc-36 applied, not invented.
How this sequences
- ✅ Slice 0 — shipped 2026-06-26 (the pattern seed): roster grouping off by default + persisted
@AppStoragetoggle in a toolbar Menu; redundant inner section titles + count bubbles removed (Albums / Tracks / Artists-flat — the screen header already names them); roster-name margin fixed. - ✅ Slice 1 — shipped 2026-06-26: reusable
ViewOptionsMenu+SortField(sort picker + optional extra controls, persisted by the caller via@AppStorage). Applied to Artists (Default / Name, with the roster toggle folded into the same menu) and Library (Albums: Default / Name · Tracks: Default / Name / Recently updated —Track.updatedAtDate). Sort keys are honest to the models (Albums/Artists carry no date, so no fake "recent").xcodebuildBUILD SUCCEEDED. - Slice 2 — Search: per-view
.searchable(scoped, in-context filter) + a global Search view (cross-hierarchy, incl. comments/notes). The backendsearchmodule today indexes only ArtistSpace/Project/Track names + tags → extend it to comments/notes + portal metadata (doc-41 §Search). "Find anywhere" is a view; "filter this list" is.searchable— two intents, two surfaces. - Slice 3: richer sort keys (incl. doc-38 plays/engagement and doc-41 status / modifiedBy / recently-modified); promote a configured view to a Saved View (doc-26) when it's worth sharing.
Dependency: richer Filters / Sorts / defined Views need real fields to key on (status, state, createdBy/modifiedBy, created/modified-on). Those are the doc-41 foundation (audit · state · lifecycle status · i18n) — build doc-41 slices 1–2 to unlock the full power of this doc's Filter/Sort/View axes.
Generalize alongside each view's other work — not a blocking phase. Until then, only Artists has Filter; other lists stay as-is.
Decisions — to confirm (flagged)
- Persistence grain — display prefs local
@AppStoragevs server-synced across devices? Recommend local for display prefs, server only for named Saved Views (doc-26). (Slice 0 already chose local.) - Control placement — toolbar Menu (sort/display) + inline LensBar (filter) +
.searchable(search), as above? Or one combined sheet? - Search scope — per-view client filter first vs global server
/search? Recommend client-first, global later. - Sort keys per view — define each view's menu (Recents = recent; Artists = name; Tracks = updated/plays…).
- Default sort per view — pick the calm default for each surface.
- Generalization order — Artists + Library next, then Shared / Teams / Recents / tracklist.
Cross-references
- The filter foundation + shareable lenses this extends:
26-IDENTITY-SHARING-NAVIGATION.md(LensBar,SavedViews) - One-surface calm (why default = quiet):
20-UI-SIMPLIFICATION.md·31-PRODUCT-VISION-360.md - Spacing/typography the roster + controls follow:
22-IOS-DESIGN-SYSTEM.md - The Roster Portal (roster's richer future home):
36-INTERACTION-PATTERNS.md - "Plays" as a future sort key:
38-ANALYTICS-INSIGHTS.md - Backend text search to wire for global search:
ctrl-audio-back/src/modules/search