Commit Graph

20 Commits

Author SHA1 Message Date
Krishna Kumar
140c761850 experiment(PodcastPlayerView): add Retry button to failedView, clear pollingTask on failure 2026-05-23 00:52:55 -05:00
Krishna Kumar
f7d5c129fc experiment(PodcastPlayerView): add playbackSpeed control (0.75×/1×/1.25×/1.5×/2×) with menu 2026-05-23 00:51:23 -05:00
Krishna Kumar
be4a4c3a5f experiment(PodcastPlayerView): add lock screen skipBackwardCommand/skipForwardCommand (15s) 2026-05-23 00:50:07 -05:00
Krishna Kumar
fc0f98f45a experiment(PodcastPlayerView): add skip ±15s buttons to full player controls 2026-05-23 00:48:46 -05:00
Krishna Kumar
ea8a425c21 experiment(BookmarkRow): add sensoryFeedback haptic on podcast trigger button 2026-05-23 00:47:40 -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 Kumar
18423a7633 Add iOS Langfuse analytics via backend events endpoint
Some checks failed
CI / build-and-deploy (push) Failing after 8s
AnalyticsService.track() is fire-and-forget — posts to POST /v1/marks/events
which records a Langfuse trace. No external SDK, no Sentry on iOS side.

Events tracked:
- podcast.started (url, cached)
- podcast.completed (url, durationSec) — fires at 97% playback
- podcast.failed (url, error)
- search.semantic (query, resultCount)
- enrich.completed (count)
- collections.generated (collectionCount)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:30:03 -05:00
Krishna Kumar
38cd72416c Xcode project sync and seek async fix
All checks were successful
CI / build-and-deploy (push) Successful in 23s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:59:22 -05:00
Krishna Kumar
abe35f47d6 Add mini-player, per-row podcast button, and resume-from-position
Some checks failed
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 Kumar
f796dabcff Fix target=_blank links opening Safari — add WKUIDelegate
All checks were successful
CI / build-and-deploy (push) Successful in 21s
Without a UI delegate, WKWebView hands target="_blank" and window.open()
links to the system (Safari). createWebViewWith now loads them in the
existing webview instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:47:29 -05:00
Krishna Kumar
544fcd442c Add podcast index: JSON mapping of article URLs to cached MP3 files
All checks were successful
CI / build-and-deploy (push) Successful in 25s
PodcastIndex.swift stores {articleUrl, filename, title, createdAt} in
App Support/podcasts/index.json. Updated on every successful download.
Enables building a podcast library view later.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:45:38 -05:00
Krishna Kumar
476ed9b2a1 Podcast: local MP3 cache, AirPods, lock screen controls, background audio
All checks were successful
CI / build-and-deploy (push) Successful in 23s
- Cache generated MP3 to App Support/podcasts/<hash>.mp3 — re-opening
  the same article plays instantly with zero backend calls
- AVAudioSession .playback/.spokenAudio + .allowBluetoothA2DP — routes
  audio to AirPods and Bluetooth headsets
- MPRemoteCommandCenter play/pause/seek — AirPods controls + lock screen
- MPNowPlayingInfoCenter — title and scrubber on lock screen
- UIBackgroundModes: audio in Info.plist — playback continues when backgrounded
- Auto-play when player is ready

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:39:14 -05:00
Krishna Kumar
525e6557c8 Replace manual backend config with auto JWT auth
Some checks failed
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 Kumar
9f0a10d228 Always show headphones button; alert if AI not configured
All checks were successful
CI / build-and-deploy (push) Successful in 24s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:04:24 -05:00
Krishna Kumar
db5092ce02 Add reading progress tracking
All checks were successful
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 Kumar
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 Kumar
ac2f9cf85d Add in-app browser with reader mode
All checks were successful
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 Kumar
7d1b53bac6 Seed edit form tags from effective tags (real + AI fallback)
All checks were successful
CI / build-and-deploy (push) Successful in 24s
The list row shows effectiveTags which merges real linkding tagNames with
AI-enriched aiTags. If a bookmark has only AI tags, the edit form was
appearing empty because it only seeded from tagNames. Now falls back to
aiTags when tagNames is empty.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:33:24 -05:00
Krishna Kumar
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 Kumar
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