Skip to content

Conversation

@Jhonattan2121
Copy link
Collaborator

@Jhonattan2121 Jhonattan2121 commented Aug 7, 2025

Problem

App crashed when renaming tabs with invalid characters. Unhandled throws broke React components.

Changes Made

TabBar.tsx

  • Replaced throw new Error() with console.error() + return
  • Removed AnimatePresence causing unnecessary re-renders
  • Added early return in handleTabClick for same tab

editable-text.tsx

  • Complete rewrite with proper validation
  • Added visual error feedback (red text)
  • Used useCallback for optimization
  • Prevent double processing during edits

spaceStore.ts

  • Wrapped critical operations in try/catch
  • Replaced throws with safe logging
  • Added error tooltip for users

Summary by CodeRabbit

  • New Features

    • Editable text fields now validate input and prevent invalid edits, keeping edits active until corrected.
  • Bug Fixes

    • Reduced crashes by replacing exception flows with safe early returns and visible error feedback (tooltips/logs).
    • Prevent accidental tab re-selection via improved click handling.
  • Refactor

    • Simplified and more robust tab creation, renaming, and deletion flows with clearer failure fallbacks and streamlined event handling.

sktbrd and others added 4 commits July 18, 2025 15:26
Co-authored-by: Jesse Paterson <jesse.r.pat@gmail.com>
- Replace throw statements with safe return/logging in TabBar validation
- Add comprehensive error handling and validation in editable-text component
- Wrap spaceStore operations in try/catch to prevent React crashes
- Remove AnimatePresence to fix unnecessary component re-renders
- Add visual error feedback for invalid tab names
- Optimize tab switching with early return for same tab
Copilot AI review requested due to automatic review settings August 7, 2025 06:11
@vercel
Copy link

vercel bot commented Aug 7, 2025

@Jhonattan2121 is attempting to deploy a commit to the Nounspace Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors error handling and async flows across EditableText, TabBar, and spaceStore: input validation guards prevent invalid edits, tab operations switch from throwing to logging and early returns with fallbacks, and store functions stop throwing on validation failures.

Changes

Cohort / File(s) Change Summary
EditableText
src/common/components/atoms/editable-text.tsx
Added input validation in onEditEnd (rejects chars matching /[^a-zA-Z0-9-_ ]/), reverts to original text and logs warning on invalid input; preserves existing exit/update behavior for valid input. No public API signature changes.
TabBar (refactor)
src/common/components/organisms/TabBar.tsx
Removed AnimatePresence/useMobilePreview, replaced throws with console.error + early returns, added fallback timestamp name, rewrote create/delete/rename handlers to use try/catch and async commits (setTimeout), avoid redundant tab switches, ignore clicks on current tab, changed tab key to prefixed string. Some internal functions now may return `string
Space store (error-handling adjustments)
src/common/data/stores/app/space/spaceStore.ts
Validation failures no longer throw: show tooltip/log and return early; createSpaceTab substitutes fallback name on validation failure; commit/save flows wrapped to avoid propagating exceptions. No exported API signature changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditableText
    participant UpdateMethod

    User->>EditableText: Enter edit, submit text
    EditableText->>EditableText: Validate (/[^a-zA-Z0-9-_ ]/)
    alt Invalid
        EditableText-->>User: Revert text, console.warn, keep edit mode
    else Valid
        EditableText->>UpdateMethod: updateMethod(initialText, text)
        UpdateMethod-->>EditableText: Success / Error
        EditableText-->>User: Exit edit mode or show error
    end
Loading
sequenceDiagram
    participant User
    participant TabBar
    participant SpaceStore

    User->>TabBar: Create / Rename / Delete tab
    TabBar->>TabBar: Sanitize & validate name
    alt Validation fails
        TabBar-->>User: console.error / tooltip, use fallback or abort
    else Valid
        TabBar->>SpaceStore: update local tab state (order/name)
        TabBar->>SpaceStore: commit asynchronously (setTimeout)
        SpaceStore-->>TabBar: commit result (may fail silently)
        TabBar-->>User: update UI (switch tab / remove tab)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Possibly related issues

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • j-paterson
  • r4topunk

Poem

A rabbit hopped through bits and names,
Fixed the trims and tamed the flames.
Tabs no longer crash or stall,
Invalid chars bounce off the wall.
Async commits, a gentle thrum—code hummed along, all snug and calm. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2892c92 and e84d9e3.

📒 Files selected for processing (2)
  • src/common/components/atoms/editable-text.tsx (1 hunks)
  • src/common/data/stores/app/space/spaceStore.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/common/data/stores/app/space/spaceStore.ts
  • src/common/components/atoms/editable-text.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

This PR fixes a critical app crash issue when renaming tabs with invalid characters by replacing error-throwing mechanisms with graceful error handling and improved user feedback.

  • Replaced throw new Error() with console.error() and early returns to prevent React component crashes
  • Added comprehensive error handling with try/catch blocks around critical operations
  • Implemented visual error feedback and improved validation in the editable text component

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
spaceStore.ts Added try/catch blocks and replaced throws with safe error logging
TabBar.tsx Removed AnimatePresence, added error handling in tab operations, and early returns for validation
editable-text.tsx Complete rewrite with proper validation, visual error feedback, and useCallback optimization

Comment on lines +80 to 82
return uniqueName || `Tab ${Date.now()}`; // Fallback to timestamp if unique name fails
}

Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

Using Date.now() as a fallback tab name may create confusing user experience. Consider a more user-friendly fallback like 'Untitled Tab' or incrementing a counter.

Suggested change
return uniqueName || `Tab ${Date.now()}`; // Fallback to timestamp if unique name fails
}
return uniqueName || generateUniqueUntitledTabName();
}
function generateUniqueUntitledTabName(): string {
let base = "Untitled Tab";
let name = base;
let iter = 1;
while (tabList.includes(name)) {
name = `${base} - ${iter}`;
iter += 1;
}
return name;
}

Copilot uses AI. Check for mistakes.
switchTabTo(nextTab);

// Delete the tab
deleteTab(tabName);
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

The deleteTab function is called without await, but based on the original code it appears to be an async operation. This could lead to race conditions or incomplete deletion.

Suggested change
deleteTab(tabName);
await deleteTab(tabName);

Copilot uses AI. Check for mistakes.
}

// Simple rename without complex rollback logic
renameTab(tabName, uniqueName);
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

The renameTab function is called without await, but it's likely an async operation that should be awaited to ensure completion before proceeding.

Copilot uses AI. Check for mistakes.
console.warn("Invalid characters in tab name");
setHasError(true);
settext(initialText); // Reset to original text
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

The magic number 3000 is used for error timeout. Consider defining this as a named constant like ERROR_DISPLAY_DURATION = 3000 to improve maintainability.

Suggested change
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
setTimeout(() => setHasError(false), ERROR_DISPLAY_DURATION); // Clear error after 3s

Copilot uses AI. Check for mistakes.
console.error("Failed to update tab name:", error);
setHasError(true);
settext(initialText); // Reset to original text on error
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

Duplicate magic number 3000 for error timeout. This should be extracted to a constant to maintain consistency.

Suggested change
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
setTimeout(() => setHasError(false), ERROR_TIMEOUT_MS); // Clear error after 3s

Copilot uses AI. Check for mistakes.
@vercel
Copy link

vercel bot commented Aug 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
nounspace-ts ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 8, 2025 7:12pm

Copy link
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: 5

🧹 Nitpick comments (2)
src/common/data/stores/app/space/spaceStore.ts (1)

189-223: Replace DOM manipulation with React-based error handling.

The current implementation directly manipulates the DOM, which bypasses React's rendering system and could cause memory leaks or conflicts. Consider using React's state management and portal system for error notifications.

This approach has several issues:

  1. Memory leaks if the component unmounts before timeout
  2. Multiple errors will overlap at the same position
  3. Bypasses React's reconciliation

Consider using a proper toast/notification library like react-hot-toast or implementing a React-based notification system with portals.

src/common/components/organisms/TabBar.tsx (1)

131-133: Consider potential race conditions with background commits.

Using setTimeout for background commits could cause race conditions if users perform rapid actions. The commit might also fail silently.

Consider using a more robust queueing mechanism or at least tracking pending commits to prevent conflicts. You could also add error handling for the background commit:

      // Commit in background
      setTimeout(() => {
-       commitTabOrder();
+       try {
+         commitTabOrder();
+       } catch (error) {
+         console.error("Failed to commit tab order:", error);
+       }
      }, 100);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 940abde and 2892c92.

📒 Files selected for processing (3)
  • src/common/components/atoms/editable-text.tsx (1 hunks)
  • src/common/components/organisms/TabBar.tsx (5 hunks)
  • src/common/data/stores/app/space/spaceStore.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: willyogo
PR: Nounspace/nounspace.ts#1118
File: src/common/fidgets/FidgetWrapper.tsx:160-175
Timestamp: 2025-06-05T19:55:02.560Z
Learning: In the Nounspace.ts codebase, fidget edit mode icons in FidgetWrapper.tsx require an extremely high z-index (999999) to prevent being covered by the tab bar. Lower z-index values like 1050 break this fix, indicating other UI elements use very high z-index values.
📚 Learning: in the nounspace.ts codebase, fidget edit mode icons in fidgetwrapper.tsx require an extremely high ...
Learnt from: willyogo
PR: Nounspace/nounspace.ts#1118
File: src/common/fidgets/FidgetWrapper.tsx:160-175
Timestamp: 2025-06-05T19:55:02.560Z
Learning: In the Nounspace.ts codebase, fidget edit mode icons in FidgetWrapper.tsx require an extremely high z-index (999999) to prevent being covered by the tab bar. Lower z-index values like 1050 break this fix, indicating other UI elements use very high z-index values.

Applied to files:

  • src/common/components/organisms/TabBar.tsx
📚 Learning: in the nounspace.ts codebase, there is an internal phoneframe svg component at src/common/components...
Learnt from: j-paterson
PR: Nounspace/nounspace.ts#1279
File: src/app/(spaces)/Space.tsx:415-421
Timestamp: 2025-07-12T00:20:33.439Z
Learning: In the Nounspace.ts codebase, there is an internal PhoneFrame SVG component at src/common/components/atoms/PhoneFrame.tsx that should be used for mobile mockups instead of external image URLs. The component provides a scalable 344x744 phone frame with proper styling.

Applied to files:

  • src/common/components/organisms/TabBar.tsx
🔇 Additional comments (4)
src/common/data/stores/app/space/spaceStore.ts (1)

337-343: Merge behavior is intentional and documented

The custom mergeWith logic in spaceStore.ts mirrors its usage in:

  • src/common/data/stores/app/homebase/homebaseStore.ts
  • src/common/data/stores/app/homebase/homebaseTabsStore.ts

Each instance includes an inline comment explaining that nested objects are fully replaced. No code changes are required—this override is by design. If your configuration objects ever require true deep-merge behavior, consider introducing a specialized merge utility at that time.

src/common/components/organisms/TabBar.tsx (3)

76-81: Good error handling with timestamp fallback!

The fallback to timestamp when unique name generation fails is a robust solution that prevents crashes.


178-181: Good null handling for unique name generation!

Properly checking for null return from generateUniqueTabName prevents potential crashes.


228-231: Good optimization for current tab clicks!

Preventing unnecessary operations when clicking the already-selected tab improves performance and prevents potential state inconsistencies.

return event.key === "Enter" || event.key === "Escape";
}

const EditableText = ({ initialText, updateMethod }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add TypeScript type definitions for component props.

The component accepts props without type definitions, which reduces type safety and could lead to runtime errors.

Add proper TypeScript types:

-const EditableText = ({ initialText, updateMethod }) => {
+interface EditableTextProps {
+  initialText: string;
+  updateMethod?: (oldText: string, newText: string) => Promise<void> | void;
+}
+
+const EditableText: React.FC<EditableTextProps> = ({ initialText, updateMethod }) => {
📝 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
const EditableText = ({ initialText, updateMethod }) => {
interface EditableTextProps {
initialText: string;
updateMethod?: (oldText: string, newText: string) => Promise<void> | void;
}
const EditableText: React.FC<EditableTextProps> = ({ initialText, updateMethod }) => {
🤖 Prompt for AI Agents
In src/common/components/atoms/editable-text.tsx at line 7, the EditableText
component props lack TypeScript type definitions, reducing type safety. Define
an interface or type for the props specifying the types of initialText and
updateMethod, then annotate the component's props parameter with this type to
ensure proper type checking.

Comment on lines 26 to 30
try {
// Don't update if text hasn't changed
if (text === initialText) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Reset processing state when text hasn't changed.

When the text hasn't changed, the function returns early without resetting isProcessing, which will leave the component stuck in a processing state.

      // Don't update if text hasn't changed
      if (text === initialText) {
+       setIsProcessing(false);
        return;
      }
🤖 Prompt for AI Agents
In src/common/components/atoms/editable-text.tsx around lines 26 to 30, the
function returns early when the text hasn't changed but does not reset the
isProcessing state, causing the component to remain stuck in processing. Modify
the code to reset isProcessing to false before returning early to ensure the
component exits the processing state properly.

Comment on lines 32 to 37
// Add basic validation
if (!text || text.trim().length === 0) {
console.warn("Tab name cannot be empty");
settext(initialText); // Reset to original text
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Set error state for empty tab name validation.

The empty string validation doesn't set the error state, unlike the character validation. This creates inconsistent user feedback.

      // Add basic validation
      if (!text || text.trim().length === 0) {
        console.warn("Tab name cannot be empty");
+       setHasError(true);
        settext(initialText); // Reset to original text
+       setTimeout(() => setHasError(false), 3000); // Clear error after 3s
+       setIsProcessing(false);
        return;
      }
📝 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
// Add basic validation
if (!text || text.trim().length === 0) {
console.warn("Tab name cannot be empty");
settext(initialText); // Reset to original text
return;
}
// Add basic validation
if (!text || text.trim().length === 0) {
console.warn("Tab name cannot be empty");
setHasError(true);
settext(initialText); // Reset to original text
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
setIsProcessing(false);
return;
}
🤖 Prompt for AI Agents
In src/common/components/atoms/editable-text.tsx around lines 32 to 37, the
validation for empty tab names only logs a warning and resets the text but does
not set the error state, causing inconsistent user feedback compared to
character validation. Update the code to set the error state appropriately when
the tab name is empty, ensuring consistent error handling and user feedback.

Comment on lines 39 to 48
const trimmedText = text.trim();

const onEditEnd = () => {
// Validate characters
if (/[^a-zA-Z0-9-_ ]/.test(trimmedText)) {
console.warn("Invalid characters in tab name");
setHasError(true);
settext(initialText); // Reset to original text
setTimeout(() => setHasError(false), 3000); // Clear error after 3s
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enforce maxLength validation in the validation logic.

The input has a maxLength of 22, but this isn't enforced in the validation logic. Users could bypass this through programmatic means.

      const trimmedText = text.trim();
+
+     // Validate length
+     if (trimmedText.length > 22) {
+       console.warn("Tab name exceeds maximum length");
+       setHasError(true);
+       settext(initialText);
+       setTimeout(() => setHasError(false), 3000);
+       setIsProcessing(false);
+       return;
+     }

      // Validate characters
      if (/[^a-zA-Z0-9-_ ]/.test(trimmedText)) {

Also applies to: 96-96

🤖 Prompt for AI Agents
In src/common/components/atoms/editable-text.tsx around lines 39 to 48 and line
96, the validation logic currently checks for invalid characters but does not
enforce the maxLength constraint of 22 characters. To fix this, add a condition
to check if trimmedText.length exceeds 22 and handle it similarly by setting an
error state, resetting the text to initialText, and clearing the error after 3
seconds. This ensures the maxLength is enforced even if users bypass the input
attribute programmatically.

Comment on lines 298 to 302
} catch (e) {
console.error("Failed to commit space tab to remote:", e);
// Don't throw the error to prevent app crashes
// The tab is still saved locally, so the user can continue working
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Notify users when remote save fails.

When the remote commit fails, the error is only logged to console. Users should be notified that their changes are saved locally but not synced to the server.

        } catch (e) {
          console.error("Failed to commit space tab to remote:", e);
+         // Notify user that changes are saved locally but not synced
+         showTooltipError(
+           "Sync Failed",
+           "Your changes are saved locally but couldn't sync to the server. They'll sync automatically later."
+         );
          // Don't throw the error to prevent app crashes
          // The tab is still saved locally, so the user can continue working
        }
🤖 Prompt for AI Agents
In src/common/data/stores/app/space/spaceStore.ts around lines 298 to 302, the
catch block only logs the error to the console when the remote commit fails.
Modify this block to also notify the user through the UI or a notification
system that their changes are saved locally but not synced to the server,
ensuring they are aware of the sync failure without throwing an error.

@Jhonattan2121 Jhonattan2121 reopened this Aug 8, 2025
@Jhonattan2121 Jhonattan2121 marked this pull request as ready for review August 12, 2025 20:14
@willyogo willyogo requested a review from j-paterson August 12, 2025 20:31
@j-paterson j-paterson merged commit f2867cd into blankdotspace:canary Aug 18, 2025
4 checks passed
j-paterson added a commit that referenced this pull request Aug 25, 2025
Co-authored-by: xvlad <116202536+sktbrd@users.noreply.github.com>
Co-authored-by: Jesse Paterson <jesse.r.pat@gmail.com>
This was referenced Oct 10, 2025
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.

3 participants