aboutsummaryrefslogtreecommitdiffstats
path: root/src
Commit message (Collapse)AuthorAgeFilesLines
...
* "Start in point of sale" belongs to one wallet, not the whole deviceAdam Soltys2026-10-021-4/+10
| | | | | | | | | | It was one device-wide localStorage flag, so whoever signed in next on a till landed in the last owner's point of sale. Now it is keyed by the wallet (still per device); the old flag is dropped since it can't say whose it was. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
* Profile npub: full and monospace again, behind a Public key linkAdam Soltys2026-10-0222-43/+53
| | | | | | | | | The truncated key button took a whole row on every profile. A "Public key" link now ends the Following/Followers line; it reveals the npub in full, monospace, tap to copy, and hides it again. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
* Notifications screen links to its settingsAdam Soltys2026-10-022-1/+10
| | | | | | | | A Settings button beside the Notifications title opens Settings > Notifications (which kinds buzz this device); browser Back returns. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
* Balance card gear replaces Accounts; Spending's coins live behind itAdam Soltys2026-10-0223-69/+162
| | | | | | | | | | | | | | | | | | | | - The "Accounts" pill on each balance face is now a gear. It opens that face's settings: Spending leads with its coins (renewal, the co-operative move to Savings, unilateral exit), then Security (lock, change password), the recovery phrase, auto-logout; Savings adds its public key. A wallet with no nostr identity also gets sign-in and log-out there, since it ha
// Regtest end-to-end test for the native-JS DELEGATED ROUND REFRESH (hArk),
// proven by spending the refreshed vtxo:
//
// 1. alice (bark CLI) funds us with a fresh vtxo
// 2. Submit delegated round participation; wait for the round to execute
// 3. Mine confirmations for the round funding tx
// 4. MuSig2-cosign the hash-locked leaf, forfeit the old vtxo, get the preimage
// 5. Patch + validate the refreshed vtxo, then arkoor-spend it back to alice
//
// Usage: bun tools/ark/refresh-test.js

import { execSync } from 'node:child_process';
import { mnemonicToSeedSync } from '@scure/bip39';
import { HDKey } from '@scure/bip32';
import { hex } from '@scure/base';
import { sha256 } from '@noble/hashes/sha256';
import { secp256k1 } from '@noble/curves/secp256k1';

import { getArkInfo, encodeAddress, blindMailboxId, readMailbox, decodeVtxo, decodeAddress } from '../../src/ark/proto.js';
import {
  buildArkoorSend, cosignWithServer, buildSignedVtxoBytes,
  registerVtxoTransactions, postArkoorMessage,
} from '../../src/ark/send.js';
import {
  submitRoundParticipation, roundParticipationStatus, parseTx,
  cosignHarkLeaf, requestForfeitNonces, forfeitBundle, forfeitVtxos,
  encodeVtxoFromDecoded,
} from '../../src/ark/refresh.js';
import { ALICE, aliceReady } from './alice.js';

const ARK = process.env.ARK_URL || 'http://127.0.0.1:3535';
const BCLI = `docker exec bc bitcoin-cli -rpcwallet=coinos`;
const FUND_SAT = 12345;
const SPEND_SAT = 3000;

const MNEMONIC = 'rug rebuild abstract kiwi rifle vast food robust rifle spirit dilemma stumble';
const master = HDKey.fromMasterSeed(mnemonicToSeedSync(MNEMONIC));
const kp = (path) => {
  const k = master.derive(path);
  return { privkey: k.privateKey, pubkey: secp256k1.getPublicKey(k.privateKey, true) };
};
const keys = { vtxo: kp("m/350'/0'/5'"), mailbox: kp("m/350'/1'/0'"), change: kp("m/350'/0'/6'") };

const info = await getArkInfo(ARK);
const serverPub = hex.decode(info.serverPubkey);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// --- 1. get funded on a fresh key ---
const blinded = blindMailboxId(keys.mailbox.pubkey, hex.decode(info.mailboxPubkey), keys.vtxo.privkey);
const addr = encodeAddress({
  testnet: true, serverPubkey: serverPub, userPubkey: keys.vtxo.pubkey, blindedMailboxId: blinded,
});
await aliceReady();
execSync(`${ALICE} -q send ${addr} "${FUND_SAT} sat"`, { stdio: 'ignore' });
let input = null;
for (let i = 0; i < 10 && !input; i++) {
  const { messages } = await readMailbox(ARK, keys.mailbox);
  const mine = messages.filter((m) => m.kind === 'arkoor').flatMap((m) => m.vtxos)
    .filter((v) => v.policy.userPubkey === hex.encode(keys.vtxo.pubkey));
  input = mine[mine.length - 1] ?? null;
  if (!input
6-33/+85
|
* Clear abandoned payments, reset profile Pay and restore native messaging BackAdam Soltys2026-10-017-17/+55
|
* The standalone wallet opened from a disk shows its avatars, font and languages.Adam Soltys2026-10-016-7/+17
| | | | | | | | | | With no files beside it, relative punks/, locales/, nip46.js and jsqr.js looked on the disk. From file:// they now come from v3.coinos.io (images and scripts need no CORS; /locales/ now sends Access-Control-Allow-Origin on the server), and standalone.html carries Public Sans inline. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* Stream ready feed posts without waiting for catch-up batchesAdam Soltys2026-10-012-62/+115
|
* Zaps and reactions get their own notification switches.Adam Soltys2026-10-0115-4/+44
| | | | | | | | | | | | The notifier now watches kind-7 reactions p-tagged at the user (opt-in by an explicit flag, so older apps that can't word them stay quiet) and sends zap receipts as 'zap' to apps that report the category, 'payment' to older ones. Coinos zap receipts carry their amount into the push. The service worker names the reactor when it knows them and stays quiet for a muted one. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* Each post's reply button shows how many replies it has.Adam Soltys2026-10-011-2/+34
| | | | | | | | | | Replies (kind 1 and NIP-22 comments) are asked in their own batched #e request beside the likes/zaps one and counted under every post of the conversation they name; quotes ('mention') aren't replies, muted authors aren't counted, and a sent reply counts at once. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* The Back button under a post returns to the feed where the reader left it, ↵Adam Soltys2026-10-011-2/+5
| | | | | | | | | | not its top. It now walks history like the system Back (whose popstate restores the scroll place); the profile page's Back does the same. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* Serve a shared snapshot for the public feedAdam Soltys2026-10-013-11/+66
| | | | Build the default Popular feed once on the server and let browsers verify and paint its signed events before slow media finishes. Keep custom relays, pagination, filtering, and relay fallback on their existing paths.\n\nAdd stale-while-refresh persistence, service-worker bypass, development integration, and unit/browser coverage.
* A profile shows all of someone's posts, not just the two primal still holds.Adam Soltys2026-10-012-16/+59
| | | | | | | | | | | The phone had written off relay.coinos.io, nos.lol and damus after a network blip, so only primal answered. Home relays are now skipped for a minute at most and forgiven on resume/online; failures while hidden or offline don't count. Profiles also ask history-keeping relays, merge answers instead of replacing the page, and re-ask on reopen. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* The reply box in dark mode is dark with light text instead of white.Adam Soltys2026-10-011-1/+1
| | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* A video in the feed paints at its own shape, not a 16:9 frame with black ↵Adam Soltys2026-10-011-2/+74
| | | | | | | | | | | | | | | bands beside a portrait clip. The shape comes from the post's imeta dim when it gives one (Amethyst, Primal and others publish it), else from the clip's metadata fetched while the row warms (3 s cap), and is remembered on the device. The box is as wide as the column, never taller than 75vh; an unknown shape keeps 16:9. tools/feed-video-aspect-test.js: 3:4 and 16:9 clips measured ahead, a 9:16 one shaped by imeta alone (its file served too late to probe), a broken one. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* Switching feeds paints posts at once instead of a spinner while the relays ↵Adam Soltys2026-09-302-9/+65
| | | | | | | | | | | | | | | | | | | answer. The disk cache held four feeds, so with more chips than that (or one never opened here) a switch opened on nothing. Now: - up to twelve feeds on disk: the four read last keep a page of 30, the rest 5 seed posts (same 256 KB budget); - once the feed on screen settles, every other feed with nothing on disk has its first posts fetched ahead, one small request each, every 15 min; - a feed with nothing on disk borrows matching posts (its people, its topics) that open feeds already hold. tools/feed-switch-seed-test.js: with 8 s relays, a fetched-ahead feed and a never-opened topic feed both paint rows at once (fails on the old code). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* "Sign into another account" opens on Create new, not the seed import box.Adam Soltys2026-09-301-1/+3
| | | | | Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* Public pictures and videos go to blossom.ditto.pub first, then primal, ↵Adam Soltys2026-09-301-5/+7
| | | | | | | | | | | | nostr.download, nostr.build. A 2026-09-30 benchmark (4 MB mp4, 110 KB jpeg; upload then fetch, twice): ditto took the mp4 in ~0.6 s and serves from ~120 ms; primal ~1.5 s; nostr.download ~1-2 s but rejects a JPEG with data after its end marker (motion photos); blossom.nostr.build ~5 s with ~600 ms first byte. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMMwWYJkPQxLgfMsY7hWcU
* Merge remote-tracking branch 'cs/prod'Adam Soltys2026-09-3024-288/+1580
|\
| * "Post" floats at the feed's bottom right instead of sitting in the header.Adam Soltys2026-09-303-6/+23
| | | | | | | | | | | | | | | | | | | | A pill with the pencil and "Post", fixed above the bottom nav inside the page column, wherever the reader is in the feed; the tap scrolls to the top, opens the composer and focuses it. Hidden while the composer is open (it has its own Post button) and for visitors. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * A picture or video on its way shows a bar with its percentage — shrinking, ↵Adam Soltys2026-09-305-18/+67
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | then uploading; the pencil reads "New post". ui.mediaProgress { stage, pct } (a render per whole percent) drawn as a labelled bar under the reply and post composers, instead of only the paperclip turning into a spinner. Upload progress comes from XMLHttpRequest upload events (fetch() has none) in uploadPublicMedia's new onProgress; injected fetchers (tests) keep the fetch path. upload-progress-test: 1.2 MB at 400 KB/s climbs 0→100 in steps. video-compress-test: the bar counts the shrink, then the upload. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * Videos are shrunk on the phone before upload; nostr.build is the first media ↵Adam Soltys2026-09-304-5/+79
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | host. No public Blossom host transcodes (nostr.build's and primal's BUD-05 /media return the same bitrate), so a 15-20 Mbps phone clip stalled on playback. src/video-compress.js re-encodes on the device with WebCodecs via Mediabunny (a lazily loaded chunk, left out of the SW precache and the standalone file): H.264, short edge 720, ~2.5 Mbps, moov first, audio passed through, rotation baked in. Anything short of a clear win uploads the original. A toast counts the shrinking down. Media hosts: blossom.nostr.build first, nostr.download second (it was unreachable for hours); a HEAD probe skips a host that doesn't answer within 5 s instead of waiting out the whole upload timeout. video-compress-test: 1080p 20 Mbps 5 s (landscape and rotated portrait) → 1.6 MB H.264 720p 2.49 Mbps with audio, moov first, in 2-3 s. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * Topic feeds go back as far as the relays remember, instead of "all caught ↵Adam Soltys2026-09-301-5/+29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | up" after a handful. #gardenstr: 5 posts across damus/primal/nos.lol/ours (they prune), 774 once archive-keeping relays are asked too (ditto, nostr.mom, oxtr, nostrplebs, nostr21, offchain) — topic feeds now read those. And a page whose posts were all filtered out (replies, muted, spam) no longer ends the feed: paging goes on from the oldest event the relays returned, up to three asks per scroll. The quoted-note wide net gains ditto/nostrplebs/nostr21. topic-depth-test (/feed/t/gardenstr): 9 posts then "caught up" before, 73+ and still going after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * From someone's timeline, ‹ goes back to their profile and the Feed tab is ↵Adam Soltys2026-09-301-2/+10
| | | | | | | | | | | | | | | | | | | | | | | | your own feed again. The Feed tab reopened whichever feed was last on — the session timeline — and ‹ on it went to the chat list, so there was no way back to your own feed. The tab now opens the last of your own feeds; ‹ on a feed opened from somewhere (a timeline, a #tag, a pack link) steps back in history. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * NIP-22 comments (kind 1111): shown in threads, sent when replying to a poll, ↵Adam Soltys2026-09-291-9/+53
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | article or comment, and in notifications. Kind 1111 is the reply format for everything that isn't a kind 1 note: uppercase E/A + K + P name the root, lowercase e/a + k + p the parent. Threads now also ask for comments naming the root (#E, and #A for an addressable root like an article), place each under its parent, and draw them as posts. A reply to anything but a kind 1 note goes out as a 1111 with the root's scope and the parent's tags (copied from the comment when the root isn't loaded); replies to kind 1 notes stay kind 1. Comments that answer you show in Notifications. comments-test (a real article with 4 comments; events caught at the socket, nothing published): comments listed, reply to the article and to a comment tagged per NIP-22. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * Composers take pictures from the keyboard: the reply, post and chat fields ↵Adam Soltys2026-09-294-14/+237
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | are contenteditable. Android keyboards (Gboard GIFs, stickers) only offer images to fields that accept them, and Chrome says so for contenteditable, never <textarea> — hence the greyed-out GIF key and "This app does not support images here". <coinos-text> (src/rich-text.js) is a contenteditable that acts like a textarea to the code around it (value, selectionStart/End, setSelection- Range, placeholder), keeps text plain, and hands any pasted/dropped/ keyboard picture to onMedia — the paperclip's upload. The morph syncs its value like a field's and leaves its own editing attributes alone. composer-media-test: types like a textarea, strips pasted formatting, a pasted GIF uploads and lands in the reply, still editable after repaint. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * A zap to a Lightning address that doesn't answer says so, naming the host, ↵Adam Soltys2026-09-291-1/+14
| | | | | | | | | | | | | | | | | | | | | | | | instead of "signal is aborted without reason". [email protected]: the lnurlp endpoint answers in 0.15 s but its invoice callback hangs 30 s then 502s; our 8 s timeout surfaced as the browser's raw AbortError text. Errors now name the host, a timeout says their address may be down, and an error response's LNURL reason is shown. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * Quoted notes are found on more relays and never show as a bare "view note"; ↵Adam Soltys2026-09-291-17/+48
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Back from a post keeps the feed's place. - findNote: the reference's hints, its author's NIP-65 relays and ours, plus a wide net of big public relays, all at once, first answer wins. A real quote (SuiGenerisJohn → 0c5e0f08…) sat only on nostr.mom, named by neither the nevent nor its author's relay list. - A feed row whose quote isn't in yet shows the quote card with "Fetching note…" / "Couldn't find that note" instead of a link; the card replaces it once found. - Opening a post remembers the scroll position like opening a profile does; Back restores it instead of landing at the top of the feed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * The coinos logo always goes to the wallet home, from the vtxo page and every ↵Adam Soltys2026-09-291-0/+8
| | | | | | | | | | | | | | | | | | | | | | other page. goHome cleared a hand-kept list of pages and the vtxo list (arkCoinsPage) wasn't on it — nor a transaction's detail, the hat shop, the till. It now clears every page the history tracks. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * A face still being looked up shows its punk after a second at most; a ↵Adam Soltys2026-09-291-13/+29
| | | | | | | | | | | | | | | | | | | | | | | | picture found later replaces it. The quiet loading circle is capped at 1 s per face (a timer repaints when it runs out); one miss is enough to remember someone as faceless; and a room's warm-up adds members whose messages arrived after it first ran, instead of reusing a warm-up that covered only the early history. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * Someone with no profile picture paints as their punk from the first frame ↵Adam Soltys2026-09-291-4/+32
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | after a reload, not a blank circle that turns into one. Faceless answers were never persisted (they'd evict real faces from the 150-row profile blob), so every reload re-asked and showed the loading circle until the relays said "nothing". A small pubkey->time list now remembers them: no kind 0 on two asks running (a cold relay isn't enough), or a kind 0 naming nothing; finding a face takes them off it. room-faces-test --refresh: reload inside the coinos room, faces keyed by author — 2 changed (wait→punk) before, 0 after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
| * A community opens with its members' faces, not a beat of punks.Adam Soltys2026-09-291-2/+45
| | | | | | | | | | | | | | | | | | | | | | | | | | Faces (profile, then the picture decoded) are warmed when the pointer is over or presses a room's row, and while the chat list sits there; the tap waits for them, 1.5 s at most, before the room replaces the list. A face still loading in a room is the quiet circle, not a punk. room-faces-test (fresh wallet, real coinos community): 57 faces flipped punk → picture before, 0 after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp