7.2 KiB
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.
- 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:
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:
xcodegen generate
Common target-membership traps:
- The main app includes
/MarksWidget/WidgetDataStore.swiftdirectly so it can write widget JSON. - The share extension includes selected shared files from
/Marks/Modelsand/Marks/Services; 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:
BookmarkEntitymapping fromBookmark, fallback title behavior, and round-trip fields.EntityIdentifierencoding.IntentRouteropen/search setters.- Direct no-network
perform()tests for Search and Open intents. - App Shortcuts registration count.
Not currently covered by repository tests:
BookmarksViewModelload/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.
Suggested test command:
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 and an already-saved URL.
- 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.
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.
Widget/deep-link changes:
- Confirm
widget_bookmarks.jsonandwidget_podcasts.jsonare refreshed by main-app writes. - Test Recent Bookmarks, Random Bookmark, and Recent Podcasts widgets.
- Tap bookmark and podcast widget entries and verify
marks://bookmarkandmarks://podcastrouting inMainContainer.
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 byServerConfig.save().marksDeviceId,marksJWT,marksJWTExpiry— Marks backend auth state fromMarksAuth.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.jsonorbookmarks_{host}.json— cached bookmarks.podcasts/{hash}.mp3— generated podcast audio.podcasts/index.json— podcast metadata and played state.
Known caveats and stale-doc risks
/Marks/MarksApp.swiftcontains 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.OnboardingViewexists but is not the active first-launch path inMarksAppas 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-deviceFoundationModels. MarksAuthstores backend JWT/device state inUserDefaults, 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.
- Recent git history shows 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.