Replace the Tags tab with a tag picker sheet
CI / build-and-deploy (pull_request) Successful in 31s
CI / build-and-deploy (push) Successful in 32s

The tab bar drops from five to four; Tags now opens as a sheet from the +
in the library's filter strip, which is the control that was already
about adding a tag.

Picking a tag pins it as a filter tab rather than pushing to a separate
per-tag list. That made TagBookmarksView redundant — the filter tab shows
the same thing, in whichever layout you're already using — so it's gone.

This also fixes what the + could reach. The old inline menu listed tags
found on the loaded page, so it offered 21 of 96 tags, and once a tag
filter was active it collapsed to just the tags co-occurring with that
one — pinning a second unrelated tag was impossible. The sheet sources
names from the tags endpoint via a new BookmarksViewModel.loadAllTags(),
and counts still come from loaded bookmarks, so a tag we haven't paged in
shows no number rather than a wrong one.

The sheet is searchable, marks already-pinned tags with a check, and
picking a pinned tag selects that tab instead of duplicating it.

Verified by driving the UI: the tab bar is now Bookmarks/Sources/
Podcasts/Search, + opens the sheet with 42 rows where the old menu had
21, and picking "ai" pinned an "ai" tab. Suite passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
This commit is contained in:
Krishna Kumar
2026-07-26 11:12:47 -05:00
co-authored by Claude Opus 5
parent 69c2bc7514
commit 8a777ffc83
7 changed files with 130 additions and 99 deletions
+11
View File
@@ -11,6 +11,9 @@ final class BookmarksViewModel {
var searchQuery = ""
var nextPageUrl: String?
var smartCollections: [SmartCollection] = []
/// Every tag name on the server. The loaded bookmark page only ever
/// exposes the tags of the most recent 50, which is not the vocabulary.
var allTags: [String] = []
var isGeneratingCollections = false
var enrichmentProgress: Double = 0
var unreadFilter = false
@@ -200,6 +203,14 @@ final class BookmarksViewModel {
}
}
/// Best-effort: an empty tag list just means the picker falls back to the
/// tags it can see on loaded bookmarks, so a failure here isn't worth an
/// error alert.
func loadAllTags() async {
guard let tags = try? await api.fetchTags() else { return }
allTags = tags
}
func generateSmartCollections() async {
guard !bookmarks.isEmpty else { return }
isGeneratingCollections = true