Skip to content

feat: add PWA app shell and offline upload queue for mobile#307

Open
SOHALIYAJAY wants to merge 1 commit into
Abhash-Chakraborty:mainfrom
SOHALIYAJAY:feat/pwa-app-shell-offline-queue
Open

feat: add PWA app shell and offline upload queue for mobile#307
SOHALIYAJAY wants to merge 1 commit into
Abhash-Chakraborty:mainfrom
SOHALIYAJAY:feat/pwa-app-shell-offline-queue

Conversation

@SOHALIYAJAY

@SOHALIYAJAY SOHALIYAJAY commented Jun 10, 2026

Copy link
Copy Markdown

Summary

Adds the initial PWA foundation for mobile usage by introducing an installable app shell, service worker support, and an IndexedDB-backed offline upload queue. Uploads selected while offline are stored locally and can be retried when connectivity is restored.

Fixes #259

Type of change

  • Bug fix
  • Feature
  • Documentation update
  • Refactor
  • CI / tooling

What changed

  • Added a PWA manifest with required metadata for installable app behavior.
  • Added service worker support and app shell caching.
  • Implemented an IndexedDB-backed upload queue for offline-selected uploads.
  • Added logic to flush queued uploads when connectivity is restored.
  • Added upload status handling for queued, uploading, failed, and completed states.
  • Added tests covering upload queue behavior.

Screenshots / recordings (for UI changes)

Attach screenshots demonstrating:

  • PWA installation prompt/app installation.
  • Upload queued while offline.
  • Upload automatically processing after reconnecting.
  • Upload state transitions (queued, uploading, failed, completed).

How to test

  1. Install dependencies:

    npm install
  2. Run the application:

    npm run dev
  3. Verify PWA behavior:

    • Open the application in a supported browser.
    • Confirm the manifest is loaded.
    • Verify the application can be installed.
  4. Verify offline upload queue:

    • Open browser DevTools.
    • Enable offline mode.
    • Select an upload.
    • Confirm the upload is stored locally and marked as queued.
  5. Restore network connectivity:

    • Disable offline mode.
    • Verify queued uploads are processed automatically.
    • Confirm status updates are displayed correctly.
  6. Run project checks:

    npm run lint
    npm run build
    npm test

Checklist

  • I linked the related issue
  • I ran required checks from CONTRIBUTING.md
  • I updated docs/env notes if needed
  • My PR is scoped to a single issue
  • I followed commit message conventions
  • I am not committing secrets or local artifacts

GSSoC'26 checklist

  • I requested issue assignment before starting
  • I have meaningful commits (no spam commits)
  • I am ready to explain my implementation in review comments

Summary by CodeRabbit

  • New Features

    • Added offline upload queuing: files can now be queued while offline and automatically uploaded when the browser reconnects.
  • Tests

    • Added test suite for upload queue functionality.
  • Chores

    • Updated app manifest for improved icon support.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements offline upload capability with a PWA shell. It adds an IndexedDB-backed queue for storing files selected while offline, a service worker for PWA registration, automatic queue flushing when connectivity returns, and supporting UI states ("draft" status) to show queued uploads to the user.

Changes

Offline Upload Queue with PWA Support

Layer / File(s) Summary
IndexedDB Queue Schema and CRUD Operations
frontend/src/lib/uploadQueue.ts, frontend/src/__tests__/uploadQueue.test.ts
Queue item schema with draft/pending/failed status, database initialization, and four async operations: enqueue() stores files as blobs with metadata, getQueue() retrieves all items, updateStatus() transitions status, and dequeue() removes items.
Service Worker and PWA Configuration
frontend/public/sw.js, frontend/src/app/providers.tsx, frontend/public/manifest.json, frontend/package.json
Service worker with skip-waiting, client-claiming, and network fetch passthrough. Conditional registration in Providers, manifest icon marked as maskable, and fake-indexeddb added to dev dependencies for testing.
Upload Page Offline Integration and Queue Flush
frontend/src/app/upload/page.tsx
Upload page extended with "draft" processing state and offline detection. One-time useEffect flushes queued items when browser comes online by transitioning status, reconstructing files, uploading, then dequeuing on success or marking failed. onDrop branches on connectivity: offline enqueues with toast, online uploads immediately.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Abhash-Chakraborty/Find#275: Modifies the same frontend/public/manifest.json to establish PWA manifest configuration, directly related to this PR's maskable icon update.

Suggested labels

frontend, mobile-app, type:feature, ui/ux, quality:clean

Suggested reviewers

  • Abhash-Chakraborty

Poem

🐰 A queue hops offline, files in a row,
Blob-stacked in IndexedDB's burrow below,
When online returns with a whisper so sweet,
The uploads take flight, our PWA complete! 📱✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding PWA app shell and offline upload queue functionality for mobile support.
Description check ✅ Passed The description covers all required template sections with comprehensive detail on changes, testing steps, and checklist items completion.
Linked Issues check ✅ Passed The PR implements all primary objectives from issue #259: PWA manifest, service worker support, IndexedDB offline queue, auto-flush on reconnect, and status UI updates.
Out of Scope Changes check ✅ Passed All changes are scoped to the PWA and offline queue implementation as defined in issue #259; no extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown

PR Context Summary

Suggested issue links

  • No strong issue match found yet.

Use Fixes #123 or Closes #123 in the PR body when one of the suggestions is the intended issue.
Manual rerun: Actions > PR Context Triage > Run workflow > set pr_number and force_review=true.

@macroscopeapp

macroscopeapp Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Unable to check for correctness in 4d96b28. This PR introduces a new PWA feature with offline upload queuing, service worker registration, and IndexedDB storage. New user-facing capabilities like offline workflows and background sync warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@SOHALIYAJAY

Copy link
Copy Markdown
Author

now i solved the issue, please review it, and merge it.
Thank You !!

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

Actionable comments posted: 9

🧹 Nitpick comments (4)
frontend/src/app/upload/page.tsx (2)

59-61: ⚡ Quick win

Draft status display is correct, but getStatusClasses() doesn't handle draft state.

getDisplayStatus() correctly returns "draft (offline)" for draft items (lines 59-61), but getStatusClasses() (lines 137-151) has no case for processingState === "draft". Draft items will fall through to the default "accent-badge status-default" styling (line 150), which may not provide appropriate visual distinction from queued items. Users may not clearly see that these items are in a different (offline-queued) state.

Add a specific style case for draft items in getStatusClasses() to improve visual clarity.

♻️ Optional improvement: add draft-specific styling
 function getStatusClasses(item: UploadListItem) {
+  if (item.processingState === "draft") {
+    return "accent-badge status-pending";
+  }
   if (item.status === "duplicate") {
     return "accent-badge status-pending";
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/app/upload/page.tsx` around lines 59 - 61, getStatusClasses() is
missing a branch for processingState === "draft", so draft items fall through to
the default styling; add a specific case in getStatusClasses() that checks
processingState === "draft" and returns a dedicated CSS class (e.g.,
"accent-badge status-draft" or similar) to match the display returned by
getDisplayStatus() and visually distinguish offline/queued drafts from other
states; update any corresponding mapping or switch inside getStatusClasses()
(referencing the function name getStatusClasses and the processingState ===
"draft" condition used in getDisplayStatus) and ensure the new class name is
used in the component's JSX/CSS so the styling takes effect.

342-342: ⚖️ Poor tradeoff

navigator.onLine is unreliable for detecting backend connectivity.

Line 342 uses navigator.onLine to decide whether to queue files offline or upload immediately. However, navigator.onLine only indicates whether the device has a network connection, not whether the Find backend is reachable. The user might have internet access but the backend might be down, unreachable due to firewall rules, or on a different network segment. In these cases, files will attempt to upload immediately and fail, instead of being queued for later retry.

While there's no perfect solution, consider detecting upload failures and offering to queue failed uploads for retry, or implement a backend health check before deciding to upload vs queue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/app/upload/page.tsx` at line 342, The check using
navigator.onLine is unreliable for backend reachability; replace the direct
reference to navigator.onLine in the upload decision (the conditional that
currently uses navigator.onLine) with logic that attempts a short backend health
check or proceeds to upload and treats any network/fetch failure as offline:
call a lightweight endpoint (e.g., GET /api/health) or try the actual upload via
the existing upload handler (e.g., uploadFiles or handleUpload) and on any
fetch/network error or non-2xx response, call the offline enqueue function
(e.g., queueFiles or enqueueUpload) and surface a retry option in the UI; ensure
failed upload promises are caught, move the files to the offline queue, and
update status so users can retry later.
frontend/src/app/providers.tsx (1)

43-45: ⚡ Quick win

Service worker registration failure is silently ignored.

Line 44 uses void to intentionally ignore the registration promise, meaning any registration failure (e.g., SW script syntax error, browser security restrictions, or loading issues) will be completely silent. While SW registration failure is non-fatal to the app, it will prevent PWA installation and offline queueing from working, leaving users confused about why those features are unavailable.

Consider adding .catch() to log registration errors to the console for debugging, or use a toast notification if the user should be informed.

♻️ Recommended fix: log registration errors
     if ("serviceWorker" in navigator) {
-      void navigator.serviceWorker.register("/sw.js");
+      navigator.serviceWorker.register("/sw.js").catch((err) => {
+        console.error("Service worker registration failed:", err);
+      });
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/app/providers.tsx` around lines 43 - 45, The service worker
registration promise is ignored (void
navigator.serviceWorker.register("/sw.js")) so failures are silent; update the
registration call that uses navigator.serviceWorker.register to attach a .catch
handler that logs the error (e.g., via console.error or an app logger) and
optionally handle success logging; ensure the change is applied inside the same
conditional branch that checks "serviceWorker" in navigator so registration
errors are surfaced for debugging or user notification.
frontend/src/lib/uploadQueue.ts (1)

15-24: ⚡ Quick win

Cache the database connection to avoid reopening on every operation.

openDb() is called by every CRUD function (enqueue, getQueue, updateStatus, dequeue), causing repeated indexedDB.open() calls. IndexedDB connections are heavyweight; repeatedly opening and not closing them can degrade performance and may exhaust browser resources under concurrent usage.

Consider caching the database handle in a module-level promise so all operations share a single connection, or explicitly close the database after each transaction completes.

♻️ Recommended fix: cache the database connection
+let dbPromise: Promise<IDBDatabase> | null = null;
+
 function openDb(): Promise<IDBDatabase> {
+  if (dbPromise) return dbPromise;
+  dbPromise = new Promise((resolve, reject) => {
-  return new Promise((resolve, reject) => {
     const req = indexedDB.open(DB_NAME, DB_VERSION);
     req.onupgradeneeded = () => {
       req.result.createObjectStore(STORE, { keyPath: "id" });
     };
     req.onsuccess = () => resolve(req.result);
     req.onerror = () => reject(req.error);
   });
+  return dbPromise;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/uploadQueue.ts` around lines 15 - 24, openDb() currently
calls indexedDB.open() every time (used by enqueue, getQueue, updateStatus,
dequeue), which is expensive; cache a single module-level Promise for the DB
connection (e.g., let dbPromise: Promise<IDBDatabase> | null) and have openDb()
initialize that promise once and return it for all callers so all CRUD functions
reuse the same connection; ensure the cached DB handles version upgrades/close
by listening for db.onversionchange (or clearing dbPromise on close/error) so
the cache can be replaced when needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/public/sw.js`:
- Around line 1-3: The service worker currently forwards every request in the
fetch handler (self.addEventListener("fetch", (e) =>
e.respondWith(fetch(e.request)))) so there is no app-shell precaching or runtime
caching; update the SW to implement precaching and a cache-first runtime
strategy for the app shell by adding an install handler that opens a named cache
(use caches.open), adds the static asset list (precache) via cache.addAll or
integration with the repo's suggested tool like `@serwist/next`, adjust activate
to clean old caches, and replace the fetch handler with logic that tries
caches.match(e.request) first (serve cached app-shell assets) then falls back to
fetch and populates runtime cache on success; if you intentionally only want
installability, add a comment in sw.js clarifying that offline app-shell caching
is out of scope.

In `@frontend/src/app/upload/page.tsx`:
- Around line 342-356: The code enqueues offline files but incorrectly marks
them with status: "uploaded" — update the upload status handling by adding a
"draft" state to the UploadResult["status"] union in frontend/src/lib/api.ts and
use that new value when creating draft list entries in the offline branch
(replace status: "uploaded" in the block that calls enqueue and
setUploadedFiles). Ensure UploadListItem consumers (e.g., the trackedUploads
useMemo, jobStatus logic, and activeJobs filters) treat "draft" as
not-yet-uploaded so they exclude drafts from uploaded/active counts and progress
calculations.
- Around line 342-356: The code casts a partial object to UploadListItem in the
offline branch (inside the loop over acceptedFiles where enqueue(file) and
setUploadedFiles are used), which bypasses type safety and can cause runtime
errors when UI consumes fields like media_id/job_id/hash; change the queued item
type to an explicit partial/draft type (e.g., DraftUploadItem or use
Partial<UploadListItem>) instead of as UploadListItem, update setUploadedFiles
call to store that draft type, and then update any UI rendering or consumers
that access UploadListItem fields (check for presence of result.media_id,
job_id, hash, etc.) to handle nullable/undefined values safely.
- Around line 308-334: The catch block in flush updates the IndexedDB status to
"failed" but doesn't update the uploadedFiles React state, so the UI still shows
"draft"; modify the catch in flush to call setUploadedFiles and map over
uploadedFiles to replace any entry that matches the queue item (use
item.filename and/or item.id) and has processingState === "draft" to set
processingState (or status) to "failed" (and include any failure metadata),
ensuring the UI reflects the failed state; reference the flush function,
updateStatus(item.id, "failed"), uploadedFiles and setUploadedFiles so you
update the in-memory state after the IndexedDB change.
- Around line 308-334: The flush() flow currently calls getQueue(),
updateStatus(), and dequeue() without guarding for IndexedDB errors; wrap calls
to getQueue(), each updateStatus(item.id, ...), and dequeue(item.id) in
try-catch blocks inside flush(), log errors (console.error or a user
notification) and ensure status consistency (e.g., if updateStatus(item.id,
"pending") succeeds but uploadMutation or dequeue fails, revert status back to
"draft" or "failed" as appropriate); reference the flush function and the calls
to getQueue, updateStatus, dequeue, uploadMutation.mutateAsync, and
setUploadedFiles when applying these changes.
- Around line 308-334: The flush function can run concurrently causing
double-uploads; add a guard (e.g., a module-level boolean or React ref like
isFlushingRef) checked at the start of flush and set to true before awaiting
async work, then set back to false in a finally block so concurrent invocations
(initial void flush(), online event handler, network flaps) return early while
another run is active; update the useEffect to close over the same guard so the
event listener and initial call share it and keep existing calls to getQueue,
updateStatus, uploadMutation.mutateAsync, dequeue, and setUploadedFiles
unchanged except for honoring the guard to prevent re-entrancy.
- Around line 308-334: The flush logic in useEffect (function flush) incorrectly
removes draft entries by matching filename, which can collide; add an optional
queueItemId field to the UploadListItem type, populate that queueItemId when
creating/enqueuing items in onDrop (use the id returned by the IndexedDB enqueue
call), and then in flush (after successful upload and dequeue) update
setUploadedFiles to filter by queueItemId === item.id (or remove the entry whose
queueItemId matches) instead of matching on filename and processingState; ensure
enqueue/dequeue/updateStatus calls return/accept the same item.id so the mapping
is reliable.

In `@frontend/src/lib/uploadQueue.ts`:
- Around line 26-42: The enqueue function currently builds QueueItem.id as
`${Date.now()}-${file.name}`, which can collide; update enqueue to generate a
robust unique id (e.g., call crypto.randomUUID() or append a random/counter
suffix) when constructing the QueueItem so each item saved by
tx.objectStore(STORE).put(item) cannot overwrite another; change the id
assignment inside enqueue (and any code that reads/relies on this id) to use the
new UUID-based value while keeping the rest of QueueItem fields the same.
- Around line 54-67: The updateStatus function currently resolves the Promise
inside req.onsuccess right after calling store.put(), which returns before the
IndexedDB transaction commits; change it to wait for the transaction to finish
by moving the resolve() into tx.oncomplete and add tx.onabort/tx.onerror
handlers to reject the Promise on failure (mirror the pattern used in
enqueue()/dequeue()); keep using openDb(), STORE, tx, store, req and ensure
req.onsuccess only performs the put() and does not resolve the outer Promise.

---

Nitpick comments:
In `@frontend/src/app/providers.tsx`:
- Around line 43-45: The service worker registration promise is ignored (void
navigator.serviceWorker.register("/sw.js")) so failures are silent; update the
registration call that uses navigator.serviceWorker.register to attach a .catch
handler that logs the error (e.g., via console.error or an app logger) and
optionally handle success logging; ensure the change is applied inside the same
conditional branch that checks "serviceWorker" in navigator so registration
errors are surfaced for debugging or user notification.

In `@frontend/src/app/upload/page.tsx`:
- Around line 59-61: getStatusClasses() is missing a branch for processingState
=== "draft", so draft items fall through to the default styling; add a specific
case in getStatusClasses() that checks processingState === "draft" and returns a
dedicated CSS class (e.g., "accent-badge status-draft" or similar) to match the
display returned by getDisplayStatus() and visually distinguish offline/queued
drafts from other states; update any corresponding mapping or switch inside
getStatusClasses() (referencing the function name getStatusClasses and the
processingState === "draft" condition used in getDisplayStatus) and ensure the
new class name is used in the component's JSX/CSS so the styling takes effect.
- Line 342: The check using navigator.onLine is unreliable for backend
reachability; replace the direct reference to navigator.onLine in the upload
decision (the conditional that currently uses navigator.onLine) with logic that
attempts a short backend health check or proceeds to upload and treats any
network/fetch failure as offline: call a lightweight endpoint (e.g., GET
/api/health) or try the actual upload via the existing upload handler (e.g.,
uploadFiles or handleUpload) and on any fetch/network error or non-2xx response,
call the offline enqueue function (e.g., queueFiles or enqueueUpload) and
surface a retry option in the UI; ensure failed upload promises are caught, move
the files to the offline queue, and update status so users can retry later.

In `@frontend/src/lib/uploadQueue.ts`:
- Around line 15-24: openDb() currently calls indexedDB.open() every time (used
by enqueue, getQueue, updateStatus, dequeue), which is expensive; cache a single
module-level Promise for the DB connection (e.g., let dbPromise:
Promise<IDBDatabase> | null) and have openDb() initialize that promise once and
return it for all callers so all CRUD functions reuse the same connection;
ensure the cached DB handles version upgrades/close by listening for
db.onversionchange (or clearing dbPromise on close/error) so the cache can be
replaced when needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b94e706-82cc-4128-9dad-e238d797ecec

📥 Commits

Reviewing files that changed from the base of the PR and between 6b9f935 and 4d96b28.

📒 Files selected for processing (7)
  • frontend/package.json
  • frontend/public/manifest.json
  • frontend/public/sw.js
  • frontend/src/__tests__/uploadQueue.test.ts
  • frontend/src/app/providers.tsx
  • frontend/src/app/upload/page.tsx
  • frontend/src/lib/uploadQueue.ts

Comment thread frontend/public/sw.js
Comment thread frontend/src/app/upload/page.tsx
Comment thread frontend/src/app/upload/page.tsx
Comment thread frontend/src/lib/uploadQueue.ts
Comment thread frontend/src/lib/uploadQueue.ts
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.

feat: add PWA app shell and offline upload queue for mobile

1 participant