# Development and Testing This page covers local setup, generated project guidance, runtime configuration/storage, and verification strategy for changes. ## Local setup Requirements from `/README.md` and `/project.yml`: - macOS with Xcode capable of building the declared iOS 26 / Swift 6 project. - [XcodeGen](https://github.com/yonaskolb/XcodeGen). - A linkding server with API access for real sync testing. - Access to the Marks backend for AI, semantic search, smart collections, analytics, and podcast generation flows. Setup: ```bash brew install xcodegen xcodegen generate open Marks.xcodeproj ``` Build and run the `Marks` scheme in Xcode. `/project.yml` declares schemes for `Marks`, `MarksWidget`, and `ShareExtension`; the `Marks` scheme builds the app, share extension, and widget extension and runs `MarksTests` in the test action. ## Project generation rules Use `/project.yml` as the source of truth for targets, source membership, entitlements, and dependencies. Regenerate the Xcode project after changing target membership: ```bash xcodegen generate ``` Common target-membership traps: - The main app includes `/MarksWidget/WidgetDataStore.swift` directly so it can write widget JSON. - The share extension includes selected shared files from `/Marks/Models` and `/Marks/Services`, including `IngestPayload.swift` for URL extraction; if the extension needs new model/service code, add it in `/project.yml`. - The app, widget, and share extension all depend on matching app-group entitlements. ## Testing Current automated test coverage is concentrated in `/MarksTests/AppIntentsTests.swift`. Covered today: - `BookmarkEntity` mapping from `Bookmark`, fallback title behavior, and round-trip fields. - `EntityIdentifier` encoding. - `IntentRouter` open/search setters. - Direct no-network `perform()` tests for Search and Open intents. - App Shortcuts registration count. - `IngestPayloadParser` URL extraction from direct URLs, plain text, HTML-ish text, and notes. - `IngestedSourceStore` save/load behavior plus plain-text file import into a temporary root. Not currently covered by repository tests: - `BookmarksViewModel` load/cache/pagination/mutation flows. - Linkding networking with mocks. - AI enrichment, semantic search, smart collections, or podcast backend calls. - Share extension save/update/tag suggestion flows. - Widget timeline rendering/deep links. - Browser reading progress/reader mode. - Podcast generation, playback, queue, played/unplayed, and background audio behavior. - Sources tab UI, QuickLook presentation, PDF extraction edge cases, and source-backed podcast backend behavior. Suggested test command: ```bash xcodebuild test -scheme Marks -destination 'platform=iOS Simulator,name=iPhone 16' ``` Use an installed simulator name if `iPhone 16` is unavailable. ## Manual verification checklist by area Bookmark sync/list changes: - Launch with valid linkding config. - Confirm cached bookmarks render quickly, then network refresh updates the list. - Test pull-to-refresh, pagination, unread filter, add, edit, archive, delete. - Confirm widget bookmark JSON updates and Spotlight indexing still happens. Search/tag changes: - Test linkding search and semantic search separately. - Verify unread filter interactions. - Confirm Tags tab counts include expected linkding and AI tags. AI changes: - Test enrichment on add, background enrichment, Enrich All, semantic search, Smart Collections, and Summarize intent if affected. - Test Ask on a device/simulator environment where Apple on-device model availability can be observed; handle unavailable states. - Preserve backend privacy expectations: URLs/titles/tags/summaries may be sent to the Marks backend for backend AI paths. Share extension changes: - Share a new URL, an already-saved URL, and text/HTML/RTF content that contains a URL plus notes. - Verify duplicate-aware update behavior. - Test linkding auto-tags, AI suggestion chips, tag parsing, notes, read-later, and Create Podcast. - Confirm the extension works when shared config is missing; current UI shows an "Open Marks first" style failure. Source ingest changes: - Import typed text with title/tags and confirm it appears in Sources search. - Import a text file and a PDF through the file importer; confirm originals copy into Application Support and QuickLook opens imported files. - Confirm PDF text extraction works for text PDFs and handles image-only PDFs gracefully. - Delete a source and confirm metadata, copied file, and Spotlight entry are removed. - Create a podcast from a source and confirm cached playback uses the same `marks-source://` entry. Podcast changes: - Generate from bookmark row and Browser view. - Start generation while another podcast is playing; confirm background generation does not interrupt playback. - Test queue playback, played/unplayed state, delete, sleep timer, saved speed, background audio, remote controls, now-playing metadata, relaunch restore, and widget recent podcasts. - Share a URL with Create Podcast and confirm the main app drains and starts the queued request. - Generate from a local text/PDF source and confirm backend support for `text` and `source_kind`. Widget/deep-link changes: - Confirm `widget_bookmarks.json` and `widget_podcasts.json` are refreshed by main-app writes. - Test Recent Bookmarks, Random Bookmark, and Recent Podcasts widgets. - Tap bookmark and podcast widget entries and verify `marks://bookmark` and `marks://podcast` routing in `MainContainer`. App Intents changes: - Run `MarksTests`. - Test Shortcuts/Siri flows for Add Bookmark, Search, Show Unread, Summarize, and Open Bookmark. - Verify entity queries still fetch/resolve bookmarks correctly with a configured linkding server. ## Configuration and persistence map Do not read or document live secret values. Main app / standard defaults: - `serverConfig` — linkding config saved by `ServerConfig.save()`. - `marksDeviceId`, `marksJWT`, `marksJWTExpiry` — Marks backend auth state from `MarksAuth`. - `aiData` — AI summaries/tags by bookmark ID. - `aiSummaryByURL` — summary mirror used outside the main bookmark array. - `podcastProgress`, `podcastPlaybackSpeed`, `podcastSession` — podcast playback state. - `readingProgress` — in-browser reading progress. App-group defaults/files (`group.com.magicive.marks`): - `serverConfig` — shared linkding config for extensions. - `pendingPodcastRequests` — share extension to main app podcast queue. - `podcastAutoTags` — tags that auto-enable podcast creation in the share extension. - `widget_bookmarks.json` — recent bookmark widget cache. - `widget_podcasts.json` — recent podcast widget cache. Application Support files: - `bookmarks_cache.json` or `bookmarks_{host}.json` — cached bookmarks. - `Sources/sources.json` — local source metadata. - `Sources/files/{uuid}.{ext}` — copied originals for imported PDFs/files. - `podcasts/{hash}.mp3` — generated podcast audio. - `podcasts/index.json` — podcast metadata and played state. ## Known caveats and stale-doc risks - `/Marks/MarksApp.swift` contains a built-in default linkding configuration with a token-like value. Treat it as sensitive and consider replacing it with a safer onboarding/configuration flow before distribution. - `OnboardingView` exists but is not the active first-launch path in `MarksApp` as currently written. - Disconnect behavior currently resets via the default config callback path instead of clearly deleting config and returning to onboarding. - README AI provider text appears older than source: current backend AI/podcast features go through `MarksAuth` + `ClaudeService`, while Ask uses Apple's on-device `FoundationModels`. - `MarksAuth` stores backend JWT/device state in `UserDefaults`, not Keychain. - App-group ID strings are duplicated in code and entitlements; update all occurrences together if changing it. - Share-extension podcast queue draining clears requests before generation success, so crash/retry semantics deserve care. - Source-backed podcast generation sends extracted text to the Marks backend; if the backend only accepts URL fetches, local source podcasts will fail even though bookmark podcasts still work. - Recent git history shows source ingest, podcast, and on-device AI features were added across many files; avoid narrow changes that ignore cross-target data flow. ## Existing design guidance `/docs/design-wwdc26.md` is a useful source for UI direction. It emphasizes native iOS 26 UI/Liquid Glass behavior, editorial bookmark rows as the brand canvas, Dynamic Type-safe row layout, bottom/reachable search patterns, WidgetKit app-group data, and standard platform affordances. Link to it rather than duplicating the full design brief.