diff --git a/package.json b/package.json index 64e44823..036d32ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.18.27", + "version": "0.18.28", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/dataLayer/dataStore.js b/src/js/dataLayer/dataStore.js index 2be8ba37..3046bc2a 100644 --- a/src/js/dataLayer/dataStore.js +++ b/src/js/dataLayer/dataStore.js @@ -64,15 +64,26 @@ export class DataStore extends ReactiveStore { } setPosts(posts) { - for (const post of posts) { - this.$posts.set(post.uri, post); - const quotedPost = getQuotedPost(post); + const seenQuotedPostUris = new Set(); + const setQuotedPost = (quotedPost) => { if ( - quotedPost?.$type === "app.bsky.embed.record#viewRecord" && - this.$posts.get(quotedPost.uri) == null + quotedPost?.$type !== "app.bsky.embed.record#viewRecord" || + seenQuotedPostUris.has(quotedPost.uri) ) { - this.$posts.set(quotedPost.uri, embedViewRecordToPostView(quotedPost)); + return; + } + seenQuotedPostUris.add(quotedPost.uri); + + const normalizedQuotedPost = embedViewRecordToPostView(quotedPost); + if (this.$posts.get(quotedPost.uri) == null) { + this.$posts.set(quotedPost.uri, normalizedQuotedPost); } + setQuotedPost(getQuotedPost(normalizedQuotedPost)); + }; + + for (const post of posts) { + this.$posts.set(post.uri, post); + setQuotedPost(getQuotedPost(post)); } } diff --git a/src/js/dataLayer/mutations.js b/src/js/dataLayer/mutations.js index cf9c6211..dabd3e88 100644 --- a/src/js/dataLayer/mutations.js +++ b/src/js/dataLayer/mutations.js @@ -1000,8 +1000,8 @@ export class Mutations { for (const post of hydratedPosts) { // NOTE: LEXICON DEVIATION post.viewer.priorityReply = true; - this.dataStore.$posts.set(post.uri, post); } + this.dataStore.setPosts(hydratedPosts); const rootPost = hydratedPosts[0]; // If it's a reply, update the reply post thread in the store if (replyTo) { diff --git a/src/js/identityPrecaching.js b/src/js/identityPrecaching.js index 1d73736b..d6f708ce 100644 --- a/src/js/identityPrecaching.js +++ b/src/js/identityPrecaching.js @@ -1,4 +1,3 @@ -import { getQuotedPost } from "/js/dataHelpers.js"; import { effect, untrack } from "/js/signals.js"; export function setUpIdentityPrecaching(dataLayer, identityResolver) { @@ -22,20 +21,6 @@ export function setUpIdentityPrecaching(dataLayer, identityResolver) { console.error("error when setting DID from post", post); console.error(error); } - try { - const quotedPost = getQuotedPost(post); - if (quotedPost) { - setDid(quotedPost.author); - // TODO - normalize quoted posts? - const nestedQuotedPost = getQuotedPost(quotedPost); - if (nestedQuotedPost) { - setDid(nestedQuotedPost.author); - } - } - } catch (error) { - console.error("error when setting DID from quoted post", post); - console.error(error); - } } }); diff --git a/tests/unit/specs/dataLayer/dataStore.test.js b/tests/unit/specs/dataLayer/dataStore.test.js index a1705411..3d2d0213 100644 --- a/tests/unit/specs/dataLayer/dataStore.test.js +++ b/tests/unit/specs/dataLayer/dataStore.test.js @@ -30,6 +30,69 @@ describe("setPosts", () => { dataStoreB.$posts.get(post.uri), ); }); + + it("should normalize nested quoted posts", () => { + const dataStore = new DataStore(); + const nestedQuotedPost = { + $type: "app.bsky.embed.record#viewRecord", + uri: "at://did:test/app.bsky.feed.post/nested", + cid: "nested-cid", + author: { did: "did:test", handle: "nested.test" }, + value: { text: "nested" }, + indexedAt: "2026-07-19T00:00:00Z", + }; + const quotedPost = { + $type: "app.bsky.embed.record#viewRecord", + uri: "at://did:test/app.bsky.feed.post/quoted", + cid: "quoted-cid", + author: { did: "did:test", handle: "quoted.test" }, + value: { text: "quoted" }, + embeds: [ + { + $type: "app.bsky.embed.record#view", + record: nestedQuotedPost, + }, + ], + indexedAt: "2026-07-19T00:00:00Z", + }; + const post = { + uri: "at://did:test/app.bsky.feed.post/root", + record: { text: "root" }, + embed: { + $type: "app.bsky.embed.record#view", + record: quotedPost, + }, + }; + + dataStore.setPosts([post]); + + assert.deepEqual(dataStore.$posts.get(quotedPost.uri), { + uri: quotedPost.uri, + cid: quotedPost.cid, + author: quotedPost.author, + record: quotedPost.value, + embed: quotedPost.embeds[0], + labels: undefined, + likeCount: undefined, + replyCount: undefined, + repostCount: undefined, + quoteCount: undefined, + indexedAt: quotedPost.indexedAt, + }); + assert.deepEqual(dataStore.$posts.get(nestedQuotedPost.uri), { + uri: nestedQuotedPost.uri, + cid: nestedQuotedPost.cid, + author: nestedQuotedPost.author, + record: nestedQuotedPost.value, + embed: undefined, + labels: undefined, + likeCount: undefined, + replyCount: undefined, + repostCount: undefined, + quoteCount: undefined, + indexedAt: nestedQuotedPost.indexedAt, + }); + }); }); describe("setConvo", () => { diff --git a/tests/unit/specs/identityPrecaching.test.js b/tests/unit/specs/identityPrecaching.test.js index 5ae414d2..1faac61b 100644 --- a/tests/unit/specs/identityPrecaching.test.js +++ b/tests/unit/specs/identityPrecaching.test.js @@ -50,3 +50,46 @@ describe("notifications precaching", () => { assert.deepEqual(resolvedHandles.get("carol.test"), "did:plc:carol"); }); }); + +describe("post precaching", () => { + it("should cache identities for posts normalized from nested quotes", async () => { + const { dataStore, dataLayer, identityResolver, resolvedHandles } = setup(); + setUpIdentityPrecaching(dataLayer, identityResolver); + + const nestedQuote = { + $type: "app.bsky.embed.record#viewRecord", + uri: "at://did:plc:nested/app.bsky.feed.post/1", + author: { handle: "nested.test", did: "did:plc:nested" }, + value: { text: "nested quote" }, + }; + const quote = { + $type: "app.bsky.embed.record#viewRecord", + uri: "at://did:plc:quote/app.bsky.feed.post/1", + author: { handle: "quote.test", did: "did:plc:quote" }, + value: { text: "quote" }, + embeds: [ + { + $type: "app.bsky.embed.record#view", + record: nestedQuote, + }, + ], + }; + + dataStore.setPosts([ + { + uri: "at://did:plc:root/app.bsky.feed.post/1", + author: { handle: "root.test", did: "did:plc:root" }, + record: { text: "root" }, + embed: { + $type: "app.bsky.embed.record#view", + record: quote, + }, + }, + ]); + await flushEffects(); + + assert.deepEqual(resolvedHandles.get("root.test"), "did:plc:root"); + assert.deepEqual(resolvedHandles.get("quote.test"), "did:plc:quote"); + assert.deepEqual(resolvedHandles.get("nested.test"), "did:plc:nested"); + }); +});