Skip to content

feat(signer): waiting indicator + timeout for remote-signer approvals#11

Merged
DocNR merged 2 commits into
mainfrom
feat/signer-approval-timeout
Jun 14, 2026
Merged

feat(signer): waiting indicator + timeout for remote-signer approvals#11
DocNR merged 2 commits into
mainfrom
feat/signer-approval-timeout

Conversation

@DocNR

@DocNR DocNR commented Jun 14, 2026

Copy link
Copy Markdown
Owner

What

Ports the signer-approval wait indicator + timeout from upstream Jumble (e0c4dca3), adapted to jank's signer registry.

signEvent on the bunker (NIP-46) and NIP-07 signers is now wrapped in a shared withSignerApproval() helper (src/lib/signer-approval.ts):

  • After a 1s delay (so instant auto-approvals stay silent), a low-key loading toast appears: "Waiting for signer approval…", and auto-dismisses when the signature returns.
  • Concurrent sign requests are reference-counted into a single toast — frequent signing across columns never stacks up.
  • The wait is bounded by a 30s timeout: an unresponsive signer (offline bunker, closed extension popup) rejects instead of hanging the composer forever.

This is squarely jank's NIP-46-heavy model — the worst UX gap of remote signing was the silent pending sign.

Scope decisions

  • nsec / npub signers are intentionally not wrapped — local nsec signs instantly (no approval), npub is view-only.
  • Upstream's mobileOffset retune is skipped — jank already tunes mobileOffset for its own chrome; the toast inherits it.
  • Remote NIP-44 decrypt (DMs) is not wrapped in v1 (upstream parity = signEvent only). A bunker DM-decrypt can also hang; wrapping it is a candidate follow-up after smoke.

Gates

  • Gate 1 (NIP correctness): N/A — no event kind/tag/wire change; this only wraps the sign promise with UI + a timeout.
  • Gate 2 (signer/identity): withSignerApproval never touches pubkey/identity — it only races the existing signEvent promise against a timeout, so it introduces no identity drift; the downstream per-publish identity assert is unaffected. The 30s window is generous for manual approval; on timeout the sign rejects and the caller's existing error path fires (user retries).
  • Gate 3 (realistic-relay smoke): PENDING — needs a real bunker (human-in-loop). See checklist below before merge.

Smoke checklist (please run before merging)

  1. Sign/post from a bunker-paired column → toast appears ~1s after tapping, dismisses on approval in Amber/Clave.
  2. Instant auto-approve path → no toast flashes (sign returns < 1s).
  3. Unresponsive signer (don't approve / bunker offline) → rejects at ~30s with "Signer did not respond in time", composer is freed (not stuck).
  4. NIP-07 extension sign → same toast behavior.
  5. Two columns signing as different accounts at once → one shared toast, dismisses when both resolve.

Verification

🤖 Generated with Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying jank with  Cloudflare Pages  Cloudflare Pages

Latest commit: cd6e121
Status: ✅  Deploy successful!
Preview URL: https://1c3a3104.jank-4ii.pages.dev
Branch Preview URL: https://feat-signer-approval-timeout.jank-4ii.pages.dev

View logs

@DocNR

DocNR commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

Browser smoke (real Clave bunker) → one fix applied

Smoke-tested against a live Clave NIP-46 bunker. Found and fixed one issue:

Background relay AUTH leaked into the approval wait. jank signs NIP-42 relay AUTH (kind 22242) and NIP-98 HTTP auth (kind 27235) through the same signer.signEvent chokepoint as user posts, so the wrapper was popping the "Waiting for signer approval" toast + applying the 30s timeout to background AUTH. Observed live as subscribe auth function failed: Signer did not respond in time.

Fix (99f4ea6a): pass the draft kind through to withSignerApproval; AUTH kinds pass straight through with no toast and no timeout. Re-smoked — the stray toasts and the AUTH failure line are gone; posting on medium/high trust works normally. Added a unit test (src/lib/__tests__/signer-approval.spec.ts, 5 cases).

Out of scope (pre-existing, not this PR)

On Clave low-trust, posting fails immediately with Permission denied — open Clave to approve. Confirmed pre-existing (predates this PR; reproduces on main). Root cause is Clave returning a hard error on low-trust instead of the NIP-46 auth_url hold-open flow that nostr-tools already supports. Tracking separately.

Gate 3 (realistic-relay smoke) — status

  • ✅ No spurious approval toasts from background AUTH
  • ✅ Posting works on medium/high trust (normal usage)
  • ✅ Unresponsive-signer timeout covered by unit test

DocNR and others added 2 commits June 14, 2026 08:43
…vals

Wrap signEvent for the bunker (NIP-46) and NIP-07 signers with a shared
withSignerApproval helper: after a 1s delay (so instant auto-approvals stay
silent) a low-key loading toast appears and auto-dismisses when the signature
returns; concurrent signs reference-count into one toast. The wait is bounded
by a 30s timeout so an unresponsive signer (offline bunker, closed extension
popup) rejects instead of hanging the composer forever.

Ported from upstream Jumble e0c4dca3. nsec/npub signers are intentionally not
wrapped (instant local sign / view-only). Upstream's mobile toast-offset tweak
is skipped — jank already tunes mobileOffset for its own chrome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NIP-42 relay AUTH (kind 22242) and NIP-98 HTTP auth (kind 27235) flow through the
same signer.signEvent chokepoint as user posts, so the new withSignerApproval
wrapper was surfacing a "Waiting for signer approval" toast and a 30s timeout for
every background relay-AUTH sign — noisy on a multi-relay deck, and it rejected
AUTH signs the user never meant to approve (observed live: "subscribe auth
function failed: Signer did not respond in time").

Pass the draft kind through to withSignerApproval and pass auth kinds straight
through with no toast and no timeout, reverting AUTH to its prior behavior. Real
user-initiated signs still get the waiting indicator + bound. Adds a unit test.

Found in browser smoke against a real Clave bunker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DocNR DocNR force-pushed the feat/signer-approval-timeout branch from 99f4ea6 to cd6e121 Compare June 14, 2026 12:44
@DocNR DocNR merged commit 8e42fe2 into main Jun 14, 2026
2 checks passed
@DocNR DocNR deleted the feat/signer-approval-timeout branch June 14, 2026 12:46
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.

1 participant