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. */