Skip to content

feat(zap): default message + zap comment in history + completion polish#406

Merged
spe1020 merged 2 commits into
zapcooking:mainfrom
dmnyc:feat/default-zap-message
May 29, 2026
Merged

feat(zap): default message + zap comment in history + completion polish#406
spe1020 merged 2 commits into
zapcooking:mainfrom
dmnyc:feat/default-zap-message

Conversation

@dmnyc
Copy link
Copy Markdown
Collaborator

@dmnyc dmnyc commented May 28, 2026

Summary

Adds a user-configurable default zap message that pre-fills the ZapModal textarea on every open and ships with one-tap zaps. The message is then plumbed end-to-end so it surfaces wherever the zap is visible — wallet history rows, ZappersListModal entries, and the ZapModal success state. Plus a handful of related polish around the zap flow that fell out of testing on a Vercel preview:

  • Settings → Zap Settings → Default zap message textarea (live bind:value, NIP-78 relay publish on blur). 280-char cap with counter.
  • Zapper.comment plumbed through the engagement-cache. processZap parses the kind-9734 zap request from the receipt's description tag, so the message shows for everyone's zaps, not just your own optimistic ones.
  • walletManager.sendPayment forwards metadata.comment into the pending transaction. WalletPanel already rendered tx.comment in italics on outgoing rows.
  • ZapModal success state and ZappersListModal entries render the comment in italics.
  • One-tap zaps use the default message for both the zap request content and the wallet-history metadata.

Reliability fixes

  • No more phantom zap counts. Optimistic update is now applied after createZap() succeeds (so a recipient with no lud16 / a failing LNURL endpoint never produces a count that won't reconcile). New revertOptimisticZap() undoes the update if sendPayment errors.
  • Friendlier ZapModal error UI. Replaced the giant red X with an amber LightningSlash in a tinted circle, mapped raw errors to human messages ("This user hasn't set up a Lightning address yet, so zaps can't be sent to them." instead of "Profile lud16: undefined"), and made Try Again context-aware — hidden for unrecoverable cases.
  • Pending tx TTL bumped 5 min → 30 min. Was causing wallet history rows to lose their recipient name + comment if the user closed and reopened the wallet panel before the SDK history sync. The pending tx is the bridge that carries recipient pubkey + zap comment onto the SDK row; if it ages out before that migration runs, the row renders as a generic "Sent". Matching window in WalletPanel.loadTransactionHistory bumped to match.

Completion polish on the bolt

  • Bolt turns yellow + filled the moment isZapping flips (synchronous on tap, before any await). Instant tap confirmation — no waiting on LNURL.
  • One-shot sparkle burst when the zap is fully paid. Six amber particles fan out with a brief "pop" overshoot then fade. Fires via a dedicated markSelfZapCompleted(eventId) called only after sendPayment returns success (one-tap path) or after the modal's zap-complete event (modal path). Decoupled from optimisticZapUpdate so it fires exactly once at the end of the flow, not at the LNURL step and again at payment.
  • Respects prefers-reduced-motion.

Test plan

  • Settings → Zap Settings → Default zap message: type, blur, refresh page → message persists; check NIP-78 event published with content
  • Open ZapModal on any note → textarea pre-fills with your default; edit/clear works; sending uses the edited value
  • One-tap zap → wallet history row shows ⚡ to {recipient} with the message in italics
  • ZapModal success state → shows the message in italics under the amount badge
  • Click a zapper pill on a note → ZappersListModal opens with the message under each zapper that had one
  • Zap a note whose author has no lud16 → friendly "No Lightning address" error, no phantom zap count gets added (refresh and confirm)
  • Zap a note → bolt instantly turns gold on tap; sparkle burst fires once when payment confirms; second tap on same note also bursts
  • Close wallet, wait > 5 min, reopen → previously-sent zap row still shows ⚡ to {recipient} with the message (was the 5-min TTL bug)

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a user-configurable default zap message that is pre-filled into both the ZapModal and one-tap zap flows, then plumbs the comment end-to-end so it appears in wallet history, the ZapModal success state, and the ZappersListModal. Also includes several zap-flow reliability and polish fixes: deferring the optimistic count until LNURL succeeds (with a new revertOptimisticZap), a friendlier ZapModal error state, a 30-minute pending-tx TTL, instant bolt feedback on tap, and a one-shot sparkle burst on payment completion driven by a new markSelfZapCompleted / lastSelfZapAt channel.

Changes:

  • New defaultZapMessage store + NIP-78 persistence in autoZapSettings.ts, surfaced via a Settings textarea and consumed by ZapModal, oneTapZap, and wallet metadata.
  • engagementCache.ts gains Zapper.comment, lastSelfZapAt, markSelfZapCompleted, and revertOptimisticZap; processZap extracts the kind-9734 comment from the receipt description.
  • Polish: friendlier ZapModal error mapping, pending-tx TTL bumped to 30 min (matched in WalletPanel), instant gold bolt + reduced-motion-aware sparkle burst in NoteTotalZaps.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/lib/autoZapSettings.ts New defaultZapMessage store, MAX_ZAP_MESSAGE_LENGTH, setters, localStorage + NIP-78 sync.
src/routes/settings/+page.svelte Adds the "Default zap message" textarea outside the one-tap wallet-gated block.
src/lib/engagementCache.ts Zapper.comment, lastSelfZapAt, comment parsed from zap request, markSelfZapCompleted, revertOptimisticZap.
src/lib/oneTapZap.ts Defers optimistic update until after createZap, passes message to zap request + wallet metadata, reverts on failure, marks completion.
src/lib/wallet/walletManager.ts Forwards metadata.comment into pending tx; pending TTL bumped to 30 min.
src/components/wallet/WalletPanel.svelte Migration window aligned with new 30-min TTL via shared constant.
src/components/ZapModal.svelte Pre-fills message from default store, friendlier error UI with retry gating, dispatches comment and renders it on success.
src/components/ZappersListModal.svelte Renders per-zapper comment in italics under the display name.
src/components/NoteTotalZaps.svelte Instant gold bolt on isZapping, one-shot sparkle burst keyed off lastSelfZapAt, reduced-motion fallback.
src/components/NoteActionBar.svelte Modal zap-complete handler passes comment and calls markSelfZapCompleted.
package.json Version bump 4.2.448 → 4.2.449.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…polish

Adds a user-configurable default zap message that pre-fills the ZapModal
textarea on every open and ships with one-tap zaps. The message is then
threaded end-to-end so it surfaces wherever the zap is visible — wallet
history row, zappers-list modal, ZapModal success state. Plus a handful
of related polish to the zap flow that fell out of testing.

Settings + persistence
- New `defaultZapMessage` writable in autoZapSettings, persisted to
  localStorage (`zapcooking_default_zap_message`) and synced to the
  existing NIP-78 settings event alongside the one-tap toggle/amount.
- New Settings → Zap Settings → "Default zap message" textarea (live
  bind via `bind:value={$defaultZapMessage}`; relay publish on blur
  via on:change so we don't spam during typing).
- 280-char cap (MAX_ZAP_MESSAGE_LENGTH), counter shown under the field.

Comment plumbed everywhere
- `Zapper.comment` added to the engagement-cache type. `processZap`
  parses it out of the kind-9734 zap request inside the receipt's
  description tag, so zaps from anyone now carry their message
  through to UI.
- `ZappersListModal` renders the comment in italics under each zapper.
- `optimisticZapUpdate` accepts a `comment` and stores it on the
  optimistic zapper entry so the just-sent zap shows the user's own
  message before the real receipt lands.
- `walletManager.sendPayment` forwards `metadata.comment` into the
  pending transaction; WalletPanel already rendered `tx.comment` in
  italics for outgoing rows.
- ZapModal success state shows the message in italics under the amount.
- ZapModal's `zap-complete` dispatch carries `comment` so
  NoteActionBar's handler can pass it into the optimistic update.

One-tap zap uses the default
- `sendOneTapZap` reads `defaultZapMessage` and uses it for both the
  zap request content and the wallet-history description/comment.

Reliability fixes
- `revertOptimisticZap` added. Optimistic update is now applied AFTER
  `createZap()` succeeds (so a recipient with no lud16 / a failing
  LNURL endpoint never produces a phantom zap count), and reverted
  if `sendPayment` errors. No more ghost counts that never reconcile.
- Friendlier ZapModal error state: amber LightningSlash icon instead
  of the giant red X, mapped messages for no-lightning-address /
  timeout / insufficient-funds / cancelled / no-wallet, and a
  contextual Try Again button that hides itself for unrecoverable
  errors.
- Pending tx TTL bumped to 30 min (was 5) and WalletPanel's matching
  window bumped to match. The pending tx is the bridge that carries
  recipient pubkey + comment onto the SDK history row — dropping it
  too early left rows labelled "Sent" with no recipient name.

Completion polish on the bolt
- Bolt turns yellow + filled the moment `isZapping` flips (synchronous
  on tap, before any await) for instant tap confirmation — no waiting
  for LNURL to resolve before the visual responds.
- One-shot sparkle burst on the bolt when the zap is fully paid. Six
  amber particles fan out with a brief "pop" overshoot then fade.
  Fires via a dedicated `markSelfZapCompleted(eventId)` called from
  the one-tap path AFTER `sendPayment` returns success, and from
  NoteActionBar's `handleZapComplete` (modal path). Separating the
  completion signal from `optimisticZapUpdate` ensures the burst
  fires exactly once, at the end of the flow — not at the LNURL step
  and again at payment.
- Sparkle respects prefers-reduced-motion.
@dmnyc dmnyc force-pushed the feat/default-zap-message branch from b1237f3 to 34aa577 Compare May 29, 2026 21:18
@spe1020 spe1020 merged commit 9d8cbe9 into zapcooking:main May 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants