aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/messages.js
Commit message (Collapse)AuthorAgeFilesLines
* Relay groups with nothing said wait at the bottom of Messages, and a dead ↵Adam Soltys11 hours1-1/+5
| | | | | | | relay says so. "They shouldn't be at the top if they have 0 members and no recent activity" (Adam, 2026-11-12), of four groups.0xchat.com groups that came in on his kind 10009 list (saved by Armada) and topped the phone's list. A group's place was its last message, else the moment this device first recorded it, so a list adoption counted as brand new. Now a relay group without messages sorts by when it was joined on this device (joinedAt, set by Join) and one that only arrived on the list sorts last. groups.0xchat.com no longer resolves: a group whose relay can't be reached and that has shown nothing reads "<host> isn't answering" on its row ("On <host>" while it is still quiet) instead of "0 members"; the mark clears with the relay's first event and on every start. nip29-group-test joins gone.invalid'deadgroup on the second device and reads the row Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
* Messages joins NIP-29 relay groups: New group → Relay group → paste an ↵Adam Soltys36 hours1-10/+86
| | | | | | | address → Join. "can i join pyramid runners from coinos v3? … add it" (Adam, 2026-11-11), for fiatjaf's pyramid.fiatjaf.com'Tnq7x2ZTgrPZWFrC. A NIP-29 group lives on one relay, which keeps its members and signs its metadata (39000 name/about/picture, 39001 admins, 39002 members); messages are plain signed events with an h tag. src/nip29-client.js speaks it behind the same interface as the White Noise and Cordn clients, so the group screens drive all three (g.nip29; gClient/gOn/findGroup/wnGroups learn it): live kind 9/7/5 (and 9000/9001/9021/9022) on the group's relay with NIP-42 AUTH where the relay asks (never an interactive signer unasked), metadata read only from the group's own relay, replies sent as NIP-C7 q tags (shown through an added e), NIP-29 previous tags from the relay's recent events, reactions and own-message delete as before. Join sends kind 9021 and holds the group either way (an open group reads before the relay's yes; a refusal is said in a toast); leave sends 9022. Addresses: host'id, wss://host'id, an naddr of the 39000, or a client's link (chachi.chat/<host>/<id>, flotilla's /spaces/<host>/<id>). The joined groups are the NIP-51 kind 10009 other clients keep: read at start (groups joined elsewhere appear here), written back on join and leave after reading the newest copy (deep on a first write, like the mute list), and a group left this session can't be brought back by a list read still in flight. Messages and metadata kept per identity in the marmot IndexedDB store (|nip29, last 200 per group; wiped with the wallet). The member panel says it isn't encrypted, shows about, and copies the address. No attachments, no creating groups or admin actions yet. tools/nip29-group-test.js reads the real group (relays: 'read'; join, list, message and leave publishes answered in place) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
* Post search shows a bot fleet's newsletter once, not forty-six times. ↵Adam Soltys36 hours1-0/+30
| | | | | | | "searching "fiatjaf" surfaced this same post over and over i guess from different people all boosting it" (Adam, 2026-11-11). Not boosts: about a dozen keys each post the NostrMag "Quiet Coup" issue every few hours, reworded a little each time (an emoji, "BREAKING", the picture moved), so the exact-repeat spam check never matches. In a search feed a post whose words and links overlap half or more (Jaccard) with one already shown is dropped; posts under 8 words are left alone. Measured on 120 live hits for "fiatjaf" from the three search relays: newsletter copies overlap 0.53 and up, distinct posts 0.45 at most; 46 copies → 1, nothing else dropped. post-search-test adds three reworded copies from three keys Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
* Search: Enter on typed words (no suggestion picked) opens a feed of the ↵Adam Soltys37 hours1-5/+39
| | | | | | | notes and replies that say them. "if i submit just a string without selecting one of the suggestions i'd like it to do full text search on kind 1 posts and replies. so if i search fiatjaf i should find posts that are mentioning him without necessarily tagging him" (Adam, 2026-11-11). A session feed with def.search asks NIP-50 relays ({ kinds: [1], search }) — relay.ditto.pub, search.nos.today, nostr.wine, the three that answered a kind-1 search probe (nostr.band and primal answered nothing) — keeps replies as rows of their own, pages back with until, and has no live subscription. Enter in the search page's field or the desktop header field opens it, and the search page lists a “words” row ("Posts and replies that say this") above the topic and people rows. Its address is /feed?q=… so it can be shared. tools/post-search-test.js answers the search relays in the page Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
* Settings → Wallet → Delete account: the name is given up, the servers ↵Adam Soltys3 days1-0/+11
| | | | | | | forget the account, and it leaves this device. "someone's asking for a way to delete their v3 account. we allowed that in v2 … we can expose a button for it in settings somewhere" (Adam, 2026-11-10). The page first asks the registrar what stands in the way (coinosv3-server f8c9efa, POST /account/delete with check): sats still on their way here or held in a withdraw link block it, and say so. It lists what goes (the name, free for anyone after; the server's records: payment address, notification registrations, scheduled posts, hats, point-of-sale and receipt records; the wallet on this device) and what does not: the coins, which only the recovery phrase controls, and what was published to nostr relays. Coins still here are named in sats with a warning, and the box to tick then says "I have moved my funds or saved my recovery phrase". On the tap the registrar deletes (signed by the wallet's key, which also answers for a login identity's name it manages), the profile drops a lud16/nip05 that named the freed address (addressGone; publishProfileFields learns `remove`), and the wallet is wiped from the session, the saved vault and its caches. A vault behind a real password not entered this session asks for a lock and unlock first. tools/account-delete-test.js (a stand-in registrar checks the NIP-98 signature) Co-Authored-By: Claude Fable 5.2 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6V74NSFWZtNtgCfokN
* A community's admin taps a message and removes its author, with everything ↵Adam Soltys3 days1-6/+64
| | | | | | they said. "can i have some control as a chat group admin to click on a message and see an option to remove the user and all their messages from the group?" (Adam, 2026-11-10). The message sheet gains "Remove <name> and their messages" for the owner on anyone else's message; a first tap asks ("for everyone? Tap again"), the second does it. It is the owner's ban list (CORD-04 vsk 4): one owner-signed edition naming every removed key, which the app, the public /chat page and other Concord clients already fold: those authors' messages, old and new, drop out and they leave the member list. The list is replaced whole, so setBanned asks the relays for the control stream first and builds on the newest edition (banlistHead/makeBanlist chain version and hash), stopping if the relays hand back nothing. The member list shows the admin who was removed, each with Restore. Offered only where the app holds the control stream's signing key (the built-in community and ones founded here). Removed people no longer light a room's unread dot. And the control fold now lets only the owner's editions compete for a head: any member holds the control stream's key in a public community, and a higher-versioned edition of their own used to shadow the owner's ban list, name or channels (dropped, leaving nothing). tools/chat-remove-member-test.js runs it against a relay inside the test Co-Authored-By: Claude Fable 5.2 <[email protected]>
* The public chat page's button opens the chat for someone who has a wallet ↵Adam Soltys3 days1-0/+14
| | | | | | here, and stops telling them to sign in. "andy's having some trouble with the public chat … I can see the feed etc fine, so I am logged in" (Adam relaying Andy, 2026-11-10): /chat read "sign in to join the conversation" to a signed-in reader, and its "Open coinos" button went to the wallet, leaving him to find the chat. With an account on the device the line now reads "open the chat in Coinos to join the conversation" and the button "Join the chat", which goes to /?chat: the app takes the marker off the address and, once the wallet is open, opens the coinos community's room. A saved wallet counts as an account too (a tab opened from a link has no session yet). And /chat, /pos and /feed no longer paint the static profile shell (a card named "chat") while they load. tools/public-chat-join-test.js Co-Authored-By: Claude Fable 5.2 <[email protected]>
* A YouTube clip under a mouse takes its own clicks, and YouTube's full-screen ↵Adam Soltys3 days1-2/+26
| | | | | | button leaves our full screen. "The fullscreen icon on this youtube video worked to make the video fullscreen but not toggle it off … clicking the central pause button didn't pause, it fullscreened. it should just pause" (Adam, 2026-11-10). YouTube's newer player puts its pause button in the middle and its full-screen button above the bottom bar, both under the layer that takes a tap on the picture to full screen: so either press took the box full screen, and once there (the layer aside) the same button asked for full screen again from inside the frame, which shows nothing and leaves no way out. Now the layer is off while a mouse is over the box (by-mouse, set from the pointer's type; a finger gets it back), so the player's buttons do what they say, and a mouse click on the still plays it where it is. A touch's tap still goes full screen with sound; there, when the frame becomes the full-screen element in place of its box (the page sees that much), it's read as "out" and both steps are left. youtube-tap-test covers the touch runs with a finger, the button leaving full screen, and a mouse Co-Authored-By: Claude Fable 5.2 <[email protected]>
* A zap on your Zapstore app reads "zapped your app PAFF" in Notifications and ↵Adam Soltys6 days1-3/+62
| | | | | | | opens the app's Zapstore page. "Clicking this franzap zap notification keeps bringing me to his profile not a note" (Adam, 2026-11-07): franzap's 10k "Love it" was a zap on the PAFF listing (kind 32267, a-tagged in the zap request), but Zapstore's receipt carries only p and P, so the row read as a zap on nothing and fell through to the zapper's profile. Rows now take a zap's address from the receipt or, failing that, the zap request inside it (zapAddrOf; streams get that too); a 32267 one is an app zap, named from relay.zapstore.dev once per session, opening zapstore.dev/apps/<id>. A row saved before reads its receipt by id on the first tap and goes where it says; a relay handing the receipt again heals it as well. tools/notif-app-zap-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6Ts5Tktega8VbLg31q
* A flick through the feed no longer drags background work through the middle ↵Adam Soltys6 days1-18/+71
| | | | | | | of it. "When I flick to scroll, it's not getting up to speed very quickly, and clicking around in the app feels a bit sluggish at times" (Adam, 2026-11-07). Measured on his Pixel 9 in the installed PWA: idle is quiet (0.16 s of script in 10 s), but three real flicks through Following spent 1.7 s of 9 on the main thread, frames up to 200 ms, and the work was the avatar thumbnailer finishing faces as they scrolled in: a synchronous canvas.toDataURL WebP encode per face (229 ms), the profile cache JSON.stringified twice per half-second flush (once just to measure it, up to ~2 MB with thumbnails), and a whole-page repaint per finished face that changes nothing on screen (feed rows keep the face they were admitted with). Now: the crop, resize and encode happen off the main thread (createImageBitmap with resizeWidth/Height, OffscreenCanvas.convertToBlob, FileReader), with the old canvas path as the fallback; a finished face is recorded once the page settles; scheduleRepaint holds while a finger is down or the page scrolled in the last 200 ms (at most 1.5 s), and the profile flush likewise (at most 5 s, forced on an account switch) and sizes the blob by summing rows instead of stringifying it. Tests: the create flow's heads-up / passphrase / check steps from earlier today broke every test that made a wallet by waiting for the words and clicking "Skip verification"; tools/puppeteer.js exports seedWords() and skipSeedCheck() and the 96 tests use them. room-faces and avatar-profile fail the same way on 8858591 (row.hover, scrollIntoView), before today's changes Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6Ts5Tktega8VbLg31q
* The feed's post button is on screen the moment the feed is, while the page ↵Adam Soltys11 days1-13/+25
| | | | | | | is still sliding in. "I'm still not seeing the Post button right away when I open the feed. It should be there from the start" (Adam, 2026-11-02): measured on his phone over DevTools, the button was in the page 87 ms after the Feed tap — 4109 px down, below an 809 px screen — and in place at 562 ms. It is position: fixed, and it was rendered inside the page, whose entrance animation puts a transform on it; a transform makes fixed descendants position within the page, so the button rode at the foot of the feed card until the slide ended. The feed now hands the button and the new-posts pill to the shell (a pageFloaters hook, taken once per render), which paints them beside the page, outside anything that animates; the pill's CSS no longer depends on sitting inside the page, and its phone-width rule stays after the base one so it still rides above the button. tools/feed-fab-anim-test.js taps Feed from the wallet and samples the button every frame of the slide: on screen from the first. The direct-mount harnesses (feed-new-posts, feed-streaming, feed-bottom-nav) paint the floaters as the shell does Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* A face or cover photo whose host has lost the file is found on another ↵Adam Soltys11 days1-8/+94
| | | | | | | server that holds the same blob. "No avatar for karnage?" (Adam, 2026-11-02): his kind 0 names a picture and a banner on media.21media.to, and that host answers "The requested blob was not found" for both — while blossom.primal.net, the server he publishes in his kind 10063, still has them, which is why other clients show his face. A picture named by its sha256 is a Blossom blob, served under the same name by any server holding the bytes: when the thumbnail maker finds the host without it (a 404, or a host that won't answer), the blob is looked for on the person's published servers and then the public ones we know, asked together, and the first that answers stands in — the thumbnail is made from it, the profile page's big face and banner paint from it, the feed's warm-up loads it — and the find is remembered on the device so the search runs once. tools/blob-alt-test.js serves a dead host and a published server holding the copies and checks the thumbnail, the profile page and the post rows Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* A thread's heart count says what its who-panel lists. "Why does the count ↵Adam Soltys11 days1-3/+11
| | | | | | | beside the heart say 1 if I have 2 heart reactions?" (Adam, 2026-11-02): a thread remembers its counts between visits so that a short relay answer can't erase them, and the bar showed the memory alone — while the tally in hand, which the who-panel reads, had the second heart, brought by the feed's own count fetch before the thread was opened again (the memory is only brought up to date while the thread is on screen). The bar now shows the larger of the remembered number and the live tally, hearts, boosts and sats alike. tools/thread-like-count-test.js opens a thread by its link with one heart on the relay, comes back through the feed when there are two, and checks the thread says 2 Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* A quoted note, or the post a reply answers, is asked for from the near ↵Adam Soltys11 days1-9/+24
| | | | | | | relays first — the reference's own hints, ours, its author's — and from the wide net of public and archive relays only once those have come back empty, or after 1.2 s if they are slow about it. Asked all at once, as before, every quote and every reply's parent went out to a dozen relays, and in the Conversations view, where each row has a parent, half of all REQs in twenty seconds on the live app — three hundred, to 47 relays — were lookups of notes our own relays answered in the first beat. tools/relay-budget-test.js adds a far relay: the six quotes our relay holds are never asked of it, and the one only it holds is still found there Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* Comments scoped to a web address show with the page they are on. DaddyTube ↵Adam Soltys11 days1-5/+35
| | | | | | | posts NIP-22 comments (kind 1111) on a film's IMDb page: an I tag naming the address, no e or E tag. Adam's own ("Charming and wholesome", 2026-11-02, on relay.primal.net) showed on neither his profile's Replies tab — the profile page never asked for kind 1111 at all — nor in the Conversations view, whose warm-up pass asked for posts and reposts rather than the view's own kinds, and the refresh that asks properly is skipped for half a minute after it. The profile asks for comments now, the warm-up pass asks for what the view shows, and a comment with no post to show above it carries the page it is on where "Replying to" would go — "Comment on imdb.com/title/tt32565993", linked, or the comment's own alt tag when it has one ("Comment on Sheep Detectives (2025) in DaddyTube") — in place of the bare "reply" tag. Also, a report naming a post as 'other' with the author's bare p tag beside it counted as a report of the PERSON (an 'other' e tag didn't count as naming a post), and two such from follows folded everything the author wrote; every post named now counts. tools/web-comment-test.js plays all of it against a fake relay Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* The feed asks the relays for less, and asks it in fewer REQs. Measured from ↵Adam Soltys11 days1-18/+49
| | | | | | | the laptop (2026-11-02, after the resume fix), an open of Following sent some 560 REQs in twenty seconds across 57 relays, over a hundred each to damus and primal, and damus answered the home IP "banned: too many rate-limit violations". Four sources, all cut: a quoted note's author (a face tapped, a reply's target) had their relay list asked for one REQ per author per relay — six quotes on screen sent eighteen — and those asks now wait 80 ms and go out as one REQ through the feed's batched fetch, which also remembers who has no list; the live subscriptions were closed and reopened at the end of every refresh pass (seventeen REQs over eight relays, twice per open) and now stay up while the relays and authors they cover are unchanged, the head they count fresh posts against read at each event; the follows' reports were fetched afresh at every reload (an in-memory clock) and now honour the clock the device saved, asking only for what came since; and every one-shot REQ was held open on our own relay until the slowest relay in the ask had answered — the library closes them all together — so the fast one carried each for up to five seconds. One-shot queries now go out per relay (queryOn, queryStreamOn, queryManyOn, the profile and inbox lookups), each closed on that relay's own EOSE, with no more than ten open on any relay at once: the rest wait their turn, and a turn that comes after the ask's own wait is skipped. tools/relay-budget-test.js runs the feed against a fast and a slow fake relay and checks each of these Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* Back from the background, the app checks its relay sockets before it asks ↵Adam Soltys12 days1-5/+9
| | | | | | | them anything: one tiny REQ to each; a socket that answers is kept, one that doesn't is closed and redialed at once, its subscriptions refired by the library with since set to the last event each saw, and only then do the DMs resubscribe and the feed refresh. "No new notes in Following since 11:35? It's 9 pm now … it loaded more posts, it just took a while" (Adam, 2026-11-01): after hours in the background a phone's tab is thawed with every socket reading OPEN and long dead on the network, so the catch-up sent on resume vanished, and the library found a dead socket out only after its 29 s ping and that ping's 20 s timeout, then waited 10 s to redial — a minute or more before the backlog arrived through the refired subscriptions. tools/resume-wake-test.js freezes a fake relay's sockets, posts while the app "sleeps", and has the post on screen 2.7 s after resume (never within 15 s before). Also: Primal's "too many concurrent REQs" notice no longer cools the relay off for 30 s at every boot — it serves every REQ past its twenty anyway (measured 60 at once: 60 EOSEs, 12 notices), so the cool-off only lost us its answers while the relay lists, profiles and the feed all asked at once; a CLOSED with that reason still counts Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* A clip whose file its host no longer has says so where the player stood, ↵Adam Soltys12 days1-8/+44
| | | | | | | with the file's link, instead of sitting as a dead player under "Tap to play". A post's video "won't play" (Adam, 2026-11-01): the mp4 had been deleted from Primal's blossom, which answers a 404 with no CORS headers, so the probe couldn't read the status and the box waited for a tap that loaded nothing — no client renders it (the reply under the post says as much) and no other blossom host has the hash. The player is the first to know: its original failing now tries the server's copy once (a file the browser can't decode fails the same way, and the copy plays that one), and the copy failing too marks the clip gone for a day — the line and the link in the box, its cover, play button and controls off, a short box; the next paint of the post says it at once with no request, the media warm-up and the in-view hook leave it alone, and the videos grid leaves it out as it did a 404 the probe could read. Outside the feed the box used to be hidden on an error before anything was said; it gets the line too. tools/video-gone-test.js serves the clips from a second origin without CORS, one of them a 404, and checks the line, the untouched live clip and the silent repaint Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* The feed opened by URL paints as the restoring wallet's from its first ↵Adam Soltys13 days1-4/+14
| | | | | | | frame: the Post button, the feed chips and the nav come with the page, not a beat after it. /feed is a public page, so it painted as a visitor's (no button, chips or nav) the moment the app ran, and the wallet's restore — synchronous work: the vault's scrypt on a cold start, the wallet's state, every feature's init, ~0.5 s on a laptop and more on a phone — re-painted it with the button only once it had run ("the post button takes a while to appear", Adam, 2026-10-31). Boot now tells before that work whether a wallet is about to open (a session list in the tab, a vault the last seal or unlock opened without a password — noted, since the vault alone can't say short of the scrypt being avoided — or watch-only accounts) and the feed, the nav and the chips paint for it; taps still go by the wallet actually being open, which it is by the time one lands. The wide-screen wallet column waits for the numbers. A visitor with no wallet on the device gets the visitor's page as before. tools/feed-boot-chrome-test.js boots the built app at /feed behind a session, a noted vault, an unnoted vault (the old order, and the unlock leaves the note) and as a visitor Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
* A profile (kind 0) is never written over one we failed to find. 2026-10-30, ↵Adam Soltys14 days1-1/+94
| | | | | | a long-time nostr user signed in with his key for the first time; our relays had never seen his kind 0 (it lived on ditto, snort and oxtr), the 3 s lookup came back empty, and the payment-address publish went out as {lud16, nip05} alone: name, picture and banner gone from every relay (nicstazzema, shown as npub1yk2lkr7). An empty quick lookup now widens to the same search the follow list got after its own overwrite (indexes, archives, the key's own write relays, Primal's signed copy) before anything is written, once per key per session so a brand-new key's first publish waits once. For anyone already hit, the profile editor offers "Look for older copies": a kind 0 from a history-keeping relay fills only the fields still empty in the editor, and Save publishes it. tools/kind0-overwrite-test.js plays the incident against a fake relay network where only an archive holds the profile Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Scheduled posts: the composer's clock button opens a "Post at" time (an hour ↵Adam Soltys2026-10-291-15/+138
| | | | | | | ahead, on the quarter hour) and turns Post into Schedule. The note is signed now with that created_at, so a remote signer asks while you're there, and handed to the notifier, which publishes it at that time; nothing reaches a relay before then. What's waiting shows as "Scheduled (n)" under the composer and on your profile, each with Cancel (a kind 5 to the notifier, so it never goes out); one cancelled on another device leaves the list at the next status check Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TUeqTyZB8eXLGymUY
* A gift link names its sender: the link carries the sender's nostr key, ↵Adam Soltys2026-10-281-0/+15
| | | | | | signed over the gift's code (#f=…, beside the note), so the claim screen shows "From <name>" and claiming follows them. A signature that doesn't match the code names nobody, so a link can't be passed on as someone else's gift. "Show it's from me" is ticked by default when making a gift, and unticking it is remembered; the gift's record keeps the sender, so View and Copy link again keep it too Co-Authored-By: Claude Opus 5.5 <[email protected]>
* A timed comment opens with its time as a chip that plays the clip from ↵Adam Soltys2026-10-261-8/+17
| | | | | | | there; the "[0:03] " prefix other clients read is left out of the words (and out of the marker's line on the bar) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* A clip's moments sit on its bar, just under the line (under the waveform for ↵Adam Soltys2026-10-261-1/+3
| | | | | | | audio) instead of floating above it, and a reply made at a moment shows as its author's face, not ↩ (the pager's bar and the pop-up too) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* On a phone, Return in the reply box is a new line; Reply is the button. A ↵Adam Soltys2026-10-261-1/+3
| | | | | | | hardware keyboard's Enter still sends Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* Cancel on a reply discards what was typed and folds the box: it only cleared ↵Adam Soltys2026-10-261-1/+13
| | | | | | the "replying" flag, and the box stays up while a draft has text, so with anything typed it did nothing. Attachments the draft carried are dropped with it Co-Authored-By: Claude Fable 5.1 <[email protected]>
* A released placeholder address on your profile page shows as the claimed one ↵Adam Soltys2026-10-261-3/+20
| | | | | | | | on the Lightning line too, and Save in the profile editor repoints it in the kind 0 (a same-domain name that no longer answers); the background heal steps aside for an interactive signer (Amber) instead of waiting forever in the kind-0 queue Jarryd's page still showed [email protected] over ⚡ [email protected]: the header only substituted the NIP-05 line, and his kind 0 was never republished — the heal of ce96364 needs a background signature, which an Amber sign-in can't give, and Save left a lud16 that wasn't the old address alone. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Messages go through messages.coinos.io first: our own messaging relay ↵Adam Soltys2026-10-251-3/+16
| | | | | | | (Magnitude Relay, built for gift-wrapped DMs) leads the DM and group-chat relays, with relay.coinos.io and primal still read and written. A wallet key's inbox list that only ever held our old defaults is updated to name it; feeds and zaps never ask it (it takes messages, not posts) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* A post you deleted leaves your profile's list (and any feed it was reposted ↵Adam Soltys2026-10-251-3/+8
| | | | | | | into) instead of leaving a "This post was deleted by its author" row; threads still keep the deleted post's place Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* A feed clip with timed reactions seeks on the moments bar alone: the ↵Adam Soltys2026-10-251-7/+32
| | | | | | | player's own controls (its seek bar) stay off, the middle play/pause shows whenever it's paused (touch too), and the sound button stays to turn it down again Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* Feed clips with a mouse: a big play/pause in the middle on hover that plays ↵Adam Soltys2026-10-241-3/+49
| | | | | | | or pauses in place, a click on a playing clip pauses it (a paused one still opens the pager), and a tall clip is held to three quarters of a computer screen with its box narrowed to the clip's shape Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* Amber sign-in on Android works past the first request, and a migrated ↵Adam Soltys2026-10-231-27/+80
| | | | | | | | | | | | | | profile no longer keeps its released placeholder address Jarryd (DM, 2026-10-24): on Android he could not sign in with Amber and had to paste his nsec, and his profile page showed the temporary name he had picked while the editor showed his real one. Amber: Chrome lets a page open another app only on the heels of a tap. The Sign-in tap carried the get_public_key request, but the request the login fires once the key is back (decrypt the backup, sign the relay list…) had no tap behind it, so Chrome dropped the nostrsigner: navigation with a console warning the page never sees ("Navigation is blocked") and the card spun forever. Reproduced on Adam's Pixel 9 with Amber 6.6.9 (Amber 4.1.3 did not even parse the web request). Now a request without a user activation behind it waits for one: the card says the signer has another request and offers an "Open signer app" button that launches it from the tap; a launch that leaves the page in front (never hidden) is treated as blocked too and gets the same button. The clipboard is still read on return (that part worked). Profile: the migration released the placeholder name and repointed lud16/nip05 to the new one, while the legacy-avatar import ran at the same moment, read the same old kind 0, and republished it nine seconds later with the placeholder address back ([email protected], dead on both coinos.io and v3). Kind-0 publishes now go one at a time, merge over the newest copy this session sent, and are stamped strictly later than it (relays keep the lower id of two with the same second). The profile page shows the claimed address instead of a same-domain name that is no longer ours, and once the domain confirms the old name is dead it repoints the kind 0 in the background (healStaleAddress), so zaps to the published lightning address reach the wallet again. tools/profile-publish-race-test.js plays the migration's two publishes at once against an in-memory relay. tools/amber-login-test.js and identity-test pass; name-migration-test and avatar-profile-test fail the same way on the previous commit. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* The media grid's order holds for the session: clips found later go under the ↵Adam Soltys2026-10-231-2/+24
| | | | | | | tiles already showing, in their own date order; only a post newer than the top tile goes above it Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* On a phone a live stream is laid out the way Nostrich does it: the picture ↵Adam Soltys2026-10-231-2/+2
| | | | | | | edge to edge, the chat a plain full-width list under it Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
* Media grids: one tile for a clip uploaded twice, and a picture that won't ↵Adam Soltys2026-10-221-10/+33
| | | | | | | | | | | | | | | | | | | | | | | | load stays out - Videos: one person's clips of one size to the byte are one clip. Beatbox Serenade posted the same video as a note and as a video event, each upload a fresh Blossom hash, so neither the file nor the words matched. Only a size the post or the host states counts (a bitrate x duration guess is marked sizeGuess and never matched). - Images: a picture whose load failed was hidden by a class the next repaint rewrote, so the broken-picture glyph came back (a deleted nostr.media file in #artstr). Failed pictures are now remembered for the session and left out of the grid. - A tile fetching its own frame froze the whole tile against repaints, so one built for the one-to-a-row layout never got its --ar in justified rows (zero width). Only the player and its stand-ins are frozen now; the render draws the cost badge itself. Tile pictures no longer size their tile (a frame-grab <video>'s 320px stretched its row). - Test fixtures that served one file under several names (or one imeta size for every clip) now differ per clip. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01K5xtiFrkFVsrkWx6JDQjiT
* Videos grid: a clip the player can't load leaves the grid instead of sitting ↵Adam Soltys2026-10-221-0/+12
| | | | | | | | | | | | | there as a blank tile A deleted file on Primal's blossom answers 404 without CORS headers, so the probe saw only a quiet host (not a 404), the server had no still for it, and the tile's fallback player failed with nothing listening: a blank box (Adam's iPhone). The player's error now hides the tile and marks the clip gone for a day, as a 404 the probe could read already did. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01K5xtiFrkFVsrkWx6JDQjiT
* Videos: a desktop-sized window defaults to every shape (a phone keeps to its ↵Adam Soltys2026-10-221-5/+19
| | | | | | way round; the choice is stored only when it differs from the device's default). A clip's shape falls back to its still's proportions, so leaving All shapes finds the clips known. The grid's pager keeps a clip of unknown shape out until measured, as the grid does (d898efb let an unmeasured landscape clip into a phone's vertical pager). Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Images grid in justified rows too: tiles in each picture's own shape (the ↵Adam Soltys2026-10-221-7/+24
| | | | | | tile thumbnail is no longer a square crop: 360px tall, its middle cut to 1:2–2:1; vmedia pic.webp?s=row for hosts without CORS; tile cache v2) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos grid, justified rows: a tile whose clip's size isn't known yet takes ↵Adam Soltys2026-10-221-2/+16
| | | | | | its still's proportions Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos grid, "All shapes": justified rows (each tile its clip's own ↵Adam Soltys2026-10-221-5/+12
| | | | | | proportions, every row one height filling the width) instead of squares Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos grid: "All shapes" lays the clips out three across in squares (tall ↵Adam Soltys2026-10-221-4/+6
| | | | | | tiles stay for a phone held upright showing tall clips only) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos grid on a wide screen (wide clips only): one clip to a row with its ↵Adam Soltys2026-10-221-1/+4
| | | | | | 16:9 poster whole, instead of portrait slivers three across Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos: "All shapes" in a pager opened from the grid brings in the clips the ↵Adam Soltys2026-10-221-3/+6
| | | | | | other way round (the pager now holds every clip, pagerFit keeping the wrong-shaped ones out of sight until asked) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos grid: a clip whose host says it's deleted (404/410) is left out for a ↵Adam Soltys2026-10-221-5/+12
| | | | | | day instead of sitting there as a blank tile Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Media grid shows a clip or picture once however many posts carry it (same ↵Adam Soltys2026-10-221-5/+34
| | | | | | file or Blossom hash, or the same words from the same person re-uploaded); on a big window a small clip in the pager is drawn at most 1.5x its pixels instead of blown up across the monitor Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Videos on a desktop: moving the mouse over a playing clip brings the seek ↵Adam Soltys2026-10-221-2/+13
| | | | | | bar up, and it fades about 2 s after the mouse goes still (it stays while the pointer rests on the bar) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Replies whose post can't be found leave the Conversations view, and fewer go ↵Adam Soltys2026-10-201-7/+40
| | | | | | | | | | | | | | | missing A reply under "Couldn't find that note on the relays" told nobody anything (Adam, 2026-10-20), so once every lookup comes back without its post it isn't shown in a feed's Conversations view (its thread, notifications and the replier's own Replies tab keep it; a lookup that later finds the post brings it back). The lookups miss less: - A NIP-22 comment (kind 1111) names its parent's author at e-tag [3], not NIP-10's [4], so every comment's parent lost its author and the author's outbox went unasked. fiatjaf's comment on Kieran's routstr post lives on relay.snort.social (its hint), Kieran's pyramid.fiatjaf.com and ditto: none of ours. - A reply that names no author asks the outboxes of the people it's addressed to (its p tags, up to three). - The relay the reference itself names is asked even when this device has marked it sick. - A post read in Following (or the feed in use) is found in that feed's disk cache too, so a Conversations view opened right after a reload doesn't take its replies for orphans. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Timed reactions sit on the seek bar at their moment, and the usual buttons ↵Adam Soltys2026-10-201-35/+95
| | | | | | | | | carry the time The "at this time" reply/like/zap buttons are gone, and with them the panel they made over the pager. A reply, like or zap made from a post's own buttons (the feed's, or the pager's ♥ ⚡ and post) while its clip has played is about that moment; the reply box says so and its × makes it about the whole post. What was said, reacted or zapped at a moment sits over the bar at that moment (a custom :emoji: as its picture, from the reaction's own tag; it was showing as :cb-rofl: text), a tap goes there and says who and what, and in the pager it floats up off the bar as the clip plays past. Your reaction no longer turns the count red, only the heart. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Videos: the seek bar is out of sight (and out of reach) while a clip plays. ↵Adam Soltys2026-10-191-3/+15
| | | | | | | A tap near the foot of the screen brings it up for a few seconds without pausing (longer while it's dragged), and a paused clip shows it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Videos grid: a clip opens sooner. Its host is connected to ahead (a ↵Adam Soltys2026-10-191-2/+52
| | | | | | | preconnect for the grid's clip hosts: the first clip from a host took 1.8 s on Adam's phone, 0.65 s once connected), and the player starts loading when the finger comes down on the tile; the pager adopts that very element, so the load overlaps the tap and the pager's opening. A press with no tap after it lets its player go. (feed-media-views test: the Live chip, and the warm next/kept previous players.) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg