aboutsummaryrefslogtreecommitdiffstats
path: root/tools
Commit message (Collapse)AuthorAgeFilesLines
* "Sign into another account" opens on Create new, not the seed import box.Adam Soltys2026-09-301-0/+2
| | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* Public pictures and videos go to blossom.ditto.pub first, then primal, ↵Adam Soltys2026-09-301-1/+1
| | | | | | | | | | | | nostr.download, nostr.build. A 2026-09-30 benchmark (4 MB mp4, 110 KB jpeg; upload then fetch, twice): ditto took the mp4 in ~0.6 s and serves from ~120 ms; primal ~1.5 s; nostr.download ~1-2 s but rejects a JPEG with data after its end marker (motion photos); blossom.nostr.build ~5 s with ~600 ms first byte. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* A picture or video on its way shows a bar with its percentage — shrinking, ↵Adam Soltys2026-09-302-9/+71
| | | | | | | | | | | | | | | | then uploading; the pencil reads "New post". ui.mediaProgress { stage, pct } (a render per whole percent) drawn as a labelled bar under the reply and post composers, instead of only the paperclip turning into a spinner. Upload progress comes from XMLHttpRequest upload events (fetch() has none) in uploadPublicMedia's new onProgress; injected fetchers (tests) keep the fetch path. upload-progress-test: 1.2 MB at 400 KB/s climbs 0→100 in steps. video-compress-test: the bar counts the shrink, then the upload. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Videos are shrunk on the phone before upload; nostr.build is the first media ↵Adam Soltys2026-09-302-0/+119
| | | | | | | | | | | | | | | | | | | | | | host. No public Blossom host transcodes (nostr.build's and primal's BUD-05 /media return the same bitrate), so a 15-20 Mbps phone clip stalled on playback. src/video-compress.js re-encodes on the device with WebCodecs via Mediabunny (a lazily loaded chunk, left out of the SW precache and the standalone file): H.264, short edge 720, ~2.5 Mbps, moov first, audio passed through, rotation baked in. Anything short of a clear win uploads the original. A toast counts the shrinking down. Media hosts: blossom.nostr.build first, nostr.download second (it was unreachable for hours); a HEAD probe skips a host that doesn't answer within 5 s instead of waiting out the whole upload timeout. video-compress-test: 1080p 20 Mbps 5 s (landscape and rotated portrait) → 1.6 MB H.264 720p 2.49 Mbps with audio, moov first, in 2-3 s. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Topic feeds go back as far as the relays remember, instead of "all caught ↵Adam Soltys2026-09-302-1/+37
| | | | | | | | | | | | | | | | | up" after a handful. #gardenstr: 5 posts across damus/primal/nos.lol/ours (they prune), 774 once archive-keeping relays are asked too (ditto, nostr.mom, oxtr, nostrplebs, nostr21, offchain) — topic feeds now read those. And a page whose posts were all filtered out (replies, muted, spam) no longer ends the feed: paging goes on from the oldest event the relays returned, up to three asks per scroll. The quoted-note wide net gains ditto/nostrplebs/nostr21. topic-depth-test (/feed/t/gardenstr): 9 posts then "caught up" before, 73+ and still going after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* From someone's timeline, ‹ goes back to their profile and the Feed tab is ↵Adam Soltys2026-09-301-0/+18
| | | | | | | | | | | | your own feed again. The Feed tab reopened whichever feed was last on — the session timeline — and ‹ on it went to the chat list, so there was no way back to your own feed. The tab now opens the last of your own feeds; ‹ on a feed opened from somewhere (a timeline, a #tag, a pack link) steps back in history. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* NIP-22 comments (kind 1111): shown in threads, sent when replying to a poll, ↵Adam Soltys2026-09-291-0/+99
| | | | | | | | | | | | | | | | | | | | article or comment, and in notifications. Kind 1111 is the reply format for everything that isn't a kind 1 note: uppercase E/A + K + P name the root, lowercase e/a + k + p the parent. Threads now also ask for comments naming the root (#E, and #A for an addressable root like an article), place each under its parent, and draw them as posts. A reply to anything but a kind 1 note goes out as a 1111 with the root's scope and the parent's tags (copied from the comment when the root isn't loaded); replies to kind 1 notes stay kind 1. Comments that answer you show in Notifications. comments-test (a real article with 4 comments; events caught at the socket, nothing published): comments listed, reply to the article and to a comment tagged per NIP-22. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A tap on the blank band beside a fitted picture opens the post, not the picture.Adam Soltys2026-09-291-0/+70
| | | | | | | | | | The picture is fitted inside its box (object-fit: contain); on the phone the box spans the column, so a tall picture (eve's 720x900) leaves bands that used to open the lightbox. The tap is now hit-tested against where the picture is actually drawn. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Composers take pictures from the keyboard: the reply, post and chat fields ↵Adam Soltys2026-09-293-4/+107
| | | | | | | | | | | | | | | | | | | are contenteditable. Android keyboards (Gboard GIFs, stickers) only offer images to fields that accept them, and Chrome says so for contenteditable, never <textarea> — hence the greyed-out GIF key and "This app does not support images here". <coinos-text> (src/rich-text.js) is a contenteditable that acts like a textarea to the code around it (value, selectionStart/End, setSelection- Range, placeholder), keeps text plain, and hands any pasted/dropped/ keyboard picture to onMedia — the paperclip's upload. The morph syncs its value like a field's and leaves its own editing attributes alone. composer-media-test: types like a textarea, strips pasted formatting, a pasted GIF uploads and lands in the reply, still editable after repaint. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Quoted notes are found on more relays and never show as a bare "view note"; ↵Adam Soltys2026-09-292-5/+25
| | | | | | | | | | | | | | | | | Back from a post keeps the feed's place. - findNote: the reference's hints, its author's NIP-65 relays and ours, plus a wide net of big public relays, all at once, first answer wins. A real quote (SuiGenerisJohn → 0c5e0f08…) sat only on nostr.mom, named by neither the nevent nor its author's relay list. - A feed row whose quote isn't in yet shows the quote card with "Fetching note…" / "Couldn't find that note" instead of a link; the card replaces it once found. - Opening a post remembers the scroll position like opening a profile does; Back restores it instead of landing at the top of the feed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A face still being looked up shows its punk after a second at most; a ↵Adam Soltys2026-09-291-2/+5
| | | | | | | | | | | | picture found later replaces it. The quiet loading circle is capped at 1 s per face (a timer repaints when it runs out); one miss is enough to remember someone as faceless; and a room's warm-up adds members whose messages arrived after it first ran, instead of reusing a warm-up that covered only the early history. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Someone with no profile picture paints as their punk from the first frame ↵Adam Soltys2026-09-291-0/+30
| | | | | | | | | | | | | | | | after a reload, not a blank circle that turns into one. Faceless answers were never persisted (they'd evict real faces from the 150-row profile blob), so every reload re-asked and showed the loading circle until the relays said "nothing". A small pubkey->time list now remembers them: no kind 0 on two asks running (a cold relay isn't enough), or a kind 0 naming nothing; finding a face takes them off it. room-faces-test --refresh: reload inside the coinos room, faces keyed by author — 2 changed (wait→punk) before, 0 after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A community opens with its members' faces, not a beat of punks.Adam Soltys2026-09-291-0/+55
| | | | | | | | | | | | | Faces (profile, then the picture decoded) are warmed when the pointer is over or presses a room's row, and while the chat list sits there; the tap waits for them, 1.5 s at most, before the room replaces the list. A face still loading in a room is the quiet circle, not a punk. room-faces-test (fresh wallet, real coinos community): 57 faces flipped punk → picture before, 0 after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Confirm discarding substantial post draftsAdam Soltys2026-09-291-2/+5
|
* Discard canceled post draftsAdam Soltys2026-09-291-0/+17
|
* Fix public media uploads and feed tab navigationAdam Soltys2026-09-292-0/+61
|
* Cache notification note content locallyAdam Soltys2026-09-291-0/+128
|
* "Save feed" on someone's timeline adds it to your feeds at once, instead of ↵Adam Soltys2026-09-281-0/+14
| | | | | | | | | | | | | an editor listing every person they follow. The editor copied their whole follow list into the form, one row each — long enough to freeze the browser. A saved timeline now keeps only the person (of: pk); their follow list is read from the relays when it is opened, so it stays small in sync and follows who they follow. Its ⋯ offers Delete rather than Edit, and list pickers leave it out. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Someone's timeline gets a labelled feed chip instead of a blank black pill.Adam Soltys2026-09-281-0/+8
| | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Polls (NIP-88): options with each one's share of the vote, votable from the ↵Adam Soltys2026-09-282-1/+95
| | | | | | | | | | | | | | feed and the thread. A kind 1068 poll showed as "an event this app doesn't show". Now its options are rows with the vote share behind them (kind 1018 votes, each person's latest counts, single/multiple choice, endsAt honored); a tap votes, marked as yours at once. A quoted poll is pictured in its card. Feeds and profiles list polls alongside notes, and a thread's root is looked up by id with any kind, so replies to a poll find it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A thread that starts from a poll (or any non-kind-1 post) is cached too, so ↵Adam Soltys2026-09-281-0/+8
| | | | | | | | | | | a reload paints its replies at once. The thread store accepted only kind 1 and silently skipped saving a thread rooted in a kind 1068 poll — every refresh then waited on the relays behind a spinner. Also guard the profile Back listener for window-less tests. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A face tapped in the feed opens that person's profile at its top; Back ↵Adam Soltys2026-09-281-0/+73
| | | | | | | | | | returns to the same place in the feed. The profile kept the feed's scroll offset, so the reader landed deep in the person's posts — which read as a thread opening. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A quoted note that was slow to arrive turns into its card instead of staying ↵Adam Soltys2026-09-281-3/+20
| | | | | | | | | | | | | | | a "view note" link. A feed row keeps its first look, and a quote that missed the readiness gate froze as a link — with nothing asking for the note again. The frozen row now keeps asking (the existing 30s retry) and takes the card once the note and its own avatar/pictures are warm. quote-card-test: reach the feed through the bottom nav, seed the author's profile, and --slow drops the first ask for the quote to cover this. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* A zap refused for an old expired coin goes again on the other coins instead ↵Adam Soltys2026-09-281-7/+16
| | | | | | | | | | | | | of showing the server error. Sub-dust coins skip the local expiry check, so the ASP can refuse one at cosign ("vtxo … expired at height …"). The coin was already marked excluded, but the error still surfaced and the user had to tap again. send() and payLnInvoice() now retry (up to 5 attempts) when that exact refusal marks an input, without a failed history row for the retried try. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* Pinch to zoom a full-screen picture.Adam Soltys2026-09-281-0/+84
| | | | | | | | | Pinch zooms around the fingers (up to 5x), one finger pans a zoomed picture within its edges, double-tap zooms in at a spot and again to reset, and the wheel zooms on a desktop. A tap still closes an unzoomed picture (after a beat, so it can be the first half of a double-tap); a gesture never does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A reloaded thread paints whole, quoted posts included, with no spinner.Adam Soltys2026-09-281-1/+7
| | | | | | | | | The thread cache kept the replies but not the posts quoted inside them, so every reload showed "Fetching note…" until a relay answered. Quotes (of any kind — the one that showed it was a poll) are now saved with the thread and restored before the first paint. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* New posts: the pill lands on the oldest one, and each wears a tint until ↵Adam Soltys2026-09-281-0/+74
| | | | | | | | | | | you've seen it. The new-posts pill used to glide to the top of the feed; now it stops at the oldest post that came in, right above where you were reading, so you read on upward. Posts that arrive while you read are tinted until they've been in view about two seconds, then fade to the plain page (like Amethyst). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The chat list shows four conversations and four communities, the rest behind ↵Adam Soltys2026-09-281-3/+14
| | | | | | Show all. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The bottom nav rides every page: Feed, Messages, Notifications, Wallet.Adam Soltys2026-09-282-33/+40
| | | | | | | | | It now shows across the signed-in app and a visitor's public pages (never over sign-in, onboarding or the lock prompt), marks the current page, and carries the unread dot. Home is renamed Feed, and the Messages button leaves the header. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Restore public feed state when navigating Back from WalletAdam Soltys2026-09-281-0/+24
|
* Keep feed navigation visible and add a wallet shortcutAdam Soltys2026-09-281-5/+14
|
* Add scrolling feed navigation for home, messages and notificationsAdam Soltys2026-09-281-0/+73
|
* Merge remote-tracking branch 'prod/prod' into merge-tmpAdam Soltys2026-09-281-0/+51
|\
| * Fix video unmute after feed repaintsAdam Soltys2026-09-281-0/+51
| |
* | The feed header keeps its room on a phone: a ⋯ menu for the feed itself, a ↵Adam Soltys2026-09-282-3/+4
|/ | | | | | | | | | | pencil for a new post. Save feed, Relays, Share and New post sat in the title row and wrapped on a phone. Share, Relays and Save or Edit now live in the feed's ⋯ sheet, and New post is a pencil; the title has the width. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* A clip the reader turned the sound on for stays loud for the session, ↵Adam Soltys2026-09-281-0/+6
| | | | | | | | | | | | | however its box is rebuilt. Pinning the box only protected it from a patch in place. A repaint that pairs children differently, the window unmounting the row and mounting it again, or a quoted note loading all rebuilt the player muted with the button back; the URL is remembered now, and a rebuilt box starts loud with no button. Muting again from the player's own control forgets it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* The new-posts pill sits at the bottom; the sound button leaves once the ↵Adam Soltys2026-09-281-2/+2
| | | | | | | | | | | | | sound is on; the list action moves under the profile row. The pill was at the top of the window, a stretch for a thumb. An unmuted video kept a Mute button the player's own controls already provide, and in a quoted note the box could be rebuilt muted; unmuting now pins the box and removes the button. The ☰ on the profile's action row wrapped onto a line of its own; it is now "Add to a list" beside "Their timeline". Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* The default public feed is Popular, not the firehose: what people reacted ↵Adam Soltys2026-09-271-6/+6
| | | | | | | | | | | | | | | | | | | | | | to, in your language, with the machine posts gone. A brand-new post has no reactions yet (of 73 fresh English posts on the four relays, one had two), so Popular starts from the other end: the latest reactions, reposts and zaps, counted per post by distinct people, then the posts two or more people touched. Older pages walk the reactions back in time; new ones arrive by a two-minute poll. At the door, JSON and other machine output are dropped, and so is anything not in the reader's language (script first, then a vote of function words among the Latin-script languages the app speaks; unknown passes). The raw firehose stays at /feed/all. A language selector sits under every feed, and changing it rebuilds the curated ones. A tall video scrolled past used to let go of its media at once and collapse, shifting the page under a thumb; it now waits a moment and holds its height until the clip is back. A tap on a full-screen picture closes it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Everything on the coinos relay is a feed of its own, and every feed can read ↵Adam Soltys2026-09-272-7/+8
| | | | | | | | | | | | | | | | | | | | from relays of your choosing. Plain /feed is the firehose: every post relay.coinos.io carries, no filter. It sits beside Following as a built-in chip for wallets and opens as a session feed for a visitor. A feed's relay list is independent of what it asks for. The Relays button in the header opens a panel to add to or take from the list; empty means the usual choice (each author's outbox, the wide relays for a topic, the coinos relay for the firehose). The list rides in the URL as ?r=…, on every kind of feed, so a shared link reads where you read. A feed state dropped while its fetch was still out used to swallow the answers meant for its replacement; a relay change showed nothing. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Links in posts show as the cards their pages describe.Adam Soltys2026-09-271-0/+123
| | | | | | | | | | | | A plain link becomes an Open Graph card: picture, title, a line, the site. The registrar fetches the page on the reader's behalf (names/preview.js, GET /preview?url=): only public hosts, at most half a megabyte of HTML, cached a day. The client warms the card with the rest of the post so the row arrives whole, keeps a few hundred cards across sessions, and leaves a page with nothing to say as a bare link. Pictures and YouTube keep their boxes. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Accounts: tapping an identity signs in as it and opens its profile; the ↵Adam Soltys2026-09-273-23/+25
| | | | | | | | | | | | padlock moves from the header to this page. The identity chip used to put the npub in the URL and leave the list on screen (the profile only paints over the wallet screen). Lock/Unlock now sits with the other device-wide actions (log out, clear); the padlock tests reach it through the Accounts page and lose their stale "Wallets" button. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Amber (NIP-55) signs you in on Android: one intent per request, the answer ↵Adam Soltys2026-09-271-0/+93
| | | | | | | | | | | | | | | read off the clipboard or pasted. Closes #7. A "Sign in with Amber" door on Android opens nostrsigner: intents for get_public_key, sign_event and nip44 encrypt/decrypt, with a waiting card that reads the clipboard when the tab returns (or takes a paste). Requests queue one at a time — a sign-in fires the backup, relay list and follows together — and each answer is checked for its type so a stale clipboard is never mistaken for one. An interactive signer is not asked to decrypt DMs or private lists that arrive on their own; each trip through Amber is a deliberate one. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* moderation test: the mis-flagged person's post is unfolded (check the fold ↵Adam Soltys2026-09-271-1/+1
| | | | | | | row, not the revealed text) Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Reports: a person-level flag needs two follows to fold their posts; 'other' ↵Adam Soltys2026-09-271-3/+9
| | | | | | | | | | | never counts. One follow's mis-tapped "impersonation" on fiatjaf's key was folding his entire timeline. A flag on a specific post from one follow still folds that post. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Feed: the foot says when older posts are loading, and when there are none left.Adam Soltys2026-09-271-3/+16
| | | | | Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Media sources wait until the node is in the page; thumbnails use the cache.Adam Soltys2026-09-262-3/+8
| | | | | | | | | | | | | | | | | | | | | Every render builds a fresh tree to diff against the live one, and a media element starts downloading the moment its src is set, even off the document: a sixteen-second feed scroll built 204 <video> and 1,416 <img> elements whose loads the morph then threw away — 375 cancelled video range requests, and full re-downloads from hosts that send no cache headers. h() now puts the source on data-lazy-src and it becomes src only once the node is in the page (render promotes it; a MutationObserver covers nodes appended by hand); the morph treats the two as one attribute. The thumbnail maker fetched every original with cache: 'reload' (38 MB in one scroll); the face was just painted from that URL, so the cache serves. holdScroll now recovers an anchor row that a large insert above pushed out of the window, from the height cache. Tests: the YouTube test accepts the in-view players autoplay loads; the stability test watches the notice for the catch-up, since the new rows land inside the window's spacer. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Thread: the reply box appears on Reply, full width, with its tools on a row ↵Adam Soltys2026-09-263-6/+11
| | | | | | | | | | | | | | | | | beneath. A thread opened to read came with an empty field under its first post. The box now appears under the note whose Reply was tapped (or while a draft is pending), folds away once the reply is sent or Cancel is tapped, and takes Amethyst's shape: the field across the full width, attach and preview on a row beneath it with Cancel and Reply at the right. Tests: thread-reply's helper follows the new shape and expects the box to fold after sending; compose-preview and feed-list tap Reply before expecting a box (the thread card carries .thread-card as the marker). Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Notifications: the faces the page will paint are warmed before it opens.Adam Soltys2026-09-261-0/+100
| | | | | | | | | | | | | | | | The list was already cached locally and refreshed from the chat home, but each row only asked for its author's profile when painted, so the page opened as quiet circles filling in one by one. Now the actors' names are fetched in one batch and each picture decoded into a thumbnail the moment the list is read from disk or grows — which the chat home does before the page is ever opened. Test: notif-faces (stub relay answers the kind-0 batch; the pictures are fetched and cached from the chat home; every row has its face on the first frame). Note for tests: serve /verify-worker.js from dist, or every relay answer waits four seconds for the inline fallback. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Feed: only the rows near the viewport are in the DOM, however far you scroll.Adam Soltys2026-09-263-3/+135
| | | | | | | | | | | | | | | | | | A long scroll left hundreds of rows mounted, and every repaint (a zap count, a profile landing, the thirty-second sync) rebuilt and diffed all of them — the slowdown deep in the feed. Rows beyond two and a half screens either side of the viewport are now replaced by a spacer at their measured height, so the page keeps its length (scrollbar, Back, arrival anchoring behave as before) while a repaint touches a few dozen rows. After svelte-virtual-list: measure after paint, estimate unmeasured rows at the running average, hold the reader's row still when a measurement corrects an estimate. Applies to the profile notes list too. Tests: feed-window-dom (120 posts, 27,800px page, 12–24 rows mounted, anchor held, spacers both sides); feed-live looks at the top after scrolling up; the moderation test's stub relay no longer lets real posts in. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Feed: cached posts paint at once, never a spinner over them; the chat home ↵Adam Soltys2026-09-251-3/+6
| | | | | | | | | | | | | | prefetches the feed. The boot gate hid everything, cached posts included, until the opening page's faces and pictures had decoded — seconds of spinner on a feed we already held. Cached rows now paint immediately as they are and take their prepared presentation once the warm-up lands (the container carries data-booting meanwhile). The relay fetch starts at once instead of after the warm-up; arrivals still wait at the door. Opening the chat home warms and refreshes the current feed, so the Feed tap opens on the latest posts. Co-Authored-By: Claude Fable 5.1 <[email protected]>