A dame.is & pds.dad production bsky38.com

Suggest who to nominate and vote for: people you engage with, and people you follow #56

Merged dame.is
targeting main from deleted fork:nominate-vote-suggestions
Merged opened by and assigned to dame.is
targeting main from deleted fork:nominate-vote-suggestions

Both pages used to open on an empty handle field. Most people know who they want to put forward but not how to spell the handle, and the poster they mean is almost always someone they already talk to. Two suggestion lists now sit under the input on the nominate page, and one above the pool list on the vote page. Neither writes anything: a pick runs the same select-then-confirm flow the typeahead does, so the single nomination still takes an explicit tap.

People you engage with. The browser reads the signed-in account's own app.bsky.feed.like, repost and post collections straight off their PDS. com.atproto.repo.listRecords is public, so this is a plain fetch with no token, and nothing goes through this server. Every one of those records names its target inside itself: a like's subject.uri, a reply's parent.uri, a quote's embedded record, a mention facet's did. Bluesky clients write at-uris with the author's DID as the authority, so scoring needs no lookups; only the handful of names shown are hydrated afterwards, through the appview's getProfiles. Weights are reply 3, quote 3, repost 2, mention 2, like 1 (src/lib/atproto/engagement.ts). Engagement with oneself counts for nothing.

The scan is progressive by construction (engagement-scan.ts). listRecords pages newest first, so the three streams each hold every record newer than the oldest one they have seen, and a window is complete the moment every stream has read back past its start. The 7-day window lands after a page or two, then 14, 30, 60 and 90 as each completes, each emitted exactly once; the chips in the UI double as the progress indicator and the list follows the deepest finished window until the reader picks one. A stream that hits the page cap (100 pages, 10,000 records) or fails leaves the windows it did not reach starred as partial rather than silently thin. On one heavy account the 7-day list showed in about ten seconds and the whole 90 days read 6,600 records. The PDS base URL is the token audience the OAuth client already resolved at login (auth.pds), read back from the stored session with no request. fetch and now are injectable, so the whole walk runs against canned pages in the tests, including the order windows are emitted in and what happens under a cap, a failing collection and an abort.

The vote page may only offer pool members, so each ranked account is checked against this app's own getProfile, four at a time, and rows carry the page's own Vote control. The ranking runs once per session (engagement.svelte.ts): the vote and nominate pages share it, and sign-out forgets it.

People you follow. The nominate page also offers six of the account's follows at random, from the appview's getFollows, shuffled a page at a time; Show more walks the pool, prefetches the next page as it runs low, and wraps once the last page is in. Already-nominated accounts are marked rather than offered.

Also: the ballot card's Vote and Another buttons drop under the nominee on a phone, where beside it the name had two characters before it truncated; the account menu's ballot link is just "Cast a vote", since the count sits right above it; and the README, CLAUDE.md and env comments now describe the three browser-side appview reads instead of "only the pinned-post embed".

Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com

Commits 5

Rule the blue blocks, and make the mobile tab bar read as cells
Page through the engagement ranking with a Show more button
batch profiles
caching and tid tricks
Suggest who to nominate and vote for: people you engage with, and people you follow