Commit graph

94 commits

Author SHA1 Message Date
shakespeare.diy
a31583079b Private Contacts: fetch-before-write to prevent list clobber
Same encrypted-replaceable-list data-safety pattern as the Stash fix, applied
to usePrivateContacts (kind 30000, NIP-44 encrypted to self).

add/remove built the new list from the React Query cache, which is empty until
the Contacts page has decrypted — and with Amber the read query is gated /
staleTime:Infinity. Saving a contact before that decrypt (fresh device, or if a
"save privately" action is ever wired up elsewhere) could publish a list with
only the new pubkey and silently wipe every other private contact.

Fix: add/remove now call fetchLatest() — a fresh relay fetch + decrypt of the
current list immediately before publishing — and union it with the warm cache
so an optimistic just-added pubkey is never dropped. On fetch/decrypt failure
it falls back to the cache rather than risk a wipe.

UX: clarified the contacts search placeholder ("Paste an npub to filter…") so
it no longer implies name search that the npub-only filter doesn't do. Left the
star toggle un-confirmed on purpose — it's a low-stakes, one-tap-reversible
action and a dialog would only add friction. ContactsPage is otherwise already
clean/elegant (tabbed, skeletons, thoughtful empty states, encryption notice).

Bumps to 1.2.24 (Android versionCode 10224 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:36:01 +07:00
shakespeare.diy
29d41b81eb Stash: prevent data-loss clobber on save + confirm before deleting gear
Audited the Stash — a private, NIP-44-encrypted, Amber-sensitive surface.

DATA-SAFETY FIX (the important one):
The stash is a single replaceable event whose encrypted content is the WHOLE
item list, so every add/update/remove rewrites the entire event. The mutations
built the new list from the `stash` closure value — but that's legitimately
`undefined` before the decrypt completes, and with Amber the read query is
gated / staleTime:Infinity, so it may never have been decrypted at mutation
time. Result: adding one item while `stash` was empty/undefined could publish a
list containing only that item and silently WIPE all existing gear.

Fix: mutations now call `fetchLatestStash()` — a fresh relay fetch + decrypt of
the current stash immediately before saving — and build the new list on top of
that. It falls back to React Query cache (never an empty list) on failure, and
throws rather than risk a clobber if it truly can't load. saveStash also writes
the just-published value straight into the cache to keep reads consistent.

The extra decrypt per mutation is an acceptable Amber tradeoff: a mutation is
already a deliberate write (encrypt + sign prompt), and data integrity beats
prompt count.

UX:
• Delete now asks for confirmation via an AlertDialog (item name + a note that
  public items get unlisted). Previously a single tap wiped an item outright —
  destructive and, with Amber, a surprise signer prompt.

The rest of StashPage (login gate, encryption-required state, distinct
wishlist/stash/filtered empty states, skeletons, rarity cards, grid/list) is
already excellent — left as-is.

Bumps to 1.2.23 (Android versionCode 10223 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:31:48 +07:00
shakespeare.diy
7f49df1503 Wallet UX polish: elegant recovery-phrase reveal, separate amounts, loading states
Continued the wallet audit with a focus on intuitive, minimal, elegant UX.

• Recovery phrase — replaced the raw browser alert() (which dumped the seed
  as unstyled, uncopyable plaintext) with a proper RecoveryPhraseDialog:
  hidden/blurred by default with a deliberate "tap to reveal" gate (no
  shoulder-surfing on open), a numbered 12-word chip grid for accurate
  transcription, a copy button (enabled only after reveal), and an honest
  "paper is safest, clipboards leak" note. Re-hides on every close. This was
  the wallet's most sensitive surface and its worst blemish.

• Fixed shared-state bug: "Create Cashu Token" and "Pay Lightning Address"
  both wrote to the same `sendAmount`, so switching between them carried a
  stale amount across two unrelated intents. The address form now owns its
  own amount state.

• Added loading feedback + double-tap protection to every money action that
  lacked it — Generate Invoice ("Generating…"), Create Token ("Creating…"),
  Receive Token ("Receiving…"), Pay Lightning Address ("Sending…"). Each hook
  now exposes an in-flight flag and guards re-entry.

WalletPage and the browser "app-only" notice were already clean/elegant —
left as-is.

Bumps to 1.2.22 (Android versionCode 10222 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:20:51 +07:00
shakespeare.diy
39ac517610 Fix Cashu zap: pay in one click + honor custom-amount balance guard
Audit of the wallet/zap flow surfaced a real money-path bug in ZapDialog's
Cashu branch:

• One-click Cashu zap never worked. The Cashu payment branch only ran
  `if (... && invoice)`, but no invoice exists on the first click — so
  selecting "Cashu" and tapping Zap silently fell through to the Lightning
  flow (generating an invoice / paying via NWC-WebLN) and the user's Cashu
  balance was never used. Now the Cashu path generates the recipient's
  amount-verified zap invoice and melts it from the Cashu wallet in a single
  click.

  To support this cleanly, useZaps now exposes `generateZapInvoice()` —
  builds + signs the NIP-57 request, fetches the LNURL invoice, and runs the
  same `assertInvoiceAmount` security check the Lightning rail uses — WITHOUT
  auto-paying. The Lightning `zap()` path was refactored to use it too, so
  both rails share one verified-invoice codepath.

• Custom-amount balance guard bypassed. The insufficient-balance warning and
  the disabled Send state only checked `typeof amount === 'number'`, but the
  custom-amount input stores a string — so a typed amount over the Cashu
  balance slipped past both. Both now coerce to a number first.

• Smarter defaults: when the dialog opens and the user has Cashu funds but no
  connected Lightning wallet, Cashu is pre-selected and the "connect a wallet"
  setup nag is skipped (they can already pay).

• Removed a dead `useLightningOperations` import.

Also audited mobile layout (Layout.tsx — mature, safe-area-aware, no changes
needed) and ZapCrewDialog (uses the separate useCrewZap splitter, no Cashu
option, no bug).

Bumps to 1.2.21 (Android versionCode 10221 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:15:10 +07:00
shakespeare.diy
48b127f7b0 Amber UX: honest multi-approval hint for encrypted chat/DM sends
Audit outcome for the two suspected "double-prompt" Amber flows:

• Image upload — CONFIRMED already a single signature. BlossomUploader
  (even at the pinned 0.47.1) signs one kind-24242 auth event and reuses
  it across all servers via Promise.any, so uploads = 1 Amber prompt. No
  change needed.

• DM / crew-chat send — the multiple prompts are protocol-inherent and
  MUST NOT be optimized away: NIP-17 seals+signs once per recipient (and
  to self); a Cordn crew message signs an author proof then encrypts once
  per roster member. Reducing the count would weaken confidentiality or
  authorship. So instead of a code shortcut we add transparency.

New <AmberApprovalHint>: a tiny heads-up shown ONLY to interactive
(Amber) signers before an inherently multi-approval action, telling them
how many Approve screens to expect and that it's normal. Renders nothing
for silent signers (extension/nsec/bunker), where the ops are invisible.

Wired into:
  • CrewChat composer — count derived from transport + roster size
    (Cordn: roster+1; NIP-17: roster*2).
  • ConversationPage (1:1 DM) composer — 4 approvals (encrypt+sign to the
    recipient and to yourself).

Dependencies: reviewed the full manifest; deliberately left unchanged.
Every upgrade candidate is either on the critical crypto/Nostr/payments
path (nostrify, nostr-tools, cashu, bitcoinjs, noble, scure — pinned for
safety) or a documented major bump (lucide v1, react 19, react-router 7)
with breaking changes and no user-facing benefit. No security advisories
force a change, and caret ranges already pull safe patch/minor updates.
"It must work" wins over churn.

Bumps to 1.2.20 (Android versionCode 10220 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:08:39 +07:00
shakespeare.diy
56f29e0a29 Amber: make crew-notification dismissal local-only for interactive signers
Accepting/declining a crew invite (and handling join-requests) fired an
encrypted NIP-78 cross-device sync on every dismissal, costing an extra
encrypt + sign Amber approval on top of the required membership signature.

For interactive (Amber) signers, dismissal is now kept local-only:
  • Accept invite  → 1 Amber prompt (membership attestation only)
  • Decline invite → 0 Amber prompts (local dismissal only)

The dismissal still applies instantly and permanently on-device; only the
cross-device "clear it on my other devices too" nicety is dropped for Amber.
Extension/nsec/bunker signers keep the silent encrypted sync (free for them).

Bumps to 1.2.19 (Android versionCode 10219 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:02:45 +07:00
shakespeare.diy
c9b0571de9 feat(ux): graceful image fallbacks for broken content images (1.2.18)
Added <ImageWithFallback> — a drop-in <img> that shows a subtle placeholder
(skate emoji or a caller-supplied icon) instead of the browser's broken-image
glyph when a user-supplied URL is missing or 404s. Fills the same layout box so
nothing shifts.

Swapped it into the content-image surfaces that display user-uploaded URLs:
- Marketplace item cards (grid + purchase dialog)
- Spot detail (hero + gallery thumbs) and map list/popup cards
- Shop detail (hero + gallery)
- Crew & Lab cross-link cards (icon fallback)
- Crew agent panel, newsletter issue view + notification bell newsletter thumb
- Stash gear cards (list + grid), profile banner (NIP19), newsletter spot cards

Left untouched: avatars (shadcn Avatar already has fallbacks), QR codes and
rider-card/print images (data URLs, never 404), and in-form upload previews
(local object URLs).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 19:46:40 +07:00
shakespeare.diy
f1b8d01144 chore(release): bump to 1.2.17 (stash decrypt on every page fix)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 19:15:37 +07:00
shakespeare.diy
b910141699 fix(amber): stop passive decryption from storming Amber on navigation (1.2.16)
With Amber as signer, every nip44 decrypt is a foreground intent (visible
popup). Several hooks decrypt inside auto-running queries, so Amber flashed on
navigation/focus and (worst case) storms — one popup per encrypted event, and
polling queries re-fired every few seconds.

New helper isInteractiveSigner() (signerCapabilities.ts) detects the Amber
(x-amber) signer, where each crypto op prompts the user. Hooks now gate passive
decryption for interactive signers only; extension/nsec/bunker keep their
silent, live-polling behaviour.

Fixes:
- useCrewDismissals (GLOBAL via header NotificationBell — the main offender):
  Amber users now use the instant localStorage mirror only, never passively
  decrypt the synced list; disabled refetchOnWindowFocus + Infinity staleTime.
  Dismissals still sync OUT on the write path.
- useDirectMessages (all 4 DM query hooks): added amberSafeQueryOptions() —
  no background polling and no focus refetch for Amber (these decrypt up to
  ~200 gift wraps × 2 each); silent signers keep the 60s poll.
- useStash / usePrivateContacts: disabled focus refetch + Infinity staleTime
  for Amber so the single decrypt runs once, not on every focus.
- useCordnChat: dropped the 5s live poll for Amber (was a popup every 5s);
  silent signers keep it.

Scanned every signEvent call site: all are inside mutations/user callbacks
(zaps, invites, DMs, chat, follows, purchases, NIP-98 auth) — no passive
auto-signing anywhere. Deliberate user actions still prompt Amber as intended.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 14:57:35 +07:00
shakespeare.diy
fcf8059860 chore(release): bump to 1.2.15 (Amber foreground-intent signer)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 13:32:51 +07:00
shakespeare.diy
63db160b80 fix(amber): Amethyst-style foreground-intent signer to end the GrapheneOS sign hang
Root cause: the stock nostr-signer-capacitor-plugin (0.0.5) tries Amber's
background ContentProvider first, which GrapheneOS blocks and never returns,
with no clean fallback — so signing hung 90s and Amber never appeared.

Fix (matches Amethyst, the reference NIP-55 client):
- New native Capacitor plugin AmberIntent (android-templates/AmberIntentPlugin.java)
  that goes STRAIGHT to a foreground nostrsigner: intent via a properly-registered
  ActivityResultLauncher, tracks each request by id, and times out gracefully.
- MainActivity now registers the launcher in onCreate (AndroidX contract) and
  hands it to the plugin, while staying a safe stock BridgeActivity (no
  WebViewClient override → no black screen).
- inject-main-activity.mjs now installs both MainActivity.java and AmberIntentPlugin.java.
- capacitorAmberSigner.ts rewired to registerPlugin('AmberIntent'); every op is a
  user-visible Amber Approve/Reject popup; rejections surface as a friendly message.
- Removed the dead npm nostr-signer-capacitor-plugin dependency (and its phantom
  coracle fork spec) plus its vite external entry — signing no longer uses it.
- Delete flow copy updated to the new foreground UX (Approve in Amber / Cancelled
  on reject) instead of the diagnostic SIGN/PUBLISH narration.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 12:39:00 +07:00
shakespeare.diy
be05a603be security: verify invoice amounts + require provider-issued zap receipts (1.2.14)
Fixes the audit findings:

HIGH-1 (NWC/WebLN auto-pay of unverified invoices): add assertInvoiceAmount and
call it before every programmatic payment in useZaps, useCrewZap, and
SpamFeePayment. A malicious LNURL/Lightning-address server can no longer return
an inflated invoice and drain the wallet — amount is decoded from bolt11 and must
match the intended sats (±1 sat), amountless invoices are rejected.

HIGH-2 (forgeable trust signals):
- useSasquatchPro: Pro is granted ONLY by a zap receipt AUTHORED by the
  platform's Lightning provider, embedding a user-signed zap request that tags
  the platform, with a real paid amount >= smallest tier. The self-signed
  kind:32671 receipt is now advisory only and never grants access (closed the
  "self-grant permanent Pro" hole).
- useFeaturedVerification: a listing is 'verified' only when the matching 9735 is
  authored by the provider pubkey (not any self-published receipt).
- useCrewGoal: tally only invoice-backed (bolt11) zaps; the forgeable amount tag
  is no longer summed, so goal progress can't be inflated with fake receipts.

Bump to 1.2.14.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 11:45:21 +07:00
shakespeare.diy
850c37dbaa diag(delete): narrate SIGN vs PUBLISH phases on-screen to locate the hang (1.2.13)
We can't identify where delete freezes. Split the flow into two independently
timed, on-screen-narrated phases: Step 1 SIGN (Amber) and Step 2 PUBLISH
(relays). Whichever toast is on screen when it hangs tells us exactly which layer
is at fault — Amber vs relay — so we fix the right thing. Bump to 1.2.13.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 23:07:13 +07:00
shakespeare.diy
52fd6f7a81 fix(delete): single signature for admin takedown — stop Amber collision hang (1.2.12)
Admin delete fired THREE signed events (kind:5 deletion, kind:1985 label, NIP-86
banevent). Each opens Amber, but Amber handles one approval at a time — the
extra background signs collided with the primary one on Android and hung the
whole delete ("taking longer than expected"). Now the admin path publishes only
the kind:5 deletion (one Amber approval) and hides locally (no signature); the
optional label/relay-ban are dropped from the interactive flow. Also raise the
awaited timeout 40s→120s to fit a real Amber round-trip. Bump to 1.2.12.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 22:56:38 +07:00
shakespeare.diy
955a823b02 fix(android): ship stock MainActivity so the app launches (fixes crash) (1.2.11)
The manifest launches org.skatespots.app.MainActivity; when that class was
missing/wrong the app crashed on startup. Add a STOCK MainActivity (extends
BridgeActivity, no WebViewClient override) plus an idempotent injector that
guarantees it's always present after cap add / rm -rf android. This is the safe
default — the earlier custom version broke startup. Delete fix (setPackageName)
remains intact. Bump to 1.2.11.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 22:20:18 +07:00
shakespeare.diy
88685262ce fix(android): remove custom MainActivity that broke app startup (black screen)
The injected MainActivity overrode Capacitor's WebViewClient, wiping out the
client Capacitor needs to load the web layer — causing a black-screen crash on
launch. Remove the MainActivity template, its injection script, and the build
wiring. The real signing fix (setPackageName before signEvent) is unaffected and
remains in place. Bump to 1.2.10.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 22:02:23 +07:00
shakespeare.diy
50914b7b9b fix(amber): call setPackageName before signing so the plugin opens Amber (1.2.9)
Verified against the plugin's Java source: signEvent/nip*Encrypt/Decrypt
IMMEDIATELY reject with "Signer package name not set" when no package is
configured — which is exactly what my earlier "omit packageName" change caused,
and that rejection surfaced as the endless spinner on delete/accept/zap.

With the package set, the plugin tries the content resolver, and when that
returns null (permission not remembered) it falls back to
startActivityForResult(nostrsigner:) — the SAME native call login uses that
reliably opens Amber. Restore native plugin signEvent/nip04/nip44 with
setPackageName + AMBER_PACKAGE, reconstruct signature-only responses, and remove
the unused JS-intent bridge. Bump to 1.2.9.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 21:44:27 +07:00
shakespeare.diy
fea9ab3091 fix(android): native intent hand-off so Amber actually opens for signing (1.2.8)
Amber never appeared for delete/accept-invite/zap because Capacitor's default
WebView silently drops JS-initiated intent:/nostrsigner: navigations — the
request that opens Amber never reached the OS. Login worked only because it
uses the plugin's native startActivityForResult.

Add a custom MainActivity (android-templates/MainActivity.java) that overrides
shouldOverrideUrlLoading to launch intent:/nostrsigner: URLs via startActivity()
— a real native launch that opens Amber on GrapheneOS, matching login. Injected
into the generated android/ project at build time by inject-main-activity.mjs,
wired into android:build/sync/init. Simplify the JS launcher to navigate the
WebView (now intercepted natively). Bump to 1.2.8.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 21:23:38 +07:00
shakespeare.diy
6030c76256 chore(release): bump to 1.2.7 with Amber intent-signing fix + release notes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-04 21:06:45 +07:00
shakespeare.diy
e223e59389 Bump version to 1.2.6 (1.2.5 already published to Zapstore)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 23:23:16 +07:00
shakespeare.diy
a3e04de1c7 Restore caret ranges for @nostrify packages
Pinning exact versions produced a broken @nostrify/nostrify variant on
some installs (raw .ts source missing @std/encoding / websocket-ts deps).
Revert to caret ranges so npm resolves the complete, working tree.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 23:10:54 +07:00
shakespeare.diy
144227b88b Fix zap request construction for newer nostr-tools types
The clean node_modules reinstall pulled newer @nostrify/* and nostr-tools
minors whose nip57.makeZapRequest signature changed (event param became a
required full NostrEvent), breaking tsc in useZaps and usePurchasePro.

Build the kind:9734 zap request events directly per NIP-57 instead of
relying on nip57.makeZapRequest, so we're decoupled from that helper's
changing type signature. Preserves correct target tagging:
- addressable events → 'a' tag (kind:pubkey:dTag)
- other events       → 'e' tag (event id)
- profile-only (pro purchase) → no event tag, keeps pro-tier/pro-expiry.

Pin @nostrify/nostrify, @nostrify/react and nostr-tools so the tree
stays reproducible.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 22:14:46 +07:00
shakespeare.diy
0665d94a30 Bump version to 1.2.5 (Amber signing hang fix)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 22:09:03 +07:00
shakespeare.diy
635e52df4a Bump version to 1.2.4 and update release notes (Amber signing hardening, upload fallbacks, URL sanitizing)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 20:48:07 +07:00
shakespeare.diy
3582262a44 Bump web version to 1.2.3
Ships the crew-aware Sasquatch agent, Nostr-native newsletter (subscribe,
mailbox/notifications, admin publishing + archive backfill) and subscriber
count. Prep for desktop deploy + Android 1.2.3 APK release to Zapstore.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 19:27:35 +07:00
shakespeare.diy
538ae4dfb1 v1.2.2: bump version + release notes (Sasquatch left panel, full data coverage, Judges, clickable stats)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 16:30:25 +07:00
shakespeare.diy
94a88b2ba0 v1.2.1: Community stats now show ALL Lab crafts + count every craft type
- useGrowthStats now queries and exposes every Lab craft individually: artists,
  graffiti, tattoo, trainers, musicians and judges (previously folded into an
  opaque "other" bucket with no visible breakdown).
- CommunityStatsPage renders a card for all 11 Lab crafts, so the section matches
  what /lab actually lists instead of only showing 5 categories.
- labTotal + the content-mix donut's "Lab" slice now sum every craft, keeping the
  "total" badge and chart consistent.

Note on relay mismatch: the app dedupes addressable events (keeps newest per
pubkey+kind+d) and merges across the community relay + the user's relays, so
counts can legitimately differ from a raw single-relay browser that shows stale
duplicate versions.

Bump version to 1.2.1 (versionCode 10201).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:07:13 +07:00
shakespeare.diy
677ccb8a82 v1.2.0: Spedn label, multi-relay community stats, GrapheneOS Near-Me fix
1. Map filter: renamed the "Bitcoin" toggle to "Spedn" (matches the toolbar
   button); tooltip now says "Spedn your sats".

2. Community stats (/stats): useGrowthStats now queries the same relay set as the
   rest of the app (community relay + user relays) via useSkateRelays instead of
   only the single hardcoded SKATE_RELAY. Lab profiles / spots / etc. that live on
   a user's own relay are now counted, so the numbers match what /lab shows.

3. Near Me on GrapheneOS: even after granting the OS location permission, the
   WebView's navigator.geolocation stays blocked → "permission denied". Fixed by
   fetching the position from the native Capacitor Geolocation plugin first
   (LocationManager, honours the granted permission), falling back to
   navigator.geolocation only if that fails. Also auto-inject
   ACCESS_FINE/COARSE_LOCATION into AndroidManifest via the manifest script.

Bump version to 1.2.0 (versionCode 10200).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:54:00 +07:00
shakespeare.diy
07699592da Fix Amber "Waiting…" hang on GrapheneOS: unblock setPackageName + auto-inject <queries> (v1.1.9)
Two concrete root causes for the eternal "Waiting for Amber…":

1. setPackageName could hang forever on GrapheneOS. getPlugin() awaited it with
   no timeout, so getPublicKey() (and its 90s timeout) was never even reached.
   Now setPackageName races a 3s timeout and continues regardless — the Amber
   package is passed explicitly on every call anyway, so it's non-essential.

2. AndroidManifest.xml had no <queries> block. On Android 11+/GrapheneOS the OS
   silently blocks the nostrsigner intent without it, so Amber never opens. Added
   scripts/inject-amber-queries.mjs (idempotent) and wired it into android:init /
   android:sync / android:build so the block is guaranteed present in every APK,
   surviving cap sync regeneration.

Bump version to 1.1.9.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:27:00 +07:00
shakespeare.diy
67597dc767 Bump version to 1.1.8 for Zapstore release (Amber native detection + GrapheneOS <queries> manifest fix)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:10:21 +07:00
shakespeare.diy
c6372742a9 Fix Amber native login in APK: reliable Capacitor detection (v1.1.7)
Root cause: LoginDialog and useCurrentUser detected the native app via
window.Capacitor, which is unreliable inside the WebView (false at mount / not
attached in some builds). So the ZapStore APK fell into the web intent:// redirect
flow and never opened Amber ("Waiting for Amber…" hang).

Fix: add isNativeAmberAvailable() in capacitorAmberSigner.ts using the imported
@capacitor/core Capacitor + native plugin presence, and use it (evaluated at
click time) in LoginDialog.handleAmberLogin and in useCurrentUser's signer
selection. The APK now calls getAmberPublicKeyViaPlugin() → native intent → Amber
opens directly, no redirects.

Bump version to 1.1.7.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:00:39 +07:00
shakespeare.diy
3dea1e42dc Bump version to 1.1.6 for Zapstore release (crew UI, Amber login/signup, Stash fixes)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:37:28 +07:00
shakespeare.diy
37f07931d9 Bump version to 1.1.3 for Zapstore release (one-tap signup + recovery-code retrieval)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:16:47 +07:00
shakespeare.diy
8a36e281f2 Bump version to 1.1.2 + release notes for Lab mobile grid fix
1.1.2 maps to versionCode 10102 via the sync-android-version scheme.
Release notes lead with the Lab craft-filter mobile grid fix, keeping the
1.1.1 highlights since this is the release carrying all of it to Zapstore.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 23:08:44 +07:00
shakespeare.diy
ceea96eac6 Bump version to 1.1.1
Single source of truth is package.json (scripts/sync-android-version.mjs
derives versionName/versionCode from it at android build time). 1.1.1 maps
to versionCode 10101 via the major*10000+minor*100+patch scheme.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 22:48:40 +07:00
shakespeare.diy
3c960b9753 Bump version to 1.1.0 (beta disclaimer dialog)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-01 12:16:40 +07:00
shakespeare.diy
4bef95132b Bump to 1.0.93 (1.0.92 already live) + versionCode scheme matches published 1.0.x
1.0.92 was already published to Zapstore, so move to 1.0.93. Adjusted
sync-android-version.mjs so 1.0.x maps versionCode = patch (1.0.93 -> 93),
continuing the existing published scheme instead of jumping to 10093.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 22:43:32 +07:00
shakespeare.diy
41595159d9 Auto-sync Android version from package.json (no more manual gradle bumps)
Adds scripts/sync-android-version.mjs which writes versionName (semver) and a
monotonic versionCode (major*10000+minor*100+patch) into android/app/build.gradle
from package.json. Wired into `npm run android:build` and `android:sync`, plus a
standalone `npm run android:version`. Now you bump ONE number (package.json) and
never hit Zapstore's "already exists" again. Exits quietly if android/ isn't
generated yet.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 22:38:13 +07:00
shakespeare.diy
d056f41ac7 Align version to 1.0.92 (1.0.91 already live on Zapstore)
Local android/ build reported 1.0.91 which is already published, so bump the
repo to 1.0.92 to stay consistent. Note: android/app/build.gradle versionCode/
versionName must be bumped locally too (cap sync does not propagate it from
package.json).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 21:54:09 +07:00
shakespeare.diy
c3a4721f21 Release v1.0.88 — bump version + release notes
Bump package.json 1.0.87 -> 1.0.88 (Capacitor propagates this into the
generated Android versionName/versionCode at android:build time).

Release notes cover this batch: richer profiles (crews/bot/edit-link),
searchable New Message picker, crew bands & gigs, CLINK tips on profiles,
and navigation cleanup.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 21:51:02 +07:00
shakespeare.diy
af6847c669 chore: bump to v1.0.87, add release notes for Zapstore publish
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 13:32:29 +07:00
shakespeare.diy
6dc7de9e5c Add @capacitor/cli and @capacitor/android for native Android builds
Fixes 'npx cap: could not determine executable to run' and the Gradle
'No variants exist for :capacitor-android' error by tracking the Capacitor
build tooling in package.json so it survives npm install.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:23:32 +07:00
shakespeare.diy
d3ae0672b7 Bump version to 1.0.86 (sync with ZapStore, latest published was 1.0.85)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:21:07 +07:00
shakespeare.diy
b2aba6322c Bump version to 1.0.77 for ZapStore release
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:19:28 +07:00
shakespeare.diy
7aad972099 Quick Snap: navigate to Add Spot immediately, upload photo on the form (v1.0.76)
Bug: after taking a Quick Snap photo and choosing "keep", the user never
reached the Add Spot form. handleQuickCameraCapture uploaded the photo to
Blossom FIRST (which needs an Amber signature and can be slow/fail) and only
navigated on success — so a stalled upload stranded the user right after the
camera.

Fix: Quick Snap now prioritizes speed —
- MapPage stashes the raw photo as a data URL (quickCapturePendingPhoto),
  grabs location (non-blocking), and navigates to /create-spot immediately.
- CreateSpotPage reads the pending photo on mount, converts it back to a File,
  and uploads it in the background with an "Uploading photo…" tile, adding the
  resulting URL to the images list. A failed upload now shows a toast on the
  form instead of blocking navigation. Old quickCaptureImage (already-uploaded
  URL) path kept for backward-compat.
- Removed the now-unused useUploadFile from MapPage.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:55:15 +07:00
shakespeare.diy
59493ac784 Geolocation: coarse fallback + clearer native error copy (v1.0.75)
The native Geolocation fix only takes effect once the APK is rebuilt — the
current phone build still uses navigator.geolocation, hence the error. To make
the rebuilt version more robust:

- If a high-accuracy fix fails/times out (common indoors or weak GPS), retry
  with a coarse (network/wifi) fix and a longer window before giving up. This
  rescues most "Location unavailable" cases.
- On native, the "unavailable" toast now tells the user to turn on Location/GPS
  AND allow the app permission, instead of the generic web message.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:17:20 +07:00
shakespeare.diy
04b8b0a8b1 Amber signing: add timeout + one-time setPackageName so hangs surface as errors (v1.0.74)
Symptom: crew logo upload spins forever, and crew creation appears to do
nothing. Both go through Amber signEvent. Login (getPublicKey) works because
it uses Amber's silent content-resolver path, but signEvent must launch
Amber's approval Activity and wait for the result — and if that result never
comes back (buggy plugin build, user backs out, missing permission) the
bridge promise never settles, so the UI hangs with no error.

Changes:
- withTimeout() wraps every Amber bridge call (sign/getPublicKey/nip04/nip44)
  with a 90s cap. A hung round-trip now rejects with a clear, actionable
  message instead of spinning forever, which also unblocks the CrewForm
  (isUploading/isPublishing finally resolve).
- setPackageName is now called ONCE per session (cached promise) instead of
  before every op — repeated calls are needless bridge chatter and can wedge
  on some builds.

NOTE (root cause, fixed on the build machine): package.json requests the
GitHub `main` build of nostr-signer-capacitor-plugin, but package-lock.json
still pins the old npm 0.0.5 (which the APK bundles). 0.0.5 is the version
prone to the signEvent activity-result hang. Regenerate the lockfile with
`npm install` so the `main` build is used — see chat for steps.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:13:58 +07:00
shakespeare.diy
b3a025ca77 Native geolocation for Near Me + desktop map fits viewport with footer (v1.0.73)
1. Near Me "location blocked" on the APK:
   - navigator.geolocation returns PERMISSION_DENIED inside the Android WebView
     because the WebView never gets the OS location permission, so the app
     showed a bogus "your browser blocked location" help sheet.
   - New src/lib/geolocation.ts: platform-aware getCurrentPosition(). On native
     it uses Capacitor's Geolocation plugin via registerPlugin('Geolocation')
     from bundled @capacitor/core (same runtime pattern as the camera/Amber
     fixes) and calls requestPermissions() to trigger the real Android prompt;
     on web it falls back to navigator.geolocation.
   - MapPage handleNearMe, proximity sort, and quick-snap location now use it.
     On native, a denied permission shows an OS-permission toast instead of the
     browser help sheet.
   - Added @capacitor/geolocation dependency (native side picked up via cap sync;
     no bare import in the JS bundle).

2. Desktop map didn't fit the viewport (had to scroll to see footer):
   - Map wrapper was md:h-[calc(100vh-4rem)] — full height minus header only,
     leaving no room for the footer.
   - Added --footer-desktop token (5rem) and changed the desktop height to
     calc(100vh-4rem-var(--footer-desktop)) so map + footer fit without scroll.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:28:06 +07:00
shakespeare.diy
a6a377f468 Add delete/dissolve for crews and Lab profiles via NIP-09 (v1.0.72)
- New useDeleteEntity hook: publishes a kind:5 deletion request referencing
  the target addressable event by `a` coord, `e` id, and `k` kind. Owner-only.
- New reusable DeleteEntityDialog: owner-gated trigger + confirmation with a
  clear "deletion is best-effort on decentralized relays" warning, redirect,
  and query invalidation so the item drops out of lists.
- Wired "Disband" into CrewDetailPage (founder only, redirects to /crews).
- Wired a "Manage → Delete Profile" card into CoachDetailPage for all Lab
  profile types (coach/filmer/editor/photographer/builder), redirects to /lab.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:02:33 +07:00
shakespeare.diy
def67d4df2 Fix "Capacitor Amber plugin not available" on upload/signing (v1.0.71)
Root cause (same class as the camera bug): capacitorAmberSigner.ts used
`await import('nostr-signer-capacitor-plugin')`, but that package is marked
external in vite.config.ts and the native build uses inlineDynamicImports, so
the bare specifier survives in the APK bundle and the Android WebView can't
resolve it at runtime. The import threw, getPlugin() returned null, and every
Amber signing op — including the Blossom upload auth event that a Crew logo
upload needs — failed with "Capacitor Amber plugin not available".

Fix: access the native signer via Capacitor's registerPlugin('NostrSignerPlugin')
from the bundled @capacitor/core, and reimplement the plugin's thin JS wrapper
logic directly against the native bridge (options-object API per the plugin's
main source). This removes the broken dynamic import entirely and is version-
independent as long as the native plugin is registered. Covers both native
Amber login (getAmberPublicKeyViaPlugin) and all signing/encryption paths.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:57:55 +07:00