Fix starter pack navigation from search and explore (#11563)

This commit is contained in:
Samuel Newman
2026-08-26 20:13:51 +03:00
committed by GitHub
parent ed4126f5a2
commit a0ed2da006
3 changed files with 59 additions and 10 deletions
@@ -147,10 +147,7 @@ export function StarterPackScreenInner({
const isValid =
starterPack &&
(starterPack.list || starterPack?.creator?.did === currentAccount?.did) &&
// Cards may precache a synthetic full view while navigating. Its list CID
// is intentionally empty until the server response replaces it, so use
// the trusted app-view discriminator here instead of strict validation.
bsky.isType(app.bsky.graph.defs.starterPackView, starterPack) &&
bsky.starterPack.isTrustedView(starterPack) &&
bsky.matches(app.bsky.graph.starterpack, starterPack.record)
if (!did || !starterPack || !isValid || !moderationOpts) {
+45 -6
View File
@@ -1,7 +1,11 @@
import {type app} from '#/lexicons'
/* Full schema matching exercises CID validation, so use the real CID parser. */
jest.unmock('multiformats/cid')
import {app} from '#/lexicons'
import {
type AnyStarterPackView,
isBasicView,
isTrustedView,
isView,
} from '#/types/bsky/starterPack'
@@ -9,15 +13,21 @@ const now = () => new Date().toISOString()
const creator = {
$type: 'app.bsky.actor.defs#profileViewBasic',
did: 'did:plc:abc',
handle: 'alice.test',
did: 'did:plc:qrllvid7s54k4hnwtqxwetrf',
handle: 'joshuajfriedman.com',
}
const basicView = {
$type: 'app.bsky.graph.defs#starterPackViewBasic',
uri: 'at://did:plc:abc/app.bsky.graph.starterpack/123',
cid: 'bafypack',
record: {},
uri: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.starterpack/3l4poszxde32k',
cid: 'bafyreiaxduxpwdpjgvve3klfs4flkwjwfqiurszw4o6jvjarpqqmeqwiza',
record: {
$type: 'app.bsky.graph.starterpack',
createdAt: '2024-09-22T03:52:03.686Z',
feeds: [],
list: 'at://did:plc:qrllvid7s54k4hnwtqxwetrf/app.bsky.graph.list/3l4posztwzy2e',
name: 'Bluesky for Art History',
},
creator,
indexedAt: now(),
}
@@ -27,6 +37,18 @@ const fullView = {
$type: 'app.bsky.graph.defs#starterPackView',
}
const {$type: _, ...directFullView} = fullView
const syntheticFullView = {
...fullView,
list: {
uri: 'at://did:plc:abc/app.bsky.graph.list/123',
cid: '',
name: 'Starter pack',
purpose: 'app.bsky.graph.defs#referencelist',
},
}
/*
* Type-level assertions for the view alias: it must accept both the basic and
* the full starter pack view. Compile-time only - a failure surfaces as a
@@ -76,6 +98,23 @@ describe('types/bsky/starterPack guards', () => {
})
})
describe('isTrustedView', () => {
it('accepts a direct full view with an omitted $type', () => {
expect(isTrustedView(directFullView)).toBe(true)
})
it('accepts a typed synthetic view with placeholder fields', () => {
expect(
app.bsky.graph.defs.starterPackView.matches(syntheticFullView),
).toBe(false)
expect(isTrustedView(syntheticFullView)).toBe(true)
})
it('rejects the basic view', () => {
expect(isTrustedView(basicView)).toBe(false)
})
})
it('narrows a view from either world to a readable shape', () => {
/*
* The `$type` string is world-independent, so one guard narrows values from
+13
View File
@@ -25,6 +25,19 @@ export function isView(v: unknown): v is app.bsky.graph.defs.StarterPackView {
)
}
/**
* Accepts both forms of a full starter pack view used by the app:
*
* - direct lexicon refs returned by the app view, where `$type` may be omitted
* - trusted synthetic cache entries, which carry `$type` but may contain
* placeholder fields that do not yet pass full schema validation
*/
export function isTrustedView(
v: unknown,
): v is app.bsky.graph.defs.StarterPackView {
return isView(v) || app.bsky.graph.defs.starterPackView.matches(v)
}
/**
* Matches any starter pack view exported by our SDK.
*/