OverviewFilesCommitsIssuesDocsReleasesPeers

favela-gateway commits / db97ccc

0.6.1: a labeler coming online re-runs the member projector
Measured on stortron 2026-10-14: the projector ran once at boot against an hpp that was not yet a labeler ("this DID is not a labeler"). hpp came back with labeler: true and announced a label core, and with an unchanged roster nothing re-ran, so the member badges never appeared. The lens now subscribes to the router's onLabels and re-projects the feeds whose community DID gained the label core. label-intent is idempotent, so a repeat writes nothing. Test-first (red: 1 run, expected 2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
db97ccc71069fba47e81f6c7a5edd55f75c644cd
by plankatron <fleeky@gmail.com> · 2026-10-14T15:06:37.000Z

Changed files (4) +57 -3

lib/lens.js +18 -0
……@@ -175,8 +175,25 @@ export function createLens ({ membrane, feeds = [], log = () => {}, debounceMs =
175175 membrane.peers.front(did, { timeout: frontTimeoutMs }).then((fr) => labels.watch(did, fr)).catch(() => {})
176176 }
177177
178 // ⛔ A LABELER COMING ONLINE IS AN EVENT, NOT ONLY A ROSTER CHANGE. Measured 2026-10-14: the projector's one
179 // run was at boot, against an hpp that was not yet a labeler; hpp came back with `labeler: true` and began
180 // announcing a label core, and with an unchanged roster nothing ever re-ran. The router's onLabels fires
181 // when a front first opens a DID's label core, so re-project the feeds whose community DID that is. Safe to
182 // repeat: label-intent is idempotent (an unchanged state writes nothing).
183 let unLabels = null
184 const onLabelerOnline = (did) => {
185 if (stopped || !memberLabels) return
186 for (const f of state) {
187 if (f.communityDid !== did) continue
188 memberLabels.apply({ id: f.id, communityDid: f.communityDid, members: [...f.members] }).catch(() => {})
189 }
190 }
191
178192 async function start () {
179193 if (labels) await labels.start()
194 if (memberLabels && membrane.peers && typeof membrane.peers.onLabels === 'function') {
195 unLabels = membrane.peers.onLabels((did) => onLabelerOnline(did))
196 }
180197 rooms = await membrane.communityRooms()
181198 for (const f of state) {
182199 f.unwatch = await rooms.watch(f.roomKey, async (members) => {
……@@ -201,6 +218,7 @@ export function createLens ({ membrane, feeds = [], log = () => {}, debounceMs =
201218 async function stop () {
202219 stopped = true
203220 clearTimeout(appendTimer)
221 if (unLabels) { try { unLabels() } catch {} ; unLabels = null }
204222 for (const nsid of Object.keys(XRPC)) membrane.extensions.xrpc.delete(nsid)
205223 for (const path of Object.keys(WS)) membrane.extensions.ws.delete(path)
206224 if (membrane.relay && membrane.relay.seq && membrane.relay.seq.core && membrane.relay.seq.core.off) membrane.relay.seq.core.off('append', onAppend)
package-lock.json +2 -2
……@@ -1,12 +1,12 @@
11{
22 "name": "favela-gateway",
3 "version": "0.6.0",
3 "version": "0.6.1",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "favela-gateway",
9 "version": "0.6.0",
9 "version": "0.6.1",
1010 "dependencies": {
1111 "@ipld/dag-cbor": "^9.2.1",
1212 "ap-bridge": "file:../ap-bridge",
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-gateway",
3 "version": "0.6.0",
3 "version": "0.6.1",
44 "description": "The favela gateway: the bsky membrane (atproto-bridge) and the fediverse bridge (ap-bridge) in one process, keyless towards atproto. Later roles: the bsky lens and a blind seeder (hyper-social #80).",
55 "type": "module",
66 "main": "lib/gateway.js",
test/lens.test.js +36 -0
……@@ -20,14 +20,18 @@ const post = (rkey, createdAt) => ({ uri: 'x/' + rkey, value: { $type: 'app.bsky
2020
2121function fakeMembrane ({ members = [WJ, E2], records, revs }) {
2222 const listeners = []
23 const labelListeners = []
2324 const state = { members, records, revs, listCalls: 0, watchers: [] }
2425 const membrane = {
2526 state,
2627 extensions: { xrpc: new Map(), ws: new Map() },
2728 relay: { seq: { core: { on: (ev, fn) => { if (ev === 'append') listeners.push(fn) }, off: () => {} } } },
2829 appendNow: () => { for (const fn of listeners) fn() },
30 // The router's onLabels: fires when a DID's front first opens a label core (atproto-did-peer).
31 labelsAppear: (did) => { for (const fn of labelListeners) fn(did, { labels: {} }) },
2932 metrics: () => ({ dids: Object.keys(state.revs).map((did) => ({ did, rev: state.revs[did] })) }),
3033 peers: {
34 onLabels: (fn) => { labelListeners.push(fn); return () => { const i = labelListeners.indexOf(fn); if (i >= 0) labelListeners.splice(i, 1) } },
3135 isFronted: (did) => did in state.revs,
3236 front: async (did) => ({
3337 listRecords: async (col, { limit = 100, cursor = null } = {}) => {
……@@ -207,3 +211,35 @@ test('★ with a projector: every roster change is applied, with the community D
207211 t.alike(memberLabels.applied[memberLabels.applied.length - 1].members, [WJ], 'a member leaving is applied too')
208212 t.alike(lens.metrics().memberLabels, { feeds: { 'loser-club': { added: 2 } } })
209213})
214
215// ⛔ MEASURED 2026-10-14 on stortron: the projector's only run was at boot, while loser-club's hpp was not yet a
216// labeler ("this DID is not a labeler"). hpp was restarted with `labeler: true` and began announcing a label
217// core, and nothing re-ran — the roster had not changed, so the badges never appeared. A labeler coming online
218// IS the event: the router's onLabels fires when the community DID's front first opens a label core.
219test('⛔ the community DID gaining a label core re-runs the projector; another DID does not', async (t) => {
220 const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
221 const memberLabels = fakeProjector()
222 const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, memberLabels })
223 await lens.start()
224 t.teardown(() => lens.stop())
225 const before = memberLabels.applied.length
226 t.is(before, 1, 'the boot run')
227
228 m.labelsAppear(WJ)
229 t.is(memberLabels.applied.length, before, 'a member DID\'s label core is not the labeler coming online')
230
231 m.labelsAppear(LC)
232 t.is(memberLabels.applied.length, before + 1, 'the community DID\'s label core re-runs the projector')
233 t.alike(memberLabels.applied[before], { id: 'loser-club', communityDid: LC, members: [WJ, E2] }, 'with the current roster')
234})
235
236test('stop unsubscribes from onLabels — a stopped lens projects nothing', async (t) => {
237 const m = fakeMembrane({ records: baseRecords(), revs: { [WJ]: 'r1', [E2]: 'r1', [LC]: 'r1' } })
238 const memberLabels = fakeProjector()
239 const lens = createLens({ membrane: m, feeds: [{ id: 'loser-club', roomKey: ROOM }], log: () => {}, debounceMs: 5, memberLabels })
240 await lens.start()
241 await lens.stop()
242 const n = memberLabels.applied.length
243 m.labelsAppear(LC)
244 t.is(memberLabels.applied.length, n)
245})