Preserve thread numbering in placeholders (#11616)

This commit is contained in:
DS Boyce
2026-09-02 08:53:49 -07:00
committed by GitHub
parent 83cd5033b2
commit ae971db765
7 changed files with 282 additions and 26 deletions
+54
View File
@@ -0,0 +1,54 @@
import {type app} from '#/lexicons'
import {createFeedViewPostsSlices} from './feed-manip'
jest.mock('./feed/home', () => ({
FALLBACK_MARKER_POST: {post: {uri: 'at://did:plc:test/app.bsky.feed.post/1'}},
}))
const author = {
$type: 'app.bsky.actor.defs#profileViewBasic',
did: 'did:plc:alice',
handle: 'alice.test',
} as app.bsky.actor.defs.ProfileViewBasic
function post(id: string) {
return {
$type: 'app.bsky.feed.defs#postView',
uri: `at://did:plc:alice/app.bsky.feed.post/${id}`,
cid: id,
author,
record: {
$type: 'app.bsky.feed.post',
text: id,
createdAt: '2026-08-31T00:00:00.000Z',
},
indexedAt: '2026-08-31T00:00:00.000Z',
} as app.bsky.feed.defs.PostView
}
describe('createFeedViewPostsSlices', () => {
it('preserves selected numbering and infers hydrated parent and root numbering', () => {
const root = post('root')
const parent = post('parent')
const selected = post('selected')
const feedPost = {
post: selected,
reply: {root, parent},
opThreadPostIndex: 3,
opThreadPostCount: 4,
} as app.bsky.feed.defs.FeedViewPost & {
opThreadPostIndex: number
opThreadPostCount: number
}
const [slice] = createFeedViewPostsSlices([feedPost])
expect(
slice.items.map(item => [item.post.uri, item.postNumbering]),
).toEqual([
[root.uri, {opThreadPostIndex: 1, opThreadPostCount: 4}],
[parent.uri, {opThreadPostIndex: 2, opThreadPostCount: 4}],
[selected.uri, {opThreadPostIndex: 3, opThreadPostCount: 4}],
])
})
})
+20 -14
View File
@@ -9,7 +9,7 @@ export type FeedPostNumbering = Pick<
'opThreadPostIndex' | 'opThreadPostCount'
>
type ValidFeedPostNumbering = Required<FeedPostNumbering>
export type ValidFeedPostNumbering = Required<FeedPostNumbering>
// AppView adds these fields to feed responses ahead of their feed lexicon.
type FeedViewPost = app.bsky.feed.defs.FeedViewPost & FeedPostNumbering
@@ -62,7 +62,7 @@ export type FeedTunerFn = (
type FeedSliceItem = {
post: app.bsky.feed.defs.PostView
record: app.bsky.feed.post.Main
postNumbering: FeedPostNumbering | undefined
postNumbering: ValidFeedPostNumbering | undefined
parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined
isParentBlocked: boolean
isParentNotFound: boolean
@@ -88,7 +88,7 @@ export class FeedViewPostsSlice {
constructor(
feedPost: FeedViewPost,
postNumberingByUri: Map<string, FeedPostNumbering>,
postNumberingByUri: Map<string, ValidFeedPostNumbering>,
) {
const {post, reply, reason} = feedPost
this.items = []
@@ -286,6 +286,22 @@ export class FeedViewPostsSlice {
}
}
export function createFeedViewPostsSlices(
feed: FeedViewPost[],
): FeedViewPostsSlice[] {
const postNumberingByUri = new Map<string, ValidFeedPostNumbering>()
for (const item of feed) {
const postNumbering = getPostNumbering(item)
if (postNumbering) {
postNumberingByUri.set(item.post.uri, postNumbering)
}
}
return feed
.map(item => new FeedViewPostsSlice(item, postNumberingByUri))
.filter(slice => slice.items.length > 0 || slice.isFallbackMarker)
}
export class FeedTuner {
seenKeys: Set<string> = new Set()
seenUris: Set<string> = new Set()
@@ -299,17 +315,7 @@ export class FeedTuner {
dryRun: false,
},
): FeedViewPostsSlice[] {
const postNumberingByUri = new Map<string, FeedPostNumbering>()
for (const item of feed) {
const postNumbering = getPostNumbering(item)
if (postNumbering) {
postNumberingByUri.set(item.post.uri, postNumbering)
}
}
let slices: FeedViewPostsSlice[] = feed
.map(item => new FeedViewPostsSlice(item, postNumberingByUri))
.filter(s => s.items.length > 0 || s.isFallbackMarker)
let slices = createFeedViewPostsSlices(feed)
// run the custom tuners
for (const tunerFn of this.tunerFns) {