OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer commits / b7a0505

own edges, with their targets intact
`myReactions()` collapses to `targetId -> Set(value)`, which is right for the question a PAGE asks ("is this one liked?") and useless for the question a BRIDGE asks ("what have I liked"). The targetId is a HASH, so the feed, collection and name that built it cannot be recovered — and those are exactly what naming the subject on another network needs. `ownEdges(collection)` returns whole rows with the target normalised, oldest first so a reply lands after the thing it replies to when a history is projected at once. MY OWN FEED ONLY, the same scoping myReactions has and for the same reason: reading across readable feeds would project somebody else's like as mine. Exposed as `edges-by`, a READ — it writes nothing, so it is correctly outside MUTATIONS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b7a0505d0594034a1c43e43614e2d80013aab711
by plankatron <fleeky@gmail.com> · 2026-09-12T01:50:59.000Z

Changed files (2) +41 -0

lib/peer.js +34 -0
……@@ -621,6 +621,40 @@ export class Peer {
621621 return out
622622 }
623623
624 /**
625 * MY OWN edges in one collection, whole — target components included.
626 *
627 * ⛔ `myReactions()` COLLAPSES TO targetId -> Set(value), which is right for rendering a page ("is
628 * this one liked?") and useless for projecting one: the targetId is a HASH, so the feed, collection
629 * and name that built it cannot be recovered from it, and a bridge needs those to name the subject
630 * on another network.
631 *
632 * ⚠ MY OWN FEED ONLY. Reading this across readable feeds would project somebody else's like as mine
633 * — the same reason myReactions is scoped that way.
634 */
635 async ownEdges (collection) {
636 const out = []
637 for (const row of await this.feed.list(collection)) {
638 const fm = (row.record && row.record.frontmatter) || {}
639 const target = Peer.normalizeTarget(fm.target)
640 // A row whose target will not normalise names nothing, so there is nothing to project. Skipped
641 // rather than thrown: one malformed record must not stop a projection pass.
642 if (!target) continue
643 out.push({
644 name: row.key,
645 collection,
646 target,
647 value: fm.value || null,
648 createdAt: fm.createdAt || null,
649 body: (row.record && row.record.body) || ''
650 })
651 }
652 // Oldest first: an edge is projected in the order it was made, so a reply lands after the thing
653 // it replies to when a whole history is projected at once.
654 out.sort((a, b) => String(a.createdAt || '') < String(b.createdAt || '') ? -1 : 1)
655 return out
656 }
657
624658 /** Have I liked this target? The single-target form, for a page that renders one post. */
625659 async likedByMe (target) {
626660 const t = Peer.normalizeTarget(target)
lib/rpc.js +7 -0
……@@ -214,6 +214,13 @@ export async function dispatch (node, cmd, a) {
214214 return withLikes(peer, await peer.timeline({ limit: a.limit || 50 }))
215215 // One author's posts. ⛔ Not a filtered timeline: the timeline excludes peeked feeds, so
216216 // filtering it could never render the page of somebody you had only peeked.
217 // ⛔ MY OWN edges, with their targets intact — what a bridge needs to project a like or a reply.
218 // `reactions`/`likes` answer "who reacted to THIS", which is the page's question; this answers
219 // "what have I reacted TO", which is the projector's.
220 case 'edges-by': {
221 await peer.update()
222 return peer.ownEdges(String(a.collection || ''))
223 }
217224 case 'posts-by': {
218225 await peer.update()
219226 return withLikes(peer, await peer.postsBy(a.feed))