Every bookmark failed to reach the Spotlight index. Each item died in
translation with "Provided object for field url is of class NSURL,
expected class: NSString", so on-device search and Ask Your Bookmarks
were retrieving from an index that was effectively empty.
Left to itself, App Intents indexes BookmarkEntity's `url` property under
the attribute set's own `url` key, which Spotlight's Cascade translator
types as NSString. Giving the property an explicit
`indexingKey: \.contentURL` sends it to a URL-typed field instead — and
contentURL is the right field for "where this content lives" regardless.
Keeping the property a URL rather than retyping it to String means
existing Shortcuts that read it are unaffected.
The attribute set now sets contentURL too, and the retrieval side reads
it back, so the round trip stays on one field.
Why it went unnoticed: translation happens after `indexAppEntities`
returns, so indexing logged success the whole time. Measured on the
simulator against the live library — 202 translation failures per launch
before, 0 after.
Adds SpotlightRetrievalTests, which indexes a bookmark and retrieves it
through the assistant's own path. Nothing weaker would have caught this,
since the failure was silent at every layer above the index.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
Every screen now speaks the paper vocabulary rather than only the
bookmarks screen: Search, Sources, Podcasts and the player, Settings,
Add/Edit, Smart Collections, Ask, Onboarding, the browser toolbar, the
Siri snippets, and the share extension's save card.
The change is mostly a design system rather than per-screen tweaks.
LibraryKit gains:
- Semantic colors — secondary/tertiary/faint text, `raised` surfaces,
and accent/alarm/affirm, so screens stop reaching for .secondary,
systemGray, .blue, .red and .green. The three status colors come out
of the palette (the swatch blue, vermilion and forest) rather than
from the system set, so the design keeps spending one set of inks.
- PaperType — the two voices made explicit. Prose (titles, summaries,
anything a person wrote) is serif; anything the machine contributes
(domains, dates, counts, tags, labels, buttons) is monospaced. Keeping
that split strict is what makes the design read as archival rather
than as decoration.
- paperSurface() / paperField() / paperCard() for grounds, inputs and
raised blocks.
- PaperEmptyState, because ContentUnavailableView can't be restyled — it
draws its own bold system type and grey, which was the loudest
remaining system voice once the screens moved onto the sheet. All nine
usages are converted.
The app also sets one accent for the system chrome it doesn't draw —
tab bar, search fields, switches, swipe actions — which otherwise stayed
system blue around paper screens.
BookmarkRow is deleted. Once Search moved to the library row and
TagBookmarksView was gone, nothing passed .classic, so the style fork in
BookmarkListRow went with it. There is one bookmark row now.
The share extension compiles LibraryKit directly, since its save card is
a user-facing surface and should not be the one place still using system
styling.
Verified on device in both color schemes; screens toured with a
temporary UI test harness (removed). Suite passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
The tab bar drops from five to four; Tags now opens as a sheet from the +
in the library's filter strip, which is the control that was already
about adding a tag.
Picking a tag pins it as a filter tab rather than pushing to a separate
per-tag list. That made TagBookmarksView redundant — the filter tab shows
the same thing, in whichever layout you're already using — so it's gone.
This also fixes what the + could reach. The old inline menu listed tags
found on the loaded page, so it offered 21 of 96 tags, and once a tag
filter was active it collapsed to just the tags co-occurring with that
one — pinning a second unrelated tag was impossible. The sheet sources
names from the tags endpoint via a new BookmarksViewModel.loadAllTags(),
and counts still come from loaded bookmarks, so a tag we haven't paged in
shows no number rather than a wrong one.
The sheet is searchable, marks already-pinned tags with a check, and
picking a pinned tag selects that tab instead of duplicating it.
Verified by driving the UI: the tab bar is now Bookmarks/Sources/
Podcasts/Search, + opens the sheet with 42 rows where the old menu had
21, and picking "ai" pinned an "ai" tab. Suite passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
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>
- 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>
- 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>
Expose Marks to Siri, Spotlight, and Shortcuts via App Intents:
- BookmarkEntity (AppEntity + IndexedEntity, keyed on the Linkding
server id) with a server-backed EntityStringQuery for live lookup
- Intents: Add, Open (OpenIntent), Search, ShowUnread, and an AI
Summarize intent backed by ClaudeService; AppShortcutsProvider with
spoken phrases; Siri snippet views and dialog
- SearchMarksIntent conforms to the system.search App Schema
(@AppIntent(schema:), ShowInAppSearchResultsIntent) so it is
executable by conversational Siri / Apple Intelligence
- IntentRouter bridges intents (run in the app process) to the SwiftUI
scene for open/search navigation; widgets refreshed after adds
- Link AppIntents.framework so the metadata processor extracts shortcut
phrases and validates the assistant schema
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>