Skip to content

feat: improve PWA version visibility and updates - #42

Merged
absolutepraya merged 80 commits into
mainfrom
feat/pwa-version-updates
Aug 16, 2026
Merged

feat: improve PWA version visibility and updates#42
absolutepraya merged 80 commits into
mainfrom
feat/pwa-version-updates

Conversation

@absolutepraya

@absolutepraya absolutepraya commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Problem

Installed PWAs can keep running an older frontend build without clearly showing which build is actually executing. The fork also versions its service-worker URL as /sw.js?v=<build>, so an old frontend cannot discover a newly deployed worker URL by calling registration.update() on its old URL alone.

The shared profile menu also still contained upstream Karakeep app/docs/social links instead of Marka-owned surfaces.

Solution

  • add a shared PWA lifecycle provider that distinguishes the running frontend build from the currently deployed server build
  • check /api/version with no-store on initial load and whenever the document returns to the foreground
  • bound version discovery with an abort timeout so a stalled request cannot suppress later checks
  • register the deployed /sw.js?v=<commit> silently when a newer build exists
  • keep newly discovered updates waiting so an active session is never reloaded just because an update downloaded
  • on a later fresh load, request activation only when the waiting worker matches the running build and can safely take over as the sole relevant window client
  • reload exactly once after an explicitly armed controllerchange handoff
  • close the installing-worker readiness race by checking readiness both immediately after listener attachment and on later state changes
  • preserve existing logout cache clearing, document-cache session scoping, offline-library state, and service-worker cache boundaries
  • show the running build and update state in the desktop sidebar and mobile profile footer
  • remove upstream/social profile links and replace Apps & extensions / Documentation with disabled Marka Coming soon rows
  • add a typed client-side profile_menu namespace while keeping server translations scoped to the configured default namespace

Validation

Current head 3773b295183f1a9b6dd778bca109ce1fefaa43bc is synced with current main and validated by:

  • focused PWA lifecycle, service-worker, version UI, profile-menu, timeout, stale-worker, readiness-race, and non-SHA build regression tests
  • full @karakeep/web Vitest suite
  • repository format check
  • repository lint
  • repository typecheck
  • React Doctor
  • Knip
  • OpenAPI regeneration/check
  • shared-package tests
  • tRPC tests
  • worker tests
  • repository E2E tests

Final synced-head CI run: https://github.com/absolutepraya/karakeep/actions/runs/31957306794

CodeRabbit reviewed the final implementation changes with no actionable comments. The later sync commit only incorporated six disjoint reviewer-policy/docs files from main; relative to current main, this PR still changes only the intended 18 PWA/version files. All existing inline review threads are resolved.

Device acceptance

Real installed-PWA acceptance remains to be recorded on:

  • Safari-installed iPhone PWA
  • Android installed PWA from a supported browser

The automated suite covers the lifecycle contracts, but this PR is not described as fully device-verified until those real-device checks are completed.

Docs

  • docs/superpowers/specs/2026-08-16-pwa-version-updates-design.md
  • docs/superpowers/plans/2026-08-16-pwa-version-updates.md
  • docs/superpowers/specs/2026-07-12-offline-library-pwa-design.md

Out of scope

  • native Expo app updates
  • browser-extension updates
  • semantic versioning or release notes
  • update channels or manual rollback
  • deployment/Watchtower changes

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5001981a-8df0-424b-80e7-623685dec98b

📥 Commits

Reviewing files that changed from the base of the PR and between 699af9e and 60425ed.

📒 Files selected for processing (7)
  • apps/web/components/pwa/ServiceWorkerRegistration.readiness.test.tsx
  • apps/web/components/pwa/ServiceWorkerRegistration.test.tsx
  • apps/web/components/pwa/ServiceWorkerRegistration.timeout.test.tsx
  • apps/web/components/pwa/ServiceWorkerRegistration.tsx
  • apps/web/components/shared/sidebar/SidebarVersion.test.tsx
  • apps/web/lib/i18n/server.ts
  • docs/superpowers/plans/2026-08-16-pwa-version-updates.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/components/pwa/ServiceWorkerRegistration.timeout.test.tsx
  • apps/web/components/shared/sidebar/SidebarVersion.test.tsx
  • apps/web/components/pwa/ServiceWorkerRegistration.test.tsx
  • apps/web/components/pwa/ServiceWorkerRegistration.tsx

Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The PR adds build-aware PWA lifecycle state, safe service-worker activation, update checks, shared version displays, mobile profile integration, localized profile entries, and focused test coverage.

Changes

PWA version updates

Layer / File(s) Summary
Lifecycle provider and update discovery
apps/web/components/pwa/ServiceWorkerRegistration.tsx, apps/web/lib/providers.tsx, apps/web/components/pwa/*test.tsx, docs/superpowers/...
The application checks /api/version, tracks current, available, and ready states, registers build-specific workers, monitors worker readiness, reloads once after handoff, and exposes lifecycle state through context.
Safe service-worker activation
apps/web/public/sw.js, apps/web/components/pwa/sw.test.ts, docs/superpowers/specs/...
The service worker activates a waiting update only when the requester is the sole window client. It reports blocked activation to the requester otherwise.
Build display and profile menu integration
apps/web/components/shared/sidebar/*, apps/web/components/dashboard/header/*, apps/web/lib/i18n/*, apps/web/@types/i18next.d.ts, docs/superpowers/...
Sidebar and mobile profile views display build and update information. Profile links become localized disabled Coming soon entries. Client and server i18n declarations use the configured namespaces.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to 60425

The PR improves PWA version visibility and safe updates, but merge readiness is still affected by a CI check that may not fail on formatter changes and a timing race that can leave update status incorrect; these issues should be fixed or explicitly accepted before merging.

Possibly related PRs

  • absolutepraya/karakeep#31: Both changes modify ProfileOptions.tsx; this PR replaces the affected links with localized disabled features.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the PR's primary PWA version visibility and update-handling changes.
Description check ✅ Passed The description clearly explains the problem, solution, validation, documentation, device acceptance status, and out-of-scope items.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pwa-version-updates

Comment @coderabbitai help to get the list of available commands.

@absolutepraya

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai

sourcery-ai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a PWA lifecycle context/provider that tracks running vs deployed builds, coordinates safe service worker updates, and exposes version status to shared UI, while cleaning up profile menu links and adding a typed i18n namespace and documentation for the new PWA versioning behavior.

Sequence diagram for PWA version check and safe update activation

sequenceDiagram
    actor User
    participant Window
    participant ServiceWorkerRegistration
    participant ApiServer as ApiServer_/api/version
    participant NavigatorSW as navigator.serviceWorker
    participant Worker as sw.js

    User->>Window: Open PWA
    Window->>ServiceWorkerRegistration: Mount provider

    ServiceWorkerRegistration->>NavigatorSW: getRegistration("/")
    NavigatorSW-->>ServiceWorkerRegistration: existingRegistration
    ServiceWorkerRegistration->>NavigatorSW: register(serviceWorkerUrl)
    NavigatorSW-->>ServiceWorkerRegistration: registration

    ServiceWorkerRegistration->>ApiServer: fetch("/api/version", {cache:"no-store"})
    ApiServer-->>ServiceWorkerRegistration: { version: deployedBuild }
    ServiceWorkerRegistration->>NavigatorSW: register("/sw.js?v="+deployedBuild)
    NavigatorSW-->>ServiceWorkerRegistration: registration (installing/waiting)
    ServiceWorkerRegistration-->>ServiceWorkerRegistration: setUpdateStatus("available" / "ready")

    rect rgb(230,230,250)
      ServiceWorkerRegistration->>NavigatorSW: getRegistration("/")
      NavigatorSW-->>ServiceWorkerRegistration: registration(waiting for appBuild)
      ServiceWorkerRegistration-->>NavigatorSW: waiting.postMessage({ type:"ACTIVATE_UPDATE" })
      ServiceWorkerRegistration-->>ServiceWorkerRegistration: handoffArmedRef = true
    end

    ServiceWorkerRegistration->>Worker: postMessage({ type:"ACTIVATE_UPDATE" })
    Worker->>Worker: handleActivateUpdate(event)
    Worker->>Worker: clients.matchAll({ type:"window", includeUncontrolled:true })
    alt single matching client
      Worker->>Worker: skipWaiting()
    else multiple window clients
      Worker-->>ServiceWorkerRegistration: postMessage({ type:"UPDATE_ACTIVATION_BLOCKED" })
    end

    Worker-->>Window: controllerchange
    Window->>ServiceWorkerRegistration: handleControllerChange
    ServiceWorkerRegistration-->>Window: window.history.go(0) (once when handoffArmed)
Loading

File-Level Changes

Change Details Files
Add a PWA lifecycle provider around service worker registration to track app vs deployed builds and coordinate safe update activation.
  • Wrap the app tree with ServiceWorkerRegistration so it becomes a provider that accepts children.
  • Track appBuild from NEXT_PUBLIC_SERVICE_WORKER_BUILD_VERSION and maintain deployedBuild and updateStatus state.
  • On mount, inspect existing registrations, register the appropriate worker for the running app build, and optionally trigger an ACTIVATE_UPDATE handoff when a matching waiting worker exists.
  • Handle controllerchange with an armed handoff flag so the page reloads exactly once after a controlled update.
  • Keep existing session sync, logout cache clearing, and thumbnail message handling behavior intact.
apps/web/components/pwa/ServiceWorkerRegistration.tsx
apps/web/lib/providers.tsx
Implement build-aware update discovery and background registration of newer service workers using the live /api/version endpoint.
  • Add a no-store fetch to /api/version on initial mount and when the document becomes visible.
  • Validate version strings as deploy build identifiers and compute versioned /sw.js?v= URLs.
  • Register service workers for newer builds and track installing/waiting workers, marking updates as available vs ready based on exact build matches.
  • Introduce timeout-bound, deduplicated update check logic so hung requests don't block subsequent foreground checks.
  • Expose a usePwaLifecycle hook backed by context to share appBuild, deployedBuild, and updateStatus across the app.
apps/web/components/pwa/ServiceWorkerRegistration.tsx
apps/web/components/pwa/ServiceWorkerRegistration.test.tsx
apps/web/components/pwa/ServiceWorkerRegistration.timeout.test.tsx
Extend the service worker script to support controlled ACTIVATE_UPDATE handoffs that respect multi-window safety.
  • Handle ACTIVATE_UPDATE messages by checking window clients with clients.matchAll before calling skipWaiting.
  • Allow activation only when the requester is the sole window client; otherwise send UPDATE_ACTIVATION_BLOCKED back to the requester.
  • Keep existing cache-boundary behavior, offline fallbacks, and CLEAR_USER_CACHES logic unchanged.
  • Augment the service worker test harness with clients.matchAll and skipWaiting mocks, and add tests for ACTIVATE_UPDATE behavior with one vs multiple clients.
apps/web/public/sw.js
apps/web/components/pwa/sw.test.ts
Update sidebar and profile UI to display running build and update status from the shared PWA lifecycle, and clean up upstream profile links.
  • Refactor SidebarVersion to consume usePwaLifecycle and show appBuild plus any newer deployed build as Update available/ready, with GitHub commit links for SHA builds.
  • Add a placement prop to SidebarVersion for sidebar vs profile layouts and wire it into Sidebar and ProfileOptions.
  • Remove upstream Karakeep apps/docs/X links from the profile dropdown and replace them with disabled Apps & extensions and Documentation Coming soon rows.
  • Insert the version footer into the mobile profile menu only, using SidebarVersion with placement="profile".
  • Add tests verifying the new SidebarVersion and ProfileOptions behaviors.
apps/web/components/shared/sidebar/SidebarVersion.tsx
apps/web/components/shared/sidebar/Sidebar.tsx
apps/web/components/dashboard/header/ProfileOptions.tsx
apps/web/components/shared/sidebar/SidebarVersion.test.tsx
apps/web/components/dashboard/header/ProfileOptions.test.tsx
Introduce a typed profile_menu i18n namespace and use it for profile and version strings.
  • Add an English profile_menu.json with build and profile-menu strings.
  • Overload useTranslation in the client i18n helper to support both default and profile_menu namespaces.
  • Extend i18next custom types to include the profile_menu resource for typed keys.
  • Wire profile menu and SidebarVersion text to useTranslation("profile_menu").
apps/web/lib/i18n/locales/en/profile_menu.json
apps/web/lib/i18n/client.ts
apps/web/@types/i18next.d.ts
apps/web/components/shared/sidebar/SidebarVersion.tsx
apps/web/components/dashboard/header/ProfileOptions.tsx
Align offline-PWA documentation with the new PWA version/update lifecycle and add dedicated design/plan docs.
  • Update the offline-library PWA design doc to reference the new PWA version visibility and safe auto-update design.
  • Add a detailed version visibility and safe auto-update design spec describing goals, lifecycle, UI, safety constraints, and tests.
  • Add an implementation plan doc outlining the final implementation, guardrails, files, tests, and verification checklist for PWA version updates.
docs/superpowers/specs/2026-07-12-offline-library-pwa-design.md
docs/superpowers/specs/2026-08-16-pwa-version-updates-design.md
docs/superpowers/plans/2026-08-16-pwa-version-updates.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="apps/web/lib/i18n/client.ts" line_range="33-42" />
<code_context>
   });

-export const useTranslation = useTranslationOrg;
+export function useTranslation(): ReturnType<
+  typeof useTranslationOrg<"translation">
+>;
+export function useTranslation(ns: "translation"): ReturnType<
+  typeof useTranslationOrg<"translation">
+>;
+export function useTranslation(ns: "profile_menu"): ReturnType<
+  typeof useTranslationOrg<"profile_menu">
+>;
+export function useTranslation(ns: "translation" | "profile_menu" = "translation") {
+  return useTranslationOrg(ns);
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** The `useTranslation` overload return types use an invalid `typeof useTranslationOrg<...>` construction and are likely to break TypeScript.

The current overloads are invalid because `typeof` can’t be used with a type argument (`typeof useTranslationOrg<"translation">` won’t compile or will infer incorrectly).

You can keep overloads but rely on `ReturnType<typeof useTranslationOrg>` and let the `ns` argument drive the generic:

```ts
export function useTranslation(): ReturnType<typeof useTranslationOrg>;
export function useTranslation(ns: "translation"): ReturnType<typeof useTranslationOrg>;
export function useTranslation(ns: "profile_menu"): ReturnType<typeof useTranslationOrg>;
export function useTranslation(ns: "translation" | "profile_menu" = "translation") {
  return useTranslationOrg(ns);
}
```

For per-namespace precision, consider using the `Namespace`/`KeyPrefix` generics from `react-i18next` instead of encoding them via `typeof`.
</issue_to_address>

### Comment 2
<location path="docs/superpowers/plans/2026-08-16-pwa-version-updates.md" line_range="28" />
<code_context>
+
+### Client lifecycle provider
+
+`apps/web/components/pwa/ServiceWorkerRegistration.tsx` remains the service-worker integration point and now also provides shared PWA lifecycle state.
+
+It exports:
</code_context>
<issue_to_address>
**nitpick (typo):** Consider dropping the hyphen in “service-worker” for consistency.

In this doc you use “service worker” elsewhere, so please update this instance to “service worker integration point” for consistency with the rest of the text and common usage.

```suggestion
`apps/web/components/pwa/ServiceWorkerRegistration.tsx` remains the service worker integration point and now also provides shared PWA lifecycle state.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/web/lib/i18n/client.ts Outdated
Comment thread docs/superpowers/plans/2026-08-16-pwa-version-updates.md Outdated

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@absolutepraya
absolutepraya merged commit d027283 into main Aug 16, 2026
8 checks 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.

1 participant