Experimental Bluesky client for agents
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498import { afterAll, beforeAll, describe, expect, test } from 'bun:test';import { mkdtemp, readFile, rm } from 'node:fs/promises';import { tmpdir } from 'node:os';import { join } from 'node:path';import { chromium, type Browser } from 'playwright';import type { FeedSnapshot, PostNode, ReadingView, ThreadSnapshot } from '../src/model.ts';import { captureView, renderHtml, renderText } from '../src/render.ts';import { ReadingStore } from '../src/store.ts';
const fetchedAt = '2026-09-05T16:02:34.000Z';const source = { mode: 'authenticated' as const, service: 'https://pds.example', viewerDid: 'did:plc:feedviewer',};
function post(id: string, overrides: Partial<PostNode> = {}): PostNode { return { uri: `at://did:plc:${id}/app.bsky.feed.post/${id}`, availability: 'available', author: { did: `did:plc:${id}`, handle: `${id}.example`, displayName: id }, text: `Original speech from ${id}.`, createdAt: '2026-09-05T15:02:34.000Z', indexedAt: '2026-09-05T15:02:36.000Z', replies: [], repliesFetched: false, facets: [], media: [], quotes: [], cards: [], labels: [], unsupported: [], counts: {}, ...overrides, };}
function feedSnapshot(entries: FeedSnapshot['entries'], nodes: PostNode[]): FeedSnapshot { return { kind: 'feed', feed: 'following', fetchedAt, source, entries, nodes, limit: 20, cursor: 'opaque-next-page', maxNodes: 600, locallyTruncated: false, warnings: [], };}
function storedView(snapshot: FeedSnapshot | ThreadSnapshot, session = 'feed-render'): ReadingView { const store = new ReadingStore(':memory:'); try { const position = store.position(session); return store.commit(session, snapshot, position.revision); } finally { store.close(); }}
let browser: Browser;beforeAll(async () => { browser = await chromium.launch(); });afterAll(async () => { await browser?.close(); });
describe('following-feed rendering', () => { test('keeps duplicate occurrences distinct while provenance, refs, and engagement stay compact and exact', async () => { const node = post('shared', { text: 'One canonical post surfaced twice.', counts: { replies: 0, likes: 3_327, reposts: 920 }, }); const snapshot = feedSnapshot([ { id: '1', postUri: node.uri }, { id: '2', postUri: node.uri, reason: { kind: 'repost', by: { did: 'did:plc:reposter', handle: 'reposter.example', displayName: 'Reposter' }, indexedAt: '2026-09-05T15:32:34.000Z', }, }, ], [node]); const view = storedView(snapshot); const original = JSON.stringify(view); const text = renderText(view); expect(text).toContain('Occurrence ID: 1'); expect(text).toContain('Occurrence ID: 2'); expect(text).toContain('Reason actor: Reposter'); expect(text.match(/One canonical post surfaced twice\./gu)).toHaveLength(2);
const page = await browser.newPage({ viewport: { width: 1280, height: 960 } }); try { await page.setContent(renderHtml(view)); const cards = page.locator('.feed-card'); expect(await cards.count()).toBe(2); expect(await cards.evaluateAll((elements) => elements.map((element) => element.getAttribute('data-feed-occurrence')))).toEqual(['1', '2']); expect(await cards.nth(0).locator('.feed-reason').count()).toBe(0); expect(await cards.nth(1).locator('.feed-reason').innerText()).toContain('Reposted by'); expect(await cards.nth(1).locator('.feed-reason').innerText()).toContain('Reposter'); const ordinaryReferenceStyle = await cards.nth(0).locator('.feed-author-identity .reference').evaluate((element) => { const style = getComputedStyle(element); return { color: style.color, backgroundColor: style.backgroundColor }; }); const reasonReferenceStyle = await cards.nth(1).locator('.feed-reason .reference').evaluate((element) => { const style = getComputedStyle(element); return { color: style.color, backgroundColor: style.backgroundColor }; }); expect(reasonReferenceStyle).toEqual(ordinaryReferenceStyle); expect(reasonReferenceStyle.color).not.toBe(reasonReferenceStyle.backgroundColor); expect(await cards.nth(0).innerText()).toContain('[v1-e1]'); expect(await cards.nth(1).innerText()).toContain('[v1-e2]'); expect(await cards.nth(0).innerText()).toContain('[v1-p1]'); expect(await cards.nth(1).innerText()).toContain('[v1-p1]'); expect(await cards.nth(0).innerText()).toContain('1h ago'); expect(await cards.nth(0).innerText()).not.toContain('before page entry'); expect(await cards.nth(0).locator('.feed-count strong').allTextContents()).toEqual(['0', '3.3K', '920', '—']); expect(await cards.nth(0).locator('.feed-record-details').textContent()).toContain('Likes 3327'); expect(await cards.nth(0).locator('.feed-copy.body').evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize))).toBeGreaterThanOrEqual(16); expect(await page.locator('.feed-card .reference').evaluateAll((elements) => elements.every((element) => getComputedStyle(element).textTransform === 'none'))).toBe(true); expect(await page.locator('.feed-card .reference').evaluateAll((elements) => elements.every((element) => element.textContent === element.textContent?.toLowerCase()))).toBe(true); expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)).toBe(true); } finally { await page.close(); } expect(JSON.stringify(view)).toBe(original); });
test('keeps feed prose, reason, identity, alt, and external preview data inert without changing exact source', async () => { const injected = '<script>globalThis.feedSourceRan = true</script><iframe src="https://bad.example"></iframe><img src=x onerror="globalThis.feedSourceRan=true">'; const node = post('unsafe', { author: { did: 'did:plc:unsafe', handle: 'unsafe.example', displayName: injected, avatar: 'javascript:globalThis.feedSourceRan=true', }, text: injected, media: [{ kind: 'image', url: 'javascript:globalThis.feedSourceRan=true', alt: injected }], cards: [{ uri: 'javascript:globalThis.feedSourceRan=true', title: injected, description: injected }], }); const view = storedView(feedSnapshot([{ id: '1', postUri: node.uri, reason: { kind: 'unknown', type: injected, by: { did: 'did:plc:reason', handle: 'reason.example', displayName: injected }, }, }], [node]), 'unsafe-feed'); const original = JSON.stringify(view); const html = renderHtml(view); const page = await browser.newPage(); try { await page.setContent(html); expect(await page.locator('script, iframe, [onerror], [onload]').count()).toBe(0); expect(await page.locator('.feed-card').innerText()).toContain(injected); expect(await page.evaluate(() => 'feedSourceRan' in globalThis)).toBe(false); expect(html).toContain("default-src 'none'"); expect(html).toContain('img-src https://cdn.bsky.app'); } finally { await page.close(); } expect(renderText(view)).toContain(injected); expect(JSON.stringify(view)).toBe(original); });
test('uses shallow parent and quote excerpts while one disclosure retains every exact original text', async () => { const primaryText = `Primary exact text: ${'long original speech '.repeat(30)}`; const parentText = `Parent exact text: ${'earlier context '.repeat(20)}`; const quoteText = `Quote exact text: ${'another voice '.repeat(20)}`; const rootText = 'Hydrated root text retained in exact details without another feed card.'; const parent = post('parent', { text: parentText }); const root = post('root', { text: rootText }); const quote = post('quote', { text: quoteText }); const primary = post('focus', { text: primaryText, parentUri: parent.uri, rootUri: root.uri, quotes: [quote.uri], }); const view = storedView(feedSnapshot([ { id: '1', postUri: primary.uri, parentUri: parent.uri, rootUri: root.uri }, ], [primary, parent, root, quote])); const text = renderText(view); for (const exact of [primaryText, parentText, quoteText, rootText]) expect(text).toContain(exact);
const page = await browser.newPage({ viewport: { width: 1280, height: 960 } }); try { await page.setContent(renderHtml(view)); expect(await page.locator('[data-panel-block]').count()).toBe(1); expect(await page.locator('.feed-context-stack > .feed-glimpse').count()).toBe(2); expect(await page.locator('[data-excerpted="true"]').count()).toBe(3); expect(await page.locator('.feed-copy.body.excerpted').evaluate((element) => getComputedStyle(element).webkitLineClamp)).toBe('6'); expect(await page.locator('.feed-copy.glimpse.excerpted').evaluateAll((elements) => elements.map((element) => getComputedStyle(element).webkitLineClamp))).toEqual(['2', '2']); const details = page.locator('.feed-record-details'); expect(await details.getAttribute('open')).toBeNull(); const retained = await details.textContent(); for (const exact of [primaryText, parentText, quoteText, rootText]) expect(retained).toContain(exact); expect(await page.locator('.feed-context-stack').innerText()).not.toContain(rootText); expect(await page.locator('.feed-glimpse .reference').evaluateAll((elements) => elements.every((element) => getComputedStyle(element).textTransform === 'none'))).toBe(true); } finally { await page.close(); } });
test('bounds galleries and external previews while unknown, zero, large, and unavailable context remain distinct', async () => { const primary = post('bounded', { text: '', counts: { replies: 0, reposts: 1_000_000, quotes: 0 }, parentUri: 'at://did:plc:missing/app.bsky.feed.post/parent', media: [ { kind: 'image', width: 640, height: 480, alt: 'first' }, { kind: 'video', width: 1920, height: 1080 }, { kind: 'image', alt: '' }, { kind: 'image' }, ], cards: [ { uri: 'https://example.invalid/one', title: 'First card', description: 'First exact description' }, { uri: 'https://example.invalid/two', title: 'Second card', description: 'Second exact description' }, ], }); const view = storedView(feedSnapshot([ { id: '1', postUri: primary.uri, parentUri: primary.parentUri }, ], [primary])); const page = await browser.newPage({ viewport: { width: 1280, height: 960 } }); try { await page.setContent(renderHtml(view)); expect(await page.locator('.feed-card > .empty-copy').count()).toBe(0); expect(await page.locator('.feed-media .media-item').count()).toBe(2); expect(await page.locator('.feed-media').innerText()).toContain('2 more media previews omitted'); expect(await page.locator('.feed-media').innerText()).toContain('[v1-m3]'); expect(await page.locator('.feed-media').innerText()).toContain('[v1-m4]'); expect(await page.locator('.feed-media').textContent()).toContain('Video poster · not playback'); expect(await page.locator('.feed-link-preview').count()).toBe(1); expect(await page.locator('.feed-links').innerText()).toContain('1 more external-card preview omitted'); expect(await page.locator('.feed-glimpse-missing').innerText()).toContain('Parent text unavailable; this occurrence remains a reply.'); expect(await page.locator('.feed-count strong').allTextContents()).toEqual(['0', '—', '1M', '0']); const visible = await page.locator('.feed-card').innerText(); expect(visible).not.toContain('640 × 480'); const details = await page.locator('.feed-record-details').textContent(); expect(details).toContain('Text is present and empty.'); expect(details).toContain('640 × 480'); expect(details).toContain('Reposts 1000000'); expect(details).toContain('Second exact description'); } finally { await page.close(); } });
test('renders honest empty and unavailable feed surfaces without invented values', async () => { const emptySnapshot = feedSnapshot([], []); delete emptySnapshot.cursor; const empty = storedView(emptySnapshot, 'empty-feed'); expect(renderText(empty)).toContain('FEED OCCURRENCES: <empty>'); expect(renderText(empty)).toContain('Next cursor: <not supplied; next page unavailable>'); const emptyPage = await browser.newPage(); try { await emptyPage.setContent(renderHtml(empty)); expect(await emptyPage.locator('.feed-empty').innerText()).toContain('No feed occurrences supplied'); expect(await emptyPage.locator('.feed-card').count()).toBe(0); } finally { await emptyPage.close(); }
const blocked = post('blocked', { availability: 'blocked', author: undefined, text: undefined, counts: {}, }); const unavailable = storedView(feedSnapshot([{ id: '1', postUri: blocked.uri }], [blocked]), 'blocked-feed'); const unavailablePage = await browser.newPage(); try { await unavailablePage.setContent(renderHtml(unavailable)); const card = unavailablePage.locator('.feed-card.unavailable'); expect(await card.innerText()).toContain('Post availability: blocked'); expect(await card.innerText()).toContain('Author identity not supplied'); expect(await card.innerText()).toContain('Post text was not supplied.'); expect(await card.locator('.feed-count strong').allTextContents()).toEqual(['—', '—', '—', '—']); } finally { await unavailablePage.close(); } });
test('adds an encounter breadcrumb only to threads opened from a feed occurrence', async () => { const focus = post('thread-focus'); const snapshot: ThreadSnapshot = { kind: 'thread', requested: focus.uri, focusUri: focus.uri, fetchedAt, source, nodes: [focus], ancestors: [], replyOrder: 'source', limits: { depth: 40, parentHeight: 40, maxNodes: 60 }, locallyTruncated: false, warnings: [], encounter: { feedViewId: 'v7', occurrenceId: '14', postUri: focus.uri, reason: { kind: 'repost', by: { did: 'did:plc:encounter', handle: 'encounter.example', displayName: 'Encounterer' }, indexedAt: '2026-09-05T15:32:34.000Z', }, }, }; const encountered = storedView(snapshot, 'encountered-thread'); expect(renderText(encountered)).toContain('Encounter feed view: v7'); expect(renderText(encountered)).toContain('Encounter occurrence ID: 14'); expect(renderText(encountered)).toContain('Encounter Reason actor: Encounterer'); const page = await browser.newPage(); try { await page.setContent(renderHtml(encountered)); const breadcrumb = page.locator('.encounter-breadcrumb'); expect(await breadcrumb.innerText()).toContain('View v7 · occurrence 14'); expect(await breadcrumb.innerText()).toContain('Reposted by'); expect(await breadcrumb.innerText()).toContain('Encounterer'); expect(await page.locator('[data-panel-block]').count()).toBe(1); } finally { await page.close(); }
const plainSnapshot = structuredClone(snapshot); delete plainSnapshot.encounter; const plain = storedView(plainSnapshot, 'plain-thread'); expect(renderText(plain)).not.toContain('Encounter feed view:'); const plainPage = await browser.newPage(); try { await plainPage.setContent(renderHtml(plain)); expect(await plainPage.locator('.encounter-breadcrumb').count()).toBe(0); } finally { await plainPage.close(); } });
test('wraps an unbroken overview warning without widening the reading surface', async () => { const node = post('warning'); const snapshot = feedSnapshot([{ id: '1', postUri: node.uri }], [node]); const warning = `Warning:${'X'.repeat(10_000)}`; snapshot.warnings = [warning]; const view = storedView(snapshot, 'warning-feed'); expect(renderText(view)).toContain(warning); const page = await browser.newPage({ viewport: { width: 1280, height: 960 } }); try { await page.setContent(renderHtml(view)); expect(await page.locator('.notice').innerText()).toBe(warning); expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)).toBe(true); expect(await page.locator('.notice').evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true); } finally { await page.close(); } });
test('captures five ordinary entries in one readable panel with paired exact source outputs', async () => { const nodes = Array.from({ length: 5 }, (_, index) => post(`ordinary-${index + 1}`)); const entries = nodes.map((node, index) => ({ id: String(index + 1), postUri: node.uri })); const view = storedView(feedSnapshot(entries, nodes), 'capture-feed'); const directory = await mkdtemp(join(tmpdir(), 'perch-feed-render-')); try { const files = await captureView(view, { outputDir: directory }); expect(files.images.filter((path) => path.includes('-panel-'))).toHaveLength(1); expect(JSON.parse(await readFile(files.json, 'utf8'))).toEqual(view); expect(await readFile(files.text, 'utf8')).toBe(renderText(view)); const html = await readFile(files.html, 'utf8'); expect(html).toContain('Following feed in supplied source order'); expect(html).toContain("default-src 'none'"); const png = await readFile(files.images.find((path) => path.includes('-panel-'))!); expect(png.subarray(0, 8)).toEqual(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])); expect(png.readUInt32BE(16)).toBeGreaterThan(1_000); } finally { await rm(directory, { recursive: true, force: true }); } }, 30_000);});
describe('author-feed rendering', () => { test('reuses compact feed cards under an actor-scoped heading with exact paging and return context', async () => { const actor = { did: 'did:plc:profilefixture', handle: 'reader.example', displayName: 'Profile Reader', pronouns: 'she/her', }; const node = post('author-post', { author: actor, text: 'Compact author-feed speech.', parentUri: 'at://did:plc:parent/app.bsky.feed.post/parent', rootUri: 'at://did:plc:root/app.bsky.feed.post/root', media: [{ kind: 'image', url: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', alt: 'One bounded image' }], counts: { replies: 2, likes: 3_327, reposts: 4, quotes: 1 }, }); const snapshot = feedSnapshot([{ id: 'author-occurrence-1', postUri: node.uri, reason: { kind: 'repost', by: { did: 'did:plc:curator', handle: 'curator.example', displayName: 'Curator' }, indexedAt: '2026-09-05T15:32:34.000Z', }, }], [node]); snapshot.feed = 'author'; snapshot.actor = actor; snapshot.limit = 3; snapshot.requestCursor = 'opaque-request-page'; snapshot.cursor = 'opaque-next-page'; const view = storedView(snapshot, 'author-feed-render'); const original = JSON.stringify(view); const profileReference = Object.entries(view.refs).find(([, target]) => target.kind === 'profile' && target.value === actor.did); const occurrenceReference = Object.entries(view.refs).find(([, target]) => target.kind === 'occurrence' && target.value === 'author-occurrence-1'); expect(profileReference).toBeDefined(); expect(occurrenceReference).toBeDefined(); const text = renderText(view); expect(text).toContain('AUTHOR FEED'); expect(text).toContain('AUTHOR FEED ACTOR'); expect(text).toContain('Author: Profile Reader'); expect(text).toContain('Handle: @reader.example'); expect(text).toContain('DID: did:plc:profilefixture'); expect(text).toContain('Request cursor: opaque-request-page'); expect(text).toContain('Next cursor: opaque-next-page'); expect(text).toContain('This page is explicitly scoped to the supplied actor');
const page = await browser.newPage({ viewport: { width: 1280, height: 960 } }); try { await page.setContent(renderHtml(view)); expect(await page.locator('h1').innerText()).toBe('Recent posts from Profile Reader'); expect(await page.locator('#overview-title').innerText()).toBe('Author posts page'); expect(await page.locator('body').innerText()).not.toContain('Following page'); const actorHeading = page.locator('.author-feed-heading'); expect(await actorHeading.innerText()).toContain('@reader.example'); expect(await actorHeading.innerText()).toContain(`[${profileReference![0]}]`); expect(await page.locator('.feed-sequence').getAttribute('aria-label')).toBe('Author feed in supplied source order'); const card = page.locator('.feed-card'); expect(await card.count()).toBe(1); expect(await card.innerText()).toContain('Compact author-feed speech.'); expect(await card.innerText()).toContain(`[${occurrenceReference![0]}]`); expect(await card.locator('.feed-reason').innerText()).toContain('Reposted by'); expect(await card.locator('.feed-reason').innerText()).toContain('Curator'); expect(await card.locator('.feed-media .media-item').count()).toBe(1); expect(await card.locator('.feed-engagement').innerText()).toContain('3.3K'); expect(await card.locator('.feed-count').first().getAttribute('title')).toBe('Replies: 2'); expect(await page.locator('.feed-page-details').textContent()).toContain('opaque-request-page'); expect(await page.locator('.feed-page-details').textContent()).toContain('opaque-next-page'); expect(await page.locator('.footer-note').innerText()).toContain('Return to the profile through the actor reference.'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)).toBe(true); } finally { await page.close(); } expect(JSON.stringify(view)).toBe(original); });
test('keeps absent and blank media URLs inert while making each supplied state explicit', async () => { const node = post('blank-media', { author: { did: 'did:plc:blankmedia', handle: 'blank.example', displayName: ' ', avatar: ' ', }, media: [ { kind: 'image', alt: 'No source field' }, { kind: 'image', url: ' ', alt: 'Blank source field' }, ], cards: [{ uri: 'https://example.com/card', title: 'Text-only card', description: 'No image should be requested.', thumbnail: '' }], }); const snapshot = feedSnapshot([{ id: 'blank-occurrence', postUri: node.uri }], [node]); snapshot.feed = 'author'; snapshot.actor = node.author; const view = storedView(snapshot, 'author-feed-blank-media'); const text = renderText(view); expect(text).toContain('URL: <not supplied>'); expect(text).toContain('URL: <present but blank: " ">'); expect(text).toContain('Thumbnail: <present but empty>');
const page = await browser.newPage(); try { await page.setContent(renderHtml(view)); expect(await page.locator('h1').innerText()).toBe('Recent posts from @blank.example'); expect(await page.locator('.author-feed-heading .author-name').innerText()).toBe('@blank.example'); expect(await page.locator('img').count()).toBe(0); expect(await page.locator('.feed-author-row .feed-avatar-state').innerText()).toBe('avatar URL blank'); expect(await page.locator('.feed-media .media-missing').count()).toBe(2); expect(await page.locator('.feed-media').innerText()).toContain('No preview URL supplied'); expect(await page.locator('.feed-media').innerText()).toContain('Preview URL is present but blank'); expect(await page.locator('.feed-link-preview.no-thumbnail').count()).toBe(1); expect(await page.locator('.feed-record-details').textContent()).toMatch(/Thumbnail:\s*present and empty/iu); expect(await page.locator('[src=""]').count()).toBe(0); } finally { await page.close(); } });});