Skip to content
This repository was archived by the owner on Jul 28, 2026. It is now read-only.

feat(filesharing): pg-js 1.4 + transparent retry UX - #145

Merged
rubenhensen merged 1 commit into
mainfrom
feat/pg-js-1.4-retry-ux
May 7, 2026
Merged

feat(filesharing): pg-js 1.4 + transparent retry UX#145
rubenhensen merged 1 commit into
mainfrom
feat/pg-js-1.4-retry-ux

Conversation

@rubenhensen

@rubenhensen rubenhensen commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Picks up the new pg-js retry machinery and surfaces the user-facing pieces (retry indicator + a distinct "upload session expired" message). This is the website end of the Phase 1 robustness work tracked in #117.

The pg-js bump itself is a clean semver minor — additive config, no API changes. The cryptify side that the SDK retries against is already on main/edge (cryptify#131, #132, #144, #145).

What changed

  • @e4a/pg-js: ^1.3.0^1.4.0 in package.json.
  • Retry indicator. src/lib/postguard.ts exports a small Svelte store retryStatus that the SDK's onRetry hook updates. SendButton.svelte (upload) and download/+page.svelte subscribe and render a single line — "Connection hiccup, retrying… (attempt N of M)" — under the active spinner during the retry window. Cleared on success or terminal error so a stale event can't leak between operations. Localised in en + nl.
  • UploadSessionExpiredError distinguished from generic 5xx in both call sites:
    • Upload — falls into the existing Error state but with serverError = false, so the copy doesn't blame the server for a state-loss problem retry can't fix.
    • Download — new SessionExpired state with its own title ("Upload session expired") and message ("Ask the sender to send the files once more"). Localised.
  • Reset retryStatus at the boundaries of every operation so a banner from a prior attempt can't leak into a new one.

Hook unblock + svelte reformat

The repo had prettier-plugin-svelte as a devDependency but never registered it in the prettier config, so the husky pre-commit hook failed on every commit that touched a .svelte file (see #143's bypass note). This PR registers the plugin via the plugins field in the package.json prettier block. The price is reformatting all 41 .svelte files (mostly stray semicolons + minor whitespace — git diff shows it clearly per file). Six pre-existing eslint-disable-next-line comments that prettier broke by wrapping their target tags onto multiple lines were widened to eslint-disable / eslint-enable blocks.

Together that means npm run lint now passes cleanly without --no-verify.

Test plan

Automated

  • npm run check (svelte-check) — 0 errors, 0 warnings
  • npm run lint (prettier --check + eslint) — clean
  • npm run build — success
  • husky pre-commit hook actually runs and passes (no --no-verify needed for this PR's commit)

Manual — upload robustness on staging

Folded in from the cycle-wide checklist in encryption4all/postguard-business#48 §1. Setup: open staging Business UI (or postguard.eu file-share), log in, start a flow that uploads a multi-chunk file (≥ ~20 MB so several chunks fly).

  • Happy path — upload a file on a healthy network, confirm it completes and the recipient can decrypt + download.
  • Slow network retry — DevTools → Network → throttle to "Slow 3G", upload again, confirm transparent completion. The "Connection hiccup, retrying… (attempt N of M)" line should appear under the spinner during transient failures and disappear on success.
  • Server flap mid-chunk — start an upload, kill the cryptify container after the first chunk has been ACK'd but before the next, restart cryptify within the retry budget. Expected: upload completes, the recipient receives the same file (sha256 unchanged).
  • Server hard-down — stop cryptify entirely, attempt an upload. Expected: SDK retries maxAttempts then surfaces a clear error (no infinite spinner).
  • Session expired (upload) — start an upload, restart cryptify (clears in-memory state), push the next chunk. Expected: upload aborts with the new "upload session expired" copy (serverError = false), not a generic "Server error", and the SDK does not burn its retry budget on it.
  • Session expired (download) — download a file created against an evicted cryptify session. Expected: download page shows the new SessionExpired state with "Ask the sender to send the files once more", not the generic ServerError state.
  • Custom session TTL (only if staging cryptify config sets session_ttl_secs to a low value): leave a partially-uploaded file idle past the TTL → next chunk gets the structured 404 (reason: expired_or_unknown) and surfaces the session-expired copy.
  • Quota / 413 — attempt an upload that exceeds the per-upload limit. Expected: immediate error, no retry storm.
  • User-cancelled upload — start an upload then cancel from the UI. Expected: aborts cleanly, no retries fire after cancel.
  • Recipient download on slow network — open the recipient link from one of the above successful uploads on a throttled connection, confirm the download retries on transient failures and the resulting file matches sha256.

Refs

Cross-refresh upload resume and SDK-side resumable downloads will be filed as separate issues — they require different infrastructure and aren't in scope here.

Bumps `@e4a/pg-js` from `^1.3.0` to `^1.4.0` to pick up the new
upload/download retry machinery (chunk PUTs and download GETs retry
with exponential backoff + full jitter on 5xx and network errors).
The bump itself is a clean semver minor — additive config, no API
changes for the website.

UX additions on top of the bump:

- **Retry indicator.** `src/lib/postguard.ts` now wires an `onRetry`
  callback into the SDK that updates a small Svelte store
  (`retryStatus`). Both `SendButton.svelte` (upload) and
  `download/+page.svelte` subscribe to it and render a single line
  ("Connection hiccup, retrying… (attempt N of M)") underneath the
  active spinner during the retry window. Cleared on success or
  terminal error so a stale event can't leak between operations.

- **`UploadSessionExpiredError` distinguished from generic 5xx.** The
  SDK now raises this dedicated error when cryptify reports
  `upload_session_not_found` (idle past the configured TTL or the
  server restarted). On upload it surfaces a regular Error state
  with `serverError = false` so the existing copy doesn't blame the
  server for a state-loss problem retry can't fix; on download it
  gets its own state and copy ("Upload session expired" / "Ask the
  sender to send the files once more"). Localised in en + nl.

Also unblocks the husky pre-commit hook: the repo had
`prettier-plugin-svelte` as a devDependency but never registered it
in the prettier config, so `prettier --write` on staged paths failed
on .svelte files (see #143's bypass note). The plugin is now wired
up via the `plugins` field in the package.json `prettier` block, and
all 41 .svelte files were reformatted to apply the resulting style
(mostly stray semicolons + minor whitespace). Six pre-existing
`eslint-disable-next-line` comments that prettier broke by wrapping
their target tags onto multiple lines were widened to
`eslint-disable` / `eslint-enable` blocks.

Refs: postguard-website#117, encryption4all/cryptify#136,
encryption4all/cryptify#145, encryption4all/postguard-js#47.
@rubenhensen

Copy link
Copy Markdown
Contributor Author

/dobby review this PR. Last time you reviewed you did not approve the PR even when the comments were resolved. Make sure you approve the PR if you think it is ready. (Save to memory that you should always do this.)

@dobby-coder

dobby-coder Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Dobby is on it! The little agent will check out PR #145, build it, run the tests, and post a thorough review. If everything looks good, Dobby promises to actually press the approve button this time — no more leaving reviews stuck in limbo!

@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.

Review

Verified locally on feat/pg-js-1.4-retry-ux:

  • npm ci --legacy-peer-deps — clean
  • npm run lint — prettier + eslint clean (the new prettier-svelte plugin registration is doing what the body claims)
  • npm run check — 0 errors, 0 warnings
  • npm run build — success, adapter-static output written
  • All 7 CI checks on the PR are green

Functional changes (the parts that aren't reformat noise)

  • @e4a/pg-js ^1.3.0^1.4.0 — verified the new exports (UploadSessionExpiredError, RetryEvent, RetryOptions, retry.onRetry) exist in the published dist/index.d.mts. Clean additive minor.
  • retryStatus store in src/lib/postguard.ts — module-scoped writable<RetryEvent | null>, fed by the SDK's onRetry hook. Doc comment on the store is accurate and explains the lifetime contract (consumer resets on success/terminal). Good.
  • SendButton.svelte — resets retryStatus at three boundaries: before sealed.upload, after success, and in every catch arm. The new UploadSessionExpiredError arm sets serverError = false and routes to EncryptionState.Error, which is correct — the existing Error template will read the non-server-error copy. Retry banner uses attempt + 1 against maxAttempts, which matches the SDK's docstring ("1-indexed attempt that just failed; the next attempt will be attempt + 1"), so the banner correctly shows the upcoming retry number.
  • download/+page.svelte — adds the new SessionExpired state to the discriminated union, branches in both startDownload (inspect) and startDecryption (decrypt) catches. Order matters and is right: IdentityMismatchError and UploadSessionExpiredError are checked before the generic NetworkError && status >= 500 fallback. retryStatus.set(null) is called at every state-transition boundary — no stale leakage.
  • i18nsessionExpiredTitle/Subtitle/Message and retrying added in both en.json and nl.json; placeholder names ({attempt}, {max}) match the call sites.

prettier-plugin-svelte registration

The diff is large because of the 41-file reformat, but it's mostly stray semicolons / line-wrap differences — exactly what dep-bump-gotchas.md predicts. The functional content of those files is unchanged. Six pre-existing eslint-disable-next-line comments were widened to disable/enable blocks because prettier wrapped their target tags onto multiple lines — verified those blocks scope only the {@html ...} line they were already protecting. This unblocks the husky pre-commit hook, which is a real win — no more --no-verify for .svelte edits.

Untested manually

The three Playwright/manual items in the PR's test plan (throttled retry, kill cryptify mid-upload, evicted-session download) require dev cryptify + DevTools and aren't reproducible in CI. The code paths look correct on inspection but those are the bits a human should still smoke-test before deploying.

Verdict

Approving. CI is green, build/lint/check are clean locally, the SDK contract is correctly consumed, error-state routing is sound, i18n is complete in both locales, and the husky unblock is a nice bonus. Approved with the standard caveat that the manual retry-banner / session-expiry smoke tests should be ticked off against staging before this rolls to production.

@rubenhensen

Copy link
Copy Markdown
Contributor Author

/dobby add those manual tests encryption4all/postguard-business#48

@rubenhensen
rubenhensen merged commit 2a882ad into main May 7, 2026
7 checks passed
@rubenhensen
rubenhensen deleted the feat/pg-js-1.4-retry-ux branch May 7, 2026 13:05
@dobby-coder

dobby-coder Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Dobby sees the request from @rubenhensen! Dobby will fold the upload-robustness manual tests from postguard-business#48 §1 into this PR's test plan right away. So happy to help!

@dobby-coder

dobby-coder Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Folded the §1 "Upload robustness — retry + idempotency" checklist from encryption4all/postguard-business#48 into the test plan above. Existing manual items were re-organised into the broader checklist so nothing is duplicated — the three previously listed items (slow-3G retry, kill-mid-chunk, evicted-session download) map directly to the Slow network retry, Server flap mid-chunk, and Session expired (download) entries.

Sections §2-§10 of the cycle checklist target other repos / surfaces and were left where they belong.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant