Skip to content

saves progress on forms when linking google drive account#56

Merged
InfinityBowman merged 5 commits into
mainfrom
persist-state
Dec 15, 2025
Merged

saves progress on forms when linking google drive account#56
InfinityBowman merged 5 commits into
mainfrom
persist-state

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Dec 15, 2025

Summary by CodeRabbit

  • New Features
    • Form data persistence across Google OAuth: in-progress Create Project and Add Studies forms are saved before redirect and automatically restored on return, resuming your workflow (including auto-switching to Drive when needed).
    • Pre-save hook to capture form fields and external state (name/description, selections) before connecting accounts.
    • Per-project restore support and automatic cleanup of expired saved form data on app load.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 15, 2025

Walkthrough

Adds IndexedDB-backed form-state persistence and URL restore for OAuth flows; components save state before redirect and restore on return. AddStudies hook gains serialization/restore, provider/context expose persistence hooks, Google Drive launcher awaits pre-save and builds restore callback URLs, and app startup cleans expired states.

Changes

Cohort / File(s) Summary
Form state persistence lib
packages/web/src/lib/formStatePersistence.js
New IndexedDB-backed module providing saveFormState, getFormState, clearFormState, hasPendingFormState, buildRestoreCallbackUrl, getRestoreParamsFromUrl, clearRestoreParamsFromUrl, and cleanupExpiredStates() with 24-hour expiration, deterministic keys, URL helpers, and cleanup.
App init cleanup
packages/web/src/main.jsx
Calls cleanupExpiredStates() on app load (non-blocking).
AddStudies hook serialization
packages/web/src/primitives/useAddStudies.js
Adds getSerializableState() and restoreState(savedState) plus ArrayBuffer cloning to serialize/restore uploaded PDFs, refs, lookups, selections, and Google Drive selections; exposes these from the hook.
AddStudies form & provider/context
packages/web/src/components/project-ui/AddStudiesForm.jsx, packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
AddStudiesForm restores from initialState using createEffect, supports formType, projectId, getExternalState, and onSaveState pre-save hook; provider/context expose reactive getters formType, projectId, onSaveFormState and new useFormPersistenceContext() hook.
Create project flow
packages/web/src/components/project-ui/CreateProjectForm.jsx
Restores create-project state on mount, builds restoredState passed to AddStudiesForm, and adds handleSaveState() to persist project fields + studies before OAuth; clears restore params after restore.
All Studies tab integration
packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
Detects addStudies restore params on mount, loads saved state via getFormState(), clears saved state and URL params on success, provides handleSaveState() and passes formType='addStudies', initialState, and onSaveState into AddStudiesForm.
Project dashboard URL handling
packages/web/src/components/project-ui/ProjectDashboard.jsx
Reads restore params via getRestoreParamsFromUrl() and initializes showCreateForm when restore type is createProject.
Google Drive integration
packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx, packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
GoogleDriveSection consumes useFormPersistenceContext() and forwards formType, projectId, onSaveFormState to launcher. GoogleDrivePickerLauncher awaits onSaveFormState() before connect and builds callback via buildRestoreCallbackUrl() when formType present; connect call sites adjusted accordingly.

Sequence Diagram

sequenceDiagram
    participant User
    participant Form as Project/Create Form
    participant AddStudies as AddStudiesForm / Hook
    participant Drive as GoogleDrivePickerLauncher
    participant IDB as formStatePersistence (IndexedDB)
    participant OAuth as OAuth Provider
    participant URL as Browser URL

    Note over User,Form: User fills project fields and studies
    User->>Form: enter fields & add studies
    Form->>AddStudies: update internal studies state

    Note over User,Drive: Before OAuth redirect
    User->>Drive: click "Connect Google Drive"
    Drive->>AddStudies: call onSaveFormState()
    AddStudies->>Form: request external state (getExternalState)
    Form-->>AddStudies: return external state
    AddStudies->>IDB: saveFormState(type, mergedState, projectId)
    IDB-->>Drive: confirm saved
    Drive->>URL: buildRestoreCallbackUrl(type, projectId)
    Drive->>OAuth: initiate OAuth redirect (callback includes params)

    Note over OAuth,URL: OAuth redirects back with params
    OAuth->>URL: redirect to app URL with restore params

    Note over URL,Form: On mount after redirect
    Form->>URL: getRestoreParamsFromUrl()
    URL-->>Form: restore type & projectId
    Form->>IDB: getFormState(type, projectId)
    IDB-->>Form: return saved state
    Form->>AddStudies: pass initialState
    AddStudies->>AddStudies: restore internal UI state
    Form->>URL: clearRestoreParamsFromUrl()
    Form->>IDB: clearFormState(type, projectId)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect packages/web/src/lib/formStatePersistence.js for IndexedDB transaction correctness, error handling, and expiration/cleanup logic.
  • Review serialization/deserialization and ArrayBuffer/File handling in packages/web/src/primitives/useAddStudies.js.
  • Verify restore guards (e.g., hasRestoredState) and correct clearing of URL params/state after restoration.
  • Confirm propagation and usage of formType, projectId, and onSaveFormState through context to GoogleDriveLauncher and correct callback URL construction.

Poem

🐰 I hid your form in a cozy burrow deep,

While OAuth winds blew you off to sleep.
Now back you hop, all fields aligned,
No bit lost — the state I find.
A carrot cheer for data kept — hooray! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main feature: form state persistence during Google Drive OAuth redirects across multiple form components.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch persist-state

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 15, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
corates 3e4ee03 Dec 15 2025, 10:41 PM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/web/src/primitives/useAddStudies.js (1)

949-964: Detached buffer detection could give false negatives.

The check buffer.byteLength === 0 && buffer.maxByteLength === undefined may incorrectly identify valid empty buffers as detached. However, the try-catch block handles actual detached buffer errors gracefully, so this is a minor edge case.

Consider simplifying:

   const cloneArrayBuffer = buffer => {
     if (!buffer || !(buffer instanceof ArrayBuffer)) return null;
     try {
-      // Check if buffer is detached by trying to access byteLength
-      if (buffer.byteLength === 0 && buffer.maxByteLength === undefined) {
-        return null;
-      }
       // Create a new ArrayBuffer copy
       const copy = new ArrayBuffer(buffer.byteLength);
       new Uint8Array(copy).set(new Uint8Array(buffer));
       return copy;
     } catch {
       // Buffer is likely detached
       return null;
     }
   };
packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx (1)

47-61: Consider returning getters for consistency with SolidJS reactivity patterns.

The returned object reads context values at call time, so consumers get static values when destructuring. This works for the current OAuth use case where formType and projectId don't change, but differs from useStudiesContext which returns the reactive context.studies object.

If these values need to be reactive in the future, consider returning getters:

 return {
-  formType: context.formType,
-  projectId: context.projectId,
-  onSaveFormState: context.onSaveFormState,
+  get formType() { return context.formType; },
+  get projectId() { return context.projectId; },
+  get onSaveFormState() { return context.onSaveFormState; },
 };

For now, the current implementation is acceptable given the static nature of these values per form instance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2dbf3a and 23e60b3.

📒 Files selected for processing (9)
  • packages/web/src/components/project-ui/AddStudiesForm.jsx (4 hunks)
  • packages/web/src/components/project-ui/CreateProjectForm.jsx (4 hunks)
  • packages/web/src/components/project-ui/ProjectDashboard.jsx (1 hunks)
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx (2 hunks)
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx (2 hunks)
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx (5 hunks)
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx (4 hunks)
  • packages/web/src/lib/formStatePersistence.js (1 hunks)
  • packages/web/src/primitives/useAddStudies.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features.
Use aliases for imports when appropriate to improve readability.
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx, move complex logic into separate utility files or primitives, or split large forms into section components.

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/primitives/useAddStudies.js
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
  • packages/web/src/lib/formStatePersistence.js
**/components/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/components/**/*.{jsx,tsx}: Keep component files small, focused, and modular, aiming for ~200-300 lines max per component file
When a component exceeds ~300 lines, extract sub-components into a folder (e.g., ComponentName/ with index.jsx and helper components)
Move complex logic from component files into separate utility files or primitives
In SolidJS components, access props directly from the props object or wrap them in a function instead of destructuring to maintain reactivity
Use createMemo to compute derived values based on props or state in SolidJS components

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use Solid's createStore for complex state or state objects in SolidJS components

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/primitives/useAddStudies.js
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
  • packages/web/src/lib/formStatePersistence.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis.
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file.
When you need to compute a value based on props or state in SolidJS, use createMemo to ensure it updates reactively.
For complex state or state objects in SolidJS, use Solid's createStore for better performance and reactivity instead of multiple createSignal calls.

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/primitives/useAddStudies.js
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
  • packages/web/src/lib/formStatePersistence.js
packages/web/src/components/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/components/**/*.{js,jsx,ts,tsx}: Use responsive design principles for UI components.
Group related components in subdirectories with an index.js barrel export.
Components should receive at most 1–5 props, and only for local configuration, not shared state. If a component would need more than 5 props, move the shared data into an external store, a primitive, or Solid context.
Do NOT destructure props in SolidJS components as it breaks reactivity. Instead, access props directly from the props object (e.g., props.name) or wrap them in a function (e.g., const name = () => props.name).
Components should be lean and focused. They should not implement business logic; move that into stores, utilities, or primitives.
When implementing UI components, use zag.js. Reuse existing Zag components in packages/web/src/components/zag/* as documented in that folder's README.md.

Files:

  • packages/web/src/components/project-ui/ProjectDashboard.jsx
  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
packages/web/src/primitives/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create reusable logic in 'primitives' (hooks) that can be shared across components to keep components clean and focused on rendering.

Files:

  • packages/web/src/primitives/useAddStudies.js
**/components/**/*[Ff]orm*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Split large forms into section components (see add-studies/ folder pattern)

Files:

  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
🧠 Learnings (6)
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Applies to **/components/**/*[Ff]orm*.{jsx,tsx} : Split large forms into section components (see `add-studies/` folder pattern)

Applied to files:

  • packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx
  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
📚 Learning: 2025-12-15T22:11:22.014Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-15T22:11:22.014Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : For complex state or state objects in SolidJS, use Solid's `createStore` for better performance and reactivity instead of multiple `createSignal` calls.

Applied to files:

  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
  • packages/web/src/components/project-ui/AddStudiesForm.jsx
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Applies to **/*.{jsx,tsx,js,ts} : Use Solid's `createStore` for complex state or state objects in SolidJS components

Applied to files:

  • packages/web/src/components/project-ui/CreateProjectForm.jsx
  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Create reusable logic in 'primitives' (hooks) that can be shared across SolidJS components

Applied to files:

  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Reuse existing Zag components: Checkbox, Collapsible, Dialog, FileUpload, PasswordInput, Splitter, Switch, Tabs, Toast, and Tooltip from `packages/web/src/components/zag/`

Applied to files:

  • packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Shared or cross-feature state must live in external stores under `packages/web/src/stores/` or relative to the component file

Applied to files:

  • packages/web/src/lib/formStatePersistence.js
🧬 Code graph analysis (7)
packages/web/src/components/project-ui/ProjectDashboard.jsx (1)
packages/web/src/lib/formStatePersistence.js (1)
  • getRestoreParamsFromUrl (167-177)
packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx (1)
packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx (2)
  • useStudiesContext (39-45)
  • useFormPersistenceContext (51-61)
packages/web/src/primitives/useAddStudies.js (2)
packages/web/src/lib/pdfUtils.js (2)
  • pdf (47-47)
  • pdf (184-184)
packages/web/src/lib/referenceParser.js (1)
  • ref (142-147)
packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx (2)
packages/web/src/lib/formStatePersistence.js (1)
  • buildRestoreCallbackUrl (147-161)
packages/web/src/api/google-drive.js (3)
  • connectGoogleAccount (106-132)
  • importFromGoogleDrive (49-73)
  • () => ({}) (86-86)
packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx (5)
packages/web/src/components/project-ui/CreateProjectForm.jsx (1)
  • handleSaveState (62-68)
packages/web/src/components/project-ui/ProjectDashboard.jsx (1)
  • restoreParams (16-16)
packages/web/src/lib/formStatePersistence.js (6)
  • getRestoreParamsFromUrl (167-177)
  • getFormState (79-106)
  • clearFormState (114-128)
  • clearRestoreParamsFromUrl (182-188)
  • state (137-137)
  • saveFormState (49-71)
packages/web/src/components/project-ui/AddStudiesForm.jsx (1)
  • AddStudiesForm (34-351)
packages/web/src/primitives/useProjectStudyHandlers.js (1)
  • handleAddStudies (34-234)
packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx (2)
packages/web/src/components/project-ui/ProjectContext.jsx (2)
  • value (33-46)
  • context (52-52)
packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx (1)
  • useFormPersistenceContext (15-15)
packages/web/src/lib/formStatePersistence.js (2)
packages/web/src/primitives/pdfCache.js (1)
  • record (116-124)
packages/web/src/components/project-ui/ProjectView.jsx (3)
  • state (78-112)
  • state (130-164)
  • state (115-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: corates
🔇 Additional comments (26)
packages/web/src/lib/formStatePersistence.js (5)

16-30: LGTM!

The IndexedDB initialization is correctly implemented with proper error handling and upgrade logic.


136-139: LGTM!

Simple and correct wrapper function.


147-161: LGTM!

Clean URL construction with proper parameter management.


167-177: LGTM!

Correctly parses URL parameters. The type validation happens at the call sites which is acceptable.


182-188: LGTM!

Correctly clears URL parameters without triggering navigation.

packages/web/src/components/project-ui/add-studies/GoogleDriveSection.jsx (2)

11-15: LGTM!

Context usage follows the documented pattern from AddStudiesContext.jsx.


67-80: LGTM!

Props correctly wired for OAuth state persistence flow.

packages/web/src/components/project-ui/ProjectDashboard.jsx (1)

14-19: LGTM!

The OAuth restore detection correctly initializes the form visibility. URL parameter cleanup is appropriately delegated to CreateProjectForm.

packages/web/src/components/project-ui/CreateProjectForm.jsx (4)

1-10: LGTM!

Imports are correctly organized for the persistence integration.


36-59: LGTM!

The restoration flow correctly handles state recovery and cleanup. Error handling is appropriate for this use case.


62-74: LGTM!

State serialization correctly captures both project fields and studies state.


171-180: LGTM!

Props correctly configure the persistence flow for the create project form.

packages/web/src/components/project-ui/google-drive/GoogleDrivePickerLauncher.jsx (3)

17-30: LGTM!

Import and JSDoc documentation correctly reflect the new persistence props.


54-74: LGTM!

The pre-OAuth state saving and callback URL construction are correctly implemented. State is persisted before the redirect, and errors are properly propagated.


83-86: LGTM!

The refactored connect() call correctly uses internal props for state persistence.

packages/web/src/components/project-ui/tabs/AllStudiesTab.jsx (4)

1-18: LGTM!

Imports correctly include all required persistence utilities.


29-51: LGTM!

The restoration correctly validates both form type and project ID match before restoring state. This prevents cross-project state restoration issues.


54-56: LGTM!

State saving correctly includes the project ID for proper scoping.


81-87: LGTM!

Props correctly configure the AddStudiesForm for OAuth-aware state persistence.

packages/web/src/primitives/useAddStudies.js (3)

971-1056: LGTM!

Comprehensive serialization that correctly handles ArrayBuffers, File objects, and Sets for IndexedDB storage.


1062-1134: LGTM!

State restoration correctly reconstructs all form state. The minimal file-like objects are sufficient for display purposes since PDFs are already processed.


1188-1191: LGTM!

Public API correctly extended with state persistence helpers.

packages/web/src/components/project-ui/add-studies/AddStudiesContext.jsx (1)

10-34: LGTM!

The getter pattern is consistent with the existing studies getter and follows the same pattern used in ProjectContext.jsx. The new form persistence properties are well-documented.

packages/web/src/components/project-ui/AddStudiesForm.jsx (3)

47-64: State restoration logic is well-implemented for the OAuth flow.

Good use of createEffect instead of onMount since initialState may arrive asynchronously. The hasRestoredState guard correctly prevents duplicate restoration within a single component instance.

One minor note: Line 61 hardcodes 'drive' tab, which is appropriate for the Google Drive OAuth flow. If state restoration is extended to other scenarios in the future, consider saving/restoring the active tab as part of the state.


66-76: LGTM!

The save handler correctly collects and combines internal studies state with external state before forwarding to the parent callback. Optional chaining properly handles missing callbacks.


253-258: LGTM!

The provider is correctly wired with the new form persistence props, enabling child components to access state persistence capabilities through useFormPersistenceContext.

Comment thread packages/web/src/lib/formStatePersistence.js
Comment thread packages/web/src/lib/formStatePersistence.js
Comment thread packages/web/src/lib/formStatePersistence.js
Comment thread packages/web/src/lib/formStatePersistence.js
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/web/src/main.jsx (1)

6-9: Consider optimizing cleanup frequency.

The non-blocking approach and best-effort error handling are appropriate for app initialization. However, running cleanup on every app load might be unnecessary. Consider throttling to run once per day using a localStorage timestamp to reduce redundant IndexedDB operations.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23e60b3 and 7b2fe8b.

📒 Files selected for processing (1)
  • packages/web/src/main.jsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features.
Use aliases for imports when appropriate to improve readability.
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx, move complex logic into separate utility files or primitives, or split large forms into section components.

Files:

  • packages/web/src/main.jsx
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use Solid's createStore for complex state or state objects in SolidJS components

Files:

  • packages/web/src/main.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis.
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file.
When you need to compute a value based on props or state in SolidJS, use createMemo to ensure it updates reactively.
For complex state or state objects in SolidJS, use Solid's createStore for better performance and reactivity instead of multiple createSignal calls.

Files:

  • packages/web/src/main.jsx
🧠 Learnings (1)
📚 Learning: 2025-12-15T16:38:07.301Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursorrules:0-0
Timestamp: 2025-12-15T16:38:07.301Z
Learning: Applies to **/*.{jsx,tsx,js,ts} : Use Solid's `createStore` for complex state or state objects in SolidJS components

Applied to files:

  • packages/web/src/main.jsx
🧬 Code graph analysis (1)
packages/web/src/main.jsx (1)
packages/web/src/lib/formStatePersistence.js (1)
  • cleanupExpiredStates (195-222)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: corates
🔇 Additional comments (1)
packages/web/src/main.jsx (1)

4-4: LGTM!

The import uses the alias convention appropriately and follows ES6+ best practices.

@InfinityBowman InfinityBowman merged commit 6d2fb7f into main Dec 15, 2025
1 of 3 checks passed
@InfinityBowman InfinityBowman deleted the persist-state branch December 15, 2025 22:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/web/src/lib/formStatePersistence.js (1)

16-30: Consider adding IndexedDB feature detection.

The module assumes IndexedDB is available but doesn't check for browser support. While IndexedDB is well-supported in modern browsers, adding a feature check could improve error handling for edge cases.

 function openDB() {
   return new Promise((resolve, reject) => {
+    if (!window.indexedDB) {
+      reject(new Error('IndexedDB is not supported in this browser'));
+      return;
+    }
     const request = indexedDB.open(DB_NAME, DB_VERSION);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b2fe8b and 3e4ee03.

📒 Files selected for processing (1)
  • packages/web/src/lib/formStatePersistence.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Use aliases for imports when appropriate to improve readability

**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features.
Use aliases for imports when appropriate to improve readability.
Keep files small, focused, and modular. If a file exceeds a high number of lines, extract sub-modules into a folder with index.jsx, move complex logic into separate utility files or primitives, or split large forms into section components.

Files:

  • packages/web/src/lib/formStatePersistence.js
**/*.{jsx,tsx,js,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use Solid's createStore for complex state or state objects in SolidJS components

Files:

  • packages/web/src/lib/formStatePersistence.js
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/web/src/**/*.{js,jsx,ts,tsx}: For UI icons, use the solid-icons library or SVGs only. Do not use emojis.
Do NOT prop-drill application state. Shared or cross-feature state must live in external stores under packages/web/src/stores/ or relative to the component file.
When you need to compute a value based on props or state in SolidJS, use createMemo to ensure it updates reactively.
For complex state or state objects in SolidJS, use Solid's createStore for better performance and reactivity instead of multiple createSignal calls.

Files:

  • packages/web/src/lib/formStatePersistence.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Workers Builds: corates
🔇 Additional comments (5)
packages/web/src/lib/formStatePersistence.js (5)

49-82: DB connection handling looks correct.

The transaction-level error handlers (tx.onerror and tx.onabort) properly ensure the database is closed in all code paths. The past review concern about DB connection leaks has been addressed.


140-165: DB connection handling is correct.

Transaction-level handlers ensure the database is properly closed in all cases. The past review concern has been addressed.


173-225: URL and utility functions look good.

The utility functions correctly handle form state checking and URL parameter manipulation. The use of history.replaceState to clear params without navigation is appropriate.


232-272: Cleanup function handles errors correctly.

The transaction-level error handlers ensure the database is closed even if cursor operations fail. The past review concerns about DB leaks and unhandled cursor.delete() failures have been addressed—transaction-level handlers will catch any failures during cursor iteration.


42-48: Verify data sensitivity and consider encryption.

IndexedDB stores data unencrypted in the browser's local storage. Depending on what form data is being persisted (e.g., PII, credentials, sensitive project details), this could pose a security risk.

Please verify:

  1. What types of data are being saved through this module (review call sites in CreateProjectForm.jsx, AllStudiesTab.jsx, etc.)
  2. Whether any of that data is sensitive or includes PII
  3. If sensitive data is stored, consider adding encryption before saving to IndexedDB

If sensitive data is confirmed, you may want to explore using the Web Crypto API for encryption/decryption.

Comment on lines +107 to +114
if (!record || Date.now() - record.timestamp > MAX_AGE_MS) {
if (record) {
clearFormState(type, projectId).catch(() => {});
}
result = null;
} else {
result = record.data;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error swallowing may hide cleanup failures.

Line 109 uses .catch(() => {}) to silently ignore errors from clearFormState. While this prevents cleanup failures from blocking the read operation, it could hide persistent storage issues.

Consider logging the error for debugging:

       if (!record || Date.now() - record.timestamp > MAX_AGE_MS) {
         if (record) {
-          clearFormState(type, projectId).catch(() => {});
+          clearFormState(type, projectId).catch(err => {
+            console.warn('Failed to cleanup expired form state:', err);
+          });
         }
         result = null;
🤖 Prompt for AI Agents
In packages/web/src/lib/formStatePersistence.js around lines 107-114 the code
currently swallows errors from clearFormState using .catch(() => {}) which hides
persistent storage failures; change the catch to log the error (include type and
projectId and any relevant metadata like record.timestamp) using the project’s
logger or console.error, and retain the non-throwing behavior so the read still
returns null after attempting cleanup.

@coderabbitai coderabbitai Bot mentioned this pull request Dec 16, 2025
InfinityBowman added a commit that referenced this pull request Dec 17, 2025
saves progress on forms when linking google drive account
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.

2 participants