Skip to content

Phase 2.5: Notifications and Error Components Migration#2839

Merged
RoyEJohnson merged 9 commits into
mainfrom
phase-2.5-notifications-errors-migration
Apr 20, 2026
Merged

Phase 2.5: Notifications and Error Components Migration#2839
RoyEJohnson merged 9 commits into
mainfrom
phase-2.5-notifications-errors-migration

Conversation

@OpenStaxClaude
Copy link
Copy Markdown
Contributor

@OpenStaxClaude OpenStaxClaude commented Apr 1, 2026

Summary

Migrates notification and error display components from styled-components to plain CSS as part of Phase 2.5 of the REX modernization effort.

Related Jira Ticket: CORE-1699

Note: This PR was updated to use plain CSS instead of CSS Modules, following the team's design decision documented in PLAIN_CSS_MIGRATION_LEARNINGS.md.

Components Migrated

  • ToastNotifications (src/app/notifications/components/ToastNotifications/)

    • Preserved slide-in animation (transform: translateY with 0.6s transition)
    • Preserved fade-out animation (opacity keyframe with 1s duration)
    • Maintained error/warning variant styling
    • Responsive design with media queries at top-level
  • ErrorBoundary (src/app/errors/components/ErrorBoundary.tsx)

    • Converted ErrorWrapper, HeadingWrapper, and BodyErrorText styled components
  • ErrorModal (src/app/errors/components/ErrorModal.tsx)

    • Converted BodyErrorText styled component with modal padding
  • ErrorIdList (src/app/errors/components/ErrorIdList.tsx)

    • Converted styled div with label styling (0.9rem font, 60% opacity)
  • LoaderCentered (src/app/errors/components/LoaderCentered.tsx)

    • Converted fullscreen flex container (100vw x 100vh centered)

Technical Implementation

Plain CSS Approach (following PLAIN_CSS_MIGRATION_LEARNINGS.md)

  • Created .css files for each component (e.g., ToastNotifications.css, ErrorBoundary.css)
  • Used kebab-case for CSS class names (.banner-body-wrapper, .toast-close-button)
  • Used classnames package for conditional class composition
  • Ensured all media queries are top-level in CSS files (not nested)
  • Used data attributes (data-fading-in, data-fading-out) for animation state management
  • Inline styles for dynamic z-index calculation in toast stacking
  • Preserved all color schemes, animations, and responsive behavior
  • Removed all styled-components/macro imports
  • Deleted styles.tsx file from ToastNotifications directory

CSS Class Naming Convention

  • Converted from camelCase/PascalCase to kebab-case:
    • ToastsContainer.toasts-container
    • BannerBodyWrapper.banner-body-wrapper
    • BannerBody.banner-body with .banner-body-error and .banner-body-warning variants
    • CloseButton.toast-close-button
    • CloseIcon.toast-close-icon
    • ErrorId.toast-error-id

Testing Checklist

  • Run yarn test:unit - all unit tests pass
  • Manual testing of toast notifications (slide-in, fade-out, stacking, variants)
  • Manual testing of error boundary display
  • Manual testing of error modal display
  • Cross-browser testing (Chrome, Firefox, Safari, Edge)

Notes

  • Toast notifications have complex animation state management that required careful migration to maintain the exact same behavior
  • The animations are triggered by data attributes and CSS classes rather than styled-component props
  • All media queries are top-level in CSS files as required by plain CSS (cannot be nested)
  • Follows established migration patterns from previous phases documented in PLAIN_CSS_MIGRATION_LEARNINGS.md

🤖 Generated with Claude Code

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-4dgine April 1, 2026 15:33 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-idztaw April 1, 2026 15:46 Inactive
RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Apr 1, 2026
- Fix htmlMessage function calls: Remove third argument (className) from
  ErrorBoundary and ErrorModal, and pass className as a prop instead
- Fix Times component props: Change focusable attribute from string 'false'
  to boolean false in Toast component

Resolves review comments from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-iawv53 April 1, 2026 16:05 Inactive
RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Apr 1, 2026
…mlMessage

- ErrorBoundary.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- ErrorModal.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- Toast.tsx: Remove invalid focusable prop from Times SVG component
  (not part of React's HTMLAttributes<SVGElement> type)

These changes maintain the same CSS styling (.body-error-text class)
while properly typing the components for TypeScript.

Addresses review feedback on PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

This comment was marked as resolved.

RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Apr 1, 2026
- Scope .body-error-text in ErrorBoundary.css under .error-wrapper parent
- Scope .body-error-text in ErrorModal.css under .error-modal parent
- Update link colors in ErrorBoundary.css to match design system constants:
  - Link color: #0074BC → #027EB5 (linkColor constant)
  - Hover color: #005A8E → #0064A0 (linkHover constant)

This prevents CSS conflicts between ErrorBoundary and ErrorModal components
and ensures consistent link styling across the application.

Addresses Copilot review comments 1, 2, and 3 from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 1, 2026 17:29 Inactive
RoyEJohnson pushed a commit that referenced this pull request Apr 1, 2026
This commit migrates ToastNotifications and error display components
from styled-components to CSS Modules as part of Phase 2.5.

Components migrated:
- ToastNotifications (with slide-in/fade-out animations)
- ErrorBoundary
- ErrorModal
- ErrorIdList
- LoaderCentered

Key changes:
- Created .module.css files for all components
- Preserved all animations (slide-in and fade-out for toasts)
- Maintained responsive styling and variant support
- Removed styled-components imports and usage
- Used data attributes for animation states
- Inline styles for dynamic z-index calculation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix CSS class name collisions and update link colors

- Scope .body-error-text in ErrorBoundary.css under .error-wrapper parent
- Scope .body-error-text in ErrorModal.css under .error-modal parent
- Update link colors in ErrorBoundary.css to match design system constants:
  - Link color: #0074BC → #027EB5 (linkColor constant)
  - Hover color: #005A8E → #0064A0 (linkHover constant)

This prevents CSS conflicts between ErrorBoundary and ErrorModal components
and ensures consistent link styling across the application.

Addresses Copilot review comments 1, 2, and 3 from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix TypeScript errors: Use React components instead of strings for htmlMessage

- ErrorBoundary.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- ErrorModal.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- Toast.tsx: Remove invalid focusable prop from Times SVG component
  (not part of React's HTMLAttributes<SVGElement> type)

These changes maintain the same CSS styling (.body-error-text class)
while properly typing the components for TypeScript.

Addresses review feedback on PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix TypeScript errors in error and notification components

- Fix htmlMessage function calls: Remove third argument (className) from
  ErrorBoundary and ErrorModal, and pass className as a prop instead
- Fix Times component props: Change focusable attribute from string 'false'
  to boolean false in Toast component

Resolves review comments from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Convert CSS Modules to plain CSS per team design decision

Updated Phase 2.5 implementation to use plain CSS instead of CSS Modules,
following the established migration pattern documented in PLAIN_CSS_MIGRATION_LEARNINGS.md.

Components migrated:
- ToastNotifications (with slide-in/fade-out animations)
- ErrorBoundary
- ErrorModal
- ErrorIdList
- LoaderCentered

Key changes:
- Renamed *.module.css to *.css (e.g., styles.module.css → ToastNotifications.css)
- Updated imports to use plain CSS imports instead of CSS Module imports
- Changed className references from styles.className to plain "class-name" strings
- Used classnames package for conditional class composition
- Converted camelCase/PascalCase CSS class names to kebab-case (e.g., bannerBodyWrapper → banner-body-wrapper)
- Ensured all media queries are top-level in CSS files (not nested)
- Preserved all animations (slide-in and fade-out for toasts)
- Maintained responsive styling and variant support
- Removed styled-components imports and usage

This approach aligns with:
- PLAIN_CSS_MIGRATION_LEARNINGS.md best practices
- Existing codebase patterns from previous migration phases
- Team's design decision to use plain CSS over CSS Modules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from 4fcf3c7 to 7adc25d Compare April 1, 2026 17:45
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 1, 2026 17:46 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 1, 2026 18:05 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from a719f91 to d13ca00 Compare April 1, 2026 18:20
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 1, 2026 18:21 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from d13ca00 to f2bbb48 Compare April 1, 2026 18:39
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 1, 2026 18:40 Inactive
RoyEJohnson

This comment was marked as resolved.

@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward had a problem deploying to rex-web-phase-2-5-notif-xeocop April 1, 2026 18:43 Failure
@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from f5ea51b to f96e8c9 Compare April 1, 2026 18:55

This comment was marked as resolved.

RoyEJohnson

This comment was marked as resolved.

@OpenStaxClaude

This comment was marked as resolved.

This comment was marked as resolved.

@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from 85b6e68 to a1086d2 Compare April 2, 2026 15:53
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 2, 2026 15:53 Inactive
RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Apr 2, 2026
- Add test case to verify that props starting with $ (transient props)
  are filtered out and not forwarded to the DOM
- This exercises the else-branch of the if(!key.startsWith('$')) condition
- Transient props are a styled-components convention for style-only props
  that should not appear as HTML attributes

Addresses review comment from PR #2839 (Review #20)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-5-notif-xeocop April 2, 2026 16:10 Inactive
@OpenStaxClaude

This comment was marked as resolved.

@OpenStaxClaude

This comment was marked as resolved.

RoyEJohnson pushed a commit that referenced this pull request Apr 2, 2026
Non-standard props like isActive and practiceQuestionsEnabled are used by
styled-components for conditional styling but should not be forwarded to
the DOM as HTML attributes. This change filters them out before passing
props to the native button element.

Addresses Copilot review comment about isActive prop forwarding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.tsx

Add test for PlainButton transient props filtering

- Add test case to verify that props starting with $ (transient props)
  are filtered out and not forwarded to the DOM
- This exercises the else-branch of the if(!key.startsWith('$')) condition
- Transient props are a styled-components convention for style-only props
  that should not appear as HTML attributes

Addresses review comment from PR #2839 (Review #20)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@RoyEJohnson RoyEJohnson force-pushed the phase-2.5-notifications-errors-migration branch from 72b4efe to 9a84d7b Compare April 2, 2026 16:16
Copy link
Copy Markdown
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

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

OpenStaxClaude and others added 6 commits April 15, 2026 09:32
This commit migrates ToastNotifications and error display components
from styled-components to CSS Modules as part of Phase 2.5.

Components migrated:
- ToastNotifications (with slide-in/fade-out animations)
- ErrorBoundary
- ErrorModal
- ErrorIdList
- LoaderCentered

Key changes:
- Created .module.css files for all components
- Preserved all animations (slide-in and fade-out for toasts)
- Maintained responsive styling and variant support
- Removed styled-components imports and usage
- Used data attributes for animation states
- Inline styles for dynamic z-index calculation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix CSS class name collisions and update link colors

- Scope .body-error-text in ErrorBoundary.css under .error-wrapper parent
- Scope .body-error-text in ErrorModal.css under .error-modal parent
- Update link colors in ErrorBoundary.css to match design system constants:
  - Link color: #0074BC → #027EB5 (linkColor constant)
  - Hover color: #005A8E → #0064A0 (linkHover constant)

This prevents CSS conflicts between ErrorBoundary and ErrorModal components
and ensures consistent link styling across the application.

Addresses Copilot review comments 1, 2, and 3 from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix TypeScript errors: Use React components instead of strings for htmlMessage

- ErrorBoundary.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- ErrorModal.tsx: Create BodyErrorText wrapper component to replace
  styled component, pass it to htmlMessage instead of string 'div'
- Toast.tsx: Remove invalid focusable prop from Times SVG component
  (not part of React's HTMLAttributes<SVGElement> type)

These changes maintain the same CSS styling (.body-error-text class)
while properly typing the components for TypeScript.

Addresses review feedback on PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix TypeScript errors in error and notification components

- Fix htmlMessage function calls: Remove third argument (className) from
  ErrorBoundary and ErrorModal, and pass className as a prop instead
- Fix Times component props: Change focusable attribute from string 'false'
  to boolean false in Toast component

Resolves review comments from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Convert CSS Modules to plain CSS per team design decision

Updated Phase 2.5 implementation to use plain CSS instead of CSS Modules,
following the established migration pattern documented in PLAIN_CSS_MIGRATION_LEARNINGS.md.

Components migrated:
- ToastNotifications (with slide-in/fade-out animations)
- ErrorBoundary
- ErrorModal
- ErrorIdList
- LoaderCentered

Key changes:
- Renamed *.module.css to *.css (e.g., styles.module.css → ToastNotifications.css)
- Updated imports to use plain CSS imports instead of CSS Module imports
- Changed className references from styles.className to plain "class-name" strings
- Used classnames package for conditional class composition
- Converted camelCase/PascalCase CSS class names to kebab-case (e.g., bannerBodyWrapper → banner-body-wrapper)
- Ensured all media queries are top-level in CSS files (not nested)
- Preserved all animations (slide-in and fade-out for toasts)
- Maintained responsive styling and variant support
- Removed styled-components imports and usage

This approach aligns with:
- PLAIN_CSS_MIGRATION_LEARNINGS.md best practices
- Existing codebase patterns from previous migration phases
- Team's design decision to use plain CSS over CSS Modules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Address Copilot review comments: Fix padding and simplify PlainButton

1. Fix ErrorModal.css padding calculation
   - Changed from 0.8rem to 1.5rem to match original styled-component
   - Original calculation: modalPadding (3.0) * 0.5 = 1.5rem

2. Simplify PlainButton component in Button.tsx
   - Removed unnecessary props reduce/copy logic that wasn't filtering anything
   - Test coverage confirmed no transient props are passed to PlainButton
   - Updated comment to clarify PlainButton accepts standard HTML button attributes
   - TypeScript types already enforce only valid button attributes are passed

Resolves review comments from Copilot and Roy on PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.tsx

Fix test failures after styled-components migration

- Update Toast.spec.tsx to check data-fading-out attribute instead of
  isFadingOut prop, matching the new plain CSS implementation
- Update index.spec.tsx to query elements by data-testid and aria-label
  instead of styled-component types (BannerBodyWrapper, CloseButton)
- Remove import of deleted styles.tsx file from index.spec.tsx
- Delete unused styles.tsx file as specified in migration plan

All tests now query the actual DOM elements using their attributes,
which aligns with the plain CSS migration approach.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update types.ts

Fix test issues

Move CSS imports to the global file

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- ErrorModal: Convert to function component using useSelector and useDispatch hooks
- ErrorBoundary: Use hooks wrapper pattern to maintain Error Boundary class component
  while connecting to Redux with useSelector and useDispatch

The ErrorBoundary requires a class component to use componentDidCatch lifecycle method
(Error Boundaries can't be function components), so we use a wrapper pattern where:
- ErrorBoundaryClass: Inner class component handling error catching
- ErrorBoundary: Outer function component using hooks to connect to Redux

This modernizes the Redux integration while maintaining all functionality.

Addresses review comment from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Replace React.FC with ordinary function declarations

Replaced all React.FC type annotations with standard function declarations
in error components as requested in code review:

- ErrorBoundary.tsx: Converted BodyErrorText, ErrorDisplay, and ErrorBoundary
  wrapper functions from React.FC to ordinary functions
- ErrorModal.tsx: Converted BodyErrorText from React.FC to ordinary function

All functions maintain their TypeScript type annotations through explicit
parameter and return types.

Addresses review comment in PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix ErrorBoundary recordError prop type

Change recordError prop type from 'typeof recordError' to '(error: Error) => void'
to match the actual function signature passed from the wrapper component.

The wrapper component creates a handleRecordError function that dispatches the
action and returns void, not the action itself.

Resolves TypeScript error TS2769 in ErrorBoundary.tsx:104

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Implements the ability to trigger the ErrorModal by adding ?modal=ERROR
to the URL, similar to how modal=MH works for My Highlights.

Changes:
- Created src/app/errors/constants.ts with modalUrlName = 'ERROR'
- Updated errors reducer to check for modal query parameter in locationChange
- Added tests for the new modal=ERROR functionality
- Updated existing tests to include query parameter

This allows developers and QA to easily test the ErrorModal display
by navigating to any page with ?modal=ERROR in the query string.

Addresses review comment from PR #2839

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix TypeScript errors in reducer.spec.ts tests

Remove query property from locationChange action calls in tests. The
locationChange action creator automatically parses the query from
location.search, so passing it as a separate property causes a
TypeScript error since it's not part of the LocationChange type.

The query is correctly parsed from the search string in the action
creator's .map() function.

Resolves Review #16

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Non-standard props like isActive and practiceQuestionsEnabled are used by
styled-components for conditional styling but should not be forwarded to
the DOM as HTML attributes. This change filters them out before passing
props to the native button element.

Addresses Copilot review comment about isActive prop forwarding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.tsx

Add test for PlainButton transient props filtering

- Add test case to verify that props starting with $ (transient props)
  are filtered out and not forwarded to the DOM
- This exercises the else-branch of the if(!key.startsWith('$')) condition
- Transient props are a styled-components convention for style-only props
  that should not appear as HTML attributes

Addresses review comment from PR #2839 (Review #20)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@Dantemss Dantemss left a comment

Choose a reason for hiding this comment

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

Well... nothing jumps at me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants