Skip to content

Conversation

@DestroyCom
Copy link
Member

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.

…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.
@DestroyCom
Copy link
Member Author

@codex review

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

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.

Comment on lines +67 to +71
const form = useForm<
z.infer<typeof registerLossesSchema>,
undefined,
z.infer<typeof registerLossesSchema>
>({
Copy link

Copilot AI Sep 23, 2025

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>>().

Suggested change
const form = useForm<
z.infer<typeof registerLossesSchema>,
undefined,
z.infer<typeof registerLossesSchema>
>({
const form = useForm<z.infer<typeof registerLossesSchema>>({

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +101
const form = useForm<
z.infer<typeof handleAddQuantitySchema>,
undefined,
z.infer<typeof handleAddQuantitySchema>
>({
Copy link

Copilot AI Sep 23, 2025

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>>().

Suggested change
const form = useForm<
z.infer<typeof handleAddQuantitySchema>,
undefined,
z.infer<typeof handleAddQuantitySchema>
>({
const form = useForm<z.infer<typeof handleAddQuantitySchema>>({

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +109
const form = useForm<
z.infer<typeof handleItemSchema>,
undefined,
z.infer<typeof handleItemSchema>
>({
Copy link

Copilot AI Sep 23, 2025

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>>().

Suggested change
const form = useForm<
z.infer<typeof handleItemSchema>,
undefined,
z.infer<typeof handleItemSchema>
>({
const form = useForm<z.infer<typeof handleItemSchema>>({

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +47
function generateQueueId(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
return crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
Copy link

Copilot AI Sep 23, 2025

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.

Suggested change
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}`;

Copilot uses AI. Check for mistakes.
serializedBodyForQueue,
requiresCsrf
);
return { queued: true } as Res;
Copy link

Copilot AI Sep 23, 2025

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.

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a 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".

Comment on lines +455 to 459
const shouldQueue = this.shouldQueueRequest(method);

if (
!this.csrfToken &&
[

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 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants