aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
Commit message (Collapse)AuthorAgeFilesLines
* Videos: the seek bar is out of sight (and out of reach) while a clip plays. ↵Adam Soltys15 hours1-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 Soltys15 hours1-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
* Videos: a warm player gets its sound going before the swipe lands. On an ↵Adam Soltys15 hours1-2/+38
| | | | | | | Android phone a player's first play with sound held a still picture ~250 ms while the audio output started (muted: 60 ms; a player that had played resumes in 20-80 ms). So the next clip (and the one behind) plays at a volume nobody hears until its picture moves, a frame or two in, and pauses there (a seek back to 0 undoes it). Only with the pager's sound on, and for clips up to 25 MB: once played, a player reads the whole file. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Videos: the clip just left keeps its player, so a swipe back starts at once ↵Adam Soltys15 hours1-10/+16
| | | | | | | too (on Adam's phone it was the slow one: 0.66-0.78 s, built anew, against 0.04-0.08 s onto the warm next clip). The swipe clock records the player's readyState and how far it had buffered. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Videos start the moment a swipe lands, and streams say who's watching.Adam Soltys15 hours1-3/+98
| | | | | | | | - The pager's next clip gets a warm player: once its opening is in the cache it reads to its first frame (preload metadata: Chrome stops ~2-4 MB in at real link speeds) and waits paused, so landing on it is only play and sound. coinosPagerStarts() in the console times every landing (swipe to moving picture, warm or cold, host, size). - A stream's "N watching" names who on hover, and a tap shows their faces: NIP-53 presence (kind 10312, as Shosho and zap.stream say it), fresh for half an hour. coinos says it too while a stream is open (every 5 min, with a key held here only, never for your own stream) and takes it back on leaving. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Zaps are signed as the person zapping: the login identity (extension, ↵Adam Soltys15 hours1-4/+16
| | | | | | | bunker, passkey, Google) when there is one, else the wallet's key. They were always signed by the wallet's own key, so a signed-in person's zaps showed in stream chats and streamers' overlays under a stranger npub. Zap requests also carry created_at now (a real signer refused them without it). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Posts: a relay named in a post (wss://…) is a link to what's on it — the ↵Adam Soltys19 hours1-1/+9
| | | | | | | raw firehose of that relay alone (/feed/all?r=…). Trailing punctuation stays outside the link. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Profiles: past streams move off the top into a Streams tab (beside Posts and ↵Adam Soltys25 hours1-11/+9
| | | | | | | Replies, shown once they've streamed), as the Live chip's grid of tiles. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Streams: Delete recording is a trash-can button and Share is the share icon ↵Adam Soltys25 hours1-3/+4
| | | | | | | (three joined circles), so the stream's buttons fit on one row on a phone. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Add timestamp comments, reactions and zaps with audio waveform seekingAdam Soltys25 hours3-25/+90
|
* Streams: Delete recording, and Past streams that scroll on.Adam Soltys26 hours1-14/+82
| | | | | | | | | - **Delete recording:** your own ended stream's page has the button. The live server lets the recording go, and the stream is announced again without it, so other apps drop the link too. A deleted one leaves Past streams and says the streamer deleted it. - **Admin:** the Take down button no longer shows on the admin's own streams. - **Past streams** scroll on: 24 at a time as the grid's foot comes near, then older ones from the relays (before the oldest held), until a page brings nothing. They're no longer cut off at a month. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Live streams over WebRTC. Going live also sends the camera to Cloudflare's ↵Adam Soltys30 hours1-3/+107
| | | | | | | | | | | | SFU, which the live server brokers, and a viewer in the app watches from there: 0.1 s behind the broadcaster in go-live-test LATENCY=1, against 3.5 s on HLS. - Switching to the screen and back swaps the track in place. - A reconnect that makes a new stream publishes again, and its viewers follow. - A viewer falls back to HLS if there's no WebRTC for the stream or no connection in 8 s. HLS is still sent, for the recording and the other nostr apps. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Notifications: tapping a zap with no post behind it does something. A zap on ↵Adam Soltys30 hours1-1/+7
| | | | | | | your profile opens the zapper's profile, and a zap on a stream opens the stream. The tap used to do nothing, because the row only knew how to open a post. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Watching a stream: a stream that's on plays through hls.js, held near the ↵Adam Soltys37 hours1-2/+10
| | | | | | | live edge (1.5 s back, up to 1.1× when it drifts past 5 s) — Chrome's own HLS player starts three segments back, never catches up, and fails outright (DEMUXER_ERROR_COULD_NOT_PARSE) if a page changes its speed. With the live server's 1 s segments a viewer is 3.5 s behind the broadcaster, from 8.6 s (go-live-test LATENCY=1 measures it: the broadcaster's camera is a clock in black and white blocks, read off the viewer's frame). Recordings, and streams where hls.js can't run (iOS), stay on the browser's player. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Screen sharing. In a call: Share screen in the call's bar (where the browser ↵Adam Soltys38 hours1-8/+23
| | | | | | | can — desktops) puts your screen in your camera's place, or into the call if your camera's off; Stop sharing, or the browser's own, brings the camera back or stops the picture, and the other side's view follows (a picture that stops coming is hidden). Live: Share screen / Back to camera on the broadcast bar switches without stopping the stream; the browser's Stop sharing goes back to the camera instead of ending it, and a cancelled screen picker leaves the camera going. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A stream's chat is the app's chat: each comment is a bubble like a DM's or a ↵Adam Soltys38 hours1-42/+152
| | | | | | | group's — tap for the emoji row, reply, zap or copy; reactions (kind 7, tagged with the stream and the comment, one per person, deletable) and zap totals show on the comment for every viewer; the zap goes to the commenter, for that comment, on that stream. The composer is the one the other chats use (drafts kept per stream). A reaction no relay took is taken back. (Begun by Codex; finished here.) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Leaving a stream: Feed (and the rest of the nav) leaves it on the first tap ↵Adam Soltys38 hours1-3/+7
| | | | | | | — with the feed under the stream, Feed thought it was already there and only scrolled the hidden feed, and the nav's page-leaving didn't close the stream or Go live. On the stream's own page, the host's ringed face opens their profile instead of the same stream again. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* DMs and calls go to relay.coinos.io and relay.primal.net (both take gift ↵Adam Soltys38 hours1-2/+21
| | | | | | | wraps and pass call messages on live), not nos.lol, which refused or ignored the household IP's publishes. A relay that turns a call message away is left out of the next ones on this device: a day for one that won't take the kind (nip17.com: only kind 1059), ten minutes for a rate limit or ban. publishOn tells its caller which relay refused and why (onrefused). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Calls: the routes found before the offer or answer ride inside it (until ↵Adam Soltys38 hours1-3/+11
| | | | | | | there's a direct and a relayed one, 1.5 s at most) and only later ones go one by one — each route was its own signed message, dozens per call; a household's two devices calling each other got its IP banned by damus and rate-limited by relay.coinos.io, and the answer was refused with them: the callee stuck on Connecting…, the caller ringing on. An offer, answer, hang-up or decline no relay took is tried again (1.5, 3, 6 s) before the call gives up. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A voice call can turn video: Turn on camera in the call's bar puts your ↵Adam Soltys38 hours1-3/+7
| | | | | | | camera in (a renegotiation, NIP-AC's kind 25055); the other side sees you at once and gets the same button for theirs. The call's sound carries on throughout. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Streams on coinos's server: a ⚑ Report button (what's wrong, a note) for ↵Adam Soltys39 hours1-5/+69
| | | | | | | anyone signed in, and Take down for the admin (asks whether to bar the account too); a taken-down stream says so and plays nothing. Go live asks the server first: a name claimed less than a day ago is told when it can, and a barred account that it can't, Start off. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Streams: one our live server has ended reads ended, whatever its last ↵Adam Soltys39 hours1-5/+70
| | | | | | | announcement says (an app closed mid-stream never says so, and it read live for an hour with no way to stop it); its host gets End — on the stream and on their profile — which publishes the ended announcement with the recording. Each announcement is now newer than the last to the second (an ended stamped the same second as the live before it lost to it). A recording that is a web page (zap.stream's Rumble links) gets a Watch the recording link instead of a player that can't play it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Going live: when the server can't take a dropped stream back (it restarted) ↵Adam Soltys39 hours1-8/+26
| | | | | | | and starts a new one, the app now announces the new one, once it plays, and says the old one is over (no recording: there's nothing in it). It went on announcing the dead address, and viewers saw "The stream isn't playing right now" while the camera sent to an address nobody knew. The streamer's own view follows. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Past streams: under the Live chip, the feed's streams of the last month that ↵Adam Soltys39 hours1-13/+60
| | | | | | | left a recording (newest first, when each ended); a profile lists its person's too. A tap plays the recording. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A Lightning invoice from Receive says to keep coinos open until it's paid ↵Adam Soltys40 hours1-0/+11
| | | | | | | — paid while coinos is closed, the money goes back to the sender — and that it lasts an hour (ark.coinos.io's invoices now expire in 1 h, not 48); when the hour is up the QR gives way to "expired" and a New invoice button. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Settings → Wallet → Transaction history: Export CSV — every payment in ↵Adam Soltys47 hours1-2/+21
| | | | | | | Spending and Savings as a line of a CSV file, oldest first: date (UTC), account, type, rail (Lightning, Ark, on-chain), what was paid, its fee, the change to the balance and its worth in your currency at the time, status, the transaction or payment hash, who it went to, the invoice. A failed payment is left out; a renewal is its fee. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Live streams have their own chip, after Videos: a grid of the feed's streams ↵Adam Soltys2 days1-12/+19
| | | | | | | on now, a red dot on the chip while someone in the feed is streaming — no longer a strip above every feed's posts. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Answering a call no longer takes seconds: while it rings, the connection is ↵Adam Soltys2 days1-1/+14
| | | | | | | made with the caller's offer and its routes (TURN's too) gather, and the caller's relays and TURN credentials are looked up — Answer only adds the microphone and camera and replies (3.2 s → 0.2 s on a simulated network). An open conversation warms the same for calling out. A remote signer's offer or answer waits for a direct and a relayed route or 1.5 s, not for all of them (up to 4 s). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A call from someone you don't follow and haven't written to no longer ↵Adam Soltys2 days1-6/+34
| | | | | | | vanishes: it doesn't ring, but it's a missed call in your conversation with them, saying it didn't ring and why, a tap away from calling back (with the app shut, the worker notes it for the app, without a notification). A muted person's still leaves nothing. A logged call no longer counts as having written to someone. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Call history: each call is a row in its conversation — Outgoing / Incoming ↵Adam Soltys2 days1-3/+90
| | | | | | | video call · 4:12, Missed, Declined, No answer, Cancelled, Busy — a tap calls back. Kept like a message and sent to our own inbox (kind 1405, gift-wrapped to us alone) so our other devices list it; a call logged by two of our devices shows once, by its call-id. A call that rang while the app was shut and went unanswered is a missed call when it next opens. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Calls ring a closed app: the notifier wakes the phone for a call offer, the ↵Adam Soltys2 days1-4/+32
| | | | | | | service worker fetches the offer's wrap, opens it with your key and rings — "Alice is calling", until answered or missed — for someone you follow or talk to; the tap opens coinos ringing with the offer it kept. A call answered from a push still connects: the caller sends its candidates again once answered. Settings → Notifications → Calls. Also: the worker's DM key mirror retries when the wallet's keys aren't there yet (it waited five minutes). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Allow custom Blossom servers for attachments and post mediaAdam Soltys2 days1-2/+3
|
* Voice and video calls in DMs (NIP-AC, as Amethyst, nospeak and Noscall speak ↵Adam Soltys2 days1-6/+210
| | | | | | | it): 📞 and 🎥 in a conversation's header; a call from someone you follow or have written to rings over any page (tone, buzz, a notification when the app is out of sight) with Answer and Decline; mute, camera off, switch camera, minimize to a bar, hang up. WebRTC device to device (DTLS-SRTP), set up by signed messages in ephemeral gift wraps to the DM inboxes; TURN from coinos's live server (Cloudflare) for the connections that can't go direct. A remote signer puts the candidates inside the offer and answer instead of signing one each. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Live rings and going live. A face whose person is streaming wears a red ring ↵Adam Soltys2 days1-16/+301
| | | | | | | (LIVE under the bigger ones) and opens the stream. Go live (the composer's red dot, or your profile) sends your camera or screen to coinos's live server (coinosv3-server live/: the recording over a WebSocket, HLS out), announces it as your kind 30311 once three segments exist (Chrome's own player won't start a live playlist with fewer), refreshes it each minute with how many watch, reconnects to the same stream if the link drops, and ends it with its recording. Needs a coinos name. Also: the service worker leaves /live/ and /vmedia/hls alone (it froze a live playlist at its first copy). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Hide new posts notice when scrolling upwardAdam Soltys2 days1-9/+6
|
* Live streams (NIP-53), to watch: a row of what's on above a feed (Following: ↵Adam Soltys2 days2-17/+335
| | | | | | | your follows' streams; Everything and Popular: all), a stream's own page at /naddr1… (the HLS stream playing — hls.js where the browser has no HLS — its chat and zaps as they come, a chat line sent as kind 1311, ⚡ to the host), a card for a post linking a stream, and a LIVE line on a streamer's profile. A stream quiet for an hour is over; one that drops is retried; zaps on a stream carry its a tag. Also: the single-file build's video stub gains streamVideo (its uploads threw since the HLS uploads). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A clip in the post a reply answers (the "Replying to" card) goes full ↵Adam Soltys2 days1-4/+16
| | | | | | | screen: a tap on it or its full-screen button used to do nothing, the full-screen handler taking it back out for a media pager that doesn't have it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The media pager no longer goes round from its last post to its first (or ↵Adam Soltys2 days1-30/+9
| | | | | | | first to last): a step, a flick or the wheel at an end stays there. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Clips too big to play as they are (past 1080p, over the data budget, ↵Adam Soltys2 days1-7/+23
| | | | | | | unmeasurable) stream as HLS from vmedia and play by themselves instead of waiting behind a tap-to-play cover; with Data Saver or a slow link the cover stays. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* HLS for long videos: vmedia serves a long clip as a 360p/720p stream made as ↵Adam Soltys2 days1-15/+89
| | | | | | | it's watched (/vmedia/hls.m3u8), the feed and pager play long clips and posted .m3u8 streams through the browser's own HLS, and a long video posted from the app goes up as an HLS stream made on the device (master playlist on Blossom). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The media grid is never all caught up: once the feed's relays have nothing ↵Adam Soltys3 days1-13/+18
| | | | | | | older it reads on from the archive relays, and a bottom it does find is looked past again a few minutes on (no longer kept for a day). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A touch stops a pager flight on the post most on screen: a tap only stops ↵Adam Soltys3 days1-2/+19
| | | | | | | it, a swipe back from there is the post before, and one the way it was flying takes it on. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The picture pager works like the video pager: pictures join once decoded, ↵Adam Soltys3 days1-21/+89
| | | | | | | | | | | | | | | | | | the screen's way round with the shape toggle, and it flies (and counts) once ten are ready; flights end without the crawl, and the feed beneath doesn't repaint mid-flight. Adam, 2026-10-17: "image pager should work similar. we need the tall only chip there too. i'm noticing some stutter/choppiness when flying images. especially towards the end of the fly as it's snapping to the one it's landing on." Measured on his phone (Pixel, 60 Hz) over synthesized touch flings in the live image pager: frames of 33–183 ms mid-flight, some with no script running (pictures fetched and decoded as the flight came near them: the near observer attached every post within 250% of the screen, a few of them originals — GIFs, hosts the server copies can't reach); the feed under the pager re-rendering (renderThreadStable, 40–55 ms each) during flights; and the flight's ease-out cube spending its last ~400 ms under 3 px a frame, the landing picture stepping a pixel at a time. - A picture post waits out of the carousel until its first picture is decoded (img.decode(), the copy falling back to the original as before). pagerWarmPics keeps four decoding at a time, up to sixteen ready ahead of the reader and three behind; a post whose copy and original both fail stays out. The picture's size, learned from the decode, places it for the shape rule. - The shape rule and its toggle ("Tall only" / "Wide only" / "All shapes", one setting with the videos) are in the picture pager's top bar too. - Flying needs ten posts ready, for pictures as for clips; the counter is hidden until then while posts are still warming (a pager with fewer than ten, all of them ready, counts them). The counter fade on settling applies to every pager (the frame loop's motion flag now runs for pictures too). - A flight eases out on a square instead of a cube: no long crawl at the end, and it sets off nearer the finger's speed. - scheduleRepaint holds off while the pager is moving. - pagerLoadAhead fills to the posts found (waiting ones too), not just the ready ones, so a gated pager doesn't crawl page after page. Tests: new tools/picture-pager-ready-test.js (a picture held back by the server waits out and a step passes over it; it joins above the one on screen without moving it; the wide one is out; no counter while one is warming, 3 / 6 once all are in; the toggle lets the wide one in and out and is as tall as the close button). pager-gone-picture now checks the dead picture is passed over. Picture tests not about shape turn all shapes on; pager-swipe-fly has twelve posts (ten ready before it flies) and waits out the longer flights; pager-loading's fixture gains pagerFound; pager-ready-gate: four clips all ready show their count. All pager tests pass except feed-pager-ahead and feed-video-pager, which fail the same checks as on a clean checkout. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The video pager shows no position counter until ten clips are ready, the ↵Adam Soltys3 days1-0/+3
| | | | | | | | | | | same point it starts to fly. Adam, 2026-10-17: "let's also not show the position/total counter until we have 10". The count is recomputed whenever a clip joins the carousel, so it appears as the tenth one does. Picture pagers and Data Saver are unchanged. tools/pager-ready-gate-test.js: with four clips ready the counter is hidden. pager-seek-hide and pager-swipe-fly pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The video pager flies only once ten clips are ready to play; until then a ↵Adam Soltys3 days1-2/+9
| | | | | | | | | | | flick or a spin of the wheel is one post at a time. Adam, 2026-10-17: "let's not allow flying/coasting until we have at least 10 videos fully warmed up". Right after a refresh a fling outran the warming, landing on whatever was ready while clips joined around it. Ready is in the carousel: the clip's first frame on its slide and its opening cached. Picture pagers and Data Saver are unchanged. tools/pager-ready-gate-test.js: with four clips ready a hard flick goes one post (it came round to the first with the gate off). pager-swipe-fly and pager-wheel pass; feed-pager-ahead fails the same two picture checks as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The pager's counter runs through the posts as a flight passes them, instead ↵Adam Soltys3 days1-3/+14
| | | | | | | | | | | of jumping on landing. Adam, 2026-10-17: "the counter should animate as we fly, right now the current position only updates when we land". While a flight is under way (a flung finger or a spin of the wheel), each scroll counts the post under the middle of the screen (a binary search over the posts' tops); the landing sets the post landed on, as before. tools/pager-swipe-fly-test.js: a flick from post 2 shows 2, 3, 4 … 9 on the way. pager-wheel, pager-ready-gate, profile-pager and thread-pager pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* Pager flicks are judged by when the finger moved, not when the move was ↵Adam Soltys3 days1-8/+15
| | | | | | | | | | | | | handled; the counter counts the reachable posts again. Right after a refresh, with clips warming, touch moves queue behind other work and arrive in bursts. The flick's speed was timed on handling, so a burst after a stall read as a fling (a short flick flew) and a stall before the last move as a crawl (a long flick took one step) (Adam, 2026-10-17: "sometimes a quick long flick only proceeds to the next video and doesn't coast as i would expect it to and sometimes the opposite happens -- a short flick does fly"). Each move is now timed by its event's timeStamp, when the finger made it; moves that share one (synthetic ones can) fall back to the handling time. The counter goes back to counting the posts in the carousel (e529ab1 undone): counting the ones still warming confused, since they can't be scrolled to (Adam: "maybe we should go back to showing the total loaded"). tools/pager-swipe-fly-test.js: a gentle swipe whose moves are handled in one burst goes one post; a fast flick whose last move is handled 300 ms late still flies (it took one step with the old timing). pager-ready-gate is back to "the counter grew". pager-ready-gate, pager-tap-slop, pager-seek-hide, pager-first-frame, thread-pager and profile-pager pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The video pager never puts up a stand-in still: a clip joins the carousel ↵Adam Soltys3 days1-8/+8
| | | | | | | | | | | only once its own first frame is on its slide. Where the server had no first frame for a clip, its slide showed the post's still (a kept frame or the post's picture) and swapped it for the clip's first frame when one came — often a different picture, and the swap jarred (Adam, 2026-10-17: "i don't want to show posters and then switch to the first frame because sometimes the poster and first frame were not the same and that was jarring. could we just always wait until we have a non-black frame before including the video in the pager?"). Now such a clip gets no picture at all and waits out of the carousel until its first frame is drawn from its cached opening as the clip ahead; one with no frame to be had stays out. The clip tapped to open the pager is unchanged. tools/pager-first-frame-test.js: the clip without a server frame has no stand-in and waits out of the carousel, then gets its drawn frame. pager-ready-gate, pager-no-poster, pager-eager, pager-loading and pager-swipe-fly pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* A flick near the end of the pager coasts round to the first post, rather ↵Adam Soltys3 days1-2/+10
| | | | | | | | | | | than stopping on the last. A fling whose coast ran past the end used to land on the last post, and a second flick was needed to wrap. Near the end of a video pager the clips still warming join the carousel one by one, so that second flick usually landed on the newest clip instead, and getting back to 1 took flick after flick (Adam, 2026-10-17: "it's a bit difficult to get back around to 1 … can we do something to make it coast right around to the start again even if a video near the end is loading in?"). Now when most of the coast lies beyond the end (fewer posts left before it than half the flick's worth), the flick wraps straight to the first post; flung up from near the top, to the last. A big flick from further back still flies to the last post. tools/pager-swipe-fly-test.js: from post 7 of 9 a flick lands on 1, a flick up from 1 lands on 9, down again to 1, a gentle swipe from 1 is 2; the long flight from post 2 still lands on 9. pager-ready-gate, pager-tap-slop, pager-wheel and pager-close pass; feed-video-pager fails the same two checks as on a clean checkout. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
* The video pager's counter counts every post it has found, not just the clips ↵Adam Soltys3 days1-1/+6
| | | | | | | | | | | already warmed up: a refresh no longer shows "1 / 2" over a grid of a dozen. The posts were all there (the grid showed them); the carousel holds a post back until its clip is ready to play — its first frame on the slide and its opening cached — and only the next couple are warmed at a time, so after a refresh the total started at 1 or 2 and crept up as clips warmed (Adam, 2026-10-17: "On refresh the total video count seems to reset to 1 and slowly more load despite showing posters for several more being available"). Now the total, and the place in it, count the waiting posts too; only posts kept out for their shape are left out. A swipe still passes over a post not yet ready. tools/pager-ready-gate-test.js: the counter reads 3 / 4 both before and after the waiting post joins. pager-swipe-fly, profile-pager, thread-pager and pager-seek-hide pass. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg