Library design: cards + redesigned list on the bookmarks screen, tag picker sheet #9
@@ -25,6 +25,7 @@
|
||||
457FCE503CCA82C5F27C6C90 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA428181B35885F7D9F4D55 /* Bookmark.swift */; };
|
||||
50F3BED92EBA34F863C9F8A0 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7510EB352E624C7C9656EA33 /* LibraryView.swift */; };
|
||||
55CDFFAB5530D08861F85363 /* LibraryGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF2B010DADCCFBC282D37F0 /* LibraryGridView.swift */; };
|
||||
59E46C9653C9033F5BB1CEC7 /* SpotlightRetrievalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2526E22D8EC5453358F0FCF9 /* SpotlightRetrievalTests.swift */; };
|
||||
5D86F3F0F603B248776916C7 /* BookmarksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFB5EFC9764B22A2622EA4A /* BookmarksViewModel.swift */; };
|
||||
5ED7F0AB24549BA01757A39C /* PodcastPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4EB8C63735A267B81030CB5 /* PodcastPlayerView.swift */; };
|
||||
66D5D90A5FAF842BCA0FE72D /* PodcastRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27A97922BAEBDC9C5A7385C /* PodcastRequests.swift */; };
|
||||
@@ -136,6 +137,7 @@
|
||||
22E006A11D594BFC00A9C4B4 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
23F172EC9977CD5C51B228B9 /* MarksWidget.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = MarksWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
240DBB87940F8D255A812EB2 /* BookmarkActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkActions.swift; sourceTree = "<group>"; };
|
||||
2526E22D8EC5453358F0FCF9 /* SpotlightRetrievalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightRetrievalTests.swift; sourceTree = "<group>"; };
|
||||
41DDBB04346F3BF06DE233D2 /* SpotlightBookmarkSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightBookmarkSearch.swift; sourceTree = "<group>"; };
|
||||
47CB3AAED5B64809B06A9650 /* RecentPodcastsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentPodcastsWidget.swift; sourceTree = "<group>"; };
|
||||
49685B8F3FEC72E8CF75843E /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
|
||||
@@ -244,6 +246,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7623601C25E481DF58371F2A /* AppIntentsTests.swift */,
|
||||
2526E22D8EC5453358F0FCF9 /* SpotlightRetrievalTests.swift */,
|
||||
);
|
||||
path = MarksTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -528,6 +531,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD656A44CEE8AE28B136AD85 /* AppIntentsTests.swift in Sources */,
|
||||
59E46C9653C9033F5BB1CEC7 /* SpotlightRetrievalTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,17 @@ struct BookmarkEntity: AppEntity, IndexedEntity {
|
||||
@Property(title: "Title")
|
||||
var title: String
|
||||
|
||||
@Property(title: "URL")
|
||||
/// The explicit `indexingKey` is load-bearing. Left to itself App Intents
|
||||
/// indexes this property under the attribute set's own `url` key, and
|
||||
/// Spotlight's Cascade translator types that field as NSString — so every
|
||||
/// item failed with "Provided object for field url is of class NSURL,
|
||||
/// expected class: NSString" and nothing reached the index. `contentURL`
|
||||
/// is URL-typed there, and is the right field for "where this lives"
|
||||
/// anyway. Routing it there keeps the property a URL for Shortcuts.
|
||||
///
|
||||
/// The failure is silent: translation happens after `indexAppEntities`
|
||||
/// returns, so indexing logs success either way.
|
||||
@Property(title: "URL", indexingKey: \.contentURL)
|
||||
var url: URL
|
||||
|
||||
@Property(title: "Website")
|
||||
@@ -53,7 +63,7 @@ struct BookmarkEntity: AppEntity, IndexedEntity {
|
||||
attrs.title = title
|
||||
attrs.contentDescription = details.isEmpty ? summary : details
|
||||
attrs.keywords = tags
|
||||
attrs.url = url
|
||||
attrs.contentURL = url
|
||||
return attrs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ enum SpotlightBookmarkSearch {
|
||||
Log.spotlight.debug("Search query=\(rawQuery, privacy: .public) predicate=\(queryString, privacy: .public)")
|
||||
|
||||
let context = CSSearchQueryContext()
|
||||
context.fetchAttributes = ["title", "contentDescription", "keywords", "url"]
|
||||
context.fetchAttributes = ["title", "contentDescription", "keywords", "contentURL"]
|
||||
let query = CSSearchQuery(queryString: queryString, queryContext: context)
|
||||
|
||||
var out: [RetrievedBookmark] = []
|
||||
@@ -33,9 +33,9 @@ enum SpotlightBookmarkSearch {
|
||||
let a = result.item.attributeSet
|
||||
out.append(RetrievedBookmark(
|
||||
title: a.title ?? "Untitled",
|
||||
host: a.url?.host() ?? "",
|
||||
host: a.contentURL?.host() ?? "",
|
||||
description: a.contentDescription ?? "",
|
||||
url: a.url?.absoluteString ?? ""
|
||||
url: a.contentURL?.absoluteString ?? ""
|
||||
))
|
||||
if out.count >= limit { break }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import Testing
|
||||
import CoreSpotlight
|
||||
@testable import Marks
|
||||
|
||||
/// End-to-end guard for the whole Spotlight path: index a bookmark, then
|
||||
/// retrieve it the way the on-device assistant does.
|
||||
///
|
||||
/// Worth keeping. The bug this was written for made every item fail to
|
||||
/// translate into the index while `indexAppEntities` still reported success,
|
||||
/// so nothing short of a round trip would have caught it.
|
||||
struct SpotlightRetrievalTests {
|
||||
@Test func indexedBookmarkIsRetrievable() async throws {
|
||||
let b = Bookmark(
|
||||
id: 999_001,
|
||||
url: "https://github.com/example/zqxjkltest",
|
||||
title: "Zqxjkltest concurrency notes",
|
||||
description: "A distinctive probe document about zqxjkltest.",
|
||||
tagNames: ["zqxjkltest"],
|
||||
dateAdded: Date(), dateModified: Date(),
|
||||
isArchived: false, unread: false, shared: false,
|
||||
websiteTitle: nil, websiteDescription: nil,
|
||||
faviconUrl: nil, previewImageUrl: nil,
|
||||
aiSummary: nil, aiTags: nil
|
||||
)
|
||||
await SpotlightIndexer.index([b])
|
||||
var hits: [RetrievedBookmark] = []
|
||||
for _ in 0..<20 {
|
||||
try? await Task.sleep(for: .milliseconds(700))
|
||||
hits = await SpotlightBookmarkSearch.run(query: "zqxjkltest", limit: 5)
|
||||
if !hits.isEmpty { break }
|
||||
}
|
||||
await SpotlightIndexer.remove(ids: [999_001])
|
||||
#expect(!hits.isEmpty, "Spotlight returned no hits for an indexed bookmark")
|
||||
let hit = try #require(hits.first)
|
||||
#expect(hit.title.contains("Zqxjkltest"))
|
||||
#expect(hit.url == "https://github.com/example/zqxjkltest",
|
||||
"url did not survive the round trip: \(hit.url)")
|
||||
#expect(hit.host == "github.com", "host was \(hit.host)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user