Adds a cards/list toggle to the bookmarks screen. List mode is the
existing screen, untouched and still the default; cards mode is the
library presentation running on real bookmarks.
Structure:
- LibraryKit.swift holds what both the shipping screen and the prototype
draw from — the paper palette, the Bookmark -> LibraryItem projection,
the color card, and the tab strip. The prototype in Views/Prototypes
keeps only its standalone chrome and sample data, so the two can no
longer drift.
- BookmarkActions.swift extracts the context menu and the podcast launch
path out of BookmarkListRow. Cards and rows now offer exactly the same
actions because they are the same code. The menu is a @ViewBuilder
rather than a ViewModifier: each host already owns the sheets it
presents, and a modifier would have forced a second copy of that state.
- LibraryGridView.swift is the Bookmark-driven grid, with the same tap-to
-open, long-press-for-menu, pagination and podcast sheets as the list.
Two behaviors worth calling out:
Tag tabs filter server-side through linkding's `#tag` search syntax.
Filtering the loaded page client-side would only ever search the most
recent 50 of 600+ bookmarks and quietly look empty; verified against the
server that `q=#dev-tools` returns 64 tagged results where `q=dev-tools`
returns 0.
Leaving cards mode clears any tag filter. The classic list has no filter
strip to display one, so a filter that survived the switch would be
invisible and the list would look like it had lost bookmarks.
The layout choice persists in @AppStorage. The toolbar button shows the
layout it switches *to* — a two-state toggle labelled with its current
state reads as a status light rather than a control.
Verified on an iPhone 17 Pro simulator against the live linkding server in
both layouts and both color schemes. Full test suite passes (16 tests).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
Resolves the two deviations from the episode detail sheet:
- AI summary now shows in the detail sheet. Added a URL-keyed AISummaryStore
that BookmarksViewModel populates as it enriches/loads bookmarks, so the
decoupled Podcasts tab can surface a summary from just the episode URL —
without recoupling to BookmarksViewModel.
- "Open Bookmark" opens the in-app BrowserView as a nested sheet (generator
threaded through PodcastLibraryView / EpisodePickerView), alongside the
existing "Open in Safari" and Share.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turns the Podcasts tab into a proper podcast-app experience across three
features that compose on a shared background-generation service.
Background generation (#2)
- New PodcastGenerationManager runs generate→poll→download off the player,
so producing a new episode never stops current playback.
- Headphone taps never interrupt: idle → generate-and-play in the foreground
(unchanged); already playing → generate in the background, episode lands in
the library. A "Generating…" banner surfaces active jobs.
Played/unplayed + queue (#1)
- PodcastEntry gains playedAt (old index.json decodes as unplayed).
- Podcasts tab splits into Up Next / Played with a Play All queue that
auto-advances; finishing marks played; swipe to toggle played state.
Share-extension audio (#3)
- "Create podcast" toggle on the save card, plus configurable Auto-Podcast
Tags in Settings. The extension enqueues a request into the App Group; the
main app drains it on launch/foreground and generates in the background.
Closes#1Closes#2Closes#3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply WWDC26 design-brief fixes to the bookmark list reading surface:
- Dynamic Type: replace fixed .system(size:) points with text styles
(title .headline, domain/date .footnote, summary .subheadline, tags
.caption, podcast glyph .title3). Rows now scale and wrap with the
user's text-size setting instead of staying fixed.
- Perf: podcastCached was a FileManager.fileExists stat evaluated inside
body — i.e. per row, per scroll frame. Move it to @State populated by a
.task(id:) that stats off the main actor, once per appearance.
- Forgiveness: destructive trailing swipe is now allowsFullSwipe: false so
a single fling can't permanently delete; requires tapping the button.
Adds docs/design-wwdc26.md (consolidated brief + codebase audit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace Button wrapper with onTapGesture so swipe gestures don't compete
with the row tap recognizer and accidentally open bookmarks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Save serverConfig to App Group on every app launch so ShareExtension
can always find credentials without requiring a disconnect/reconnect flow
- Add MarksWidget target with RandomBookmarkWidget (random saved bookmark,
reloads hourly) using file-based App Group storage to avoid CFPreferences issues
- Extract BookmarkListRow as shared component with editorial typography tweaks
(17pt semibold title, 13pt domain, relative date, 32×32 favicon, italic AI summary,
20pt podcast icon, 16pt horizontal insets)
- Add MarksWidget and ShareExtension Xcode schemes for direct run/debug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>