Skip to content

refactor(call): extract VideoCall.jsx into focused custom hooks - #1200

Merged
JamesPHoughton merged 2 commits into
mainfrom
refactor/videocall-extract-hooks
Mar 4, 2026
Merged

refactor(call): extract VideoCall.jsx into focused custom hooks#1200
JamesPHoughton merged 2 commits into
mainfrom
refactor/videocall-extract-hooks

Conversation

@JamesPHoughton

Copy link
Copy Markdown
Collaborator

Summary

  • Decompose the 1622-line VideoCall.jsx monolith into 11 focused custom hooks, reducing it to a ~290-line orchestrator
  • Consolidate 3 near-identical device alignment functions (~120 lines each) into a single generic alignInputDevice function
  • No behavior changes — pure structural refactor

New hooks

Hook Purpose
useDisplayNameSync Mirror Empirica name → Daily displayName
useDailyIdTracking Store dailyId/dailyIdHistory on player
useCallStartSignaling Signal server to start recording
useVisibilityTracking Log page blur/focus for debugging
useTrackMonitor Poll for ended tracks, auto-recover
usePermissionMonitor Watch Permissions API for revocations
useCallLifecycle Join/leave Daily room + Firefox stall detection
useDeviceErrors Device errors + fatal + network with priority dedup
useGesturePrompt Gesture-gated operations + computed prompt state
useDeviceRecovery W4: devicechange auto-recovery
useDeviceAlignment Consolidated alignment + device switch handler

Test plan

  • All 459 mocked component tests pass across chromium, firefox, and webkit
  • No new lint errors

Part of #1199

🤖 Generated with Claude Code

Decompose the 1622-line VideoCall.jsx monolith into 11 focused hooks,
reducing it to a ~290-line orchestrator. No behavior changes — all 459
component tests pass across chromium, firefox, and webkit.

New hooks:
- useDisplayNameSync: mirror Empirica name → Daily displayName
- useDailyIdTracking: store dailyId/dailyIdHistory on player
- useCallStartSignaling: signal server to start recording
- useVisibilityTracking: log page blur/focus for debugging
- useTrackMonitor: poll for ended tracks, auto-recover
- usePermissionMonitor: watch Permissions API for revocations
- useCallLifecycle: join/leave Daily room + Firefox stall detection
- useDeviceErrors: device errors + fatal + network with priority dedup
- useGesturePrompt: gesture-gated operations + computed prompt state
- useDeviceRecovery: devicechange auto-recovery (W4)
- useDeviceAlignment: consolidated 3x ~120-line alignment functions

Part of #1199

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 19:13
@github-actions

github-actions Bot commented Mar 4, 2026

Copy link
Copy Markdown
/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/Call.jsx
  73:9  warning  The 'players' logical expression could make the dependencies of useMemo Hook (at line 213) change on every render. To fix this, wrap the initialization of 'players' in its own useMemo() Hook  react-hooks/exhaustive-deps
  73:9  warning  The 'players' logical expression could make the dependencies of useMemo Hook (at line 242) change on every render. To fix this, wrap the initialization of 'players' in its own useMemo() Hook  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/FixAV.jsx
  332:36  error  Return values from promise executor functions cannot be read  no-promise-executor-return
  510:38  error  Return values from promise executor functions cannot be read  no-promise-executor-return
  658:25  error  Do not use Array index in keys                                react/no-array-index-key
  692:25  error  Do not use Array index in keys                                react/no-array-index-key
  749:25  error  Do not use Array index in keys                                react/no-array-index-key
  806:25  error  Do not use Array index in keys                                react/no-array-index-key
  856:25  error  Do not use Array index in keys                                react/no-array-index-key

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/layouts/defaultResponsiveLayout.test.js
  1:32  error  'vi' is defined but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/utils/avRecovery.js
  221:13  error  Use object destructuring                                                                                                                                                   prefer-destructuring
  251:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  257:21  error  Unexpected use of continue statement                                                                                                                                       no-continue
  317:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  320:7   error  Unexpected use of continue statement                                                                                                                                       no-continue
  331:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  347:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  365:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  376:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  393:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  404:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  455:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  457:21  error  Unexpected use of continue statement                                                                                                                                       no-continue

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/utils/avRecovery.test.js
   42:7   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  506:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars
  545:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars
  586:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/chat/Chat.jsx
  37:7  warning  React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/EmpiricaMenu.jsx
  51:3  error  React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/IdleProvider.jsx
   57:6   warning  React Hook useEffect has a missing dependency: 'resetTimer'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
  120:55  error    ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'                                                react/no-unescaped-entities

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/PermissionRecovery.jsx
  79:5  error  Do not nest ternary expressions  no-nested-ternary

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/SharedNotepad.jsx
  27:6  warning  React Hook useEffect has missing dependencies: 'defaultText', 'game', and 'record'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/scroll/useScrollAwareness.js
   67:19  error  Use object destructuring                                                                prefer-destructuring
  108:39  error  Use the '**' operator instead of 'Math.pow'                                             prefer-exponentiation-operator
  108:39  error  'Math.pow' is restricted from being used. Use the exponentiation operator (**) instead  no-restricted-properties

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/Qualtrics.jsx
  55:10  error  'state' is assigned a value but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/Survey.jsx
  9:26  error  Unexpected function expression  prefer-arrow-callback

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/TrainingVideo.jsx
  61:6  warning  React Hook useEffect has missing dependencies: 'timer?.elapsed' and 'url'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Consent.jsx
  132:6  warning  React Hook useEffect has a missing dependency: 'player'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Countdown.jsx
  8:21  error  Use default import syntax to import 'ReactCountdown'  import/no-named-default

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Lobby.jsx
  64:19  error  ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'  react/no-unescaped-entities
  64:51  error  ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'  react/no-unescaped-entities

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/AudioEquipmentCheck.jsx
   87:6   warning  React Hook useEffect has a missing dependency: 'permissionsStatus'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
  104:24  error    Unnecessary return statement                                                                                          no-useless-return
  112:5   error    Arrow function expected no return value                                                                               consistent-return

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/CameraCheck.jsx
  130:6  warning  React Hook useEffect has a missing dependency: 'devices'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/GetPermissions.jsx
    3:38  error  'useCallback' is defined but never used  no-unused-vars
  149:5   error  Do not nest ternary expressions          no-nested-ternary

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/LoopbackCheck.jsx
  165:36  error  Return values from promise executor functions cannot be read  no-promise-executor-return

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/MicCheck.jsx
  118:7  error  React Hook "useCallback" is called conditionally. React Hooks must be called in the exact same order in every component render  react-hooks/rules-of-hooks

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/01_Normal_Paths_Omnibus.js
   176:34  error  'actualOrder' is already declared in the upper scope on line 157 column 13  no-shadow
   203:31  error  'newOrder' is already declared in the upper scope on line 192 column 13     no-shadow
  1100:9   error  Expected an assignment or function call and instead saw an expression       no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/03_Text_Chat.js
  200:9  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  215:9  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/15_Scroll_Indicator.js
   48:21  error  Assignment to property of function parameter '$el'  no-param-reassign
   69:21  error  Assignment to property of function parameter '$el'  no-param-reassign
  110:21  error  Assignment to property of function parameter '$el'  no-param-reassign

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/16_Video_Discussion_Layouts.js
  162:11  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  179:11  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  187:9   error  Expected an assignment or function call and instead saw an expression  no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/getTreatments.js
    8:33  error  Missing file extension for "./preFlight/validateTreatmentFile"  import/extensions
  327:30  error  Unexpected 'await' inside a loop                                no-await-in-loop

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/index.js
  42:43  error  'p' is defined but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/dispatch.js
  477:9  error  Unexpected use of continue statement  no-continue

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/preFlightChecks.js
  20:5  error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/validateDlConfig.test.js
  2:32  error  Missing file extension for "./validateDlConfig"                                                                            import/extensions
  3:1   error  'vscode' import should occur before import of './validateDlConfig'                                                         import/order
  6:25  error  Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the '=>'  arrow-body-style

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/validatePromptFile.test.js
  7:8  error  Missing file extension for "./validatePromptFile"  import/extensions

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/providers/dailyco.js
   37:23  error  Expected to return a value at the end of async function 'createRoom'     consistent-return
  157:23  error  Expected to return a value at the end of async function 'stopRecording'  consistent-return
  229:7   error  Unexpected if as the only statement in an else block                     no-lonely-if

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/utils/logging.js
  1:17  error  'warn' is defined but never used  no-unused-vars
  1:29  error  'log' is defined but never used   no-unused-vars

✖ 76 problems (67 errors, 9 warnings)
  7 errors and 0 warnings potentially fixable with the '--fix' option.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors the VideoCall.jsx component by extracting large chunks of call/device lifecycle logic into focused custom hooks, aiming to keep behavior the same while improving maintainability and reuse.

Changes:

  • Extracts multiple concerns (join/leave, device alignment, gesture gating, permissions, track monitoring, visibility tracking, etc.) into dedicated hooks under client/src/call/hooks/.
  • Consolidates device alignment logic (camera/mic/speaker) into a single useDeviceAlignment hook using the shared findMatchingDevice utility.
  • Reduces VideoCall.jsx to an orchestrator that wires hooks into UI components/modals.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
client/src/call/VideoCall.jsx Replaces in-component monolith logic with hook composition; keeps UI orchestration.
client/src/call/hooks/useDisplayNameSync.js Syncs Empirica name/title into Daily displayName.
client/src/call/hooks/useDailyIdTracking.js Tracks dailyId + structured dailyIdHistory on player at join time.
client/src/call/hooks/useCallStartSignaling.js Signals stage callStarted on joined-meeting, with retry when stage becomes available.
client/src/call/hooks/useVisibilityTracking.js Logs visibility/focus/blur events into player state for debugging/analytics.
client/src/call/hooks/useTrackMonitor.js Polls local track readyState and attempts auto-recovery if ended.
client/src/call/hooks/usePermissionMonitor.js Uses Permissions API to detect mid-call revocations and route into device error UI.
client/src/call/hooks/useCallLifecycle.js Joins/leaves Daily room and implements Firefox blur-stall detection behavior.
client/src/call/hooks/useDeviceErrors.js Centralizes Daily error listeners + deduped per-device error state.
client/src/call/hooks/useGesturePrompt.js Tracks gesture-gated operations and derives unified prompt/modal state.
client/src/call/hooks/useDeviceRecovery.js Listens for devicechange to auto-switch after not-found errors.
client/src/call/hooks/useDeviceAlignment.js Aligns Daily devices to stored Empirica preferences and exposes picker switch handler.

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

You can also share your feedback on Copilot code review. Take the survey.

try {
if (!callObject.isDestroyed?.()) {
await devices.setSpeaker(targetId);
setSpeakerError((prev) => (prev?.dailyErrorType === "not-found" ? null : prev));

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

setSpeakerError((prev) => ...) has the same issue as the camera/mic path: setSpeakerError from useDeviceErrors does not accept functional updaters, so this won’t reliably clear a stale not-found picker error (and can even set state to a function). Clear conditionally by inspecting the current speakerError value and calling setSpeakerError(null) only when it’s a not-found error, or adjust the useDeviceErrors API to handle updater functions.

Suggested change
setSpeakerError((prev) => (prev?.dailyErrorType === "not-found" ? null : prev));
if (errorValues?.speakerError?.dailyErrorType === "not-found") {
setSpeakerError(null);
}

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +34
const allDevices = await navigator.mediaDevices.enumerateDevices();
const isSpeaker = deviceError.type === "speaker-error";
const isCamera = deviceError.type === "camera-error";
const relevantDevices = allDevices.filter((d) => {
if (isCamera) return d.kind === "videoinput";
if (isSpeaker) return d.kind === "audiooutput";
return d.kind === "audioinput";
});

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

useDeviceRecovery treats any deviceError that isn’t camera-error or speaker-error as a microphone failure (audioinput). If the active error is fatal-devices-error (which useDeviceErrors stores in cameraError) and happens to have dailyErrorType: 'not-found', this hook will auto-switch the mic incorrectly. Consider explicitly handling only camera-error / mic-error / speaker-error (early-return otherwise) and branching on deviceError.type === 'mic-error' for the mic case.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +98
// Track blur events during join to detect stalled joins
const handleBlurDuringJoin = () => {
if (joiningMeetingRef.current) {
blurredDuringJoinRef.current = true;
// If page blurs during join, show prompt after short delay (not 5s)
setTimeout(() => {
if (joiningMeetingRef.current && blurredDuringJoinRef.current) {
console.warn("[VideoCall] Join stalled - tab blurred during join");
setJoinStalled(true);
}
}, 500);
}
};
window.addEventListener("blur", handleBlurDuringJoin);

// Fallback stall detection - if join takes > 5s and tab was blurred, prompt user
const stallTimer = setTimeout(() => {
if (joiningMeetingRef.current && blurredDuringJoinRef.current) {
console.warn(
"[VideoCall] Join appears stalled due to tab blur - prompting user"
);
setJoinStalled(true);
}
}, 5000);

const joinRoom = async () => {
const meetingState = callObject.meetingState?.();
if (meetingState === "joined-meeting" || joiningMeetingRef.current)
return;

const joinStartTime = Date.now();
joinStartTimeRef.current = joinStartTime;
// Check if page is ALREADY unfocused when join starts
const alreadyUnfocused = !document.hasFocus();
blurredDuringJoinRef.current = alreadyUnfocused;

// If page is already unfocused, show prompt quickly (Firefox suspends WebRTC when unfocused)
if (alreadyUnfocused) {
setTimeout(() => {
if (joiningMeetingRef.current) {
console.warn(
"[VideoCall] Join stalled - page was unfocused at start"
);
setJoinStalled(true);
}
}, 500);
}

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

The 500ms setTimeout callbacks (blur-during-join and already-unfocused paths) aren’t tracked/cleared on cleanup. If the component unmounts or roomUrl changes while a join is in progress, these timers can still fire and call setJoinStalled(true) after unmount. Consider storing these timeout IDs and clearing them in the cleanup, and/or setting joiningMeetingRef.current = false in cleanup so the callbacks bail out reliably.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +100
// Log to Sentry for monitoring browser policy trends
Sentry.captureMessage("Setup operation requires user gesture", {
level: "info",
tags: {
operation,
browser: navigator.userAgent,
},
extra: { error: error?.message, details },
});

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

Sentry.captureMessage is adding navigator.userAgent as a tag (tags: { browser: navigator.userAgent }). Full UA strings are high-cardinality and can significantly increase Sentry index volume/cost; tags are best kept to low-cardinality values. Consider tagging a coarse browser name (e.g., "Chrome"/"Firefox"/"Safari") and putting the full UA string into extra/contexts instead.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +43
const checkTracks = async () => {
try {
const [audioTrack, videoTrack] = await Promise.all([
callObject.localAudio?.(),
callObject.localVideo?.(),
]);

if (audioTrack?.readyState === "ended") {
Sentry.addBreadcrumb({
category: "track-monitor",
message: "Audio track ended — attempting auto-recovery",
level: "warning",
});
await callObject.setInputDevicesAsync({ audioDeviceId: true });
}

if (videoTrack?.readyState === "ended") {
Sentry.addBreadcrumb({
category: "track-monitor",
message: "Video track ended — attempting auto-recovery",
level: "warning",
});
await callObject.setInputDevicesAsync({ videoDeviceId: true });
}

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

The track polling loop only checks callObject.isDestroyed?.() once when the effect mounts. If the call object is destroyed later, the interval can keep firing and attempt localAudio/localVideo/setInputDevicesAsync, causing avoidable warnings/errors. Add an isDestroyed guard inside checkTracks (and/or stop the interval when the call transitions to a destroyed state).

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +163
console.log("Disabled AGC via setInputDevicesAsync");
} catch (agcErr) {
console.warn("Failed to disable AGC:", agcErr);

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

This block documents that setInputDevicesAsync({ audioSource: constraintsObject }) is known not to work (expects a MediaStreamTrack and may silently no-op), but the code still logs "Disabled AGC via setInputDevicesAsync" on success. Since Daily may not throw on a no-op, that log can be misleading during debugging. Consider removing this call until implemented properly, or changing the log to reflect that it’s only an attempted (and currently ineffective) configuration.

Suggested change
console.log("Disabled AGC via setInputDevicesAsync");
} catch (agcErr) {
console.warn("Failed to disable AGC:", agcErr);
console.log(
"Attempted to disable AGC via setInputDevicesAsync (this configuration may currently be a no-op; see comment above)."
);
} catch (agcErr) {
console.warn("Failed to attempt AGC disable via setInputDevicesAsync:", agcErr);

Copilot uses AI. Check for mistakes.
try {
if (!callObject.isDestroyed?.()) {
await callObject.setInputDevicesAsync({ [inputDeviceKey]: targetId });
setError((prev) => (prev?.dailyErrorType === "not-found" ? null : prev));

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

setError((prev) => ...) passes a function into setCameraError/setMicError, but those setters (from useDeviceErrors) expect an error object (or null) and implement their own dedupe logic. This will either set state to a function or (more likely) fail to clear a stale not-found picker error. Use setError(null) when the current error is dailyErrorType === 'not-found' (you already have cameraError/micError available via errorValues), or update useDeviceErrors setters to explicitly support functional updaters.

Suggested change
setError((prev) => (prev?.dailyErrorType === "not-found" ? null : prev));
// Clear stale "not-found" picker errors after successful alignment.
let currentError = null;
if (deviceType === "camera") {
currentError = errorValues?.cameraError ?? null;
} else if (deviceType === "microphone") {
currentError = errorValues?.micError ?? null;
} else if (deviceType === "speaker") {
currentError = errorValues?.speakerError ?? null;
}
if (currentError?.dailyErrorType === "not-found") {
setError(null);
}

Copilot uses AI. Check for mistakes.
@cypress

cypress Bot commented Mar 4, 2026

Copy link
Copy Markdown

Deliberation    Run #2087

Run Properties:  status check passed Passed #2087  •  git commit 2442a58296: fix(call): address Copilot review feedback on extracted hooks
Project Deliberation
Branch Review refactor/videocall-extract-hooks
Run status status check passed Passed #2087
Run duration 09m 06s
Commit git commit 2442a58296: fix(call): address Copilot review feedback on extracted hooks
Committer James Houghton
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 1
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 21
View all changes introduced in this branch ↗︎

- Fix functional updater bug: useDeviceErrors setters don't support
  React-style functional updaters, so replace setError(prev => ...)
  with conditional setError(null) using the actual error values
- Add explicit type guard in useDeviceRecovery to only handle
  camera-error/mic-error/speaker-error (avoid mis-routing)
- Track and clear inline setTimeout IDs in useCallLifecycle cleanup
  to prevent state updates after unmount
- Add isDestroyed guard inside useTrackMonitor polling interval
- Replace high-cardinality navigator.userAgent Sentry tag with
  coarse browser name (Chrome/Safari/Firefox/Edge/Other)
- Fix misleading AGC success log (the call is currently a no-op)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Mar 4, 2026

Copy link
Copy Markdown
/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/Call.jsx
  73:9  warning  The 'players' logical expression could make the dependencies of useMemo Hook (at line 213) change on every render. To fix this, wrap the initialization of 'players' in its own useMemo() Hook  react-hooks/exhaustive-deps
  73:9  warning  The 'players' logical expression could make the dependencies of useMemo Hook (at line 242) change on every render. To fix this, wrap the initialization of 'players' in its own useMemo() Hook  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/FixAV.jsx
  332:36  error  Return values from promise executor functions cannot be read  no-promise-executor-return
  510:38  error  Return values from promise executor functions cannot be read  no-promise-executor-return
  658:25  error  Do not use Array index in keys                                react/no-array-index-key
  692:25  error  Do not use Array index in keys                                react/no-array-index-key
  749:25  error  Do not use Array index in keys                                react/no-array-index-key
  806:25  error  Do not use Array index in keys                                react/no-array-index-key
  856:25  error  Do not use Array index in keys                                react/no-array-index-key

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/layouts/defaultResponsiveLayout.test.js
  1:32  error  'vi' is defined but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/utils/avRecovery.js
  221:13  error  Use object destructuring                                                                                                                                                   prefer-destructuring
  251:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  257:21  error  Unexpected use of continue statement                                                                                                                                       no-continue
  317:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  320:7   error  Unexpected use of continue statement                                                                                                                                       no-continue
  331:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  347:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  365:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  376:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  393:15  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  404:13  error  Unexpected 'await' inside a loop                                                                                                                                           no-await-in-loop
  455:3   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  457:21  error  Unexpected use of continue statement                                                                                                                                       no-continue

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/call/utils/avRecovery.test.js
   42:7   error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax
  506:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars
  545:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars
  586:45  error  'label' is defined but never used                                                                                                                                          no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/chat/Chat.jsx
  37:7  warning  React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/EmpiricaMenu.jsx
  51:3  error  React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?  react-hooks/rules-of-hooks

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/IdleProvider.jsx
   57:6   warning  React Hook useEffect has a missing dependency: 'resetTimer'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
  120:55  error    ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'                                                react/no-unescaped-entities

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/PermissionRecovery.jsx
  79:5  error  Do not nest ternary expressions  no-nested-ternary

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/SharedNotepad.jsx
  27:6  warning  React Hook useEffect has missing dependencies: 'defaultText', 'game', and 'record'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/components/scroll/useScrollAwareness.js
   67:19  error  Use object destructuring                                                                prefer-destructuring
  108:39  error  Use the '**' operator instead of 'Math.pow'                                             prefer-exponentiation-operator
  108:39  error  'Math.pow' is restricted from being used. Use the exponentiation operator (**) instead  no-restricted-properties

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/Qualtrics.jsx
  55:10  error  'state' is assigned a value but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/Survey.jsx
  9:26  error  Unexpected function expression  prefer-arrow-callback

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/elements/TrainingVideo.jsx
  61:6  warning  React Hook useEffect has missing dependencies: 'timer?.elapsed' and 'url'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Consent.jsx
  132:6  warning  React Hook useEffect has a missing dependency: 'player'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Countdown.jsx
  8:21  error  Use default import syntax to import 'ReactCountdown'  import/no-named-default

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/Lobby.jsx
  64:19  error  ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'  react/no-unescaped-entities
  64:51  error  ''' can be escaped with '&apos;', '&lsquo;', '&#39;', '&rsquo;'  react/no-unescaped-entities

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/AudioEquipmentCheck.jsx
   87:6   warning  React Hook useEffect has a missing dependency: 'permissionsStatus'. Either include it or remove the dependency array  react-hooks/exhaustive-deps
  104:24  error    Unnecessary return statement                                                                                          no-useless-return
  112:5   error    Arrow function expected no return value                                                                               consistent-return

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/CameraCheck.jsx
  130:6  warning  React Hook useEffect has a missing dependency: 'devices'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/GetPermissions.jsx
    3:38  error  'useCallback' is defined but never used  no-unused-vars
  149:5   error  Do not nest ternary expressions          no-nested-ternary

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/LoopbackCheck.jsx
  165:36  error  Return values from promise executor functions cannot be read  no-promise-executor-return

/home/runner/work/deliberation-empirica/deliberation-empirica/client/src/intro-exit/setup/MicCheck.jsx
  118:7  error  React Hook "useCallback" is called conditionally. React Hooks must be called in the exact same order in every component render  react-hooks/rules-of-hooks

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/01_Normal_Paths_Omnibus.js
   176:34  error  'actualOrder' is already declared in the upper scope on line 157 column 13  no-shadow
   203:31  error  'newOrder' is already declared in the upper scope on line 192 column 13     no-shadow
  1100:9   error  Expected an assignment or function call and instead saw an expression       no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/03_Text_Chat.js
  200:9  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  215:9  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/15_Scroll_Indicator.js
   48:21  error  Assignment to property of function parameter '$el'  no-param-reassign
   69:21  error  Assignment to property of function parameter '$el'  no-param-reassign
  110:21  error  Assignment to property of function parameter '$el'  no-param-reassign

/home/runner/work/deliberation-empirica/deliberation-empirica/cypress/e2e/16_Video_Discussion_Layouts.js
  162:11  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  179:11  error  Expected an assignment or function call and instead saw an expression  no-unused-expressions
  187:9   error  Expected an assignment or function call and instead saw an expression  no-unused-expressions

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/getTreatments.js
    8:33  error  Missing file extension for "./preFlight/validateTreatmentFile"  import/extensions
  327:30  error  Unexpected 'await' inside a loop                                no-await-in-loop

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/index.js
  42:43  error  'p' is defined but never used  no-unused-vars

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/dispatch.js
  477:9  error  Unexpected use of continue statement  no-continue

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/preFlightChecks.js
  20:5  error  iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations  no-restricted-syntax

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/validateDlConfig.test.js
  2:32  error  Missing file extension for "./validateDlConfig"                                                                            import/extensions
  3:1   error  'vscode' import should occur before import of './validateDlConfig'                                                         import/order
  6:25  error  Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the '=>'  arrow-body-style

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/preFlight/validatePromptFile.test.js
  7:8  error  Missing file extension for "./validatePromptFile"  import/extensions

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/providers/dailyco.js
   37:23  error  Expected to return a value at the end of async function 'createRoom'     consistent-return
  157:23  error  Expected to return a value at the end of async function 'stopRecording'  consistent-return
  229:7   error  Unexpected if as the only statement in an else block                     no-lonely-if

/home/runner/work/deliberation-empirica/deliberation-empirica/server/src/utils/logging.js
  1:17  error  'warn' is defined but never used  no-unused-vars
  1:29  error  'log' is defined but never used   no-unused-vars

✖ 76 problems (67 errors, 9 warnings)
  7 errors and 0 warnings potentially fixable with the '--fix' option.

@JamesPHoughton
JamesPHoughton merged commit 62df4f4 into main Mar 4, 2026
10 checks passed
@JamesPHoughton
JamesPHoughton deleted the refactor/videocall-extract-hooks branch March 4, 2026 19:46
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