Skip to content

Conversation

@dougfabris
Copy link
Member

@dougfabris dougfabris commented Sep 19, 2025

Proposed changes (including videos or screenshots)

As soon the videoconf popup opens, the focus should be moved to the first focusable element on the popup. We use the FocusScope from react-aria, which has the autoFocus to handle this automatically, but for some reason its not working consistently for this case. We do have an e2e test to ensure this behavior, but the test reveled its not working properly.

After tons of attempts to find out why FocusScope can't add the focus properly, I decided to change the approach and dispatch the focus using the useFocusManager which showed itself more consistent, potentially removing the flakiness

Issue(s)

Steps to test or reproduce

Further comments

FLAKY-1424

Summary by CodeRabbit

  • New Features
    • Added a loading skeleton for the Video Conference popup.
  • Accessibility
    • Popup now auto-focuses the first interactive element for improved keyboard navigation.
  • Performance
    • Video Conference popup is lazy-loaded with a skeleton fallback for smoother loading.
  • UI Improvements
    • Refined popup positioning for better layout flow.
    • Ensured consistent header height.
    • Updated video conference message icon colors to match theme tokens.
  • Documentation
    • Added Storybook story for the popup skeleton state.
  • Chores
    • Clean build output before compiling; renamed lint scripts; added type checking script.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 19, 2025

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 19, 2025

⚠️ No Changeset found

Latest commit: 5bc5de6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

Adds focus management to TimedVideoConfPopup, introduces lazy loading with Suspense and a skeleton fallback for VideoConfPopup, updates UI theme tokens, adjusts popup positioning and header min-height, adds a new VideoConfPopupSkeleton component and Storybook story, and updates ui-video-conf package scripts.

Changes

Cohort / File(s) Summary
Focus management in timed popup
apps/meteor/client/.../TimedVideoConfPopup.tsx
Imports useEffect and react-aria useFocusManager; on mount, calls focusFirst() to set initial focus.
Lazy-loading and suspense wrapper
apps/meteor/client/.../VideoConfPopups.tsx
Replaces static import with React.lazy; wraps popup in Suspense with VideoConfPopupSkeleton fallback; adjusts FocusScope props.
ui-video-conf build/lint scripts
packages/ui-video-conf/package.json
Build now cleans dist before tsc; renames eslint scripts to lint/lint:fix; adds typecheck script.
Theme token updates for message icon
packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageIcon.tsx
Replaces Palette usage with string theme tokens for color/backgroundColor across variants.
Popup layout and header sizing
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx, .../VideoConfPopupHeader.tsx
Container position changed from absolute to relative; header adds minHeight='x28'.
Skeleton component and exports
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupSkeleton.tsx, .../index.ts, .../VideoConfPopup.stories.tsx
Adds VideoConfPopupSkeleton component; exports it publicly; adds Storybook Skeleton story rendering the new loader.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant RV as RoomView
  participant FS as FocusScope
  participant S as Suspense
  participant VCPS as VideoConfPopupSkeleton
  participant VCP as VideoConfPopup (lazy)
  participant TVP as TimedVideoConfPopup

  U->>RV: Open video conf popup
  RV->>FS: Render with restoreFocus
  FS->>S: Render Suspense(fallback=VCPS)
  Note over S,VCPS: While lazy module loads, show skeleton
  S->>VCP: Lazy-load ./VideoConfPopup
  VCP-->>S: Module resolved
  S->>VCP: Render VideoConfPopup
  VCP->>TVP: Render TimedVideoConfPopup
  TVP->>TVP: useEffect -> focusManager.focusFirst()
  Note over TVP: Initial focus moves to first focusable element
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • MartinSchoeler

Poem

A hop and a skip through Suspense we go,
Skeletons shimmer with a gentle glow.
Focus finds the very first key,
Tokens hum in harmony.
Popups perch, now snug, not absolute—
Thump-thump! says the rabbit, “What a cute reboot.” 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR includes a number of edits that are not required to satisfy the focus-related objective, namely theme token replacements in packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageIcon.tsx, package.json script renames and build cleanup in packages/ui-video-conf/package.json, a CSS positioning change in packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx, and Storybook/export additions and index exports; while VideoConfPopupSkeleton and lazy-loading are related to the focus timing change, these other edits broaden the scope. Including them increases review surface and the risk of unrelated regressions in theming, build behavior, or layout. These out-of-scope changes should be split out or explicitly justified and covered by tests. Split the PR: keep only the focus-management changes (TimedVideoConfPopup and the minimal VideoConfPopups adjustments) in this branch and move theme, build-script, layout, and storybook changes into separate focused PRs; if they must remain together, add a clear justification in the PR description and include automated tests or visual/regression checks plus CI confirmation that the CSS and package script edits do not introduce regressions.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change: replacing the focus strategy for video conference popups to improve focus behavior and test stability; it directly reflects the code changes (useFocusManager/focusFirst) and is concise and specific.
Linked Issues Check ✅ Passed The changes explicitly implement explicit focus management by adding useFocusManager.focusFirst in TimedVideoConfPopup and adjusting VideoConfPopups to lazy-load with a skeleton while retaining FocusScope for restoreFocus; this targets the keyboard-focus behavior required by FLAKY-1424. From the provided diffs the popup now dispatches focus on mount to the first focusable element, which directly addresses the linked issue's objective to allow creating a video conference using only the keyboard. Based on the summaries, the coding changes meet the linked issue requirements.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/videoconfpopup-focus

Comment @coderabbitai help to get the list of available commands and usage tips.

@dougfabris dougfabris added this to the 7.11.0 milestone Sep 19, 2025
@codecov
Copy link

codecov bot commented Sep 19, 2025

Codecov Report

❌ Patch coverage is 77.77778% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.27%. Comparing base (4a20b82) to head (5bc5de6).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36999      +/-   ##
===========================================
- Coverage    66.35%   66.27%   -0.08%     
===========================================
  Files         3391     3392       +1     
  Lines       115313   115379      +66     
  Branches     21137    21169      +32     
===========================================
- Hits         76513    76466      -47     
- Misses       36187    36297     +110     
- Partials      2613     2616       +3     
Flag Coverage Δ
e2e 56.93% <100.00%> (+0.01%) ⬆️
unit 71.25% <69.44%> (-0.17%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dougfabris dougfabris force-pushed the chore/videoconfpopup-focus branch from 2473b44 to 967ad80 Compare September 19, 2025 14:43
@MartinSchoeler MartinSchoeler added the stat: QA assured Means it has been tested and approved by a company insider label Sep 19, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Sep 19, 2025
@dougfabris dougfabris marked this pull request as ready for review September 19, 2025 19:12
@dougfabris dougfabris requested a review from a team as a code owner September 19, 2025 19:12
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx (1)

29-33: Modal dialog should declare aria-modal and have a reliable accessible name

For a modal dialog, expose modal semantics explicitly. Add aria-modal and rely on existing aria-label/aria-labelledby passthrough from props.

Apply this diff:

-    <VideoConfPopupContainer role='dialog' ref={ref} position={position} {...props}>
+    <VideoConfPopupContainer role='dialog' aria-modal='true' ref={ref} position={position} {...props}>
🧹 Nitpick comments (9)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx (1)

19-23: Tighten props typing to the concrete element

Use HTMLAttributes to align with the rendered element.

-type VideoConfPopupProps = {
+type VideoConfPopupProps = {
   children: ReactNode;
   position?: number;
-} & HTMLAttributes<HTMLElement>;
+} & HTMLAttributes<HTMLDivElement>;
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupHeader.tsx (1)

5-5: LGTM — minHeight improves layout stability

Optional: add alignItems='center' to vertically center header controls if designs expect that.

-  <Box display='flex' minHeight='x28' justifyContent='space-between'>
+  <Box display='flex' minHeight='x28' alignItems='center' justifyContent='space-between'>
packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageIcon.tsx (1)

27-36: Mark decorative icon as hidden from assistive tech (unless meant as status text)

If the surrounding UI already conveys state textually, hide this icon from SRs.

-    <Icon size='x20' name={styles[variant].icon} color={styles[variant].color} />
+    <Icon size='x20' name={styles[variant].icon} color={styles[variant].color} aria-hidden='true' />

If the icon is the only state indicator, instead provide an aria-label derived from variant.

packages/ui-video-conf/package.json (1)

11-14: Make build cleanup cross‑platform

rm -rf breaks on Windows. Prefer rimraf (or shx) for portability.

-    "build": "rm -rf dist && tsc -p tsconfig.build.json",
+    "build": "rimraf dist && tsc -p tsconfig.build.json",

Add devDependency (or reuse a workspace tool if available):

   "devDependencies": {
+    "rimraf": "^5.0.0",

Please confirm CI runners aren’t Windows-only; otherwise the current script will fail there.

apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/TimedVideoConfPopup.tsx (1)

45-48: Stabilize initial focus timing

A small defer can reduce races with late-mounted children (e.g., icons/images). Optional but tends to reduce flakiness.

-  useEffect(() => {
-    focusManager?.focusFirst();
-  }, [focusManager]);
+  useEffect(() => {
+    const raf = requestAnimationFrame(() => focusManager?.focusFirst());
+    return () => cancelAnimationFrame(raf);
+  }, [focusManager]);
apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopups.tsx (1)

51-56: Fix position calculation; default value on index is ignored

map’s index starts at 0; the default index = 1 doesn’t apply. Use (index + 1) * 10 to offset the first popup too, and drop the default value on index.

-          {(children ? [children, ...popups] : popups).map(({ id, rid, isReceiving }, index = 1) => (
+          {(children ? [children, ...popups] : popups).map(({ id, rid, isReceiving }, index) => (
               <VideoConfPopupBackdrop key={id}>
-                <Suspense fallback={<VideoConfPopupSkeleton />}>
-                  <FocusScope restoreFocus>
-                    <VideoConfPopup id={id} rid={rid} isReceiving={isReceiving} isCalling={isCalling} position={index * 10} />
-                  </FocusScope>
-                </Suspense>
+                {/* see focus trap change above */}
+                <VideoConfPopup id={id} rid={rid} isReceiving={isReceiving} isCalling={isCalling} position={(index + 1) * 10} />
               </VideoConfPopupBackdrop>
           ))}
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupSkeleton.tsx (3)

12-12: A11y: Keep dialog name stable; prefer aria-busy over temporary aria-label.

Using aria-label="Loading" changes the dialog’s accessible name while the fallback is shown. Better: keep the same name and mark the container busy.

Apply this diff:

-	<VideoConfPopup aria-label='Loading' {...props}>
+	<VideoConfPopup {...props} aria-busy={true}>

19-19: Use design tokens for spacing (mis).

Use Fuselage tokens for consistency with the rest of the sizes.

-				<Skeleton mis={8} variant='rect' height='x24' width='x120' />
+				<Skeleton mis='x8' variant='rect' height='x24' width='x120' />

9-10: Export the props type for consumers.

Handy for typing wrappers/tests and keeps public API consistent with other components.

-type VideoConfPopupSkeletonProps = Omit<ComponentProps<typeof VideoConfPopup>, 'children'>;
+export type VideoConfPopupSkeletonProps = Omit<ComponentProps<typeof VideoConfPopup>, 'children'>;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b76d99b and 967ad80.

⛔ Files ignored due to path filters (2)
  • packages/ui-video-conf/src/VideoConfPopup/__snapshots__/VideoConfPopup.spec.tsx.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/TimedVideoConfPopup.tsx (2 hunks)
  • apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopups.tsx (2 hunks)
  • packages/ui-video-conf/package.json (1 hunks)
  • packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageIcon.tsx (2 hunks)
  • packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.stories.tsx (2 hunks)
  • packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx (1 hunks)
  • packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupHeader.tsx (1 hunks)
  • packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupSkeleton.tsx (1 hunks)
  • packages/ui-video-conf/src/VideoConfPopup/index.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.stories.tsx (1)
packages/ui-video-conf/src/VideoConfPopup/index.ts (2)
  • VideoConfPopup (14-14)
  • VideoConfPopupSkeleton (24-24)
apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopups.tsx (1)
packages/ui-video-conf/src/VideoConfPopup/index.ts (2)
  • VideoConfPopup (14-14)
  • VideoConfPopupSkeleton (24-24)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupSkeleton.tsx (2)
packages/ui-video-conf/src/VideoConfPopup/index.ts (5)
  • VideoConfPopup (14-14)
  • VideoConfPopupSkeleton (24-24)
  • VideoConfPopupHeader (15-15)
  • VideoConfPopupContent (18-18)
  • VideoConfPopupFooter (22-22)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.stories.tsx (1)
  • Skeleton (57-57)
🔇 Additional comments (6)
packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.tsx (1)

6-17: Relative positioning change: verify stacking/offset behavior within the backdrop

Switching to position: relative while still using top/left offsets can change layout flow and hit‑testing. Please validate across portals/overlays that:

  • the popup remains above the backdrop content,
  • offsets still produce the intended diagonal stacking,
  • no unexpected layout shift occurs.

Add a z-index here if any layering issues appear.

packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageIcon.tsx (1)

11-23: Theme token migration looks correct

Color tokens map cleanly to status variants. No functional concerns.

packages/ui-video-conf/src/VideoConfPopup/index.ts (1)

10-10: LGTM — public export of the skeleton

Exporting VideoConfPopupSkeleton aligns the library with the new Suspense fallback use.

Also applies to: 24-24

packages/ui-video-conf/src/VideoConfPopup/VideoConfPopup.stories.tsx (1)

57-58: LGTM — Skeleton story

Good to have parity with the runtime fallback.

apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopups.tsx (1)

11-12: Lazy + Suspense integration: looks good

The lazy import and Suspense usage are correct. Once focus containment is restored, this should help with the flakiness goal.

To be safe, rerun the flaky test (FLAKY-1424) and a manual keyboard-only flow after the focus trap fix.

Also applies to: 16-16

packages/ui-video-conf/src/VideoConfPopup/VideoConfPopupSkeleton.tsx (1)

11-26: Verify focus behavior with the Suspense fallback.

Ensure that while the skeleton is visible:

  • Focus lands on the popup container (or remains on the trigger) without bouncing.
  • When the real content mounts, focus reliably moves to the first focusable element via useFocusManager.

If helpful, I can draft a lightweight Playwright check that opens the popup, asserts activeElement on the container during fallback, then waits for content and asserts focus moved to the intended control.

@kodiakhq kodiakhq bot merged commit e748e94 into develop Sep 19, 2025
49 checks passed
@kodiakhq kodiakhq bot deleted the chore/videoconfpopup-focus branch September 19, 2025 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants