Skip to content

StepperHostPlugin's showTab() steals focus from the current tab on every language switch #70

Description

@Shrey5132

Summary

StepperHostPlugin's constructor (src/web/stepper/src/StepperHostPlugin.tsx:77-78) calls:

tabService.registerTab(tab);
tabService.showTab(tab.id);

ITabService.showTab() doesn't just make a tab visible — per its implementation in frontend (SideContentManager.ts's TabService.showTab), it also dispatches visitSideContent, unconditionally changing the currently-selected tab. Since the plugin's constructor runs as soon as the language/evaluator loads (well before the student has actually used the stepper), this means every language switch to a language with the stepper available steals focus away from whatever tab the student had open (typically the welcome/introduction tab) and jumps straight to the Stepper tab, even though nothing has happened yet.

Where this was found

While migrating a Data Visualizer plugin for py-slang, I copied this exact registerTab+showTab pattern from StepperHostPlugin.tsx (it's the established precedent for a Conductor host plugin's tab registration) and it produced the identical bug: the Data Visualizer tab was auto-selected on every language switch, when the introduction tab should stay selected until the student navigates on their own.

Fix applied for Data Visualizer (same fix needed here)

Rather than patch showTab's behavior (which is intentional and correct for plugins that do want to steal focus the moment they have something to show, per its doc comment — e.g. a sound module the instant it starts play()/record()), I added a new method to ITabService:

/**
 * Makes a registered tab visible in the tab bar without changing which tab is currently selected —
 * the visible-but-unfocused counterpart to {@link showTab}.
 */
revealTab(id: string): void;

implemented in TabService/SideContentManager.ts (visibility only, no visitSideContent dispatch) and in DeferredConductorTabService (tracking visibility and "last explicitly focused tab" separately, so replay-on-activate() only re-selects a tab that was genuinely showTab'd, not merely revealed).

DataVisualizerHostPlugin.tsx now calls tabService.revealTab(tab.id) instead of showTab. StepperHostPlugin.tsx should get the same one-line change once revealTab is available on ITabService — I didn't make that change here since it's outside the scope of what I was asked to fix, but it's a straight swap of showTabrevealTab at StepperHostPlugin.tsx:78.

Note: revealTab isn't on main yet — it's part of the Data Visualizer PR I'm opening alongside this issue. This issue (and the corresponding stepper fix) is a follow-up, not blocking that PR.

Reproduction

  1. Load the Playground with any language, confirm the introduction/welcome tab is selected.
  2. Switch to a language for which the Stepper plugin is available (e.g. any Source chapter with stepperEvaluatorId set, under the conductor).
  3. Observe the Stepper tab becomes selected automatically, even though draw_data/stepping has never been invoked.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions