From 2e9cb0e6f0fa31766a82ed7995b14b52c49751a7 Mon Sep 17 00:00:00 2001 From: "@permadeath.com" Date: Mon, 17 Aug 2026 10:46:48 -0400 Subject: [PATCH] feat(popup)!: offer only readers that open both a publication and an article MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reader menu mixed two kinds of entry: Leaflet worked on a publication but greyed out on an article, Anisota did the reverse. Which rows were usable changed with the page, and a greyed row gave no reason, so the menu read as broken rather than as a list of choices. Leaflet turns out to have an article view after all — /lish/{did}/{pubRkey} one level deeper, confirmed by rendering permadeath.com's "How I got a rare GitHub t-shirt". The catalog builds /p/{handle}, the account profile, for every record type, which is how it came to be recorded as having none. Anisota has no publication view (that route returns an empty page), so it moves to REJECTED with the rest, and its vendored icon goes with it. Every remaining reader opens both, so no row is ever greyed for the page you are on; a test pins that. Anyone whose stored default was Anisota falls back to the first reader, which the popup already did for an unknown id. --- public/vendored/README.md | 1 - public/vendored/anisota.png | Bin 2131 -> 0 bytes scripts/capture-status-docs.mjs | 7 +++---- src/lib/readers.test.ts | 33 +++++++++++++++++++---------- src/lib/readers.ts | 36 ++++++++++++++++++++------------ 5 files changed, 48 insertions(+), 29 deletions(-) delete mode 100644 public/vendored/anisota.png diff --git a/public/vendored/README.md b/public/vendored/README.md index 68bb8d0..b371825 100644 --- a/public/vendored/README.md +++ b/public/vendored/README.md @@ -14,7 +14,6 @@ Fetched 2026-08-12: - `bluesky.png` — https://web-cdn.bsky.app/static/favicon-32x32.png, as served. Used on the "Share on Bluesky" button in the popup. -- `anisota.png` — https://anisota.net/anisota-32.png, as served. The reader list itself comes from `@aturi.to/waypoints`, which ships no icons (its React companion does, at the cost of a React dependency). So a diff --git a/public/vendored/anisota.png b/public/vendored/anisota.png deleted file mode 100644 index df547f2357702c77b6392ee8cb0d15d9e52ad201..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2131 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}jKx9jP7LeL$-HD>V5mrq z@J#ddWzb?^VBlb2VU%KE1u0=*V6bD9hO^xmH5iz|;!F$-?U@WLU^WAT9|Hv3fbt-Apt~ikEI14-?iy0UcEkKyjb(&!U0|R@Br;B4q1n1G| zsU6dz1&*hez72aLwwpQdV)k)y=G}s^t|21Ke-*UMBoeyynz~ji%~&yGM_GA8?2Wr? zi=Uf+pZ9#%QM}LsX*Y?_Wb!dH#R7)@jligxwQ24wMnMgVdj6|-rk=2|KHzt z^D=_ZxSyM2`Sk2;b3^0*HJh$2^%j3FoTkiZSNG>dafZx_$jxb+QcenK#_lSa@#*8g zzrT&r&&hcD_>{a&7Qb2l^;PK2oyE&-YJY{iyR%bRcQKEP56ds_+~2MVjx%Ie1ukyO zxw9kCYnqN`@$++2Pfk{^zVMJuLYd)pgqlFl{e89CkB{|EetUbn_tjOQcYPCOx3gSt z+3CT+Z9kv;-;w?{7id^I{`6Bs6Zxy6W}o>ud3fox8h4 zwblIR*$A$=vMJU3=C<6~OZ?~G``)7S^Uu%X5;dWQDa`^)d}bP5+K}jcb8GhWHF0~R zq|Ec)WLnk!D!I9kzsmiB*DA?8SjbX1iBly#pDljw=I$4`6*{#w`aM;#se~W zN0u`1d$FEv_I@B7sqllf*tY76N8H{jRo_`AA9tUdWBE9Fwpnh?lh)8xAuE;mgckR&x2^vd!z=vd-QC?* znU_>dcYOc3G5Po}NnrvH^z`)Ut=!_LE-m#={rv1KpNVU? zn5eLqm~NEH+gn?U_i)TGOg_f5v*>Bpn#j$^E>4!3JZFwfDs!pIv^z{}Rx6ii$L!d! zE`I;L=gINU!hOBGg18M#>vy%SNp7tE{%+;ZsQI?l&-!-;>BsL|Gi_4T`Z(M4%iHts zUlHSA6p~)}<2qOSl~tkMQYIM>Oc_$FYkq9Fv#a#<)P(<6Rt7I#6}~pg^yS6H?Q?9a zudQ|XdS<%*{5dw2L0VyJBv$XJUtYu*ICm1qA%PCf>jgItG&0Ms^Uc=3{$}9^ffXVP z+4K0Z2XYZ}?jd&r|t+y9MOA08iS<>t#UVCZ3TWnvL;*_eBK+osIRYR6f-{I=)aRoRvxsuK~gE_U~` znm$=;wZm<^zt`#sEK$&6T5#vq)@qvV(|>u!~Zq@pFNVbw{enuvvL zQMVSka?dxPFOuDt775iw6mAIr|YeZ3}I9`%wif-D7R5JQl;T)eSC}K4vR9CeqM)|*EW@( ze#otEsXNwnE}yCR+=58mUtf>)N@v+NMR~0*jo!Dv`un?!vMZY=SpCaq*~c$u6A`v9 z=B2K5DdQXU8)@g}Xf9chsk^iI`L{jG73NwL9@^hI+o_f7Cv!emxQ_02c|pH^Ia{kq zeLtu4Kiw1cZuM7AIr$@eQBFr#wlXO7{os|XSn0T<{_`e<{vU2)3>y~&i2gl(U;mZu zTFG0pe(q&zDiaHtWS$>)eV*NX+a;?+rMLTS%eg6YK*vC~XN9DurceXxr^(a0{MuyL z!Za6)2Rb2P)(&Dmb^Vfl;JDdlP3 zio|cIsWJx%9c_8A@vv9swzS}xo0nZVy?JX~md49xX8)@TH*JeuYg`sCxa0Zx`Q_Wf z?5{X@vhzp?>{MV{^dY?KU8`G@+Z4Z!Ws}}bKd|uXqQi`9E { expect(docUrl('standardReader', pub, { ...doc, uri: 'at://did:plc:abc123' })).toBeNull() }) - it('returns null for readers without an article view (they fall back to the publication page)', () => { - expect(docUrl('leaflet', pub, doc)).toBeNull() + it('builds the article under its publication for Leaflet', () => { + expect(docUrl('leaflet', pub, doc)).toBe( + `https://leaflet.pub/lish/${pub.did}/3kpubrkey/3kdocrkey`, + ) + }) + + it('returns null for Leaflet when the publication uri is unparseable', () => { + expect(docUrl('leaflet', { ...pub, uri: 'at://did:plc:abc123' }, doc)).toBeNull() }) it('builds document URLs for readers that support them', () => { @@ -159,12 +165,17 @@ describe('READERS', () => { }) it('offers the readers confirmed against the live services, in catalog order', () => { - expect(READERS.map((r) => r.id)).toEqual([ - 'leaflet', - 'anisotaReader', - 'standardReader', - 'pdsls', - ]) + expect(READERS.map((r) => r.id)).toEqual(['leaflet', 'standardReader', 'pdsls']) + }) + + // The rule the menu depends on: a reader that works on a publication but + // not an article (or the reverse) shows up as a greyed row whose reason is + // invisible, which is why Anisota is not offered. + it('offers only readers that open both a publication and an article', () => { + for (const reader of READERS) { + expect(reader.pubUrl(pub), `${reader.id} publication`).toMatch(/^https:\/\//) + expect(reader.docUrl(pub, doc), `${reader.id} article`).toMatch(/^https:\/\//) + } }) it('carries no catalogued standard.site reader we have not checked', () => { @@ -205,9 +216,9 @@ describe('describe', () => { }) it('varies by record type for the readers whose description does', () => { - const anisota = READERS.find((r) => r.id === 'anisotaReader')! - expect(anisota.describe('site.standard.document')).not.toBe( - anisota.describe('app.bsky.feed.post'), + const standard = READERS.find((r) => r.id === 'standardReader')! + expect(standard.describe('site.standard.document')).not.toBe( + standard.describe('app.bsky.feed.post'), ) }) }) diff --git a/src/lib/readers.ts b/src/lib/readers.ts index a7b4b35..3a2fcf1 100644 --- a/src/lib/readers.ts +++ b/src/lib/readers.ts @@ -37,18 +37,21 @@ // github.com/hipstersmoothie/standard-reader). From that very page the // article link would be a self-link, so it falls back to the publication. // - leaflet.pub renders external standard.site publications at -// /lish/{did}/{pubRkey}. The catalog only builds /p/{handle}, which is -// the account's Leaflet profile, so the publication URL is ours. -// External documents are not reachable in Leaflet at all. -// - anisota.net renders external documents at -// /profile/{did}/document/{docRkey} (the catalog's builder, confirmed by -// rendering brennan.day's "The Weight of Words"). The same route with a -// publication rkey renders nothing, so Anisota has no publication view. +// /lish/{did}/{pubRkey} and their articles one level down, at +// /lish/{did}/{pubRkey}/{docRkey} (confirmed 2026-08-17 by rendering +// permadeath.com's "How I got a rare GitHub t-shirt"). The catalog builds +// /p/{handle} — the account's Leaflet profile — for every record type, so +// both URLs are ours. // - pdsls.dev renders any record at /{at-uri}, documents included. Ours and // the catalog's builder produce the same string. // // Catalogued and deliberately not offered: // +// - anisota.net renders external documents at +// /profile/{did}/document/{docRkey}, but the same route with a +// publication rkey returns an empty page, so it has no publication view. +// Every reader offered here now works on both a publication and an +// article, so no entry is ever greyed out for the page you are on. // - offprint.app and pckt.blog (`publications` category) 404 on the URLs // the catalog builds for them, under DID and handle form alike, for // publications and documents. Left out until they resolve; reporting @@ -119,12 +122,17 @@ const CONFIRMED: Record = { const rkey = parseAtUri(pub.uri)?.rkey return rkey ? `https://leaflet.pub/lish/${pub.did}/${rkey}` : null }, - doc: 'none', - }, - anisotaReader: { - icon: '/vendored/anisota.png', - pub: 'none', - doc: 'catalog', + // The article sits under its publication, so this needs both rkeys. The + // catalog builds /p/{handle} — the account's Leaflet profile — for every + // record type, which is why this reader was long recorded as having no + // article view at all. + doc: (pub, doc) => { + const pubRkey = parseAtUri(pub.uri)?.rkey + const docRkey = parseAtUri(doc.uri)?.rkey + return pubRkey && docRkey + ? `https://leaflet.pub/lish/${pub.did}/${pubRkey}/${docRkey}` + : null + }, }, pdsls: { icon: '/vendored/pdsls.png', @@ -144,6 +152,8 @@ const REJECTED: Record = { aturi: 'aturi.to does not resolve from here, so nothing could be rendered or claimed', anisota: 'builds the same anisota.net/profile/{did}/document/{rkey} URL as anisotaReader for standard.site records; one entry per destination', + anisotaReader: + 'renders documents but has no publication view (the same route with a publication rkey returns an empty 732-byte page, 2026-08-17), and a reader that works on only half the pages it is offered on reads as broken', } /** Reader used when the user has not stored an explicit choice. */ -- 2.51.2