The replies section heading ('Replies'/'Comments') and the count noun
('reply'/'replies'/'comment'/'comments') were hardcoded English literals
adjacent to the placeholder fixed in the previous commit. Move them to a
new postDetail locale section with i18next plural keys, translated across
all locales.
The placeholder used a hardcoded 'Write a reply...' literal and referenced
a nonexistent compose.activityPlaceholder key (which fell back to the raw
key string). Switch to translated replyModal.placeholder.writeReply /
writeComment keys and add writeReply to all locales.
- Add a 'Get the app' external link to the TopNav drawer menu, hidden
inside the native app.
- Move the home-page nudge out of the feed (it now lives on the actual
home page, CampaignsPage) and give it pt-8 breathing room above.
- Remove redundant pb-16 on main and shrink the footer's top gap on
mobile (pt-6 sm:pt-12) so spacing above the footer isn't doubled.
Remove the !loggedIn gate that hid the 'Explore campaigns' CTA from
logged-in users. The campaigns home page shouldn't gate this button
behind login state.
Prompt mobile-web visitors to install the native Android app from
Zapstore. Shows a card at the bottom of the home feed and a link in
the account switcher menu. Both are hidden inside the native app
(Capacitor.isNativePlatform) and on desktop (sm:hidden for the banner).
Adds nav.getApp and feed.getApp.* strings across all locales.
The Android 12 splash vector (splash_icon_vector.xml) and the legacy
splash PNGs still showed the old Ditto cat / single-bolt logo. Replace
the splash vector with the current double-bolt glyph from logo.svg and
regenerate splash_icon.png and all port/land splash PNGs as the white
double-bolt on the dark splash background.
The 720x880 logo.svg was force-rendered into a 512x512 square, stretching
the bolt horizontally. Render height-bounded (aspect-preserving) instead
and let the centered composite handle fit. Regenerate all Android, iOS,
and web/PWA/Zapstore icons.
The Android launcher icons and adaptive foreground were the old icon
(orange-circle single bolt / stale Ditto vector), and generate-icons.sh
still sourced the purple Ditto branding. Rewrite the generator to use
the current logo.svg double-bolt glyph in white on the brand orange
(#e9673f) circle, and regenerate:
- Android legacy + adaptive launcher icons (all densities)
- adaptive icon background color -> #e9673f
- iOS AppIcon
- web/PWA/Zapstore icons (logo.png, icon-192, icon-512, apple-touch-icon)
Also remove the unused stale adaptive foreground vector so it can't
shadow the regenerated foreground PNG.
packageRelease failed with 'Given final block not properly padded'
because the migrated PKCS12 entry was protected with the store password,
not the key password Gradle read from key.properties. Write the PKCS12
with a single uniform password ($KEY_PASSWORD) for store and entry, and
point both storePassword and keyPassword at it.
R8 release minification failed on a missing com.google.gson.annotations
.SerializedName referenced by the OutSystems barcode plugin. Suppress the
Gson missing-class warning, keep annotations, and keep the plugin's model
classes so serialized fields survive shrinking.
@capacitor/barcode-scanner v3 pulls in ionbarcode-android:2.0.1, which
declares minSdk 26. The inherited Ditto minSdk of 24 fails the manifest
merger. Raise the floor to 26 (Android 8.0) as the merger recommends.
The build-apk JKS->PKCS12 migration only supplied the store password,
so keytool prompted for the upload key's distinct password on the
non-interactive runner and failed with 'Too many failures - try later'.
Pass -srckeypass/-destkeypass ($KEY_PASSWORD) to match key.properties.
The qrcode library hard-codes inline width/height pixel styles on the
canvas, overriding the Tailwind sizing classes (h-auto w-full) callers
pass in. On viewports narrower than the QR's intrinsic size this made
the code spill outside its rounded box — visible on the campaign
details donate panel. Remove the inline styles after rendering so the
caller's className controls the responsive size.
Switching to a custom (manual-entry) wallet used to drop the friendly
accept picker entirely, leaving two unlabeled-purpose address inputs.
Restore the hand-holding: add an intro line restating the field-driven
model (public address, private code, or both) and label each input
with its meaning. The public/on-chain input is marked with a Bitcoin
icon and a 'Public. Anyone can see these donations.' caption; the
silent-payment input with an EyeOff icon and a privacy caption. Both
inputs keep the Wallet leading icon. Updates all 16 locales.
Replace the three terse jargon pills (Accept All / Public Only /
Private Only, captioned with 'on-chain' and 'silent payment') with a
vertical stack of selectable option cards. Each card has a friendly
icon, a plain-language title, and a one-sentence reassurance written
for an anxious first-time creator, with the SP-dependent options
clearly disabled when the login can't support them.
Also softens the wallet hero card: drop the linked-icon trio for a
simple campaign-to-wallet arrow, and rewrite the copy without the
key/posts technical aside or em dashes. Updates all 16 locales.
Redesign the 'My wallet' branch of the campaign wizard's donation
destination step. Replace the plain identity+balance row with a
primary-tinted hero card modelled on the onboarding 'Save your key'
surface: a linked-icon trio (campaign -> key -> wallet) explains that
donations land in the creator's own Agora wallet unlocked by the same
key that signs their posts, with the avatar+live-balance chip
confirming the exact destination and a ShieldCheck reassurance line
below.
Both PostDetailShell (Nostr event details) and ExternalContentPage
(NIP-73 external content like bitcoin:tx) rendered their <main> with no
max-width under the wide layout, stretching edge-to-edge on large
screens. Add w-full max-w-3xl mx-auto to match the narrow-layout column
width used elsewhere.
Drop the optional `deadline` tag from kind 33863 campaigns. Removes the
date input and validation from the create/edit form, the deadline chips
on the card, detail, and inline-preview surfaces, and the derived
"ended" state that disabled donations after the deadline. Cleans up the
associated locale keys and NIP.md documentation.
The home page serialized its first paint behind relay.ditto.pub:
useCampaignLists queried that one relay via nostr.relay(DITTO_RELAY)
(awaited, up to an 8s timeout) and every hero campaign was gated on
its result, so a slow ditto.pub stalled the whole page. Connection
sharing made it worse — pooled queries multiplexed onto the same
stalled socket.
Switch the home-critical moderation/list/discovery hooks from
single-relay nostr.relay(DITTO_RELAY) calls to the parallel pooled
nostr.query() fan-out:
- useCampaignLists: authors:[curator] filter enforces trust; relay
pin was unnecessary and headed the waterfall.
- useCampaignModeration: authors:[moderators] filter enforces trust.
- useFeaturedOrganizations: per-author filters enforce curation.
- useDiscoverCommunities: global discovery — fan-out broadens coverage.
useDashboardCounts stays pinned: NIP-45 COUNT is a single-relay
primitive and isn't mergeable across relays, and it's off the home
critical path.
Regression-of: 3d825aef