Commit Graph
15 Commits
Author SHA1 Message Date
Krishna KumarandClaude Opus 5 99add346fd Wire the library presentation into BookmarksView
CI / build-and-deploy (push) Successful in 19s
CI / build-and-deploy (pull_request) Successful in 20s
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
2026-07-26 01:41:23 -05:00
Krishna KumarandClaude Opus 4.8 af3112530e feat(podcasts): played/unplayed queue, background generation, share-sheet audio
CI / build-and-deploy (push) Successful in 24s
CI / build-and-deploy (pull_request) Successful in 15s
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 #1
Closes #2
Closes #3

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:37:42 -05:00
Krishna KumarandClaude Opus 4.8 f8de444f8f feat(podcasts): add a Podcasts tab to list and play on demand
CI / build-and-deploy (push) Successful in 15s
Surface every podcast in PodcastIndex as a top-level tab instead of a
buried sheet. Repurpose PodcastLibraryView into the tab: play/pause toggle
per episode, a now-playing bar that opens the full player, swipe/Edit to
delete. Uses the shared podcastPlayer so playback state is consistent
across tabs. Removes the redundant Podcasts entry from the bookmarks menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:16:49 -05:00
Krishna KumarandClaude Opus 4.8 96ea5fe6f6 feat(ai): on-device RAG over bookmarks + Spotlight indexing + unified logging
CI / build-and-deploy (push) Successful in 22s
- SpotlightIndexer: actually push BookmarkEntity into the Spotlight index via
  indexAppEntities (IndexedEntity conformance alone indexes nothing). Wired into
  every sync/mutation point. This is what lets Apple Intelligence answer
  free-form Siri questions grounded in the user's bookmarks.
- On-device RAG: BookmarkAssistant (LanguageModelSession) + BookmarkSearchTool
  + SpotlightBookmarkSearch (CSSearchQuery retrieval), surfaced via AskView with
  lightweight Markdown rendering of answers.
- Log: os.Logger facility (com.magicive.marks) replacing ad-hoc print(); shared
  with ShareExtension. ClaudeService now logs server error bodies on non-2xx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:55:32 -05:00
Krishna KumarandClaude Opus 4.8 a3059f25fc feat(intents): onscreen entity annotations + App Intents tests
CI / build-and-deploy (push) Successful in 28s
- bookmarkOnscreen(_:) publishes the viewed bookmark as the on-screen
  entity (NSUserActivity.appEntityIdentifier) so Siri can resolve "this"
  / "summarize this one"; applied to the BrowserView sheets in
  BookmarksView and SearchView
- Add MarksTests (XCTest) covering entity mapping, EntityIdentifier,
  IntentRouter, and the real perform() of SearchMarksIntent and
  OpenBookmarkIntent; wired into the Marks scheme test phase

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:16:46 -05:00
Krishna KumarandClaude Sonnet 4.6 199fc2d043 Fix ShareExtension "Open Marks first" + add widget + BookmarkListRow polish
CI / build-and-deploy (push) Successful in 41s
- 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>
2026-05-24 13:11:05 -05:00
Krishna Kumar d87500a7dc experiment(BookmarksView): row horizontal insets 20pt → 16pt for tighter editorial margins 2026-05-23 01:14:18 -05:00
Krishna Kumar ab72611d7b Podcast UX baseline: mini player during generation, end-of-track reset, library view
- Mini player shows as soon as generation starts (not just when audio ready)
- Idempotency: tapping headphones again during generation is a no-op
- Playback resets to position 0 on completion (standard player behavior)
- Stop button in full player visible during generation
- PodcastLibraryView lists all generated podcasts
- Sparkles menu gains Podcasts entry
- Fix AnalyticsService Sendable error (serialize before Task.detached boundary)
2026-05-23 00:46:46 -05:00
Krishna KumarandClaude Sonnet 4.6 abe35f47d6 Add mini-player, per-row podcast button, and resume-from-position
CI / build-and-deploy (push) Failing after 9s
- MiniPlayerView: persistent bar above tab bar, shows title/progress,
  play/pause and close buttons, slides in when audio starts
- PodcastPlayerViewModel lifted to BookmarksViewModel so it persists
  across sheet dismissals — audio keeps playing in background
- Headphone button on every bookmark row (filled = cached, outline = not)
- start() is idempotent: re-tapping same article resumes without restart
- stop() saves playback position before teardown (PodcastProgress)
- setupPlayer restores saved position on next open
- BrowserView keeps its own local VM (stopOnDismiss: true)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:19:21 -05:00
Krishna KumarandClaude Sonnet 4.6 525e6557c8 Replace manual backend config with auto JWT auth
CI / build-and-deploy (push) Failing after 6s
Device auto-registers on first launch using hardcoded backend URL
and anon key (MarksAuth.swift). 90-day JWT stored in UserDefaults,
refreshed transparently. No settings fields needed — ClaudeService
is always active and non-optional throughout the app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:14:54 -05:00
Krishna KumarandClaude Sonnet 4.6 db5092ce02 Add reading progress tracking
CI / build-and-deploy (push) Successful in 25s
- BrowserState: inject scroll-tracking JS after page load, report position
  via WKScriptMessageHandler (weak ref to avoid retain cycle)
- BrowserView: thin blue progress bar at top of viewport; saves progress
  to UserDefaults on dismiss; restores scroll position on revisit
- BookmarkRow: 2px progress bar below tags for partially-read articles
- BookmarksView: loads ReadingProgress.all() on appear, refreshes when
  browser sheet dismisses
- ReadingProgress: static helper for UserDefaults-backed [url: Double] store;
  removes entry when article is fully read (≥99%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:42:12 -05:00
Krishna KumarandClaude Sonnet 4.6 bc16ee986d Migrate AI features to backend, add podcast generation
- ClaudeService: replace direct OpenRouter calls with backend endpoints
  (/v1/marks/enrich, /v1/marks/search, /v1/marks/collections)
- ClaudeService: add podcast methods (generate, status, downloadAudio)
- SettingsView: replace OpenRouter key field with backend URL + API key
- PodcastPlayerView: new sheet — generate → poll → AVPlayer playback
- BrowserView: headphones toolbar button triggers podcast for current URL
- BookmarksView: "Convert to Podcast" context menu item + sheet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:39:15 -05:00
Krishna KumarandClaude Sonnet 4.6 ac2f9cf85d Add in-app browser with reader mode
CI / build-and-deploy (push) Successful in 22s
Tapping a bookmark now opens an in-app WKWebView instead of bouncing to
Safari. Reader mode injects JS to extract article content and rerender
with clean serif typography and dark-mode support. Bottom toolbar has
back/forward, reader toggle, share, and open-in-Safari escape hatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:43:33 -05:00
Krishna KumarandClaude Sonnet 4.6 1b40f50aab Add edit bookmark, offline cache, and unread filter
- Edit bookmark: full PATCH via swipe action or long-press context menu;
  EditBookmarkView covers URL, title, description, tags, and unread toggle
- Offline cache: bookmarks persisted to ApplicationSupport JSON and shown
  instantly on launch; cache is per-server, skipped when unread filter is on
- Unread filter: toolbar toggle sends ?unread=true to the API; title updates
  to "Unread"; no stale-cache flash when toggling
- Extract normalizedURL and parsedTags into String+Helpers, removing three
  copies of URL normalization and two of tag parsing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:51:54 -05:00
Krishna KumarandClaude Sonnet 4.6 b7ef5c2215 Replace Flutter app with native SwiftUI rewrite (iOS 26)
Complete ground-up rewrite in SwiftUI targeting iOS 26. Drops the Flutter/Dart
codebase entirely in favour of a lean native app with no third-party dependencies.

Features shipped:
- Bookmark list with pagination, pull-to-refresh, swipe delete/archive
- Add bookmark form + iOS share extension (zero-tap save from any app)
- Tags tab — all tags sorted by count, tap to browse filtered bookmarks
- Native search tab (Tab role: .search) with instant client-side filtering
- AI enrichment via OpenRouter (google/gemini-2.0-flash-lite-001): auto-summary
  and tag generation, semantic search, smart collections
- Settings: linkding server config, OpenRouter API key
- App Groups for credential sharing between main app and share extension
- Swift 6 strict concurrency throughout (@Observable, @MainActor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:33:01 -05:00