From f2b13c6ab9fe0a7acad62f608de230421b8d4cdc Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 1 Sep 2026 10:29:14 -0700 Subject: [PATCH] Bump welshman --- package.json | 20 +++++------ pnpm-lock.yaml | Bin 398156 -> 398156 bytes src/app/feeds.ts | 91 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index a9781eac..cfcd1d60 100644 --- a/package.json +++ b/package.json @@ -78,16 +78,16 @@ "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^1.0.2", "@vite-pwa/sveltekit": "^1.1.0", - "@welshman/app": "^0.9.7", - "@welshman/content": "^0.9.7", - "@welshman/domain": "^0.9.7", - "@welshman/editor": "^0.9.7", - "@welshman/feeds": "^0.9.7", - "@welshman/lib": "^0.9.7", - "@welshman/net": "^0.9.7", - "@welshman/signer": "^0.9.7", - "@welshman/store": "^0.9.7", - "@welshman/util": "^0.9.7", + "@welshman/app": "^0.9.8", + "@welshman/content": "^0.9.8", + "@welshman/domain": "^0.9.8", + "@welshman/editor": "^0.9.8", + "@welshman/feeds": "^0.9.8", + "@welshman/lib": "^0.9.8", + "@welshman/net": "^0.9.8", + "@welshman/signer": "^0.9.8", + "@welshman/store": "^0.9.8", + "@welshman/util": "^0.9.8", "cheerio": "^1.2.0", "compressorjs-next": "^1.1.2", "dompurify": "^3.4.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99f70844dd684535dd5f0f4831248e1bf1e21aba..82c00b6febe18779715dfcbc92830dd75ef672c2 100644 GIT binary patch delta 3551 zcmdT`Ta4V)8J5Y}W_J@v%5GR7>_RqDvM@AbkL?+c+6v~n;~Cp?^ITtAk3F9I*dCAP zVx^=}TjeoIEpd=gC1_RNn$n_bg#=Q0srryU@PI%iB!s>|ABv(@6nabT+3Ye&phJ`v zsUyufGnVc1f8YQA-}hg5;r$SwCGOrf&BT7%uuL9P^<>6(jvPTM8@LiRndX?^?fblBU)bnbx@ z-+TY?*87K%wY>G?OULthBH&7y!D=`@bW3fsT?iz?T|I0{ZK~ah8}-I8+Z4Syz9rM4 zMoX*J4car1Gr(hA`n5BfDLaAw^a?Y$fRvWFbAX;`}m z*6i)|OW>hxQl0JGW$^I!wgex$0-OrhbY#`u0#6|);Hx^a-w81nug5D4J7h5}8F39m zy=0@2j7r^>rUj@Go%W>!cg&+C&C#YjX1RpOnDIoLY*Mkdi!Y)^ldF`+1YL8L#AG1D z>AiBOlGUx#PalE5HISoDDSri??IUxO!_b13@Zgz-n~Wq&qbV)#JJ7`?h&Y&6@EPPK%`f?hm2;I$OtW&M*-etjO?v-Oh!vU=C2oL?Rx z$;q*iu$-k@Ejc$<2U^ENcgaGfh2=^uBbJu1m@6t*!kIe9_KQkwTqtzhMp>5$uhtAz zV^O)$4mR>=+Kfq+h}?6>3gd2y9v7i@(x%#`z*aY>siyMpz+RyiZM`}Em8x% zZZ|4VI-T_VrHvfRbhUw5Fe5oeis{-gV}_Mdu&yd%&6Vv7QPvn_-S|MQ;AtKcMLITM zxDM8&f^v;%xhwcsqVk>=o+QRNpHC0HLCReVTj#$1kahMS_rOm~Xz)LXAj`>~cMU z&MDz!NKlAY05fmEy_4XNZK-eyTccH)ka&^LyGLF@FP0;Iw#x@yY#?N0QJv`|M!GxF zmp0YRz!>S>QN2jgEiGQ?*D*QKafvlQ>#y`g-jnTz2^s5-Q*_iW?4$5oe**{Lu~Wza zc+&ax<^r+?59)}cNY}~ViN!!3ThRMTuewXO%Nt3hI8R0<`3fJ1Myn_v#uZnb3wh(g zaHH!%-SI%Dq3V9qFAlj}kmrTWh%>NeQeZGWSc`drzGl5XjEXrUlgy3^NguJ(0aNL} z9)aEKz6l%jAdyjvxd>M3Fnxn_jSHHpM6-U<&E@+>BEol)rdko2WQZ-b2`1eiVabGo zZH~OEg4Oek%cy4gm@EbQbSK01jcSqU2{*8@Zo{{`>&ThrDa8KXwbeYer+w`|I_e!d zisQo^{LX3cqh01d)8Qxn;fUewS^TzNq&@)7 zh5tDXJKnb7DDK@~U|?`20GpGU)^~?Fa$NsLLH_Tj$hLH6pqn+n++yO)bY9`r_rOz5 zn&yu=CAA7)`U8-@2#?4cd)B7yXJ#D#2aMia^zfArz!NqZGsns+KLa^sUTu(k`=wkLKL=+xhVJqmuB6(_;OzFn(2F*`lCbdwv#|2doOTMmBn t_j5S>&A+BZR#-T`3uF~muYs@GXuffFZVf&|g2&Iia}8|0b8Svp`Zoc*LM8wJ delta 3519 zcmdT`ON`sb83wbn-G^=1&f0NocQfJ}j+Ky!$1b&>|fqCj)VCCI@!Mc4bt@K4z=su&Dw!# z)G1H_nvn(^&i{S?Ki~IXyYlF@E04Z3GmFt9^UxtQUOKXXMq>6b8lOMBj7E9xsnLtG zdoUWld=q>ZEqwKZ{GCk@dk?5P{|K*KoxeGQR(|^MBg^!%Les3GFdEBh1jSM;?$ua| zQhXlVqbMq?KQaF}`oNbg?9k|ghmUXULlLZh02uhE_rUBZvv71{^;`5ZLlBIYDtake zC0U)2Ju<2JRFaU3l6`^UEf-YA@BkDmR}$H&(`K8`JA zN1v^q7Ohq#WJ}FKbv4(bJcVGr=jQcXgQl`-CL1PQwxWwUBArQ9%e<0k1hPizd_&Oq z8YLLHY6@R**Euy!`(;xtb&9d`sbV}39bJ0v+r-P!d@ykj`0|Xu&GvlaYFmYcs0Ehl(q?7AM5pfkd!Fpa7T67F7PRicGJyJVLem1<1%=^ZAU=cvR=X~p8g(T<&E zx=C-E7PGt@342@~SI~CV)0|giM9xQ*%i2Jo8VvT zV%~8mcsoeaZll^awP8fSi@iV5>*zI zLbO*(49ZbmjE9ez9(q8*t9{vJc6=4Tr&k^J!eX^qQGBAn39Q@S(nvS0 zNpiwS6BPoQ7cf73_XcpP4qd4em#Y^s^nGZpdU2|L8|nJj<{ggD3h)P)z%RErzo&zb z-NI48`?K&3e@K1;oX%iN)YpFZWAIJ}`R*hahu9ycmd4tA;~gM0a6^Q4+@HOl0vEEx z{|Qak{tQrOxoL(EzV7a#=@*}alklS#rk3Dhe|kS`Q2@YCKL?>LxVzifSN}2&a5qU% z{Q?Z`U$8dn78z4~66oaL0qXy@v*IL}+Oug=) => state?.status === "loading" || state?.status === "searching" -const makeFeedLoader = (load: () => Promise<{found: number; exhausted: boolean}>) => { +const makeFeedLoader = (load: () => Promise) => { const state = writable({status: "idle"}) let running = false @@ -348,17 +353,21 @@ const makeFeedLoader = (load: () => Promise<{found: number; exhausted: boolean}> for (let span = 0; span < SPANS_PER_TRIGGER; span++) { state.set({status: span > 0 ? "searching" : "loading"}) - const {found, exhausted} = await load() + const {found, complete, exhausted} = await load() if (exhausted) { state.set({status: "exhausted"}) return } - if (found > 0) { - state.set({status: "idle"}) - return + // A span nobody answered for hasn't moved the window, so hold here rather than walking + // past it, and give the socket a moment before the scroller comes back around + if (!complete) { + await sleep(ms(3)) + break } + + if (found > 0) break } state.set({status: "idle"}) @@ -376,7 +385,7 @@ const makeFeedLoader = (load: () => Promise<{found: number; exhausted: boolean}> // ordinary feed scrolls toward the end of its own content. export const makeScrollLoader = ( element: HTMLElement, - load: () => Promise<{found: number; exhausted: boolean}>, + load: () => Promise, options: Partial = {}, ) => { const loader = makeFeedLoader(load) @@ -452,41 +461,58 @@ export const makeFeed = ({ const unsubscribers = syncFeed({relays, filters, events, seen, insertEvents}) const loadTimeframe = async (since: number, until: number) => { + let complete = false + const found = await network.get().request({ relays, autoClose: true, signal: controller.signal, filters: filters.map(filter => ({...filter, since, until})), + onEose: () => { + complete = true + }, }) // A span that turns up nothing widens the next one, so walking a sparse history doesn't // take dozens of round trips - interval = found.length > 0 ? int(MONTH) : Math.round(interval * 1.5) + if (complete) { + interval = found.length > 0 ? int(MONTH) : Math.round(interval * 1.5) + } - return found.length + return {found: found.length, complete} } // Ask for the next span in each direction. A span that comes back empty is normal while // walking a sparse history, so the count is reported separately from whether there is any // history left — a caller watching its list for changes would never hear about an empty one. - const loadOlder = async () => { - if (oldest < now() - int(2, YEAR)) return {found: 0, exhausted: true} + // The window only moves once the relays have answered: a request the socket dropped looks + // exactly like an empty span, and walking past it would leave a hole nothing goes back for. + const loadOlder = async (): Promise => { + if (oldest < now() - int(2, YEAR)) return {found: 0, complete: true, exhausted: true} const until = oldest + const since = until - interval + const {found, complete} = await loadTimeframe(since, until) - oldest = until - interval + if (complete) { + oldest = since + } - return {found: await loadTimeframe(oldest, until), exhausted: false} + return {found, complete, exhausted: false} } - const loadNewer = async () => { - if (newest >= now()) return {found: 0, exhausted: true} + const loadNewer = async (): Promise => { + if (newest >= now()) return {found: 0, complete: true, exhausted: true} const since = newest + const until = Math.min(now(), since + interval) + const {found, complete} = await loadTimeframe(since, until) - newest = Math.min(now(), since + interval) + if (complete) { + newest = until + } - return {found: await loadTimeframe(since, newest), exhausted: false} + return {found, complete, exhausted: false} } for (const url of relays) { @@ -584,34 +610,47 @@ export const makeCalendarFeed = ({ }) const loadTimeframe = async (since: number, until: number) => { + let complete = false + const found = await network.get().request({ relays, autoClose: true, signal: controller.signal, filters: [{kinds: [EVENT_TIME], "#D": daysBetween(since, until).map(String)}], + onEose: () => { + complete = true + }, }) - return found.length + return {found: found.length, complete} } - const loadOlder = async () => { - if (oldest < now() - int(2, YEAR)) return {found: 0, exhausted: true} + const loadOlder = async (): Promise => { + if (oldest < now() - int(2, YEAR)) return {found: 0, complete: true, exhausted: true} const until = oldest + const since = until - interval + const {found, complete} = await loadTimeframe(since, until) - oldest = until - interval + if (complete) { + oldest = since + } - return {found: await loadTimeframe(oldest, until), exhausted: false} + return {found, complete, exhausted: false} } - const loadNewer = async () => { - if (newest > now() + int(2, YEAR)) return {found: 0, exhausted: true} + const loadNewer = async (): Promise => { + if (newest > now() + int(2, YEAR)) return {found: 0, complete: true, exhausted: true} const since = newest + const until = since + interval + const {found, complete} = await loadTimeframe(since, until) - newest = since + interval + if (complete) { + newest = until + } - return {found: await loadTimeframe(since, newest), exhausted: false} + return {found, complete, exhausted: false} } return {