Skip to content

feat(filesharing): one-tap Yivi app open on iOS (pre-warmed session)#321

Merged
rubenhensen merged 1 commit into
mainfrom
feat/ios-yivi-one-tap
Jul 15, 2026
Merged

feat(filesharing): one-tap Yivi app open on iOS (pre-warmed session)#321
rubenhensen merged 1 commit into
mainfrom
feat/ios-yivi-one-tap

Conversation

@rubenhensen

Copy link
Copy Markdown
Contributor

Draft — blocked on postguard-js#103. Needs @e4a/pg-js >= 2.2.0 (prepareSign + EncryptInput.signingKeys). The ^2.2.0 bump here is provisional — confirm the exact version semantic-release cuts, then run npm install to update the lockfile.

Stacked on #319 (base = fix/ios-yivi-app-deeplink). Retarget to main once #319 merges. The diff here is only the one-tap work.

What

Collapses the iOS "sign and send" flow to a single tap. Follows up #319, which fixed the "opens Safari instead of the app" bug but still needs a second tap on Yivi's own button (a Universal Link only hands off to the app inside a genuine user gesture, and the app URL doesn't exist until the session starts).

This pre-warms the Yivi signing session with pg.prepareSign() as soon as the compose form is valid, so the app deep-link URL is ready before the tap. "Send" is then a real <a href> to that URL — one tap opens the app. The pre-resolved signing keys are fed into encrypt({ signingKeys }), reusing the disclosure instead of starting a second session.

How

  • Pre-warm lifecycle ($effect): warm while composing + canEncrypt; tear down otherwise. A second $effect re-warms on window focus after the session's TTL lapses. preparedSign is intentionally non-reactive so a persistent failure can't create a re-warm loop.
  • Send control: on mobile, once yiviAppUrl is ready, render an <a href> (onSignOneTap runs alongside the navigation and must not preventDefault). Otherwise the normal button.
  • Hidden host #prewarm-yivi drives the invisible widget; the user only ever touches the send anchor.
  • onetap waiting sheet: after the tap, shows "Complete the steps in the Yivi app, then return here" while the disclosure completes; on cancel/timeout it recovers via the existing Yivi disclosure aborts when the app is closed mid-flow and does not recover #294 interrupted-banner path.
  • Best-effort with graceful fallback: until the URL is ready, on desktop, or if the pre-warmed session lapsed, it falls back to the fix(filesharing): open Yivi app from a user tap on iOS #319 two-tap button/QR flow. startEncryption() gained an optional signingKeys param; when omitted it behaves exactly as before (and retires any idle pre-warm session).

Verification

  • svelte-check 0 errors/0 warnings; eslint clean; Svelte MCP autofixer no issues; npm run test:unit 14/14; full vite build succeeds (against a locally-linked pg-js build).

The actual iOS Universal Link hand-off can't be exercised by automated tests — needs a device once pg-js is released:

  • iPhone + Safari, Yivi installed: attach a file, fill a valid recipient, wait a moment for the button to become the one-tap link, tap once → the Yivi app opens (not open.yivi.app in Safari). Complete disclosure, return to Safari → upload proceeds.
  • Let a pre-warmed session sit idle past its TTL, background/foreground the tab → button re-warms; tapping still works.
  • Invalid form → button stays the normal (validation-modal) button, no app open.

iOS keeps a sticky per-domain "open in Safari" preference; a device that previously chose Safari for open.yivi.app will keep doing so until reopened via the in-app banner. Inherent to Universal Links, not this change.

Base automatically changed from fix/ios-yivi-app-deeplink to main July 15, 2026 07:33
@rubenhensen
rubenhensen marked this pull request as ready for review July 15, 2026 07:34
Builds on #319. On iOS a Yivi Universal Link only opens the app inside a
genuine user gesture, so #319's two-tap flow needs a second tap on Yivi's own
button. This pre-warms the signing session with pg.prepareSign() as soon as the
compose form is valid, then renders "Send" as a real <a href> to the resulting
app URL — so a single tap opens the app. The pre-resolved signing keys are fed
into encrypt() via signingKeys, reusing the disclosure instead of starting a
second session.

- Pre-warm lifecycle driven by an $effect: warm while composing + form valid,
  tear down otherwise; re-warm on window focus after the session's TTL lapses.
- Hidden host (#prewarm-yivi) drives the invisible widget; the user interacts
  with the send anchor.
- Best-effort: until the URL is ready (or on desktop, or if the session
  lapsed) it falls back to #319's two-tap button/QR flow.

Requires @e4a/pg-js >= 2.2.0 (prepareSign + EncryptInput.signingKeys); bumped
dependency and lockfile to 2.2.0.
@rubenhensen
rubenhensen force-pushed the feat/ios-yivi-one-tap branch from c65a6d8 to 3430744 Compare July 15, 2026 07:37
@rubenhensen
rubenhensen merged commit 8c17d11 into main Jul 15, 2026
8 checks passed

@dobby-coder dobby-coder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Post-merge review — PR already merged by @rubenhensen; noting for the record.

Reconciled the handed-over review findings and a focused rule sweep (WCAG 2.2 AA, conventional PR title, i18n, dep-bump) against the actual diff. CI was green (svelte-check 0/0, eslint clean, 14/14 unit, build ok) and the PR title is conventional.

  • One non-blocking UX note inline.
  • A handed-over i18n finding was dropped: it referenced fallback/Decrypt.svelte, which is not part of this diff, and this PR does localize its new oneTapWaiting string in both en.json and nl.json.
  • A spinner aria-hidden a11y nit was dropped: the new spinner matches the pre-existing spinner pattern already in this file (line 595), so it is carried-forward debt, not a regression here.

</p>
{/if}
</div>
{:else if isMobileDevice && yiviAppUrl && canEncrypt}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (follow-up): this one-tap <a href> branch renders only the send label. The powered by Yivi attribution (line 664) and the Sign with Yivi on another device QR chip (line 684) live only in the {:else} button branch. So once yiviAppUrl + canEncrypt are ready on iOS, a user whose Yivi app is not on this phone loses the cross-device QR fallback, and the Yivi attribution disappears. Consider rendering both in the one-tap branch too.

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