<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/tools/feed-media-views-test.js, branch master</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-10-13T06:17:39Z</updated>
<entry>
<title>Media pager: up and down go from post to post, left and right along a post's pictures. Adam: swipes and arrows up/down should go to the next post's media, left/right to the next within a post. The pager is now posts stacked top to bottom, each a row of its pictures or clips side by side — a quoted post's join the post that quotes it — with dots along the top when a post has more than one, saying how many and which. Up and down (swipe, arrows, j/k, Page) move a post at a time and land on a post where its row was left; left and right (swipe, arrows, h/l) move within the post and stop at its ends. A post's pictures all load as it comes near (those along its row too); a clip's head start goes to the next post's (where its row rests), the next along this one, and the post after next, at half; the look-ahead (the feed paging on, the media crawl) counts posts. Pictures pan sideways as well as up and down at 1×. Tests find the slide on screen by where it sits (the pager's children are posts now); feed-picture-pager walks right within a post, down past one with none, back up to where a post was left, left to its first, and a sideways swipe next to a swipe up; quote-pager walks the quoting post's row (its own picture, then the quote's).</title>
<updated>2026-10-13T06:17:39Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T06:17:39Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=8f80948eb174a273f3b1dea963bb91745bcf323b'/>
<id>urn:sha1:8f80948eb174a273f3b1dea963bb91745bcf323b</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Media pager: the head start sizes itself to the clip and the link, and a tap pauses any playing clip. Adam: a fixed 512 KB head start isn't right for every clip; and a tap anywhere on a playing clip but the seek bar should pause it. The head start is now seconds of the clip, not bytes: two seconds' worth on a link well ahead of the clip's bitrate, four when only just ahead, and on a link slower than the clip enough to cover the shortfall over its length — at least 256 KB, at most 4 MB, the second clip ahead half. The bitrate is the file's size over its length when the post says both (imeta size and duration; duration is now kept), else guessed from its picture size, else a phone clip's; the link's speed is measured off the head starts themselves (a moving average), and until there is a measure it's two seconds' worth — Chrome's own downlink guess runs low (in the test browser it would have had every first head start at the cap). A reader who swipes on within seconds (a moving average of how long clips hold them) gets just the opening; a clip of 3 MB or less comes whole. A tap on a playing clip in the media grid's Videos pager now pauses it with its details up, as one opened from the feed does; another plays on (it only toggled the details). And mergeFeed's duplicate check now counts the posts waiting a moment to go in together: a copy from a second relay, or another version of an addressable clip, could slip in during that window (video-events-test caught the second, one run in a few). pager-eager-test: clips whose imeta states size and length; two seconds' worth ahead (half for the second) before the link is measured, still that on a fast link measured, the cap (half) on a 3 Mbps link the server paces (the player still takes the opening from the cache there — DevTools' throttling had defeated the cache, not the app); feed-media-views-test checks the tap to pause and play on in the Videos pager.</title>
<updated>2026-10-13T05:58:33Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T05:58:33Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=72705992c3d40f87052ade605c50f60a18f80453'/>
<id>urn:sha1:72705992c3d40f87052ade605c50f60a18f80453</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Media pager: past the last slide, a "fetching the next video" screen — and the next one, when it comes, is moved onto. Adam reached the last clip before the next was in, and a swipe only nudged the clip and sprang back. Now the pager ends in a screen of its own: a ring sweeping round, three sparks orbiting the other way, the play mark (a picture mark in a pictures pager) breathing in the middle, and "Fetching the next video…" (or "Fetching more pictures…") with a sheen running through it, all still under prefers-reduced-motion. Landing there stops the clip left behind and asks for more at once (the feed pages on; the media grid's pager crawls); slides that arrive go in above it, and one arriving while the reader waits is moved onto and plays. With nothing older to be had (the feed at its end, or the media index), it says "That's everything for now" and goes still. The scroller no longer anchors (overflow-anchor: none), so a slide put in above the screen doesn't hold the screen in view. Strings in all 20 languages. tools/pager-tail-test.js (relays that take 3 s for anything older: the screen at the end, the clip behind stopped, the next clip moved onto and playing, the end saying that's everything, Escape onto the last clip's post); feed-pager-ahead and feed-media-views count slides without it.</title>
<updated>2026-10-13T05:19:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T05:19:47Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6488a0a687144e217f71ab1c7a768613b0f17e58'/>
<id>urn:sha1:6488a0a687144e217f71ab1c7a768613b0f17e58</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Media pager: every clip plays when it comes up, and the next two get a half-megabyte head start. Adam: flipping through clips should just play them, large ones too, and load a bit of what's next — not the whole of it. A clip over the size budget (or one whose size the host won't say: most, behind CORS) used to wait in the pager under a tap-to-play cover, and even a small one waited on its size probe (up to 5 s of HEAD) before playing. Now, on an ordinary connection, the pager plays the clip on screen at once, streaming as it goes, and fetches the opening 512 KB of the next two into the browser's HTTP cache with a range request (a host that ignores the range has the rest cancelled); the player, given the file when it's swiped to, takes its opening from the cache and streams on from there (the test's server sees its first request start at byte 524288; playing ~300 ms after the swipe). The next clips get no player of their own beforehand: one given its file, even with preload=metadata, took the whole 34 MB on a fast link. With Data Saver on or a slow link (2g/3g), the pager is careful as before: a clip over budget waits for a tap and nothing loads ahead. tools/pager-eager-test.js (35 MB clips: covered in the feed, played in the pager, 512 KB each ahead and nothing more, the cache reused; Data Saver: nothing ahead, the cover); feed-media-views-test moves its tap-to-play checks onto a slow link and checks the 50 MB clip plays on an ordinary one (its index-last probe check fails on HEAD before this too).</title>
<updated>2026-10-13T05:10:27Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-10-13T05:10:27Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6fdca50f17b1e814bbf6ba1fc1ffcb18001c54fd'/>
<id>urn:sha1:6fdca50f17b1e814bbf6ba1fc1ffcb18001c54fd</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
</content>
</entry>
<entry>
<title>Feed videos before they play: the picture fills the box with only the play 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</title>
<updated>2026-10-07T23:33:09Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-07T23:33:09Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6dfb59316865093eda01307ee3c95f87511cdf56'/>
<id>urn:sha1:6dfb59316865093eda01307ee3c95f87511cdf56</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Browser tests: public relays answered in the page, never reached</title>
<updated>2026-10-07T19:02:51Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-07T19:02:51Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=86d9818abfc160dac031bb292abe28d6f28fc624'/>
<id>urn:sha1:86d9818abfc160dac031bb292abe28d6f28fc624</id>
<content type='text'>
A suite run let ~90 throwaway test wallets subscribe and publish on public
relays from one home address, and nos.lol and relay.damus.io banned it.
tools/puppeteer.js wraps puppeteer-core: every socket to a non-local host
gets an in-page stand-in relay (EVENT -&gt; OK, REQ -&gt; EOSE). Tests that read
real content launch with { relays: 'read' } (real reads, publishes answered
in place); feed-live, chat-layout and cordn-ui launch with { relays: 'live' }.
LIVE_RELAYS=read|1|host,... overrides per run.

profile-key-qr: measure the QR again if a repaint swapped it out.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Feed tests: catch up with Articles, Popular as the default feed, timed feed defs and the tint that fades on sight</title>
<updated>2026-10-07T15:12:38Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-07T15:12:38Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=faad243486666eb4cbefcd7ff813329e707106dd'/>
<id>urn:sha1:faad243486666eb4cbefcd7ff813329e707106dd</id>
<content type='text'>
- feed-media-views: the mode switch has an Articles button now
- feed-media-cache: the live watch's since-only subscription isn't part of the crawl
- feed-switch-seed: seeded feeds carry their edit time (the feeds sync merge drops one without)
- feed-editor-relays: nos.lol is a local stand-in relay, so the test doesn't depend on reaching it
- feed-streaming: the stub wallet opens on Following (no remembered feed = Popular); the held picture
  answers repeat fetches once released; the new-post tint is recorded as it paints (it fades on sight)

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Rebuildable caches move from localStorage to IndexedDB (cache-store.js)</title>
<updated>2026-10-07T14:24:33Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-07T14:24:33Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=78cf670939133fc4f656f0f42b21a59431a53842'/>
<id>urn:sha1:78cf670939133fc4f656f0f42b21a59431a53842</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Video frames from bytes, not from a CORS player; pager clips load plainly again</title>
<updated>2026-10-03T22:52:38Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-03T22:52:38Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=141779ab97efad6abefaec0545bba5330fd1e12d'/>
<id>urn:sha1:141779ab97efad6abefaec0545bba5330fd1e12d</id>
<content type='text'>
Regression from 6488806: pager clips were loaded crossorigin so their
frame could be kept, and on a phone the large clip's slide went black.
Some hosts (Primal's R2 bucket) send CORS headers only to a request with
an Origin and don't Vary on it, so a copy the browser cached from a plain
player load answers a later CORS load without them, and it fails. The
same thing made the size probe fail for any clip that had played in the
feed.

- Pager clips load plainly again; the still under a tap-to-play cover is
  restored on every return.
- The probe (and its HEAD) go past the browser cache (no-store).
- A clip's frame is now made from bytes: the opening of the file (index
  plus about 1 MB, no-store) decoded from a blob and kept as a JPEG —
  for tiles and as the pager's poster. No CORS player anywhere. Files
  whose index trails, or hosts whose bytes can't be read, keep the plain
  player still (shown, not kept).
- Kept frames are 480 px wide (were 270) and capped at 600.
- Probe failures are stored as failedAt, so earlier failures are asked
  again at once.
- Test: an R2-style host, its clip cached by a plain load first, is
  still measured and its frame kept (failed before this change).

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
</content>
</entry>
<entry>
<title>Pager: a large clip keeps its picture when you come back to it</title>
<updated>2026-10-03T22:41:42Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-03T22:41:42Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6488806b840c009278f4ae16de9e079ea4b892bc'/>
<id>urn:sha1:6488806b840c009278f4ae16de9e079ea4b892bc</id>
<content type='text'>
Leaving a slide lets go of its file, and coming back skipped the still
because the cover was already there — black again. The still is now
restored on every return. Pager clips also load CORS-readable (a plain
retry for hosts without CORS), so their frame is kept the first time —
the still under a cover and any clip that plays — and from then on the
poster alone covers it.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
</content>
</entry>
</feed>
