Skip to content

fix: nexu modal should re-show on refresh but not on SPA nav#2274

Merged
lefarcen merged 1 commit intomainfrom
fix/nexu-modal-spa-nav
Mar 25, 2026
Merged

fix: nexu modal should re-show on refresh but not on SPA nav#2274
lefarcen merged 1 commit intomainfrom
fix/nexu-modal-spa-nav

Conversation

@lefarcen
Copy link
Copy Markdown
Contributor

@lefarcen lefarcen commented Mar 25, 2026

Summary

  • Replace sessionStorage with a module-level flag for tracking whether the nexu modal has been shown
  • sessionStorage persists across page refresh, which incorrectly prevented the modal from re-appearing for users who haven't checked "don't show again"
  • A module-level variable resets on page refresh (JS re-executes) but persists across SPA navigation (tab switching), which is the correct behavior

Test plan

  • Refresh the workspace page — modal should appear
  • Close modal without checking "don't show again", then switch sidebar tabs — modal should NOT re-appear
  • Refresh page again — modal should appear again
  • Check "don't show again" and close — modal should never appear again

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized modal display mechanism for improved performance with no change to user experience.

sessionStorage persists across page refresh, which prevented the modal
from re-appearing for users who haven't opted out. A module-level variable
resets on refresh but persists across SPA navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Refactored NexuModal gating mechanism from session-based storage to module-level page-load flag. Removed sessionStorage usage including SESSION_KEY constant and associated getItem/setItem checks. Modal now tracks visibility state using a page-load-scoped variable instead of browser session storage.

Changes

Cohort / File(s) Summary
Gating Mechanism Update
packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx
Replaced sessionStorage-based per-session gating with module-level hasShownThisPageLoad flag; removed SESSION_KEY constant and associated storage operations. Preserves 1000ms delay and telemetry event behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Suggested reviewers

  • Siri-Ray

Poem

🐰 A hop through code, sessionStorage cast away,
Page-load flags now reign supreme today,
No browser breadcrumbs left behind—
Modal gates with clarity refined! 🎉

🚥 Pre-merge checks | ✅ 3
✅ 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 clearly and specifically describes the main change: fixing the Nexu modal to re-show on page refresh but not during SPA navigation, which directly aligns with the core objective of replacing sessionStorage with a module-level flag.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/nexu-modal-spa-nav

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.

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

Deploying refly-branch-test with  Cloudflare Pages  Cloudflare Pages

Latest commit: f381676
Status: ✅  Deploy successful!
Preview URL: https://9ea7d654.refly-branch-test.pages.dev
Branch Preview URL: https://fix-nexu-modal-spa-nav.refly-branch-test.pages.dev

View logs

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.

🧹 Nitpick comments (3)
packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx (3)

148-148: Consider avoiding inline arrow function in onChange handler.

The inline arrow function (e) => handleNeverShowChange(e.target.checked) creates a new function reference on every render.

♻️ Proposed fix

Update handleNeverShowChange to accept the event directly:

-  const handleNeverShowChange = useCallback((checked: boolean) => {
-    setNeverShow(checked);
+  const handleNeverShowChange = useCallback((e: { target: { checked: boolean } }) => {
+    setNeverShow(e.target.checked);
   }, []);

Then simplify the JSX:

-            onChange={(e) => handleNeverShowChange(e.target.checked)}
+            onChange={handleNeverShowChange}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx` at
line 148, The inline arrow in the JSX causes a new function each render; update
the handler signature of handleNeverShowChange to accept the
ChangeEvent<HTMLInputElement> (or generic event) instead of a boolean so it can
read e.target.checked internally, then replace the JSX onChange={(e) =>
handleNeverShowChange(e.target.checked)} with onChange={handleNeverShowChange}
in NexuModal.tsx to avoid recreating the function each render and keep behavior
identical.

98-102: Consider extracting inline style objects to constants.

The inline objects style={{ maxWidth: 640 }} and styles={{ body: { padding: 0 } }} are recreated on every render.

♻️ Proposed fix
 const NEXU_URL = 'https://nexu.io';
 const STORAGE_KEY = 'nexu_modal_dismissed';
+const MODAL_STYLE = { maxWidth: 640 };
+const MODAL_BODY_STYLES = { body: { padding: 0 } };
 
 // Module-level flag: resets on page refresh, persists across SPA navigation
 let hasShownThisPageLoad = false;

Then in the JSX:

       width="fit-content"
-      style={{ maxWidth: 640 }}
+      style={MODAL_STYLE}
       className="nexu-modal"
-      styles={{
-        body: { padding: 0 },
-      }}
+      styles={MODAL_BODY_STYLES}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx`
around lines 98 - 102, Extract the inline style objects used in the NexuModal
component into stable constants to avoid recreating them each render: create
named constants (e.g., MODAL_CONTAINER_STYLE = { maxWidth: 640 } and
MODAL_BODY_STYLES = { body: { padding: 0 } }) at module scope and replace the
JSX props style and styles on the Modal/element in NexuModal.tsx with those
constants; ensure the constants are immutable (const) and referenced by the
component (NexuModal) instead of using inline objects.

76-89: Memoize the features array to prevent unnecessary re-renders.

Per coding guidelines, avoid inline object/array creation in render and use useMemo for complex object creation. The features array is recreated on every render.

♻️ Proposed fix
+  const features = useMemo(
+    () => [
+      {
+        key: 'message',
+        icon: <LuMessageSquare size={18} />,
+        text: t('nexuPromotion.modal.feature1'),
+      },
+      {
+        key: 'shield',
+        icon: <LuShield size={18} />,
+        text: t('nexuPromotion.modal.feature2'),
+      },
+      {
+        key: 'fork',
+        icon: <LuGitFork size={18} />,
+        text: t('nexuPromotion.modal.feature3'),
+      },
+    ],
+    [t],
+  );
-  const features = [
-    {
-      icon: <LuMessageSquare size={18} />,
-      text: t('nexuPromotion.modal.feature1'),
-    },
-    {
-      icon: <LuShield size={18} />,
-      text: t('nexuPromotion.modal.feature2'),
-    },
-    {
-      icon: <LuGitFork size={18} />,
-      text: t('nexuPromotion.modal.feature3'),
-    },
-  ];

Then update the map to use the stable key:

-            <div
-              key={index}
+            <div
+              key={feature.key}

As per coding guidelines: "Always use useMemo for expensive computations or complex object creation in React" and "Always use proper key props when rendering lists in React (avoid using index when possible)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx`
around lines 76 - 89, The features array in NexuModal.tsx is recreated on every
render causing unnecessary re-renders; wrap its creation in React.useMemo (e.g.,
memoize the const features = [...] block) so it returns a stable reference, and
when mapping features to elements use a stable unique key (for example derive
key from text or a fixed id on each feature) instead of the array index to
prevent key-related rendering issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx`:
- Line 148: The inline arrow in the JSX causes a new function each render;
update the handler signature of handleNeverShowChange to accept the
ChangeEvent<HTMLInputElement> (or generic event) instead of a boolean so it can
read e.target.checked internally, then replace the JSX onChange={(e) =>
handleNeverShowChange(e.target.checked)} with onChange={handleNeverShowChange}
in NexuModal.tsx to avoid recreating the function each render and keep behavior
identical.
- Around line 98-102: Extract the inline style objects used in the NexuModal
component into stable constants to avoid recreating them each render: create
named constants (e.g., MODAL_CONTAINER_STYLE = { maxWidth: 640 } and
MODAL_BODY_STYLES = { body: { padding: 0 } }) at module scope and replace the
JSX props style and styles on the Modal/element in NexuModal.tsx with those
constants; ensure the constants are immutable (const) and referenced by the
component (NexuModal) instead of using inline objects.
- Around line 76-89: The features array in NexuModal.tsx is recreated on every
render causing unnecessary re-renders; wrap its creation in React.useMemo (e.g.,
memoize the const features = [...] block) so it returns a stable reference, and
when mapping features to elements use a stable unique key (for example derive
key from text or a fixed id on each feature) instead of the array index to
prevent key-related rendering issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28188112-478a-42f8-bc30-d67b4a8ffa78

📥 Commits

Reviewing files that changed from the base of the PR and between 16ad120 and f381676.

📒 Files selected for processing (1)
  • packages/ai-workspace-common/src/components/nexu-promotion/NexuModal.tsx

@lefarcen lefarcen merged commit 330c01b into main Mar 25, 2026
4 checks passed
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