Skip to content

Conversation

@roomote
Copy link
Contributor

@roomote roomote bot commented Sep 10, 2025

This PR adds a new DismissibleUpsell component that allows displaying dismissible messages/upsells to users that will never be shown again once dismissed.

Changes

  • Created DismissibleUpsell React component with variant support (banner/default)
  • Added dismissedUpsells field to GlobalState for persistent storage
  • Implemented message handlers for dismissing and retrieving dismissed upsells
  • Added comprehensive unit tests for the component

Features

  • Wrapper component that accepts children
  • "banner" variant with VSCode blue background and white text
  • Required className prop for unique identification
  • X icon button for dismissal with proper accessibility
  • Uses VSCode extension globalState for persistent storage
  • Automatically hides if previously dismissed

Testing

  • 8 comprehensive unit tests covering all functionality
  • Tests pass successfully
  • Linting and type checking pass

Usage Example

<DismissibleUpsell className="welcome-message" variant="banner">
  <p>Welcome to Roo Code! Check out our new features.</p>
</DismissibleUpsell>

Closes the request for creating a dismissible upsell component.


Important

Introduces DismissibleUpsell component for persistent dismissible messages with global state management and comprehensive testing.

  • Behavior:
    • Adds DismissibleUpsell component in DismissibleUpsell.tsx for dismissible messages with banner and default variants.
    • Uses dismissedUpsells in global-settings.ts for persistent dismissal state.
    • Handles dismissUpsell and getDismissedUpsells in webviewMessageHandler.ts for managing dismissals.
  • Testing:
    • Adds unit tests in DismissibleUpsell.spec.tsx covering rendering, dismissal, and edge cases.
  • Misc:
    • Updates ExtensionMessage.ts and WebviewMessage.ts to include new message types for upsell management.

This description was created by Ellipsis for e5e8005. You can customize this summary. It will automatically update as commits are pushed.

- Created DismissibleUpsell component with variant support (banner/default)
- Added dismissedUpsells to GlobalState for persistence
- Implemented message handlers for dismissing and retrieving dismissed upsells
- Added comprehensive tests for the component
- Uses VSCode extension globalState for persistent storage
@roomote roomote bot requested review from cte, jr and mrubens as code owners September 10, 2025 06:38
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Sep 10, 2025

interface DismissibleUpsellProps {
/** Required unique identifier for this upsell */
className: string
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using a dedicated prop (e.g. 'upsellId') for the unique identifier instead of overloading the 'className' prop, which is typically used for styling.

<DismissButton
$variant={variant}
onClick={handleDismiss}
aria-label="Dismiss"
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace hardcoded accessibility strings with translation keys using the i18n function to ensure localization.

Suggested change
aria-label="Dismiss"
aria-label={i18n('dismiss')}

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

Copy link
Contributor Author

@roomote roomote bot left a comment

Choose a reason for hiding this comment

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

Reviewing my own code is like debugging in a mirror - everything looks backwards but the bugs are still mine.


window.addEventListener("message", handleMessage)
return () => window.removeEventListener("message", handleMessage)
}, [className])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Potential memory leak here. The event listener cleanup in the useEffect return function might not execute if the component unmounts while waiting for the initial response. Consider using a mounted flag to prevent state updates after unmount.


// Call the optional callback
onDismiss?.()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Race condition: If the component unmounts immediately after clicking dismiss but before the message is sent, the dismissal won't be persisted. Consider ensuring the message is sent before hiding the component.


interface DismissibleUpsellProps {
/** Required unique identifier for this upsell */
className: string
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using className as a unique identifier is unconventional and could be confusing. The className prop is typically used for CSS styling. Consider adding a dedicated id prop instead.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is wrong. There should be two different attributes - className and name.

// Send updated list back to webview
await provider.postMessageToWebview({
type: "dismissedUpsells",
list: [...dismissedUpsells, message.upsellId],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Inefficient array operations - you're spreading the dismissedUpsells array twice unnecessarily. The updatedList already contains all items, so just use: list: updatedList


break
}
case "dismissUpsell": {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missing error handling. What happens if updateGlobalState fails? Consider wrapping in try-catch and logging any errors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's OK to fail silently in this case

@hannesrudolph hannesrudolph added the Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. label Sep 10, 2025
@brunobergher
Copy link
Collaborator

@roomote-agent read the comments and apply relevant feedback.

@roomote
Copy link
Contributor Author

roomote bot commented Sep 10, 2025

👋 Hi @brunobergher! I'm reviewing the feedback and will apply the relevant changes. I'll address the following points:

  1. Change className to a dedicated prop - Will add a separate 'id' or 'name' prop for the unique identifier instead of using className
  2. Add i18n support - Will use translation keys for accessibility labels
  3. Fix memory leak concerns - Will add proper cleanup handling for unmount scenarios
  4. Fix race condition - Will ensure message is sent before hiding component
  5. Fix inefficient array operations - Will use updatedList directly instead of spreading twice
  6. Add more test coverage - Will add tests for edge cases

Starting work on these changes now...

- Changed from className to separate 'id' and 'className' props for better semantics
- Added i18n support for accessibility labels (aria-label and title)
- Fixed memory leak by adding mounted flag to prevent state updates after unmount
- Fixed race condition by sending dismiss message before hiding component
- Fixed inefficient array operations in webviewMessageHandler
- Added comprehensive test coverage for edge cases including:
  - Multiple rapid dismissals
  - Component unmounting during async operations
  - Invalid/malformed message handling
  - Proper message sending before unmount
- Added null checks for message data to handle edge cases gracefully
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Sep 10, 2025
@roomote
Copy link
Contributor Author

roomote bot commented Sep 10, 2025

@ellipsis-dev @roomote @brunobergher

I have successfully applied all the requested changes from the PR feedback:

Changes Implemented

1. Separated ID from className

  • Changed from using className as identifier to dedicated id prop
  • className is now optional and purely for styling

2. Added i18n support

  • Integrated useAppTranslation hook for accessibility labels
  • All UI text now supports internationalization

3. Fixed memory leak

  • Added isMountedRef to track component mount status
  • Prevents state updates after component unmounts

4. Fixed race condition

  • Made dismiss handler async
  • Ensures message is sent before hiding component

5. Optimized array operations

  • Fixed inefficient spread operations in webviewMessageHandler.ts
  • Now reuses the filtered array directly

6. Enhanced test coverage

  • Added tests for rapid dismissals
  • Added tests for unmounting during dismiss
  • Added tests for malformed messages
  • Added null safety checks

CI Status

  • ✅ CodeQL, Compile, Knip, Translations checks passed
  • ✅ Ubuntu unit tests passed
  • ✅ Integration tests passed
  • ❌ Windows unit tests failed (appears to be an existing issue unrelated to these changes)

The Windows test failure seems to be a pre-existing issue as the changes are platform-agnostic and all other tests pass successfully.

Commit: e5e8005

@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Sep 10, 2025
@github-project-automation github-project-automation bot moved this from Triage to Done in Roo Code Roadmap Sep 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants