Skip to content

Comments

fix/refactor #1706: reuse StepSelector in VideoCall, fix moderated proceed flow, correct remote media status, and localize new UI strings#1707

Open
Santosh69 wants to merge 6 commits intoruxailab:developfrom
Santosh69:feat/moderated-test-session-flow-improvements
Open

fix/refactor #1706: reuse StepSelector in VideoCall, fix moderated proceed flow, correct remote media status, and localize new UI strings#1707
Santosh69 wants to merge 6 commits intoruxailab:developfrom
Santosh69:feat/moderated-test-session-flow-improvements

Conversation

@Santosh69
Copy link
Contributor

Fixes #1706

Description

This PR replaces VideoCall’s hardcoded custom stepper HTML with an embedded reusable StepSelector component, adds proper parent-bridge handling for step/task selection, fixes participant role/media status rendering, and localizes newly introduced moderated-session UI strings.

Proof of Work

Before:

Procced-to-Next-Step.Bug.mp4
Stepper.Panel.and.Participant.list.Behaviour.Before.mp4

After:

Stepper.Panel.and.Participant.list.Behaviour.After.mp4

Problem Analysis

Before Fix — What existed on develop:

Area Upstream State Status
Step display in VideoCall ~120 lines of hardcoded custom stepper HTML in VideoCall side panel ⚠️ Not reusable / tightly coupled
Task navigation goToStep() and goToSpecificTask() existed, but restart-to-welcome flow was missing ⚠️ Partial
Proceed button proceedToNextStep wrote current values back to room (globalIndex: globalIndex.value, taskIndex: taskIndex.value) without advancing ❌ Broken
Participant media status participantsList used role-based truthy fallback (role !== 'observator') for remote camera/mic chips ❌ Inaccurate
Participant role labeling Moderator/evaluator handling was inconsistent in participant list rendering ⚠️ Inconsistent

Important upstream reference (proceed no-op):
src/ux/UserTest/views/ModeratedTestView.vue (develop) around proceedToNextStep:

  • writes current globalIndex and taskIndex
  • does not compute next step/task

Solution

  • Replaced VideoCall inline custom stepper with embedded StepSelector.
  • Added step/task event bridge from StepSelector -> VideoCall -> ModeratedTestView.
  • Implemented correct progression logic in parent proceedToNextStep (including task-loop inside global step 4).
  • Added restart-to-welcome handler.
  • Fixed participant list to use real remote media flags from participant payload and consistent role resolution.
  • Localized all newly added/changed moderated StepSelector/VideoCall strings via en.json and t().

Key Changes

Commit 1c1d4fdb — Replace custom stepper with StepSelector and task navigation

src/ux/UserTest/components/StepSelector.vue

  • Added props: embedded, isModerator, taskItems, currentTaskIndex, showTaskSelector, taskSelectorDisabled
  • Embedded mode behavior:
    • hides local toggle button (v-if="!embedded")
    • auto-opens panel (showStepPanel = ref(props.embedded))
    • uses .step-panel-embedded styles for inline/panel embedding
  • Role gating via isModerator for interactive actions (step clicks, go-to/revisit, proceed, restart, task dropdown)
  • Added task dropdown + taskSelected emit
  • Added @click.stop on dropdown wrapper to avoid row click bubbling

src/ux/UserTest/components/VideoCall.vue

  • Removed hardcoded custom stepper markup and replaced with <StepSelector embedded ... />
  • Added stepsList computed (Welcome → Completion)
  • Added normalizedCurrentTaskIndex for safe task index display/use
  • Added handleStepSelectorStep() bridge
  • Added resetToWelcome()
  • Kept and wired existing goToStep(), goToSpecificTask(), taskDropdownItems, showStepperPanel, toggleStepperPanel()

src/ux/UserTest/views/ModeratedTestView.vue

  • Added correct progression logic in proceedToNextStep (switch-based lifecycle + task iteration)
  • Added handleStepSelected({ globalIndex, taskIndex }) to sync local state and room state
  • Wired @step-selected and @proceed-to-next-step from VideoCall

Commit d30e1a8d — Participant role/media consistency

src/ux/UserTest/components/VideoCall.vue

  • Improved role resolution for moderator/evaluator/observator/participant
  • Added evaluator icon/chip rendering
  • Fixed remote media status in participantsList:
    • from: role-based fallback (role !== 'observator')
    • to: participant media payload flags (p?.media?.cameraEnabled, p?.media?.microphoneEnabled)
  • Adjusted chip visibility so camera/mic status reflects participant state consistently for non-observator views

Root cause fixed: remote media chips were not using actual per-user media state; they were effectively “always enabled” for non-observators.


Commit d9268ef8 — Localize moderated StepSelector/VideoCall UI

src/app/plugins/locales/en.json

  • Added UserTestView.StepSelector.* keys:
    • showSteps, hideSteps, title, currentStep, currentStepProgress, allSteps, stepLabel, taskPlaceholder, revisit, goTo, testComplete, proceedToStep, restartTest
  • Added UserTestView.VideoCall.* keys:
    • labels/tooltips/buttons
    • participants roles/labels
    • step descriptions (welcomecompletion)
    • dialog strings (joinVideoCall, maybeLater)
  • Added moderated disconnect/inline message keys used in moderated flow

src/ux/UserTest/components/StepSelector.vue + src/ux/UserTest/components/VideoCall.vue + src/ux/UserTest/views/ModeratedTestView.vue

  • Replaced hardcoded strings with t(...) usage for new/updated moderated UI text

Impact

Area Before (develop) After
Step UI architecture Hardcoded inline stepper in VideoCall Reusable embedded StepSelector
Proceed flow No-op room sync (no advancement) Correct lifecycle + task-loop progression
Task jump/restart Partial controls Task dropdown + restart-to-welcome
Role gating Scattered checks Centralized via StepSelector isModerator
Participant media chips Role-based fallback, inaccurate Real remote media flags from participant payload
Participant roles Inconsistent moderator/evaluator treatment Consistent role resolution + chips/icons
i18n New moderated UI not fully localized New StepSelector/VideoCall strings localized via en.json

Files Modified

  • src/ux/UserTest/components/StepSelector.vue
  • src/ux/UserTest/components/VideoCall.vue
  • src/ux/UserTest/views/ModeratedTestView.vue
  • src/app/plugins/locales/en.json

Testing

  1. Admin step navigation
  • Open moderated session as admin
  • Open step panel from VideoCall
  • Use Go To on different steps
  • Verify participant view receives selected step (room sync)
  1. Proceed progression
  • Use Proceed to Next Step
  • Verify progression through lifecycle and task-loop handling in tasks step
  1. Task dropdown
  • At tasks step, select different task from dropdown
  • Verify task index sync and correct task render
  1. Permission gating
  • Open as evaluator/participant
  • Verify read-only progress visibility; no moderator-only actions
  1. Participant media status
  • Toggle camera/mic for a participant
  • Verify chips reflect actual remote media state
  1. i18n
  • Verify new StepSelector/VideoCall labels/tooltips/messages come from en.json

Future (Follow-up, out of scope)

  • Unify participant media reads in one helper path across all UI usages.
  • Add integration tests for moderated step/task synchronization.
  • Extend new i18n keys to additional locales (es.json, others).
  • Clarify room persistence/reset policy for new session vs resume behavior.

Copilot AI review requested due to automatic review settings February 19, 2026 19:11
@Santosh69
Copy link
Contributor Author

resolving the checks

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the moderated test video call interface to use a reusable StepSelector component, fixes the broken proceed-to-next-step flow, corrects participant media status rendering, and adds comprehensive i18n support for the moderated test UI.

Changes:

  • Replaced hardcoded custom stepper HTML in VideoCall with embedded StepSelector component with proper parent-child event bridging
  • Implemented correct step progression logic in ModeratedTestView that actually advances through lifecycle stages and tasks
  • Fixed participant list to use actual remote media flags (p?.media?.cameraEnabled/microphoneEnabled) instead of role-based fallback, and improved role resolution for moderator/evaluator/observator/participant

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.

File Description
src/ux/UserTest/components/StepSelector.vue Added embedded mode support, isModerator gating, task dropdown integration, and i18n for reusable step navigation component
src/ux/UserTest/components/VideoCall.vue Replaced custom stepper with embedded StepSelector, added step/task event bridges, fixed participant media status rendering, and localized all UI strings
src/ux/UserTest/views/ModeratedTestView.vue Implemented correct proceedToNextStep progression logic with switch-based lifecycle and task-loop handling, added handleStepSelected bridge, localized alert messages
src/app/plugins/locales/en.json Added comprehensive i18n keys for StepSelector, VideoCall, and moderated test alerts
Comments suppressed due to low confidence (1)

src/ux/UserTest/components/StepSelector.vue:456

  • The step items have cursor: pointer and hover effects applied unconditionally, but non-moderators cannot select steps (the click handler checks isModerator). This creates misleading UI feedback. Consider conditionally applying the cursor and hover styles based on isModerator prop, or add a disabled state class when !isModerator to visually indicate that the steps are not interactive for non-moderators.
.step-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  padding: 16px;
  margin-bottom: 8px;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  border: 2px solid transparent;
}

.step-item:hover {
  background: #f5f5f5;
  transform: translateX(4px);
}

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

default:
return
}
globalIndex.value = nextGlobalIndex
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The local state update globalIndex.value = nextGlobalIndex occurs before the database update. If the database update fails, the local state will be out of sync with the persisted state. Consider moving this assignment after the successful database update or wrapping it in a try-catch block to handle potential errors.

Copilot uses AI. Check for mistakes.
Comment on lines 830 to 834
globalIndex.value = nextGlobalIndex
const roomRef = dbRef(database, `rooms/${roomId.value}`)
await update(roomRef, {
globalIndex: globalIndex.value,
taskIndex: taskIndex.value,
globalIndex: nextGlobalIndex,
taskIndex: nextTaskIndex,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Missing taskIndex assignment before database update. While nextTaskIndex is correctly calculated, the local taskIndex.value is not updated to match. This could cause the UI to display outdated task information until the next reactive update occurs. Add taskIndex.value = nextTaskIndex after line 830.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +112 to +113
index + 1 === 5 &&
currentStep === 5 &&
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The task selector visibility is hardcoded to check index + 1 === 5, which assumes the tasks step is always at position 5. This creates a tight coupling between the step order and the component logic. If the step structure changes in the future, this condition will need manual updates. Consider making this more dynamic by either passing a prop that indicates which step should show the task selector (e.g., taskStepNumber prop) or having the parent component control this entirely through the showTaskSelector prop without the additional index + 1 === 5 check.

Suggested change
index + 1 === 5 &&
currentStep === 5 &&
currentStep === index + 1 &&

Copilot uses AI. Check for mistakes.
Comment on lines 530 to 531
.task-inline {
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Empty CSS rule .task-inline with no properties. This should either be removed if unused or have its intended styles added. Empty rules add unnecessary noise to the stylesheet.

Suggested change
.task-inline {
}

Copilot uses AI. Check for mistakes.
Comment on lines 508 to 513
margin-top: 12px;
flex: 0 0 100%;
width: 100%;
margin-top: 10px;
}

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Duplicate margin-top property in .tasks-dropdown. Line 508 sets margin-top: 12px and line 511 sets margin-top: 10px. The second declaration overrides the first, making line 508 redundant. Remove the duplicate to clean up the CSS.

Suggested change
margin-top: 12px;
flex: 0 0 100%;
width: 100%;
margin-top: 10px;
}
flex: 0 0 100%;
width: 100%;
margin-top: 10px;
}

Copilot uses AI. Check for mistakes.
<div class="step-selector">
<!-- Step Selector Button -->
<v-tooltip location="top">
<!-- Step Selector Button (Hide toggle button whenn embedded-->
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Typo in comment: "whenn" should be "when"

Suggested change
<!-- Step Selector Button (Hide toggle button whenn embedded-->
<!-- Step Selector Button (Hide toggle button when embedded-->

Copilot uses AI. Check for mistakes.
Comment on lines 815 to 822
case 4:
if (taskIndex.value < totalTasks - 1) {
nextTaskIndex = taskIndex.value + 1
} else {
nextGlobalIndex = 5
nextTaskIndex = 0
}
break
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Edge case: if there are no tasks (totalTasks === 0), the proceed logic at globalIndex 4 will immediately advance to globalIndex 5 since 0 < -1 is false. While this may be acceptable behavior (skip tasks step if no tasks), it should be explicitly validated that the test structure always has at least one task when globalIndex 4 is reached, or the UI should prevent reaching this step if no tasks exist. Consider adding a guard or comment explaining this behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 549 to 560
<v-chip
v-if="participant.isSelf && !isObservator"
v-if="!isObservator"
size="x-small"
:color="participant.hasCamera ? 'green' : 'red'"
class="ml-1"
>
{{ participant.hasCamera ? 'Cámara' : 'Sin cámara' }}
{{
participant.hasCamera
? t('UserTestView.VideoCall.participants.cameraOn')
: t('UserTestView.VideoCall.participants.cameraOff')
}}
</v-chip>
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The camera and microphone status chips use v-if="!isObservator" which checks if the current viewing user is an observator. This means observators cannot see the camera/microphone status of any participants, including the moderator and regular participants. The condition should check whether the individual participant being displayed is an observator (participant.role !== 'observator'), not whether the current user is an observator. Change the condition to v-if="participant.role !== 'observator'" to properly display media status for non-observator participants while hiding it for observators.

Copilot uses AI. Check for mistakes.
Comment on lines 561 to 572
<v-chip
v-if="participant.isSelf && !isObservator"
v-if="!isObservator"
size="x-small"
:color="participant.hasMicrophone ? 'green' : 'red'"
class="ml-1"
>
{{
participant.hasMicrophone ? 'Micrófono' : 'Sin micrófono'
participant.hasMicrophone
? t('UserTestView.VideoCall.participants.microphoneOn')
: t('UserTestView.VideoCall.participants.microphoneOff')
}}
</v-chip>
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The microphone status chip uses v-if="!isObservator" which checks if the current viewing user is an observator. This means observators cannot see the microphone status of any participants, including the moderator and regular participants. The condition should check whether the individual participant being displayed is an observator (participant.role !== 'observator'), not whether the current user is an observator. Change the condition to v-if="participant.role !== 'observator'" to properly display media status for non-observator participants while hiding it for observators.

Copilot uses AI. Check for mistakes.
Comment on lines 882 to 883
hasCamera: role !== 'observator' && p?.media?.cameraEnabled,
hasMicrophone: role !== 'observator' && p?.media?.microphoneEnabled,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The remote media status relies on p?.media?.cameraEnabled and p?.media?.microphoneEnabled which may be undefined if a participant hasn't updated their status yet. While helper functions isRemoteCameraEnabled and isRemoteMicrophoneEnabled exist (lines 1344-1355) with proper fallback logic, they are not being used here. Consider using these helper functions or adding explicit fallback handling like p?.media?.cameraEnabled ?? false to avoid displaying incorrect status when media object is undefined.

Suggested change
hasCamera: role !== 'observator' && p?.media?.cameraEnabled,
hasMicrophone: role !== 'observator' && p?.media?.microphoneEnabled,
hasCamera: role !== 'observator' && isRemoteCameraEnabled(p),
hasMicrophone: role !== 'observator' && isRemoteMicrophoneEnabled(p),

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

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐞BUG]: Moderated session uses hardcoded VideoCall stepper, proceed flow does not advance, and remote media status is inaccurate

1 participant