aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/messages.js
Commit message (Collapse)AuthorAgeFilesLines
...
* GIF search, from nostr.build's GIF index (gifs.nostr.build). A GIF button in ↵Adam Soltys5 days1-6/+31
| | | | | | | the post composer and in every chat composer (DMs, groups, communities) opens a picker: "GIFs from nostr.build" (linked, as their terms require), starter chips (gm, lol, yes, wow, …; the first runs on open), a search field (debounced 250 ms, no one-letter searches, one shared request per normalised query, never aborted, 429/403/503 answered in words), and a masonry grid whose tiles play the small H.264 copy only while near the screen, over the lqip blur, falling back to the animated preview (stills under reduced motion). A pick puts the original URL in a post — published with a NIP-92 imeta (m, dim, size, alt) — or, in a chat, sends it at once after whatever was typed. Media URLs not on gifs.nostr.build are dropped. Browser calls carry no headers: production needs v3.coinos.io registered in the nostr.build dashboard (localhost works as their shared dev client). tools/gif-picker-test.js runs against the live API. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Videos: a tap on the picture goes full screen. The corner full-screen button ↵Adam Soltys5 days1-0/+41
| | | | | | | is a reach on a phone, so a tap anywhere on a feed clip — outside the player's own big play button and control bar, judged from the tap's position since those live inside the video element — now goes full screen (a wide clip asks for landscape). Full screen, a tap pauses a playing clip, and a tap on a paused one (again outside its play button and controls) comes back out. A video's taps no longer reach the post either: a tap on the player's controls used to open the thread, which in full screen took the player out from under the reader. tools/video-tap-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Feed: a slow picture keeps loading in place instead of turning into a link. ↵Adam Soltys5 days1-3/+14
| | | | | | | A feed row waits up to 8 s for its pictures, then freezes as it is; a picture still downloading at that moment (Juraj's 3.5 MB PNG from blossom.primal.net, on a phone) was frozen as its bare URL, and only showed when the post was opened. Now a picture that was merely slow is drawn in a fixed 4:3 box (fitted inside, so nothing moves when it lands) and keeps loading; only a picture that refused to load (an error, not a timeout — mediaFailed) is still a link. tools/slow-image-test.js (a picture served after 11 s; on the old code it was a link). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Messages: NIP-42 inbox relays. isolabellart's kind-10050 inbox is ↵Adam Soltys5 days1-5/+31
| | | | | | | auth.nostr1.com and a personal relay, both of which answer a REQ for their gift wraps with CLOSED "auth-required" — we never authenticated, so DMs delivered only there (anything sent from Amethyst and other 10050-only clients) never reached v3. The DM subscription on our own inbox relays now authenticates as the DM identity (subscribeAuthed: per relay, AUTH then ask again, still closable; the AUTH event is signed before nostr-tools' cached auth promise so a failing signer can't wedge the socket), sends to a peer's inbox authenticate when a relay refuses the write with auth-required, and our sent copy also goes to our own inbox relays so other clients reading that list see it. Never Amber by intent (an app trip per connection). Checked live: auth.nostr1.com accepts the handshake. tools/dm-nip42-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Messages: Amber users can read their DMs. Signed in with Amber by intent ↵Adam Soltys5 days1-4/+36
| | | | | | | (NIP-55), nothing sealed to the login npub was ever decrypted — each decrypt would be a trip through the app — so isolabellart's thread with Adam showed only what that phone had sent. Worse, decryptorsComplete() counted the Amber signer as complete, so those wraps were tried on the wallet key alone and logged as opened, hidden for good. Now an interactive signer counts as incomplete (wraps wait, uncounted); Messages and the DM thread say "messages sent to you can't be opened here" with Connect Amber, which opens a nostrconnect:// link for the SAME account over NIP-46 (Amber's bunker mode; a different npub is refused, a callbackUrl reload resumes the handshake). Once a signer that can decrypt takes over, a wallet that ran with Amber re-runs the heal pass over every wrap the relays hold, so messages the old code wrote off come back. tools/amber-dm-upgrade-test.js (plays the remote signer over a shimmed relay). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Posts: inline markdown. **bold** / __bold__, *italic* / _italic_, ↵Adam Soltys5 days1-2/+26
| | | | | | | ***both***, ~~struck~~ and `code` render in a post's plain text — what newsletter bots and Amethyst users type and Amethyst shows; we showed the asterisks. Strict about edges, so snake_case, x__init__y, 2*3*4, "$5 * 2" and a lone star stay as typed, and markers never reach across a link, mention or hashtag. No headings or lists in posts: "#" at a line's start is a hashtag here, and articles render their own headings. tools/post-markdown-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Sensitive posts: Popular leaves them out. The NSFW posts Adam saw (and ↵Adam Soltys5 days1-4/+45
| | | | | | | blocked) in Popular were Misskey artists over the mostr bridge — every one carried a content-warning tag (Misskey's "sensitive" media), and their profiles said so too (":role_nsfw:", "🔞成人向け"); the posts had no words to fail the language test, and nothing read the tag. Popular (and its picture/video grids) now drops a post marked sensitive (NIP-36 content-warning, a NIP-32 content-warning/nsfw label, an NSFW hashtag, :nsfw: or 🔞 in the text) and the posts of accounts whose own name or bio says they post adult work. Everywhere else a marked post is folded behind its warning ("Sensitive: <reason>") with Show, and left out of media grids — Settings → Nostr → "Fold posts marked sensitive" (on by default). On the live snapshot: 6 of 184 posts dropped. tools/sensitive-feed-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Messages: a tap on a conversation's face opens THAT person. The avatar's tap ↵Adam Soltys5 days1-2/+13
| | | | | | | handlers were attached with addEventListener, holding the person it was first drawn for; the morph transplants only h()'s on* properties, so when a new message re-sorted the list and the rows were patched in place, a face kept its old handler — a tap on Bill opened someone else's profile, and once that page had opened, a fresh render hid the evidence. The avatar now sets onclick/onpointerdown claimed in _evs (transplanted like any h() handler), and conversation rows are keyed by person so a re-sort moves them. tools/dm-row-resort-test.js (on the old code, Bill's face opened Alice). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* @mentions: "@" and a few letters in a post, a reply or an edit open a list ↵Adam Soltys5 days1-3/+95
| | | | | | | of people under the box — the ones you follow whose name matches first (at once, from what's here), then the header's people search. Picking one (tap, Tab or Enter) puts nostr:npub… in the text, shown everywhere as @name (NIP-27), and the post or reply goes out with their p tag so they're told; Escape closes. coinos-text's setSelectionRange walks into the <div>s Chrome wraps lines in after an Enter — it used to put any position past the first line at the end of the field. tools/mention-ac-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Zap receipts read as zaps. A receipt (9735, or coinos's 9737) reached as a ↵Adam Soltys5 days1-3/+13
| | | | | | | note — someone reacted to a zap, and the reaction was followed — showed as "a kind 9735 event this app doesn't show" under the LNURL server's name. It now shows the zapper's face and name, "⚡ zapped 21 sats" and what they said with it. tools/zap-receipt-row-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Profiles: Posts / Replies tabs. Replies were mixed into a profile's list, ↵Adam Soltys5 days1-7/+35
| | | | | | | tagged "↩ reply" — now Posts (the default) is what they posted and Replies is their side of other people's threads (NIP-10 replies, NIP-22 comments). Profiles load thirty mixed notes a page and older pages only on scroll, so a tab that holds few of its kind (someone who mostly replies, or never does) fetches older pages by itself — five at most per tab, so a person who never replies doesn't have their whole history paged through. tools/profile-replies-test.js; profile-feed-test waits for rows (the tabs come before them; the old "Latest posts" heading only appeared with notes in hand). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Follow packs: a Draftable (mutable.top) link pasted into the feed editor's ↵Adam Soltys5 days1-2/+15
| | | | | | | pack search finds its pack. Draftable makes ordinary kind 39089 packs — the pack index already finds them by title on damus / nos.lol / primal — but its links name the author by only the first eight hex (/draftable/d/<id>?p=<prefix>), which the link parser refused; the id is now asked for and the author matched by prefix. nostr.oxtr.dev (one of Draftable's relays, and the one holding the most packs) joins the pack index. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Video stills: past a black opening. vmedia took its still at half a second, ↵Adam Soltys6 days1-1/+2
| | | | | | | and a clip that fades in from black (7 of the 68 made in three hours) gave a black tile. Each frame's mean brightness now comes out of ffmpeg (signalstats YAVG on pipe:2 — /dev/stderr can't be opened from a Bun-spawned pipe), and a dark one sends it further in (2, 5, 10, 20, 40 s) for the first bright frame; a clip dark all the way through (a night scene, a boot screen) gets its brightest frame with the levels stretched (normalize). Stills are made again under a new key and URL (v=4), so neither the server's nor the CDN's black copies are served. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Videos grid: a tile without a still of its own shows the server's (vmedia ↵Adam Soltys6 days1-2/+14
| | | | | | | thumb.jpg) at once — about a second the first time anyone asks for that clip, a CDN hit after — instead of waiting on the browser's probe (64 KB) and frame grab (≈1 MB of the clip, decoded) per tile, which on a phone left most of the grid as empty boxes. The frame grab still runs for a clip the server had no picture for. A known-heavy clip wears its size badge over the still. vmedia makes 8 stills at once (was 4). tools/video-tile-poster-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Reactions: a custom-emoji reaction draws as its picture in the feed. ↵Adam Soltys6 days1-2/+7
| | | | | | | emojiImg falls back to :code: text while a row's media isn't ready, and the feed freezes that choice per row (so a post's body never reflows) — a reaction usually lands after its row was frozen, so :kek_lord: stayed text for good. Reaction emoji sit in the action bar at a fixed height, so they skip the freeze (reactImg), on the like button, the chips and the who-reacted panel. post-edit-test checks it (fails on the old code). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Translation: a hashtag in a script the reader can't read gets its ↵Adam Soltys6 days1-5/+11
| | | | | | | translation in brackets after it — "#総合部絵チャ (…)" — the tag itself kept as the link it is. A post that is only such hashtags now counts as foreign (it was judged by its prose alone, of which it has none, so nothing ever offered to translate it), and ⋯ → Translate on a post with no words says so instead of "already in your language". The registrar's language detection reads the glossed tags too; lang-guess's scriptOf takes a minimum (a two-character tag has a script too). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Translation: posts in another language are shown in yours — "Translated ↵Adam Soltys6 days1-5/+143
| | | | | | | from Spanish · Show original" under the words, automatic by default for posts the language guess calls foreign (Settings → Nostr → Translation switches it off and leaves a Translate link instead), and ⋯ → Translate for any post. The browser's on-device Translator does it when the language pair is already installed (desktop Chrome); otherwise the registrar's new POST /translate (names/translate.js): a cached, 4-at-a-time front for a LibreTranslate container on cs (names/compose-libretranslate.yml, v1.9.6, 32 languages; none for sw/mr/ta/te, so those readers get no offer). Links, nostr: mentions, :emoji:, #tags and long codes are never sent to the model — only the prose between them, batched in one call (src/translate-split.js, shared). Translations are remembered (coinos-translations, 300). tools/translate-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Accounts: switching no longer leaks the previous identity's notifications ↵Adam Soltys6 days1-8/+39
| | | | | | | and DMs into the next. A notifications query (up to 5 s on the relays) or a wrap mid-decrypt that was still out at the switch landed in the NEW account's list and was saved there under its key — a throwaway account showed adam's zaps, reactions and conversations for good. A session counter bumped in stop() is checked after every await on both paths; notifications only take events p-tagging this identity, and an incoming DM must come from or name it. Healing: notification caches saved before this (no notifsV 2) are dropped once and refetched; a cached DM thread holding a row from someone who is neither us nor the peer is swept. tools/account-switch-leak-test.js (fails on the old code: the reaction and the thread both leak) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* Posts: edit and delete your own. The ⋯ menu on your post gains Edit (a ↵Adam Soltys6 days1-7/+154
| | | | | | | kind 1010 edit beside the original — the draft NIP-41 Amethyst reads and writes; the feed, thread and quote cards show the newest text with "· edited") and Delete (a NIP-09 kind 5, after a confirmation). Edits and deletions are asked for in the same REQ as each post's tallies and its live subscription, and count only when signed by the post's own author; they're remembered between sessions (postEdits cache) so a deleted post never flashes back from a cached feed. A post signed by the wallet key before a login was linked is still signed by that key. A deleted thread root keeps its place as "deleted by its author". relay.coinos.io now accepts kind 1010. tools/post-edit-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
* DMs: a linked nostr login whose signer hadn't resumed yet (a reload before ↵Adam Soltys6 days1-10/+43
| | | | | | | the bunker reconnected, a session just dropped) counted as "every key is here", so each wrap sealed to the login npub was tried on the wallet key alone, given up after ~40 s and logged as opened — never asked for again (a phone kept 9 of 186 messages). Since b06bbb4 the check only passes with no login or a live signer that decrypts (or Amber); a drain without one waits for the signer instead of re-running the wallet key, and the slow retry is rescheduled in finally so an early return can't end it. "All promises were rejected" (every relay refused the bunker request) and "not open anymore" count as try-later, not "not ours". Heal v2 re-opens every wrap the relays still hold, marked done only once nothing it re-opened is still waiting for the signer Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Profiles: the Following / Followers counts no longer flash a dash before ↵Adam Soltys6 days1-1/+3
| | | | | | | they load — the number's place is held by an invisible stand-in and the real count fades in over it Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Feeds: Everything and Follows + lists are no longer built-in chips; any ↵Adam Soltys6 days1-20/+50
| | | | | | | custom feed can be one. The editor gains "Everyone on my lists and packs" (the people of all your other feeds and packs, read fresh each time the feed opens) and "Posts from anyone on hashtags I follow" (your topic feeds' tags and your kind-10015 interests, OR'd in beside the feed's people, or the whole feed when it has no people). The editor reads a feed as saved, never expanded, so the lists don't get copied into it Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Feed: the "new posts" pill only ever takes you up. It counts the new posts ↵Adam Soltys6 days1-20/+57
| | | | | | | you haven't scrolled up to yet (going up to one, or past it, takes it and everything below it off), and the tap glides to the nearest of them above the post at the top of your screen — reading one newer than the oldest, you were sent back down to the oldest. A new post peeking in at the top edge still counts as above you. The count updates as you scroll up, and the notice clears when none are left (this replaces the at-the-top check) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Notifications: a reply, mention or reaction your follows reported is left ↵Adam Soltys6 days1-2/+6
| | | | | | | off the list and the unread count, as a muted one is — the thread folds that post behind who flagged it, so the tap landed on a fold instead of the reply it promised. Checked at render time, so reports that load later (or a report taken back) apply to what was kept Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Chat: a one-tap ⚡ beside every bubble from someone else, in communities ↵Adam Soltys6 days1-5/+18
| | | | | | and DMs, vertically centred on the bubble — tap zaps the default amount (the strike starts at the bolt), hold sets the amount, without opening the message sheet first. Own messages, visitors and wallets that can't zap get none; Marmot groups have no zap yet and are left alone Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Feed images: the column copy and the grid tile are decoded, resized and ↵Adam Soltys6 days1-37/+12
| | | | | | WebP-encoded in a worker, one picture at a time, instead of on the page thread between scroll frames (a profile of the live feed showed 1.8 s of convertToBlob there, in tasks up to 383 ms). A picture that hangs or crashes the worker is given up and the worker restarts for the next; three in a row and originals paint for the session, as they do wherever workers can't run Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Post composer: Preview and Cancel stand as tall as Post and the attach ↵Adam Soltys6 days1-1/+1
| | | | | | button, in the same type Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Reuse feed overscan to reduce rendering during scroll coastsAdam Soltys6 days1-6/+15
|
* Profile public key: solid border instead of dashed, a copy icon beside the ↵Adam Soltys6 days1-2/+2
| | | | | | key, and a slightly larger font (13px) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Amethyst's feed model: an Everything feed (your follows, everyone on your ↵Adam Soltys6 days1-7/+64
| | | | | | lists and packs, and posts from anyone on a hashtag you follow: your topic feeds' tags and your NIP-51 interests, kind 10015) and a Follows + lists feed beside Following, built from your feeds and offered once there's something to add. Long names in search results end in an ellipsis instead of running off the card Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Desktop layout: from 900px the bottom nav is a left rail (your profile first ↵Adam Soltys6 days1-7/+28
| | | | | | with a bigger face and name, Settings last) beside a 600px page column, and from 1220px the wallet (balance, Receive/Send, latest payments) rides in a right column; search is a full field in the header there. Floaters re-centred on the page column, open chats still fill the window, phones unchanged Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Prefetch visible post threads after a bounded reading dwellAdam Soltys7 days1-6/+27
|
* Gentler on relays: a relay that says rate-limited (CLOSED/OK/NOTICE) gets no ↵Adam Soltys7 days1-10/+59
| | | | | | | one-shot reads for 30 s, doubling to 10 min; zap/reply tallies flush at most every 2 s in one REQ (two filters) and the live #e sub swaps at most every 10 s; quoted-note lookups batch per relay over 150 ms — scrolling a 300-follow feed went from ~21 to ~8.5 REQs per 10 s on the busiest relay, primal's "too many concurrent REQs" from 19 to 0 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PCGjEEMD23CweKrefbTDJp
* Feed videos before they play: the picture fills the box with only the play ↵Adam Soltys7 days1-7/+32
| | | | | | button and size (no seek bar or sound button until playing); hovering the cover no longer paints it over with the app's button grey; upright phone clips (stored sideways with a quarter-turn matrix) paint upright, v1 size caches dropped; a clip the probe found HEVC switches to the copy before fetching the original Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos: posters for clips that came without one, and an H.264 copy for a ↵Adam Soltys7 days1-9/+59
| | | | | | clip this browser can't picture (an iPhone's HEVC on Chrome for Linux played sound over black) — vmedia container on cs (bun + ffmpeg, /vmedia/), switch on zero-width metadata or an HEVC header from the probe, a line saying why if the copy fails too Co-Authored-By: Claude Opus 5.5 <[email protected]>
* DMs: read every relay our kind-10050 inbox list names (up to 8), not just ↵Adam Soltys8 days1-1/+4
| | | | | | the first three — a reply delivered only to the fourth (primal) never showed Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Profile links paint whole on the first frame: the server renders /npub1…, ↵Adam Soltys8 days1-3/+34
| | | | | | | | | | /nprofile1… and /<name> with the kind 0, latest posts, follow counts and mentioned faces inlined (stale-while-refresh cache, 2 s budget), the app seeds its caches from it, and the big avatar never flashes punk art while a lookup runs A cold visit to a profile link showed a punk, '–' counts and no posts for 9 s. Server-side relay answers settle once events stop arriving (idle 400 ms) instead of waiting out a relay that never sends EOSE: a cold build is ~1.9 s. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Merge branch 'worktree-agent-ad1d843e9e394974e'Adam Soltys8 days1-1/+3
|\
| * Feed: a picture with no column-sized copy is decoded ahead too, so its row ↵Adam Soltys8 days1-1/+3
| | | | | | | | | | | | | | | | | | | | | | paints it on the first frame Since column-sized copies (8249fbe), a picture small enough to keep (or a GIF) was fetched for its size but never decoded ahead: the row's <img> decoded it on sight, from the network again when the host says no-store. Decode the original ahead when there is no copy. (feed-live-test caught it.) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Merge branch 'worktree-agent-ab051145d709af327'Adam Soltys8 days1-4/+27
|\ \
| * | Popular: the single file opened from a disk does not ask file:///api/feedAdam Soltys8 days1-2/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The shared Popular snapshot is fetched from a relative /api/feed. Opened from disk (dist/standalone.html) that is file:///api/feed, a request for a file beside it that never exists (standalone-file-test: 'nothing looked for beside the file'). Only ask when served over http(s); the relay path covers the rest, as it already did for a failed fetch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Feed URLs: the default feed is not opened (and fetched) a tick before the URL'sAdam Soltys8 days1-0/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A /feed?r=… (or any /feed/… link) set the feed view up synchronously but chose its feed in a setTimeout, so the first paint opened the remembered/ default feed — Popular since c89050e — and fetched the shared snapshot for it before switching. The frame now holds (header only) for that tick, the way a note link's pending frame does. public-feed-browser-test: custom relays bypass the snapshot again. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Communities: a room rebuilt with fresher keys folds its channels againAdam Soltys8 days1-1/+12
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | When our own community list came back with a channel key the local copy lacked (every community we create: its list entry carries the keys), the room was torn down to be rebuilt with them, but the wraps it had already opened stayed in seenWraps. The rebuilt room's subscription then skipped the relay replay, so its Control fold held only editions newer than the rebuild: a second channel showed up as the only one (no picker; general gone from the fold), and the guestbook lost its joins. Each room now remembers the wrap ids it opened and hands them back when it is rebuilt. chat-layout-test reaches Messages through the bottom nav. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Profiles: a remembered face beats a row asked for before the cache openedAdam Soltys8 days1-1/+7
| |/ | | | | | | | | | | | | | | | | | | | | | | Since the profile cache moved to IndexedDB, a profile asked for during boot (a restored profile page, offline or with relays failing) left a loading or miss row in memory, and the re-warm after the cache opened skipped any pk already present, so the cached name never showed (profile-draft-test: the editor reopened with an empty Display name). Placeholder rows without a name/picture (and without a real kind 0 behind them) now give way to the cached one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Merge branch 'worktree-agent-a4c5f0908bc7757bf'Adam Soltys8 days1-5/+5
|\ \
| * | Emoji packs fetch again; a like paints before the author's relays are foundAdam Soltys8 days1-5/+5
| |/ | | | | | | | | | | | | | | | | | | | | | | | | | | Two fetchPack declarations shared messagesFeature's scope since the follow packs arrived (19ac1c4): the later one won, so adding an emoji pack from its chat link (and pulling the packs on a synced kind 10030) asked for a follow pack with undefined fields and always came back "bad pack". The emoji one is fetchEmojiPack now. reactTo awaited the author's relay list (up to 3.5 s for an author with none cached) before painting the reaction it promises "on screen before the relays answer"; the lookup now only gates the publish. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* / Profile cache: room for ~350 face thumbnails and 1500 rows now that it lives ↵Adam Soltys8 days1-2/+6
|/ | | | | | | | | | in IndexedDB The 320K thumbnail budget was sized for localStorage; on a phone that follows hundreds it kept ~34 faces and the rest churned (thumbnail made, evicted, original downloaded again). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Rebuildable caches move from localStorage to IndexedDB (cache-store.js)Adam Soltys8 days1-18/+46
| | | | | | | | | | | | | | | | | | | | localStorage is ~5M characters per site whatever the device, and a phone had filled it. Profiles, faceless list, reports, follow packs, profile pages, relay lists, the moderation baseline, community directory, link previews, video sizes/probes and search profiles (~3M on that phone) now live in IndexedDB, which shares the site's disk allowance (GBs). cacheStorage keeps localStorage's string API: reads from memory, coalesced write-behind, flushed on hide. Copies migrate on open and are removed once written. Boot waits for it (max 3s) beside the Ark store; caches read at construction re-merge on ready; an early write can't replace the stored copy. A copy an older build writes to localStorage later moves in on first ask. Wallet wipes reach it; private mode stays on localStorage. Read markers, drafts, follows, note threads and sync outboxes stay in localStorage on purpose (cross-tab consistency, early server-rendered seeding). Also asks navigator.storage.persist(). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Notifications: the face opens the sender's profile; faces of people you know ↵Adam Soltys8 days1-18/+28
| | | | | | | | | | | | | | | | | | stay cached; primal-hosted faces get thumbnails again - A notification's avatar now opens the actor's profile (the name and the rest of the row still open the note). - The profile cache held 150 rows evicted by fetch time, and a phone's feed churned all of them within twenty minutes, so a reload painted a punk and an npub for people you hear from daily. 300 rows now, and follows, DM peers and notification actors are evicted last (their thumbnails too). - corsFetch's "is the host reachable?" probe was answered from the HTTP cache, so any network blip blamed the host: a phone had ~100 hosts written off as no-CORS, primal, nostr.build and coinos.io among them, and those faces painted from multi-MB originals on every repaint. The probe now goes past the cache and the polluted list is reset (v5). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* DMs: file messages keep their key across reloads, tabs no longer erase each ↵Adam Soltys8 days1-24/+80
| | | | | | | | | | | | | | | other's cached messages, one-time re-open of the last two weeks of wraps The DM cache kept only text, so a kind-15 picture (an encrypted Blossom blob) came back after a reload as a bare link with no key. Every tab also rewrote the whole cache from its own memory, so a tab asleep since yesterday erased messages another tab had received, while the wrap log still marked them opened: those messages were never fetched again (a phone showed 2 of 4). Rows now carry kind 15's tags and merge by id with what's stored; the wrap log merges too. A one-time pass re-opens wraps from the last 14 days so damaged caches heal. Co-Authored-By: Claude Opus 5.5 <[email protected]>