Skip to content

Revert Multistream Toggle (2) - #6147

Merged
michelinewu merged 15 commits into
masterfrom
mw_revert_ms_toggle_2
Aug 29, 2026
Merged

michelinewu merged 15 commits into
masterfrom
mw_revert_ms_toggle_2

Conversation

@michelinewu

@michelinewu michelinewu commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Replaces: #6142 which had a merge conflict with master. The branch tip is now the same as at 2c33aeabcaf3e3070098c1cecb45d475f793a9eb.

This PR reverts all related PRs that touch functionality:

  1. Add small and inline Spinner variants. #6111Add small and inline Spinner variants. #6111
  2. Fix onboarding test #6132Fix onboarding test #6132
  3. Add disabled radio button styling. #6112Add disabled radio button styling. #6112
  4. Add button to footer. #6113Add button to footer. #6113
  5. Fix tooltip wrapping. #6114Fix tooltip wrapping. #6114
  6. Move SwitcherCard tooltip hover to toggle. #6115Move SwitcherCard tooltip hover to toggle. #6115
  7. Add go live info banner. #6116Add go live info banner. #6116
  8. Fix Kick game persistence. #6139Fix Kick game persistence. #6139
  9. Update go live checklist. #6118Update go live checklist. #6118
  10. Edit stream window. #6119Edit stream window. #6119
  11. Add dismissable to AI Highlighter toggle in Edit Stream window. #6128Add dismissable to AI Highlighter toggle in Edit Stream window. #6128
  12. Add live output editing display selector handling. #6120Add live output editing display selector handling. #6120
  13. Disable enhanced broadcasting when live output editing is enabled. #6121Disable enhanced broadcasting when live output editing is enabled. #6121
  14. Multistream Toggle Fixes and Error Handling #6141Multistream Toggle Fixes and Error Handling #6141
  15. Fix padding on live output editing toggles. #6143Fix padding on live output editing toggles. #6143

Copilot AI lite review requested due to automatic review settings August 29, 2026 08:20

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

This PR reverts the “Multistream Toggle” / live output editing feature set and its related UI/service-layer changes (previously spread across several PRs) to restore the prior Go Live / Edit Stream behavior and reduce conflict with master.

Changes:

  • Rolls back live output editing + multistream toggle behaviors across streaming/restream services and Go Live/Edit Stream React UI.
  • Reverts error-type granularity and some display/mode handling introduced for live output editing/dual output interactions.
  • Updates i18n dictionaries and e2e test skips to match the reverted behavior.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/regular/filters.ts Adjusts which filter tests are skipped/enabled.
app/services/streaming/streaming.ts Reverts parts of restream update error handling and some helper logic.
app/services/streaming/streaming-view.ts Reverts dual output/live output editing precedence and common-fields behavior.
app/services/streaming/stream-error.ts Removes several RESTREAM-specific error types and helpers; updates messages.
app/services/restream.ts Reverts restream error typing and target/display routing behavior used during live updates.
app/services/platforms/twitch.ts Removes the live output editing → disable enhanced broadcasting enforcement.
app/services/dismissables.ts Removes Highlighter banner dismissable enum entry; adjusts dismissAll implementation.
app/i18n/fallback.ts Switches fallback dictionary inclusion from live-output-editing.json to live-outputs.json.
app/i18n/en-US/twitch.json Removes the live-output-editing enhanced broadcasting tooltip string.
app/i18n/en-US/streaming.json Adds updated enhanced broadcasting restream error string.
app/i18n/en-US/live-outputs.json Adds new live outputs dictionary (replacing the removed live-output-editing dictionary).
app/i18n/en-US/live-output-editing.json Deletes the previous live output editing dictionary.
app/i18n/en-US/highlighter.json Removes “Do not ask again” string tied to the reverted dismissable behavior.
app/components-react/windows/go-live/useGoLiveSettings.ts Reverts parts of settings coordination for dual output/live editing.
app/components-react/windows/go-live/StreamShiftCard.tsx Deletes the card-based Stream Shift UI component (reverted).
app/components-react/windows/go-live/PlatformSettings.tsx Reworks feature toggle cards and platform binding props to match the reverted model.
app/components-react/windows/go-live/platforms/TwitchEditStreamInfo.tsx Reverts enhanced broadcasting disabling/tooltip behavior for live output editing.
app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx Removes isLiveOutputEditingEnabled from platform settings params.
app/components-react/windows/go-live/LiveOutputEditingCard.tsx Deletes the card-based Live Output Editing UI component (reverted).
app/components-react/windows/go-live/GoLiveWindow.tsx Minor comment placement changes around effect dependency intent.
app/components-react/windows/go-live/GoLiveSettings.tsx Reverts feature-flag gating and layout behavior for update mode and Stream Shift toggle.
app/components-react/windows/go-live/GoLiveError.tsx Collapses multiple RESTREAM error cases back to the generic restream error handling.
app/components-react/windows/go-live/GoLive.m.less Reverts/adjusts CSS class structure for banners/update mode styling.
app/components-react/windows/go-live/EditStreamWindow.tsx Reverts update-mode layout details and cooldown timing/message text.
app/components-react/windows/go-live/DestinationSwitchers.tsx Reverts live output editing-specific switcher disabling logic.
app/components-react/windows/go-live/CommonPlatformFields.tsx Reverts mid-stream common title disabling logic.
app/components-react/windows/go-live/AiHighlighterToggle.tsx Reverts update-mode dismissable behavior and auto-expand logic.
app/components-react/windows/go-live/AiHighlighterToggle.m.less Removes CSS for the update-mode “dismissable” link.
app/components-react/shared/StreamShiftToggle.tsx Reverts gating logic/tooltip behavior; adds force-enable logic for a specific edge case.
app/components-react/shared/inputs/RadioInput.m.less Removes disabled icon opacity styling.
app/components-react/shared/DisplaySelector.tsx Reverts live-output-editing-specific display option behavior and memo deps.
Suppressed comments (1)

app/components-react/windows/go-live/PlatformSettings.tsx:129

  • SwitcherCard still calls onClick even when disabled (see SwitcherCard.tsx:106-109), but this handler doesn’t guard against isLiveOutputEditingDisabled. That allows enabling live output editing even when it’s disabled (e.g., Stream Shift on).
  const handleToggleLiveOutputEditing = useCallback(
    (status?: boolean) => {
      if (!isPrime) {
        // TODO: Comment in when ready
        // Services.MagicLinkService.actions.linkToPrime('slobs-live-output-editing', {
        //   event: 'LiveOutputEditing',
        // });
        return;
      }

      setLiveOutputEditingEnabled(status ?? !isLiveOutputEditingEnabled);
      Services.UsageStatisticsService.actions.recordAnalyticsEvent('LiveOutputEditing', {
        toggle: status ?? !isLiveOutputEditingEnabled,
      });
    },
    [setLiveOutputEditingEnabled, isLiveOutputEditingEnabled],
  );

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useMemo, useCallback } from 'react';
Comment on lines +95 to +111
const handleToggleStreamShift = useCallback(
(status?: boolean) => {
if (!isPrime) {
// TODO: Comment in when ready
// Services.MagicLinkService.actions.linkToPrime('slobs-streamswitcher', {
// event: 'StreamShift',
// });
return;
}

setStreamShift(status ?? !isStreamShiftMode);
Services.UsageStatisticsService.actions.recordAnalyticsEvent('StreamShift', {
toggle: status ?? !isStreamShiftMode,
});
},
[setStreamShift, isStreamShiftMode],
);
Comment on lines +141 to +145
console.log(
'updating ',
this.state.customDestinations[otherEnabledTargetIndex]?.name,
' to disabled',
);
import React, { useEffect, useState, memo } from 'react';
import styles from './AiHighlighterToggle.m.less';
import { Services } from 'components-react/service-provider';
import * as remote from '@electron/remote';
Comment on lines 1663 to 1666
if (e instanceof StreamError) {
this.setError({ ...e, type: resolvedType });
return resolvedType;
this.setError({ ...e, type });
return e.type;
}
Comment thread app/services/restream.ts
Comment on lines +506 to +510
throwStreamError(
'RESTREAM_UPDATE_FAILED',
e,
`Unable to fetch user stream key for ${mode}.`,
);
Copilot AI review requested due to automatic review settings August 29, 2026 08:25

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

Copilot reviewed 32 out of 32 changed files in this pull request and generated 1 comment.

Suppressed comments (13)

Previously missed (3) — in code that hasn't changed since the last review.

app/services/restream.ts:1707

  • getMode() now returns portrait for the vertical display even when dual output mode is off. In single output mode there is no portrait stream; routing targets/removals to portrait can cause updates/removals to hit a non-running stream key.
  getMode(display: TDisplayType): TOutputOrientation {
    if (!display) return 'landscape';
    return display === 'horizontal' ? 'landscape' : 'portrait';
  }

app/components-react/shared/StreamShiftToggle.tsx:35

  • useEffect uses setStreamShift but it’s missing from the dependency array, which will fail react-hooks/exhaustive-deps and can capture a stale setter in unusual cases.
  useEffect(() => {
    // Ensure that non-ultra users have the stream switcher disabled
    if (!isPrime && isStreamShiftMode) {
      setStreamShift(false);
    }
  }, [isPrime, isStreamShiftMode]);

app/components-react/shared/StreamShiftToggle.tsx:58

  • forceStreamShiftToggleEnabled is intended to bypass the dual output restriction, but disableToggle still returns isStreamShiftDisabled, and isStreamShiftDisabled already includes isDualOutputMode. This makes the force flag ineffective (toggle stays disabled even when forceStreamShiftToggleEnabled is true).
  const disableToggle = useMemo(() => {
    if (p?.disabled) return true;
    if (!isPrime) return true;
    if (isPatreonEnabled) return true;
    if (isDualOutputMode && !forceStreamShiftToggleEnabled) return true;
    return isStreamShiftDisabled;
  }, [

app/services/streaming/streaming.ts:1666

  • When e is a StreamError, handleTypedStreamError overwrites the type in state ({ ...e, type }) but then returns e.type (the original type) instead of the resolved type. Callers relying on the returned type may rethrow/report the wrong error type.
    app/services/restream.ts:510
  • throwStreamError expects an IRejectedRequest as its 2nd argument, but this call passes the caught unknown error (e). This is a type mismatch and also doesn't match how throwStreamError is intended to be used (request metadata vs. exception object).
        console.error('Restream Error: Unable to fetch user stream key for', mode, e);
        throwStreamError(
          'RESTREAM_UPDATE_FAILED',
          e,
          `Unable to fetch user stream key for ${mode}.`,
        );

app/services/restream.ts:524

  • throwStreamError expects an IRejectedRequest as its 2nd argument, but this call passes the caught unknown error (e). This should pass request metadata (or {}) and keep the actual exception in logs.
        try {
          await this.setupDisplayTargets(platforms, customDestinations, display);
        } catch (e: unknown) {
          console.error('Restream Error: Unable to create targets for', display, e);
          throwStreamError('RESTREAM_UPDATE_FAILED', e, `Unable to create targets for ${display}.`);
        }

app/services/restream.ts:533

  • throwStreamError expects an IRejectedRequest as its 2nd argument, but this call passes the caught unknown error (e). This will not typecheck and also discards the intended meaning of the rejectedRequest parameter.
        try {
          await this.addRuntimeTargets(streamKey, targetsByMode[mode] as IRestreamRuntimeTarget[]);
        } catch (e: unknown) {
          console.error('Restream Error: Unable to add targets for', display, e);
          throwStreamError('RESTREAM_UPDATE_FAILED', e, `Unable to add targets for ${display}.`);
        }

app/services/restream.ts:611

  • Same issue here: throwStreamError's 2nd arg is IRejectedRequest, but this passes the caught unknown error (e). This should pass request metadata (or {}) and keep the exception object in logging only.
      try {
        // Fetch the key for this mode rather than deriving it, the same way `addTargets` does, so
        // that targets are removed from the stream they were added to
        await this.removeRuntimeTargets(streamKey, stopTargets);
      } catch (e: unknown) {
        console.error('Restream Error: Error removing restream targets for', mode, e);
        throwStreamError('RESTREAM_UPDATE_FAILED', e, `Unable to remove targets for ${mode}.`);
      }

app/components-react/windows/go-live/useGoLiveSettings.ts:145

  • Leftover debug logging (console.log) was introduced here. This will spam production logs when toggling display/both logic.
          console.log(
            'updating ',
            this.state.customDestinations[otherEnabledTargetIndex]?.name,
            ' to disabled',
          );

app/components-react/windows/go-live/AiHighlighterToggle.tsx:6

  • @electron/remote is imported here but never used, which will fail lint/typecheck (and adds an unnecessary dependency edge).
import { Services } from 'components-react/service-provider';
import * as remote from '@electron/remote';
import { useDebounce, useVuex } from 'components-react/hooks';

app/components-react/windows/go-live/PlatformSettings.tsx:1

  • useMemo is imported but not used in this file (will fail lint/typecheck).
import React, { useMemo, useCallback } from 'react';

app/components-react/windows/go-live/PlatformSettings.tsx:109

  • This handler can still run when the Stream Shift card is disabled. SwitcherCard deliberately calls onClick even when disabled is true (to allow upgrade flows), so without an explicit isStreamShiftDisabled guard a disabled toggle can be enabled and break feature mutual-exclusion. Also, isPrime/isStreamShiftDisabled are used but missing from the hook deps.
      setStreamShift(status ?? !isStreamShiftMode);
      Services.UsageStatisticsService.actions.recordAnalyticsEvent('StreamShift', {
        toggle: status ?? !isStreamShiftMode,
      });
    },

app/components-react/windows/go-live/PlatformSettings.tsx:127

  • Same issue as Stream Shift: this handler can still fire when the card is disabled because SwitcherCard calls onClick even when disabled is true. Without an isLiveOutputEditingDisabled guard, a disabled Live Output Editing toggle can be enabled.
      setLiveOutputEditingEnabled(status ?? !isLiveOutputEditingEnabled);
      Services.UsageStatisticsService.actions.recordAnalyticsEvent('LiveOutputEditing', {
        toggle: status ?? !isLiveOutputEditingEnabled,
      });
    },

Comment on lines 590 to 593
private getValidatedDisplay(display?: TDisplayOutput): TDisplayType {
if (!display || display === 'both' || !this.dualOutputView.dualOutputMode) {
if (!display || display === 'both' || !this.isDualOutputMode) {
return 'horizontal';
}
@bundlemon

bundlemon Bot commented Aug 29, 2026

Copy link
Copy Markdown

BundleMon

Files updated (1)
Status Path Size Limits
renderer.(hash).js
10.47MB (-54.23KB -0.5%) -
Unchanged files (3)
Status Path Size Limits
vendors~renderer.(hash).js
4.67MB -
updater.js
115.29KB -
guest-api.js
40.23KB -

Total files change -54.23KB -0.35%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

Copilot AI review requested due to automatic review settings August 29, 2026 08:35

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

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

Suppressed comments (3)

app/components-react/windows/go-live/useGoLiveSettings.ts:145

  • Remove the debug console.log; this will spam the console in production/test runs and isn't guarded by a dev-mode check.
          console.log(
            'updating ',
            this.state.customDestinations[otherEnabledTargetIndex]?.name,
            ' to disabled',
          );

app/components-react/windows/go-live/useGoLiveSettings.ts:570

  • This custom-destination live check builds malformed keys (missing closing brace) and compares concatenated url+streamKey without a delimiter, which can produce false matches/never match.
      const dest = this.state.customDestinations[target];
      return this.activeDestinations?.some(
        d => `{${d.url}${d.streamKey}` === `{${dest.url}${dest.streamKey}`,
      );

app/components-react/windows/go-live/AiHighlighterToggle.tsx:6

  • Unused import remote was added but is not referenced in this file; this will fail linting with unused-import rules.
import { Services } from 'components-react/service-provider';
import * as remote from '@electron/remote';
import { useDebounce, useVuex } from 'components-react/hooks';

Comment on lines 552 to 556
xorWith(
this.activeDestinations?.map(d => getDestinationId(d)),
this.state.customDestinations.filter(dest => dest.enabled).map(d => getDestinationId(d)),
this.activeDestinations?.map(dest => dest.streamKey),
this.state.customDestinations.filter(dest => dest.enabled).map(dest => dest.streamKey),
isEqual,
).length > 0
Comment thread app/services/restream.ts
Comment on lines +768 to 791
} else if (this.streamingService.views.isDualOutputMode) {
// in dual output mode, we need to set the ingest for each display
const displays = this.streamInfo.displaysToRestream;

displays.forEach(async display => {
const mode = this.getMode(display);
const settings = await this.fetchUserSettings(mode);

this.streamSettingsService.setSettings(
{
streamType: 'rtmp_custom',
},
display,
);

this.streamSettingsService.setSettings(
{
key: settings.streamKey,
server: ingest,
},
display,
);
});
} else {
Comment thread app/services/restream.ts
Comment on lines 1248 to 1250
this.SET_STREAM_SWITCHER_STATUS('inactive');
this.updateStreamShift('approved');
}
Comment thread app/services/restream.ts
Comment on lines 1406 to 1409
private getPlatformMode(platform: TPlatform): TOutputOrientation {
const display = this.streamingService.views.getPlatformMode(platform);
return this.streamingService.views.getPlatformMode(platform);
}
Comment on lines 423 to 427
async searchGames(searchString: string): Promise<IGame[]> {
if (!searchString || searchString === '') {
console.debug('Kick search string is empty.');
return [] as IGame[];
}

const host = this.hostsService.streamlabs;
const params = new URLSearchParams({ category: searchString });
const url = `https://${host}/api/v5/slobs/kick/info?${params.toString()}`;
const url = `https://${host}/api/v5/slobs/kick/info?category=${searchString}`;
const headers = authorizedHeaders(this.userService.apiToken);
const request = new Request(url, { headers });
Comment on lines 60 to 66
if (canDualStream) {
const tooltip = isLiveOutputEditingEnabled
? $t('Dual Stream is not available while live output editing is enabled')
: $t('Stream both horizontally and vertically to %{platform}', {
platform: platformLabels(p.platform!),
});
const tooltip = p?.platform
? $t('Stream both horizontally and vertically to %{platform}', {
platform: platformLabels(p.platform),
})
: undefined;

Comment on lines +991 to 997
get canEditLiveOutputs() {
return false;
// return (
// !this.isMidStreamMode &&
// this.incrementalRolloutView.featureIsEnabled(EAvailableFeatures.liveOutputEditing)
// );
}
Comment thread app/styles/loader.less
Comment on lines 50 to 54
.s-spinner--large {
height: 80px;
width: 56px;
}
.s-spinner--small {
height: 15px;
width: 15px;
}
.s-spinner__bar {
if (!isPrime && isStreamShiftMode) {
setStreamShift(false);
}
}, [isPrime, isStreamShiftMode]);
checkIsLive().catch((e: unknown) => {
console.error('Error checking stream shift status on mount:', e);
});
}, []);
@michelinewu
michelinewu merged commit 795ee5f into master Aug 29, 2026
6 of 13 checks passed
@michelinewu
michelinewu deleted the mw_revert_ms_toggle_2 branch August 29, 2026 08:44
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