-
Notifications
You must be signed in to change notification settings - Fork 0
[KHP-247] feat(pwa): Add offline support with queue, cache, and status UI #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…s UI Implements offline capabilities for the PWA, including request queuing, GraphQL and ingredient data caching, and a status banner UI. Adds new modules for offline queue management, IndexedDB storage, cache utilities, and offline-aware GraphQL requests. Updates HTTP client to queue mutations when offline and replay them when back online. Integrates offline status into the UI and provides tests for offline behaviors.
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Implements comprehensive offline capabilities for the PWA, including request queuing, GraphQL/ingredient data caching, and offline status UI. The system allows users to continue working without internet connectivity by caching data locally and queueing mutations for later synchronization.
- Adds offline request queue with automatic retry when connectivity returns
- Implements GraphQL response caching with IndexedDB fallback to memory storage
- Integrates offline status banner and data source indicators throughout the UI
Reviewed Changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/graphql/src/schema/schema.graphql | Reorganizes GraphQL schema structure, moving enum definitions to the top |
| apps/pwa/src/utils/hash.ts | Adds lightweight string hashing utility for cache key generation |
| apps/pwa/src/ui/LoginForm.tsx | Integrates offline authentication persistence and data prefetching |
| apps/pwa/src/sw.ts | Updates service worker to handle mutation queuing with background sync |
| apps/pwa/src/stores/offline-store.ts | Creates Zustand store for offline status and queue management |
| apps/pwa/src/routes/protected/products.$id.history.tsx | Adds offline-aware GraphQL requests with cache fallback |
| apps/pwa/src/routes/_protected/products.$id.tsx | Implements offline data handling with ingredient snapshot caching |
| apps/pwa/src/routes/_protected/move-quantity.tsx | Adds offline support with cached data fallback and UI indicators |
| apps/pwa/src/routes/_protected/inventory.tsx | Integrates offline-aware inventory loading with status indicators |
| apps/pwa/src/routes/_protected/handle-item.tsx | Implements comprehensive offline handling for item management |
| apps/pwa/src/routes/__root.tsx | Adds offline authentication persistence and data prefetching |
| apps/pwa/src/pages/handleItem/registerLosses/RegisterLosses.tsx | Adds offline unavailable state handling for loss registration |
| apps/pwa/src/pages/handleItem/movequantity/MoveQuantity.tsx | Implements offline data indicators and fallback location handling |
| apps/pwa/src/pages/handleItem/addQuantity/HandleAddQuantity.tsx | Adds offline support with location fallback and status indicators |
| apps/pwa/src/pages/handleItem/addProduct/HandleAddProduct.tsx | Implements offline handling for product creation with fallbacks |
| apps/pwa/src/pages/ProductHistory.tsx | Adds offline data source indicators to product history |
| apps/pwa/src/pages/Product.tsx | Implements offline product page with cache indicators |
| apps/pwa/src/pages/Inventory.tsx | Adds offline status indicators to inventory listing |
| apps/pwa/src/main.tsx | Initializes offline status listeners and queue flushing on startup |
| apps/pwa/src/lib/prefetch.ts | Creates essential data prefetching for offline preparation |
| apps/pwa/src/lib/offline-status.ts | Manages offline status event listeners and queue state synchronization |
| apps/pwa/src/lib/offline-queue.ts | Implements request serialization, queuing, and replay functionality |
| apps/pwa/src/lib/offline-graphql.ts | Provides offline-aware GraphQL request wrapper with caching |
| apps/pwa/src/lib/offline-db.ts | Creates IndexedDB abstraction for offline data and request storage |
| apps/pwa/src/lib/offline-cache.ts | Implements GraphQL response caching with TTL support |
| apps/pwa/src/lib/ingredient-cache.ts | Manages ingredient snapshot caching for offline access |
| apps/pwa/src/lib/http-client.ts | Extends HTTP client with offline queue support and automatic replay |
| apps/pwa/src/lib/handleScanType.ts | Adds offline support to scan handling with cached ingredient lookups |
| apps/pwa/src/lib/tests/offline-status.test.ts | Tests offline status management and event handling |
| apps/pwa/src/lib/tests/offline-graphql.test.ts | Tests offline GraphQL request handling and caching |
| apps/pwa/src/lib/tests/http-client.offline.test.ts | Tests HTTP client offline queue functionality |
| apps/pwa/src/lib/tests/handleScanType.test.ts | Updates scan type tests for offline-aware implementation |
| apps/pwa/src/components/OfflineStatusBanner.tsx | Creates status banner showing offline state and sync progress |
| apps/pwa/src/components/Layout.tsx | Integrates offline status banner into main layout |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| const form = useForm< | ||
| z.infer<typeof registerLossesSchema>, | ||
| undefined, | ||
| z.infer<typeof registerLossesSchema> | ||
| >({ |
Copilot
AI
Sep 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generic type parameters are redundant. The second and third parameters default to the first one when not specified, so this can be simplified to useForm<z.infer<typeof registerLossesSchema>>().
| const form = useForm< | |
| z.infer<typeof registerLossesSchema>, | |
| undefined, | |
| z.infer<typeof registerLossesSchema> | |
| >({ | |
| const form = useForm<z.infer<typeof registerLossesSchema>>({ |
| const form = useForm< | ||
| z.infer<typeof handleAddQuantitySchema>, | ||
| undefined, | ||
| z.infer<typeof handleAddQuantitySchema> | ||
| >({ |
Copilot
AI
Sep 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generic type parameters are redundant. The second and third parameters default to the first one when not specified, so this can be simplified to useForm<z.infer<typeof handleAddQuantitySchema>>().
| const form = useForm< | |
| z.infer<typeof handleAddQuantitySchema>, | |
| undefined, | |
| z.infer<typeof handleAddQuantitySchema> | |
| >({ | |
| const form = useForm<z.infer<typeof handleAddQuantitySchema>>({ |
| const form = useForm< | ||
| z.infer<typeof handleItemSchema>, | ||
| undefined, | ||
| z.infer<typeof handleItemSchema> | ||
| >({ |
Copilot
AI
Sep 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generic type parameters are redundant. The second and third parameters default to the first one when not specified, so this can be simplified to useForm<z.infer<typeof handleItemSchema>>().
| const form = useForm< | |
| z.infer<typeof handleItemSchema>, | |
| undefined, | |
| z.infer<typeof handleItemSchema> | |
| >({ | |
| const form = useForm<z.infer<typeof handleItemSchema>>({ |
| function generateQueueId(): string { | ||
| if (typeof crypto !== "undefined" && "randomUUID" in crypto) { | ||
| return crypto.randomUUID(); | ||
| } | ||
| return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; |
Copilot
AI
Sep 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback ID generation using Math.random() could potentially create collisions. Consider using a more robust fallback like combining Date.now() with performance.now() or incrementing a counter to ensure uniqueness.
| function generateQueueId(): string { | |
| if (typeof crypto !== "undefined" && "randomUUID" in crypto) { | |
| return crypto.randomUUID(); | |
| } | |
| return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; | |
| let queueIdCounter = 0; | |
| function generateQueueId(): string { | |
| if (typeof crypto !== "undefined" && "randomUUID" in crypto) { | |
| return crypto.randomUUID(); | |
| } | |
| // More robust fallback: combine Date.now(), performance.now() (if available), and a counter | |
| const now = Date.now(); | |
| const perf = (typeof performance !== "undefined" && typeof performance.now === "function") | |
| ? Math.floor(performance.now()) | |
| : 0; | |
| queueIdCounter += 1; | |
| return `${now}-${perf}-${queueIdCounter}`; |
| serializedBodyForQueue, | ||
| requiresCsrf | ||
| ); | ||
| return { queued: true } as Res; |
Copilot
AI
Sep 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning { queued: true } as Res breaks type safety by forcing an unrelated type. Consider defining a union type that includes both the expected response type and a queued response type, or use a wrapper type that indicates the operation was queued.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codex Review: Here are some suggestions.
Reply with @codex fix comments to fix any unresolved comments.
About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".
| const shouldQueue = this.shouldQueueRequest(method); | ||
|
|
||
| if ( | ||
| !this.csrfToken && | ||
| [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Queue mutations without requiring online CSRF fetch
The new offline queuing assumes POST/PUT/PATCH/DELETE calls can be enqueued when the device is offline, but the request path still unconditionally calls initCSRF() whenever this.csrfToken is missing. initCSRF performs a network fetch and throws if the user is offline, which happens on a fresh session before the CSRF cookie is present. Because this exception occurs before the isLikelyOffline branch, the mutation is never queued and the user receives an error instead. A user opening the app offline for the first time therefore cannot record actions, defeating the offline support. Consider skipping the CSRF bootstrap when navigator.onLine is false and queueing the request with requiresCsrf: true so it can replay once back online.
Useful? React with 👍 / 👎.
Implements offline capabilities for the PWA, including request queuing, GraphQL and ingredient data caching, and a status banner UI. Adds new modules for offline queue management, IndexedDB storage, cache utilities, and offline-aware GraphQL requests. Updates HTTP client to queue mutations when offline and replay them when back online. Integrates offline status into the UI and provides tests for offline behaviors.