The Trans component's values={{ count: formatNumber(N) }} was spreading
after the numeric count prop, overriding it with a string. i18next v26
requires a numeric count for plural resolution — a string causes it to
return the raw key path (e.g. 'campaignsDetail.repost') instead of the
resolved plural form.
Rename the interpolation variable from count to formattedCount so it no
longer collides with the count prop that Trans uses for plural selection.
Update all 16 locale files to use {{formattedCount}} in the repost, quote,
and like translation strings.
Regression-of: 86a084f3
When FollowPage was removed in the unreachable-page cleanup, the
QR code on the profile kept encoding `${origin}/follow/<npub-or-nip05>`,
which falls through to NotFound because there is no `/follow/...`
route. Encode the bare identifier instead — Agora's universal
NIP-19 dispatcher at /:nip19 already resolves both npub and
`user@domain.com` to ProfilePage.
Regression-of: b975e557
The Create Campaign page initialised `walletSource` to `'custom'` and
relied on a post-mount effect to flip it to `'mine'` once
`hdWalletAvailable` became true. With Radix Select's controlled value
that left users staring at the "Choose a wallet" placeholder on the
initial paint and forced them to open the dropdown to see that their
Agora wallet was even an option.
Seed both `walletSource` and `mineAccept` lazily from the
synchronously-available HD-wallet availability so the trigger already
reads as the user's Agora wallet on first render. The existing
availability-change effects still cover the (rare) case where the hook
resolves a tick later or where the wallet disappears mid-session.
Opens the HD wallet's send dialog prefilled with the campaign's on-chain
address — same flow used at /wallet, so donors don't have to learn a
second send UI. Sits above 'Open external wallet' inside the donate
panel; the external button downgrades to the secondary variant when
both are present so only one orange CTA stacks.
When the campaign declares both an on-chain (bc1…) and a silent payment
(sp1…) endpoint, a swap link appears under the privacy disclaimer so
donors can flip to SP without leaving the modal. The toggle hides
itself if the donor manually edits the recipient field, so we never
trash their typed input.
Gated on useHdWalletAccess (nsec logins only) — extension and bunker
logins fall through to the external-wallet QR. The button is also
hidden for SP-only campaigns (no on-chain address to prefill) and for
the campaign owner themselves.
Add Kosovo (XK) and Western Sahara (EH) to the country list. Kosovo
has no Unicode emoji flag, so it follows the Tibet pattern with a
bundled SVG asset that CountryFlag swaps in.
Surface Tibet (CN-XZ) as a search-list entry so it can be picked from
country autocompletes and pickers. The on-wire identifier stays
iso3166:CN-XZ; only the picker pretends.
Route every remaining raw country.flag span through CountryFlag so
bundled SVGs render in autocomplete dropdowns, organizer selects, the
ComposeBox destination switcher, and the world stats dialog.
Picked the noun each locale uses in its existing last3h / last24h
preset translations so the standalone label stays consistent with
the dropdown options it sits underneath.
The Math.max(resume, target) clamp inside resolveWindowFromHeight
turned the Since dropdown into a no-op whenever the wallet's
scanHeight had caught up to the chosen window. Picking 'Last week'
one minute after a successful scan resolved to scanHeight + 1, not
to a week ago, so the user couldn't actually rescan history from
the primary control — they had to drop into Advanced -> From block.
Removed the clamp so Since presets honor the wall-clock window
literally. Re-scanning blocks we've already scanned is cheap (the
indexer is just iterating tweak data) and is exactly what the user
asked for. Also dropped the isPresetUpToDate predicate that, after
the original clamp landed, was the mechanism by which Start went
disabled after a scan completed. isManualUpToDate is kept so the
override path still flags 'block number past the tip'.
Regression-of: 38946cbc
Adds a Custom… entry to the Since select that, when picked, reveals
an hours number input directly under the select. The hours value is
parsed as a positive (fractional allowed) number, multiplied to
seconds, and fed through the same mempool.space resolver as the
fixed presets. The Start button stays disabled until a valid hours
value is entered so there's no ambiguous empty-equals-zero submit.
Power users who want sub-hour granularity (e.g. 0.5) get it without
having to drop into the Advanced block-height path.
Bitcoin block timestamps obey BIP-113's median-time-past rule — they
must exceed the median of the previous 11 timestamps but don't have
to be strictly monotonic. Empirically, 1-2-block inversions show up
in the live chain regularly; 3-block rewinds covered the common case
but could still miss a payment landed in a block whose timestamp ran
backwards near the user's selected cutoff. 11 is the principled
upper bound (no inversion can extend further under MTP) and costs
only ~8 extra block fetches per scan.
Regression-of: 46d9952a
mempool.space's timestamp-to-block endpoint is the sole source of
truth now. The Blockbook binary search fallback was sequential and
could stall the UI for ~20 round-trips with no progress feedback
when mempool.space was down. When the lookup fails, surface a toast
that points the user at the Advanced -> From block override and
auto-open the disclosure so the escape hatch is immediately visible.
Regression-of: d6e6d616
Two small fit-and-finish tweaks the previous restyle missed:
1. The tab strip's baseline border sat 1px above the rounded panel's top
edge, so the panel's rounded corners visually overlapped the active
tab's under-rule. TabsContent now carries an explicit mt-4 (16px) so
the panel floats cleanly under the tab strip with breathing room
above its rounded top corners.
2. Tab labels were spaced gap-1 (4px), too tight for labels that read
as section headers. Bumped to gap-8 (32px) so 'Comments & donations'
and 'Ledger' read as separate section titles.
CommentsSection's outer mt-4 is overridden with className=mt-0 at the
call site so the TabsContent gap controls the spacing — no more 32px
double-margin.
The shadcn default 'muted pill control' tab strip felt bolted onto the page
between the action bar and the comments panel. The tab labels also doubled
up with the 'Comments & donations' heading rendered inside the panel — two
section titles stacked.
Replaces the pill control with an underline-style tab strip that visually
serves as the section header for the panel below. The active tab label is
rendered at the same size and weight as the page's other h2 section
headings (text-lg font-semibold tracking-tight), inactive tabs are muted
siblings, and a 1px baseline border carries across the panel width while
the active tab paints a thicker primary under-rule that flows into the
content surface.
CommentsSection's title becomes optional and the campaign page omits it
now that the tab label owns the heading. The dead 'commentsAndDonations'
i18n key is removed from all 16 locales (tabComments carries the same
copy).
Flips the headline + subline on each ledger row: USD is now the prominent
top number and the BTC equivalent (full units, not sats) sits below it in
muted text. Falls back to BTC-as-headline when the BTC/USD price isn't
available yet. Renames the i18n leaf 'campaignsDetail.ledger.satsUnit' to
'btcUnit' across en.json and all 15 other locales — BTC is a universal
ticker, so the unit string itself is now 'BTC' everywhere.
Moves the existing comments + donations panel under a 'Comments & donations'
tab and introduces a sibling 'Ledger' tab that surfaces public on-chain
activity for the campaign's bc1 address — receives, sends, confirmation
status, block height, and the equivalent USD — sourced from the
already-configured Esplora endpoints with mempool.space deep links per row.
The Ledger tab is rendered only when the campaign declares an on-chain
endpoint ('bc1q…' / 'bc1p…'). Silent-payment-only campaigns intentionally
have no scannable address and degrade to the un-tabbed comments surface to
avoid showing a lone disabled tab.
A new fetchAddressTxs helper in src/lib/bitcoin.ts wraps Esplora's
/address/:addr/txs + /chain/:last_seen_txid pagination, and useAddressLedger
exposes it as an infinite query (50 confirmed tx page size, plus mempool on
the first page).