Skip to content

feat: add offline interview capability#607

Open
jthrilly wants to merge 11 commits intonew-form-systemfrom
fresco-offline
Open

feat: add offline interview capability#607
jthrilly wants to merge 11 commits intonew-form-systemfrom
fresco-offline

Conversation

@jthrilly
Copy link
Member

@jthrilly jthrilly commented Feb 3, 2026

  • Add service worker with Serwist for offline caching of dashboard and interview pages using NetworkFirst strategy
  • Add IndexedDB storage (Dexie) for offline interviews and cached protocols
  • Add Start Interview buttons to Protocols and Participants tables
  • Add protocol caching/downloading for offline use
  • Add sync status indicator in navigation bar
  • Add offline error boundary and indicator for interview sessions
  • Add offline settings section in dashboard settings
  • Add ENABLE_SW=true env variable for testing service worker in development
  • Add documentation for testing service worker changes

- Add service worker with Serwist for offline caching of dashboard and
  interview pages using NetworkFirst strategy
- Add IndexedDB storage (Dexie) for offline interviews and cached protocols
- Add Start Interview buttons to Protocols and Participants tables
- Add protocol caching/downloading for offline use
- Add sync status indicator in navigation bar
- Add offline error boundary and indicator for interview sessions
- Add offline settings section in dashboard settings
- Add ENABLE_SW=true env variable for testing service worker in development
- Add documentation for testing service worker changes
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fresco-sandbox Ready Ready Preview, Comment Feb 4, 2026 5:33am
fresco-storybook Ready Ready Preview, Comment Feb 4, 2026 5:33am

Request Review

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

- Fix UnorderedList import to use named export
- Remove unused scroll handler from EgoForm
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

Cross-origin requests (e.g., to UploadThing file storage) were being
intercepted by the service worker and failing due to CORS. Now they
pass through directly to the network using NetworkOnly strategy.

The offline system stores protocol assets in IndexedDB, not the
service worker cache, so this doesn't affect offline functionality.
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

The previous NetworkOnly approach still went through Serwist's fetch
handling which caused CORS issues with UploadThing. Now we add a
fetch event listener BEFORE Serwist that intercepts cross-origin
requests and does a direct fetch, completely bypassing Serwist.
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

- Remove defaultCache to prevent caching login/auth pages
- Scope static asset caching to dashboard/interview paths only
- Add public/sw.js to .gitignore (generated file)
- Add webpack watchOptions to ignore sw.js in dev mode
- Fixes infinite recompile loop in development
- Fixes inability to login if localStorage is cleared but SW is registered
Copilot AI review requested due to automatic review settings February 4, 2026 05:26
The proper solution is to disable Serwist in development mode (already
configured via the disable option). Only use ENABLE_SW=true when
specifically testing offline/PWA functionality.
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds comprehensive offline interview capability to Fresco, enabling users to conduct interviews without network connectivity. The implementation includes a service worker for caching, IndexedDB for local storage, offline-first UI components, and automatic data synchronization when connectivity is restored.

Changes:

  • Added service worker with Serwist for offline page caching and asset management
  • Implemented IndexedDB-based storage (Dexie) for offline interviews, cached protocols, and sync queue management
  • Added offline-capable UI components including indicators, status badges, dialogs, and error boundaries
  • Integrated offline functionality into protocols/participants tables with "Start Interview" and "Enable Offline" actions

Reviewed changes

Copilot reviewed 72 out of 75 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
lib/pwa/sw.ts Service worker implementing NetworkFirst, StaleWhileRevalidate, and CacheFirst strategies for offline support
lib/offline/db.ts IndexedDB schema using Dexie for storing offline interviews, cached protocols, assets, and sync queue
lib/offline/syncManager.ts Manages synchronization of offline interviews with retry logic and conflict detection
lib/offline/assetDownloadManager.ts Handles downloading and caching protocol assets with progress tracking
components/offline/* UI components for offline mode including indicators, badges, dialogs, and error boundaries
app/dashboard/*/_components/*Table/ActionsDropdown.tsx Integration of "Start Interview" and "Enable Offline" actions in protocols/participants tables
tests/e2e/specs/offline/*.spec.ts Comprehensive E2E tests for offline functionality including conflict resolution
package.json Added dependencies: @serwist/next, dexie, dexie-react-hooks, fake-indexeddb
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +50
useEffect(() => {
if (progress.status !== 'downloading') {
setDownloadSpeed(null);
return;
}

const now = Date.now();
const timeDiff = (now - lastTime) / 1000;
const bytesDiff = progress.downloadedBytes - lastBytes;

if (timeDiff > 0 && bytesDiff > 0) {
const speed = bytesDiff / timeDiff;
setDownloadSpeed(speed);
setLastBytes(progress.downloadedBytes);
setLastTime(now);
}
}, [progress.downloadedBytes, progress.status, lastBytes, lastTime]);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Potential memory leak in download speed tracking. The effect at lines 34-50 updates state based on progress changes but doesn't clean up when the component unmounts. If the component unmounts while a download is in progress, the state updates will continue to be attempted. Consider adding a cleanup function or using a ref to track mounted state.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +57
const [scrollProgress] = useState(0);
const [showScrollStatus] = useFlipflop(true, 7000, false);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Unused state variables detected. The variables scrollProgress and showScrollStatus are declared but never used after the handleScroll function was removed. Consider removing these unused state declarations to clean up the code.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,40 @@
export function registerServiceWorkerIfEnabled(): void {
// CRITICAL: Synchronous check BEFORE any async operations
if (!localStorage.getItem('offlineModeEnabled')) return;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The service worker registration logic should verify that offlineModeEnabled is actually stored before attempting registration. The current code only checks if the key exists in localStorage, but doesn't validate the value. Consider changing line 3 to: if (localStorage.getItem('offlineModeEnabled') !== 'true') return; to ensure the service worker only registers when explicitly enabled.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +19
const isInterview =
window.location.pathname.startsWith('/interview/');
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Race condition in service worker update detection. The check on line 18 for whether the user is on an interview page uses window.location.pathname, which may not be accurate if the URL changes between the time the event fires and when the check executes. Consider storing the pathname in a variable at the start of the event handler to avoid potential race conditions.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +22
const isValidDate = !isNaN(date.getTime());
const localisedDate = isValidDate
? new Intl.DateTimeFormat(navigator.language, dateOptions).format(date)
: 'Unknown';

const [timeAgo, setTimeAgo] = useState<string>('');

useEffect(() => {
if (!isValidDate) {
setTimeAgo('Unknown');
return;
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Invalid date handling issue. The TimeAgo component now checks for invalid dates, but if an invalid date is detected, it returns "Unknown" for both the tooltip and the display text. This could be confusing to users. Consider adding more context about why the date is invalid, or handle this at the data source level to ensure only valid dates are passed to the component.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +49
async checkStorageQuota(): Promise<{
available: number;
used: number;
total: number;
percentUsed: number;
}> {
if (!navigator.storage?.estimate) {
return { available: 0, used: 0, total: 0, percentUsed: 0 };
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Missing error handling for storage quota check. The checkStorageQuota function returns default values when navigator.storage?.estimate is unavailable, but this could lead to incorrect decisions about whether to download protocols. Consider explicitly indicating when storage quota information is unavailable, or returning an error state that calling code can handle appropriately.

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +274
resumeDownload(): void {
throw new Error(
'Resume not implemented - please restart the download from the beginning',
);
}
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The resumeDownload function throws an error indicating the feature is not implemented. Either implement resume functionality or remove this method entirely, as exposing an unimplemented public method can confuse API consumers and lead to runtime errors. If resume is planned for the future, consider marking it as deprecated or private.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🎭 Playwright E2E Test Report

Tests failed. View the full report here:

👉 https://complexdatacollective.github.io/Fresco/pr-607/

Report details

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