aboutsummaryrefslogtreecommitdiffstats
path: root/tools
Commit message (Collapse)AuthorAgeFilesLines
...
* Call the protocol by its name: 'Marmot (MLS)' in the group-type picker and ↵Adam Soltys8 days1-2/+2
| | | | | | the send-by menu (White Noise is one app that speaks it); a zap on something no relay has says it may be a private message instead of claiming the post vanished Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Setup wizard: a cover behind the avatar — twelve nature covers ↵Adam Soltys8 days1-0/+64
| | | | | | (self-hosted, Unsplash) cycled with arrows or a swipe, or upload your own; untouched, each pubkey gets its own default, published as the kind-0 banner beside the punk Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Profile links paint whole on the first frame: the server renders /npub1…, ↵Adam Soltys8 days1-0/+126
| | | | | | | | | | /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]>
* History: a Lightning payment still settling (a zap, mostly) shows at once as ↵Adam Soltys8 days1-0/+54
| | | | | | | | a pending Sent row with the recipient's face, and gives way to its completed row One-tap zaps now record who they went to, so their rows carry a face too. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Send suggestions: a name lookup's avatar is a quiet circle until its profile ↵Adam Soltys8 days1-0/+87
| | | | | | arrives — the default punk flashed in front of a new account's chosen picture Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Browser tests: public relays answered in the page, never reachedAdam Soltys8 days91-113/+241
| | | | | | | | | | | | | | 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 -> OK, REQ -> 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 <[email protected]>
* Desktop: the Send amount field takes the cursor as soon as it appears (once; ↵Adam Soltys8 days1-0/+9
| | | | | | never off another field, never on phones) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Swiping to Savings opens its Receive pane (the deposit address, no extra ↵Adam Soltys8 days1-0/+136
| | | | | | tap); Enter on Send picks the top suggestion, and Enter walks amount → review → confirm Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ark ui-test and comments-test: no more timing racesAdam Soltys8 days2-10/+76
| | | | | | | | | | | | | ark ui-test: wait for the selected balance card to stick before opening Receive (the carousel lands on a card's history, and a glide could settle back on Savings), wait for the first mailbox read before paying, paste addresses whole (typed a key at a time, a re-render mid-typing broke the checksum), and poll for alice's balance and the history rows. 8/8 runs. comments-test: the article and comments it fetched are handed to the app on every relay socket that asks, so it no longer depends on the app reaching the same public relays. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Merge branch 'worktree-agent-a9ba2f8bea9a7dafc'Adam Soltys8 days18-635/+262
|\
| * ln-e2e-test: wait for both CLN nodes to reach the chain tip before the hold ↵Adam Soltys8 days1-0/+5
| | | | | | | | | | | | | | | | | | receive Run right after tests that mine, cln-ark could trail by a block or two and the receive failed with final_incorrect_cltv_expiry. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * ark ui-test: port the browser e2e to the current appAdam Soltys8 days2-142/+127
| | | | | | | | | | | | | | | | | | | | | | | | The old test spawned ~/halwallet's dev server and drove its long-gone UI (receive-mode select, Settings 'Refresh / consolidate'). It now builds this repo's app bundle and drives it against the regtest stack: set up Spending, receive over Ark from alice, send back from the Send form, fund Savings on-chain and move it into Spending (board), reload. Only the page and the local backends are reachable. aliceReady uses 'bark maintain' so actions a killed run left half-done get resumed instead of holding coins locked. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * expired-dust-test: skip unless captaind runs a short vtxo lifetimeAdam Soltys8 days1-1/+10
| | | | | | | | | | | | | | | | It needs vtxo_lifetime <= 60 so it can mine past expiry; against the normal 4320-block server it would age the whole shared chain. Also import the manager relative to the repo instead of the main checkout's absolute path. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * ark tests: shared aliceReady() keeps the bark funding wallet usableAdam Soltys8 days6-22/+58
| | | | | | | | | | | | | | | | | | scheduled-refresh-test mines a whole vtxo lifetime, which expires alice's coins; every test run after it funded nothing (spendableSat 0) or was handed an expired coin (receive-test). The helper refreshes expired coins and boards more when alice runs low, and the funding tests call it before paying. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * ark board/refresh tests: current regtest chain and server rulesAdam Soltys8 days4-7/+15
| | | | | | | | | | | | | | | | | | | | | | - mine/fund through the bc container (the standalone :18543 node was retired in the July consolidation) - board-test hands the server the funding tx (require_board_funding_tx) and waits for esplora to see the board confirmed: bark silently drops a received vtxo whose board tx it can't find yet - refresh-test rounds the ppm fee up like the server (and the manager) do Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * Drop the third-party HTLC swap bridge tests: the policy they exercise is goneAdam Soltys8 days2-391/+0
| | | | | | | | | | | | | | | | | | | | 68fd614 removed the VtxoPolicy::Htlc encoding from send.js/proto.js (upstream took type 0x08) and the bridge was retired from the app on 2026-08-02, so htlcLock can no longer build its output (policyTaproot throws on the missing userPubkey). bridge-e2e-test also drove bridge/server.js, which sits on the same dead primitives. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * electrum-ws.test: a real bun test that skips when no bridge listens on :50003Adam Soltys8 days1-27/+39
| | | | | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * bolt12-test: committed regtest offer/invoice fixtures instead of a vanished ↵Adam Soltys8 days3-4/+9
| | | | | | | | | | | | | | | | | | | | | | scratchpad The default fixture paths pointed into an old session's scratchpad, so the test died reading them (exit 1, no output). Fresh pair from the regtest stack: offer from clc, invoice fetched by cln-ark. Network and expiry checks no longer assume mainnet or a recent wall clock. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * Drop swap-vectors.test: src/swap.js (Boltz swaps) was removed in 348a570Adam Soltys8 days1-42/+0
| | | | | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]>
* | Merge branch 'worktree-agent-ad1d843e9e394974e'Adam Soltys8 days15-100/+153
|\ \
| * | Feed tests (port 5298 group): bottom-nav Feed, Following seeded, today's ↵Adam Soltys8 days4-29/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | prepend and editor - feed-list, markdown-note: open the Feed tab, on Following; feed-list opens the editor from the feed's ⋯ menu - feed-stability: the warm-up query asks kinds 1+1068 (polls); a live post with the header on screen goes in above the first post, held still (it no longer waits); the per-frame drift check uses a second post once the header is away - relays-settings: a new identity puts out its DM inbox list at boot, and the page has other Save buttons; the no-change save clears the log and clicks the card's own Save Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Feed tests: open the Feed from the bottom nav, on FollowingAdam Soltys8 days6-56/+73
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The feed left the chat home for a bottom-nav tab, and a wallet with no feed remembered opens on Popular, so these tests click the Feed tab and seed Following as the wallet's last feed. Also brought up to date: - feed-live: the pill lands on the oldest new post; arrivals at the top go in above the first post (held still), not behind the pill; scroll up a screen at a time (one jump lands before the windowed rows are back) - feed-custom: only the feeds' chips are counted (not All/Images/…); the follows box is not the first checkbox any more; the last feed is remembered across a reload; the pack shows in the feed's editor (the chat home no longer lists the feed) - feed-follow: Feed tab + Following chip instead of the messages-home row; the feed opens at ten posts (tall ones, so a page doesn't fill the screen) - moderation: nos.lol is a local stand-in (a relay that won't connect held the mute list back past the checks) Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Feed tests: catch up with Articles, Popular as the default feed, timed feed ↵Adam Soltys8 days5-15/+36
| |/ | | | | | | | | | | | | | | | | | | | | | | | | defs and the tint that fades on sight - 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 <[email protected]>
* | Merge branch 'worktree-agent-ab051145d709af327'Adam Soltys8 days11-57/+100
|\ \
| * | profile-feed-test: the ⚡ lives in each note's action bar; holding it sets ↵Adam Soltys8 days1-4/+18
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | the amount A tap pays the default amount at once (21 sats unless set), so the test holds the button to open the amount screen instead of tapping an empty wallet into a failed payment. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | unread-dot, sw-reload tests follow the bottom nav and the split buildAdam Soltys8 days2-8/+10
| | | | | | | | | | | | | | | | | | | | | | | | | | | - unread-dot: the front door's way to import is Create a new wallet → Have an existing seed; the dot is the Messages bottom-nav button's - sw-reload: the split build loads app-<token>.js, so a restamped build asks for app-deadbeef….js; serve the real entry for it Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Communities: a room rebuilt with fresher keys folds its channels againAdam Soltys8 days1-3/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
| * | padlock-flow, onboard-backup, amber-login tests follow today's UIAdam Soltys8 days3-33/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - padlock-flow: the padlock lives behind the balance card's gear (wallet settings), declining lands back there; another wallet is added from the profile's 'Sign into another account' (import tab) and counted in the identity switcher, since the Accounts list is no longer reached from the wallet - onboard-backup: the welcome card lost its blurb (42c8d4e); the way in now says what the wallet is by putting the twelve words on screen at once - amber-login: the starter follows wait on the deep contact-list lookup (follows-overwrite fix), so their signature can come after the wallet opens; keep answering sign requests until things go quiet Co-Authored-By: Claude Opus 5.5 <[email protected]>
| * | Profile tests: reach the feed and chats through the bottom nav, start on ↵Adam Soltys8 days5-9/+12
| |/ | | | | | | | | | | | | | | | | | | | | | | | | | | Following, follow today's onboarding - avatar-profile, profile-open: a new wallet opens the feed on Popular, so seed this wallet's last feed as Following; profile-open opens Feed from the nav - profile-feed: a mainnet import offers a username first (Not now), Messages is a bottom-nav button - profile-draft, profile-recovery: the import button reads 'Have an existing seed or entropy to import?'; profile batches stream through queryStreamOn, so the recovery test answers that too Co-Authored-By: Claude Opus 5.5 <[email protected]>
* / Feed, composer and zap tests follow today's appAdam Soltys8 days12-33/+60
|/ | | | | | | | | | | | | | | | | | | | - feed tests reach the Feed from the bottom nav (the chat-home row is gone) and seed this wallet's last feed as Following (new wallets open on Popular, c89050e) plus the author's profile (readiness gate) - post-actions: its picture is made locally (it read another session's scratchpad), the post's own ⋯ (not the feed header's), a real tap on the DM picture (a tap beside a fitted picture is a tap on the post) - composer-media / video-compress: intercept every public media host (ditto is first now; the tests were uploading for real) - compose-preview: Preview is one toggle button (aria-pressed) - thread-reply: harness has a wallet (else visitor: Reply asks to sign in), registers <coinos-text>, holds the root's id-only query; atomic send tap - video-unmute: stubs what videoNode reaches outside its slice; preload none means the in-view clip is told to load - zap-deep-profile: zap button scrolled to mid-screen (the bottom nav covered it) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Rebuildable caches move from localStorage to IndexedDB (cache-store.js)Adam Soltys8 days7-17/+155
| | | | | | | | | | | | | | | | | | | | 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]>
* notif-faces-test: open Notifications from the bottom nav (it's no longer a ↵Adam Soltys8 days1-2/+8
| | | | | | row on the chat home) Co-Authored-By: Claude Opus 5.5 <[email protected]>
* White Noise groups reach every device of an accountAdam Soltys8 days1-0/+117
| | | | | | | | | | | | | | | An invitation adds all of a person's fresh KeyPackages (one per slot, at most ten) instead of one, and where our account is an admin a device that finds a sibling's KeyPackage not yet in a group adds it, so the Welcome reaches the other device through our own inbox. These are the same-account rules Marmot's multi-device direction marks as valid today; the device group and pairing it plans are not here. Verified against MDK (wn): a group with two coinos leaves of one npub plus a wn member, all three reading and writing (tools/marmot-multidevice-test.js). marmotClient takes an injectable store for tests. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
* Cordn group chats: MLS groups delivered through a coordinator (Amethyst 1.17 ↵Adam Soltys9 days2-0/+216
| | | | | | | | | | | | | | | | | | | | interop) src/contextvm.js speaks ContextVM (kind 25910 inside ephemeral gift wraps) to a coordinator; src/cordn.js is the protocol (KeyPackages with hex-text identities, the 0xC04D metadata extension, exporter-keyed ChaCha20 sealing, unsigned nostr-shaped envelopes with the sender as MLS AAD, cordn1 group refs, a record engine like marmot.js); src/cordn-client.js mirrors marmot-client.js with the same interface, so the Messages group screens drive both — New group gains a White Noise / Cordn toggle. Verified against @cordn/cli (ts-mls) on the public coordinator both ways, from the engine (tools/cordn-interop-test.js) and from the real app (tools/cordn-ui-test.js). mls.js: the OpenMLS required_capabilities rule for GroupContextExtensions now applies only to groups that carry one. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
* README rewritten for today's coinos v3; build switches are COINOS_* (HAL_* ↵Adam Soltys9 days1-1/+1
| | | | | | | still accepted); silent-payment leftovers removed Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
* Ark: a double-tapped zap no longer spends one coin twiceAdam Soltys11 days1-0/+22
| | | | | | | | | | | | A reconcile pass that began before the first zap took its coin handed it back as spendable (the server still said so) while the zap was in flight; the second zap picked it and the server refused: 'vtxo … already spent'. Coins whose state changed during the pass are left alone. And a payment refused for an already-spent coin now reconciles and pays once more instead of failing with the raw error. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_011bvzKaZosXGHkigcGcSzAR
* Renewing asks first, and a renewal links to its round's transactionAdam Soltys12 days2-0/+177
| | | | | | | | | | | | | | | | | | | | | - The coins page's Renew button opens a confirmation instead of renewing at once: how many coins, their amount, the fee, and what is left to spend meanwhile — with a red notice when that is nothing (the button selects every coin by default, which is how a user locked their whole balance for an hour on 2026-10-03). It says the coins can't be spent until the round's transaction confirms and that a renewal can't be cancelled; the wallet renews for free near expiry anyway. The inline notice from 328f2db moves into it. - A renewal keeps its round's transaction id as soon as the round has run (before it confirms): the in-progress card on the coins page links to it, and the finished renewal's history row carries it, so its detail shows the id with View/Copy like any on-chain movement. Renewals finished before this change have no id recorded. tools/ark/renew-confirm-ui-test.js, tools/ark/renew-txid-test.js Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* A Lightning payment that can't be covered says why, not 'Payment failed'Adam Soltys12 days1-0/+52
| | | | | | | | | | | | | | | | | | | | | | | | A user renewed all ten of their coins (55,611 sats) from the coins page, tried to pay a 55,300-sat invoice three minutes later, and was told only 'Payment failed'. Nothing had failed: the renewal's round ran at once, but its coins stay unspendable until the round's transaction confirms on-chain, and that block took 64 minutes. The quote found no spendable coin and gave up with the generic message; nothing reached the server. - The quote now tells apart (lnShortfall): money held by another payment (wait, as before — now by sum, not one coin covering it all), money a renewal holds ('Nothing was sent. N sats are being renewed…'), and a real shortfall ('needs X sats with its fee, Y available'). Coins leaving in an offboard or exit are never waited for. - The manager's 'insufficient ark balance' names a renewal too. - The coins page says before the tap that renewed coins can't be spent until the round confirms, and that renewing every coin leaves nothing to pay with meanwhile. tools/ark/ln-shortfall-test.js Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Video frames from bytes, not from a CORS player; pager clips load plainly againAdam Soltys12 days2-10/+25
| | | | | | | | | | | | | | | | | | | | | | | | | | | 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 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Pager: a large clip keeps its picture when you come back to itAdam Soltys12 days1-0/+4
| | | | | | | | | | | | 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 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Pager: a large clip shows its frame under the tap-to-play coverAdam Soltys12 days1-1/+4
| | | | | | | | | | | | | The slide's poster was fixed when the pager was built, and a clip over budget held no source, so a clip whose frame was kept a moment later (or never) was a black screen with a button. Now the kept frame is looked up as a slide comes near; without one, the clip's first frame is loaded under the cover (only the start of the file, when it leads with its index or is served in ranges) and kept for next time. A tap plays it from the start. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Video tiles: a frame even when the size check fails; retry it laterAdam Soltys12 days1-10/+27
| | | | | | | | | | | | | | | | | | | | Efrat's clip (blossom.primal.net → r2a.primal.net) showed a blank tile on a phone: the 64 KB probe failed there (slow connection), the failure was kept for good, and a tile with a failed probe never tried for a frame. - A failed probe is dated and asked again after an hour (it was kept forever; old 'failed: true' entries are retried). The probe waits 10 s. - A tile whose probe failed still tries for a first frame, giving up and letting go of the file after 8 s. - Primal's CDN lets scripts read the bytes but not Content-Range, so the size was unknown: a HEAD's Content-Length (readable by any page) fills it in. - No size in imeta but a bitrate and duration (Primal posts those): the size is estimated from them. 'dim 1080.0x1920.0' is understood. - A tile with nothing to show is dark, like a video, not a pale gap. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Media views keep their own cache and crawl for media directlyAdam Soltys12 days1-0/+145
| | | | | | | | | | | | | | | | | | | | | | | | | | Videos was slow because the grid read from the feed's page of posts: the feed cache holds 30, so after every reload the grid re-crawled the relays, ten posts a step, each post first waiting (up to 8 s) for its avatar, pictures and link cards — none of which the grid shows. - Each feed now keeps a media index (IndexedDB, src/media-cache.js): the posts carrying pictures and those carrying clips, 400 of each, and how far back it has looked. The grid and pager read from it; a reload paints them at once. - The crawl reads older posts straight into the index, 100 at a time, without the readiness wait and without touching the feed's own page; it resumes from where it stopped. The bottom of a feed is looked for again after a day. Curated feeds still page the usual way. - A tile's first frame is kept (a small JPEG in the Cache API, when the host lets its pixels be read) and painted as a picture from then on: in the grid, as the pager's poster and the feed's. - Probe results kept for 2000 clips (was 600). - Forgetting a wallet wipes the index and the frames. tools/feed-media-cache-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Videos opens on a grid of thumbnails; the pager waits for a tapAdam Soltys12 days1-5/+14
| | | | | | | | | | | | | | Choosing Videos no longer jumps into the full-screen pager: it shows the vertical clips as a grid, like Images, and a tile opens the pager on that clip. Every tile gets a first frame when that costs only the start of the file (the index leads, or the server serves ranges), whatever the clip's size; a clip over budget wears its cost as a badge. Only an index-last file from a server without ranges shows its cost instead. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Videos: know what a clip costs before fetching it; vertical clips onlyAdam Soltys12 days1-6/+59
| | | | | | | | | | | | | | | | | | | | | | | | | | - What a clip costs is learned up front: the post's imeta (size, dim, image) when it says, else one 64 KB ranged request — the total from Content-Range, whether ranges are served, whether the MP4's index (moov) comes before its data, and the picture size from the track header. Up to three at a time; remembered across sessions. It also replaces the player-based shape probe the feed ran on every clip. - Clips over 25 MB (8 MB on 2G/3G, 3 MB with data saver), above 1080p, index-last files over 8 MB from servers without ranges, or clips that couldn't be measured wait for a tap: a cover saying what they cost ("50 MB", "4K"). Settings → Nostr → Videos: Autoplay large videos. - The pager loads only the clip on screen in full; the next gets its opening if within budget; everything else holds nothing. Sizes are asked three slides ahead. - Feed clips fetch nothing until they're about to play (preload none), with the post's still as a poster. - Video grid tiles show a first frame only for small clips that start quickly; others show their cost. - Videos lists only clips known to stand taller than wide; one of unknown shape is measured and joins when it qualifies; choosing Videos with none known yet opens the pager on the first that turns up. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Feed media views: a grid of pictures, videos to flip throughAdam Soltys12 days1-0/+167
| | | | | | | | | | | | | | | | | | | | | | | All / Images / Videos sits under the feed chips (remembered). - Images lays the feed's pictures out as a three-column grid. - Videos opens straight into a full-screen pager: one clip per screen, snapping, the one on screen playing with sound (muted with an Unmute button when the browser refuses), the next a flick or ↓ away. Closing it leaves a grid of the clips. - The pager shows only the media until tapped; a tap shows the author (tap for their profile), the post's words as a caption without the media links (tap to expand), and likes and sats in the bottom right, which also like and zap. - It is a place: Back or Escape closes it. Built outside the morph, so a repaint never restarts a clip; off-screen clips let go of their media. - Near the end of the grid or the pager the feed fetches older posts and new slides are appended. - Toasts now sit above full-screen viewers. tools/feed-media-views-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Settings → Nostr: manage your relaysAdam Soltys12 days1-0/+138
| | | | | | | | | | | | | | | | | | | | A Relays card edits the two relay lists other Nostr apps go by: the NIP-65 list (each relay Read, Write or both) and the NIP-17 DM inbox. Each relay shows whether it answers (a dot; 'unreachable' spelled out). Both lists are replaceable events other clients write too, so the card edits only a copy it looked up across the index and archive relays, looks again just before publishing, and backs off (reloading the newer copy) if another app saved since. Only a list that changed is republished, to the relays it names, the ones it used to name, and the indexers. Saving updates our own idea of where we write (outbox lookups and wallet sync relays) and resubscribes the DM inbox at once. tools/relays-settings-test.js Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ULQZS9MQa6iAsXnrJUD9Du
* Zaps find lightning addresses our relays have prunedAdam Soltys12 days1-0/+56
| | | | | | | | | | | | | | | Zapping a post looked the author's lightning address up on our four home relays only. Those prune old events, so anyone whose kind 0 hadn't changed in a while (1776, Brad Mills) came back as "no Lightning address or Ark address" — while their profile page, which also asks the index relays and the author's own relays, showed the address right there. The zap lookup now asks both ways at once and takes whichever carries a lightning address. And when someone does have one but Lightning can't be paid right now, the toast says that instead of claiming they have no address. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VKv2uEFRR42Gp6NyqcN3A9
* White Noise attachments (encrypted-media-v2)Adam Soltys12 days2-0/+100
| | | | | | | | | | | | | | | | | | | Pictures and files now travel in White Noise groups and direct chats, both ways. A file is sealed under the group's media secret for the epoch its message goes out in (MLS exporter "encrypted-media" -> HKDF -> ChaCha20-Poly1305, AAD bound to hash, type and name), uploaded to Blossom, and sent as a kind 9 with a v2 imeta tag. A commit landing mid-upload moves the epoch, so the file is sealed again rather than sent under a stale key. Receivers keep each epoch's media secret for as long as a message from it is in the log, so older pictures still open. Groups founded here now carry and require marmot.group.encrypted-media.v2 (blossom-v1 on blossom.ditto.pub and nostr.download). Checked against MDK's wn: our pictures decrypt in wn, wn's in ours, and wn uploads into our groups by our policy. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* DM threads: choose by hand whether a message goes by White Noise or Nostr DMAdam Soltys12 days1-0/+22
| | | | | | | | | | Where a person can be reached both ways (a direct White Noise group with them, or a KeyPackage from another app), the thread header now carries a small pill naming the way the next message will travel. Tapping it pins White Noise or Nostr DM for that thread, or puts it back on Automatic (reply the way they last wrote). Threads with only one road show nothing. Co-Authored-By: Claude Fable 5.1 <[email protected]>