Skip to content

:fix: prevent form prefilling when creating new Blueprint during acti…#289

Open
wryonik wants to merge 2 commits intostagingfrom
shubham/reg-609-new-blueprint-opens-with-old-data-while-generation-is-in
Open

:fix: prevent form prefilling when creating new Blueprint during acti…#289
wryonik wants to merge 2 commits intostagingfrom
shubham/reg-609-new-blueprint-opens-with-old-data-while-generation-is-in

Conversation

@wryonik
Copy link
Copy Markdown
Collaborator

@wryonik wryonik commented Jan 5, 2026

…ve generation

When navigating to create a new Blueprint while another generation was in progress, the form would be prefilled with data from the previous Blueprint. This was caused by a race condition where the zustand store's reset() was called before the persist middleware finished rehydrating from IndexedDB, causing the hydrated state to overwrite the reset.

Added hydration tracking to the create blueprint store:

  • Added hasHydrated state and onRehydrateStorage callback
  • Updated useEffect to wait for hydration before calling reset()
  • Added loader during hydration to prevent flash of stale content

Fixes #273

Summary

Type of Change

  • Bug fix (hotfix)
  • New feature
  • Refactor
  • Documentation
  • Other (please describe)

Target Branch

  • dev - New feature or non-urgent fix
  • staging - Ready for QA validation
  • main - Hotfix for production issue

Changes Made

Breaking Changes

Testing

  • Tested locally
  • Added/updated tests
  • Verified in development environment

Checklist

  • Code follows the project's style guidelines
  • Self-review completed
  • Comments added for complex code sections
  • Documentation updated if needed
  • No new warnings generated

Screenshots/Recordings

Related Issues

Additional Notes

Summary by CodeRabbit

  • Bug Fixes
    • Fixed race condition in create blueprint workflow that could cause state inconsistencies during page load by ensuring initialization operations respect component hydration timing.

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

…ve generation

When navigating to create a new Blueprint while another generation was in
progress, the form would be prefilled with data from the previous Blueprint.
This was caused by a race condition where the zustand store's reset() was
called before the persist middleware finished rehydrating from IndexedDB,
causing the hydrated state to overwrite the reset.

Added hydration tracking to the create blueprint store:
- Added hasHydrated state and onRehydrateStorage callback
- Updated useEffect to wait for hydration before calling reset()
- Added loader during hydration to prevent flash of stale content

Fixes #273
@wryonik wryonik requested review from BornPsych and rutefig January 5, 2026 08:14
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 5, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Introduced hydration guards to the create blueprint store and page component to prevent state race conditions. Added hasHydrated flag to track store rehydration completion, gating lifecycle effects until after persistent state restores from storage.

Changes

Cohort / File(s) Summary
Store Hydration State Management
src/app/create/[id]/store.ts
Added hasHydrated boolean field and setHasHydrated function to track store rehydration status. Configured onRehydrateStorage handler to set flag true after persistence rehydration completes. Excluded both fields from persistent storage layer.
Component Hydration-Gated Effects
src/app/create/[id]/page.tsx
Extracted storeHasHydrated from store and added conditional checks in reset/load effects to skip execution until hydration completes. Updated effect dependency arrays to include hydration state, ensuring effects retrigger when hydration status changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • BornPsych

Poem

🐰 A hydration hop, so clean and pure,
No stale old states will now endure,
We wait for storage, then proceed,
Fresh blueprints bloom from careful seed!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing form prefilling when creating a new Blueprint during active generation, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed All coding requirements from issue #273 are met: hydration tracking was added to the store, useEffect waits for hydration before reset, and a loader prevents stale content display.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the race condition between store reset and hydration; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@rutefig
Copy link
Copy Markdown
Member

rutefig commented Jan 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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

🤖 Fix all issues with AI agents
In `@src/app/create/`[id]/store.ts:
- Around line 345-348: onRehydrateStorage currently uses optional chaining and
silently skips setting hydration flag when the persisted state is missing or
malformed; replace that with an explicit guard: if the incoming state is
undefined or falsy, call the store's setter to mark hasHydrated true via the
current store API (so hasHydrated is set even on rehydration failure), otherwise
call state.setHasHydrated(true); follow the defensive pattern used in
useAuthStore and reference onRehydrateStorage and setHasHydrated when making the
change.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7131f6a and ffe1c91.

📒 Files selected for processing (2)
  • src/app/create/[id]/page.tsx
  • src/app/create/[id]/store.ts
🔇 Additional comments (5)
src/app/create/[id]/store.ts (2)

28-29: LGTM! Hydration state tracking looks correct.

The type definitions and initial state setup correctly implement hydration tracking. The hasHydrated flag starts as false and will be set to true via onRehydrateStorage after the store rehydrates from IndexedDB.

Also applies to: 81-82


342-344: LGTM! Correctly excludes hydration state from persistence.

Excluding hasHydrated and setHasHydrated from persistence ensures these runtime-only fields don't get stored in IndexedDB, which would defeat the purpose of tracking hydration status.

src/app/create/[id]/page.tsx (3)

51-52: LGTM! Good aliasing to avoid confusion.

Renaming the store's hasHydrated to storeHasHydrated clearly distinguishes it from the auth store's hasHydrated (line 38), improving code readability.


122-150: LGTM! Hydration guard correctly prevents the race condition.

The early return when !storeHasHydrated ensures that reset() is only called after the persist middleware has finished rehydrating from IndexedDB. This directly addresses the root cause: previously, reset() would run immediately, but then the late hydration would overwrite the reset state with stale data.

The updated dependency array [id, storeHasHydrated] ensures the effect re-runs once hydration completes, which will then properly execute the reset or load logic.


454-461: LGTM! Dual hydration check prevents flash of stale content.

Checking both hasHydrated (auth store) and storeHasHydrated (create blueprint store) before rendering ensures the component doesn't briefly show stale form data while either store is rehydrating.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +345 to +348
onRehydrateStorage: () => (state) => {
// This is called after the store has been rehydrated from storage
state?.setHasHydrated(true);
},
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jan 15, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the store file to understand the context
fd "store.ts" | head -20

Repository: zkemail/registry

Length of output: 288


🏁 Script executed:

# Let's examine the file structure
git ls-files "src/app/create" | head -20

Repository: zkemail/registry

Length of output: 318


🏁 Script executed:

# Let's read the store.ts file to see the code around lines 345-348
wc -l src/app/create/[id]/store.ts

Repository: zkemail/registry

Length of output: 92


🏁 Script executed:

# Read the file content around the specified lines
sed -n '330,360p' src/app/create/[id]/store.ts

Repository: zkemail/registry

Length of output: 992


🏁 Script executed:

# Let's see more context about the store initialization and state structure
sed -n '1,100p' src/app/create/[id]/store.ts

Repository: zkemail/registry

Length of output: 3240


🏁 Script executed:

# Check if there are other stores using onRehydrateStorage for comparison
rg "onRehydrateStorage" --type ts -B 2 -A 5

Repository: zkemail/registry

Length of output: 958


🏁 Script executed:

# Let's check the useAuthStore implementation for comparison
cat -n src/lib/stores/useAuthStore.ts

Repository: zkemail/registry

Length of output: 2142


🏁 Script executed:

# Search for how hasHydrated is used in the codebase
rg "hasHydrated" --type tsx --type ts -B 2 -A 2

Repository: zkemail/registry

Length of output: 87


🏁 Script executed:

# Search for hasHydrated usage without specifying file type
rg "hasHydrated" -B 2 -A 2

Repository: zkemail/registry

Length of output: 2971


🏁 Script executed:

# Check how hasHydrated is actually used in components
fd -e tsx -e ts | xargs rg "hasHydrated"

Repository: zkemail/registry

Length of output: 816


🌐 Web query:

Zustand persist middleware onRehydrateStorage state undefined behavior

💡 Result:

Short answer: onRehydrateStorage can receive undefined when there is no saved state (or when stored data fails to parse/version-check/migrate). Also beware that the persisted JSON only contains serializable values — functions/actions are not stored — and the default merge is a shallow merge, so a persisted object can overwrite initial fields (including replacing objects that held methods). Handle both cases by checking for undefined and reattaching any functions or defaults in the after-rehydrate callback or using merge/migrate/versioning.

Key points and examples:

  • The persist docs describe onRehydrateStorage's signature and show it may be called with state (or nothing) and that it should return a post‑hydrate function. Use the returned function to update store flags or reattach things. [1][2]
  • If nothing was persisted (or version/migrate rejects the stored value), the callback will get undefined — check for that before using state. [1][2]
  • Non-serializable values (functions) are not saved; actions must come from the initial creator or be restored/rewired after hydration. Otherwise calling them will be undefined after rehydration. See common reports and notes in community threads. [3]
  • If you need to preserve methods or complex objects, either store only plain data and recreate methods after rehydrate, or use persist options like merge/migrate/version/partialize to control what’s saved and how it’s merged. [1][2][4]

References:

  • Zustand persist middleware docs (options, onRehydrateStorage). [1]
  • Zustand “Persisting store data” examples including onRehydrateStorage usage. [2]
  • Community/StackOverflow discussion showing functions/actions omitted on hydration. [3]
  • Implementation/merge notes showing shallow-merge behavior and rehydrate flow. [4]

If you want, tell me your store code and storage contents and I’ll point exactly where to add undefined checks / restore logic.


Align with existing codebase pattern and add fallback for rehydration failures.

The state parameter in onRehydrateStorage can be undefined if there's no saved state or if storage parsing fails. The current optional chaining silently fails, leaving hasHydrated as false indefinitely. The sibling useAuthStore in the codebase uses an explicit guard pattern, which is more defensive.

Consider:

      onRehydrateStorage: () => (state) => {
        // This is called after the store has been rehydrated from storage
-        state?.setHasHydrated(true);
+        if (state) {
+          state.setHasHydrated(true);
+        } else {
+          useCreateBlueprintStore.setState({ hasHydrated: true });
+        }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onRehydrateStorage: () => (state) => {
// This is called after the store has been rehydrated from storage
state?.setHasHydrated(true);
},
onRehydrateStorage: () => (state) => {
// This is called after the store has been rehydrated from storage
if (state) {
state.setHasHydrated(true);
} else {
useCreateBlueprintStore.setState({ hasHydrated: true });
}
},
🤖 Prompt for AI Agents
In `@src/app/create/`[id]/store.ts around lines 345 - 348, onRehydrateStorage
currently uses optional chaining and silently skips setting hydration flag when
the persisted state is missing or malformed; replace that with an explicit
guard: if the incoming state is undefined or falsy, call the store's setter to
mark hasHydrated true via the current store API (so hasHydrated is set even on
rehydration failure), otherwise call state.setHasHydrated(true); follow the
defensive pattern used in useAuthStore and reference onRehydrateStorage and
setHasHydrated when making the change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@wryonik can you check this?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@wryonik
Copy link
Copy Markdown
Collaborator Author

wryonik commented Jan 15, 2026

Fixed

Base automatically changed from dev to staging April 8, 2026 15:56
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