From eab7c4b5fc03e07e9754b7e4fabecddd1b5dab1e Mon Sep 17 00:00:00 2001 From: Farhad Jay Date: Mon, 16 Feb 2026 12:42:43 -0800 Subject: [PATCH 1/5] Add a dev mode toggle that enables logging of IVS worker messages --- package-lock.json | 30 ++++++++++++++ package.json | 1 + src/components/Switch.jsx | 15 +++++++ src/components/Switch.scss | 51 ++++++++++++++++++++++++ src/controls/settings/MainMenu.jsx | 46 ++++++++++++++------- src/controls/settings/SettingsButton.jsx | 5 +++ src/controls/settings/useDevMode.js | 22 ++++++++++ src/main.scss | 1 + 8 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 src/components/Switch.jsx create mode 100644 src/components/Switch.scss create mode 100644 src/controls/settings/useDevMode.js diff --git a/package-lock.json b/package-lock.json index c7cf360..b54985b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@number-flow/react": "^0.5.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-slider": "^1.2.0", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1648,6 +1649,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", diff --git a/package.json b/package.json index fdc9eef..ec746a8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@number-flow/react": "^0.5.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-slider": "^1.2.0", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/Switch.jsx b/src/components/Switch.jsx new file mode 100644 index 0000000..667c266 --- /dev/null +++ b/src/components/Switch.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import * as SwitchPrimitive from "@radix-ui/react-switch"; + +export default function Switch({ checked, onCheckedChange, ...props }) { + return ( + + + + ); +} diff --git a/src/components/Switch.scss b/src/components/Switch.scss new file mode 100644 index 0000000..fa445bb --- /dev/null +++ b/src/components/Switch.scss @@ -0,0 +1,51 @@ +@use "~@destinygg/libstiny" as dgg; +@use "@destinygg/libstiny/lib/utils/transitions" as transitions; + +.switch { + &__slider { + @include transitions.create-transition(all, default); + position: relative; + display: inline-block; + width: dgg.$switch-width; + height: dgg.$switch-height; + flex-shrink: 0; + cursor: pointer; + background-color: dgg.$switch-background-default-rest; + border-radius: dgg.$semantic-radii-pill; + border: none; + padding: 0; + + &:hover { + background-color: dgg.$switch-background-default-hover; + } + + &[data-state="checked"] { + background-color: dgg.$switch-background-active-rest; + + &:hover { + background-color: dgg.$switch-background-active-hover; + } + } + + // Override libstiny's ::before thumb in favor of the Radix UI Switch thumb element. + &::before { + content: none; + } + } + + &__thumb { + @include transitions.create-transition(all, movement); + display: block; + position: absolute; + height: dgg.$switch-toggle-size; + width: dgg.$switch-toggle-size; + left: dgg.$space-1; + bottom: dgg.$space-1; + background-color: dgg.$switch-toggle-background; + border-radius: 50%; + + &[data-state="checked"] { + transform: translateX(dgg.$switch-toggle-size); + } + } +} diff --git a/src/controls/settings/MainMenu.jsx b/src/controls/settings/MainMenu.jsx index 7280bfe..0685216 100644 --- a/src/controls/settings/MainMenu.jsx +++ b/src/controls/settings/MainMenu.jsx @@ -1,21 +1,39 @@ import React from "react"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { ChevronRight } from "lucide-react"; +import Switch from "../../components/Switch.jsx"; -export default function MainMenu({ onNavigateQuality, selectedQuality }) { +export default function MainMenu({ + onNavigateQuality, + selectedQuality, + isDevMode, + onDevModeChange, +}) { return ( - { - e.preventDefault(); - onNavigateQuality(); - }} - > - Quality - - {selectedQuality.label} - - - + <> + { + e.preventDefault(); + onNavigateQuality(); + }} + > + Quality + + {selectedQuality.label} + + + + + { + e.preventDefault(); + }} + > + Dev Mode + + + ); } diff --git a/src/controls/settings/SettingsButton.jsx b/src/controls/settings/SettingsButton.jsx index bb1ad95..1596c72 100644 --- a/src/controls/settings/SettingsButton.jsx +++ b/src/controls/settings/SettingsButton.jsx @@ -8,6 +8,7 @@ import MainMenu from "./MainMenu.jsx"; import QualityMenu from "./QualityMenu.jsx"; import { useSettings } from "./useSettings.js"; import { useQualitySelector } from "./useQualitySelector.js"; +import { useDevMode } from "./useDevMode.js"; export default function SettingsButton({ core, container, shouldShow }) { const { @@ -22,6 +23,8 @@ export default function SettingsButton({ core, container, shouldShow }) { const { selectedQuality, qualityOptions, handleQualityChange } = useQualitySelector(core); + const { isDevMode, setIsDevMode } = useDevMode(core); + return ( )} diff --git a/src/controls/settings/useDevMode.js b/src/controls/settings/useDevMode.js new file mode 100644 index 0000000..606dbd7 --- /dev/null +++ b/src/controls/settings/useDevMode.js @@ -0,0 +1,22 @@ +import { useState, useEffect } from "react"; + +export function useDevMode(core) { + const [isDevMode, setIsDevMode] = useState(false); + + useEffect(() => { + if (!isDevMode) { + return; + } + + const handler = (event) => { + console.debug("[Kickstiny][IVS]", event.data); + }; + + core.worker.addEventListener("message", handler); + return () => { + core.worker.removeEventListener("message", handler); + }; + }, [isDevMode, core]); + + return { isDevMode, setIsDevMode }; +} diff --git a/src/main.scss b/src/main.scss index c5c3e74..a4e1b64 100644 --- a/src/main.scss +++ b/src/main.scss @@ -3,6 +3,7 @@ @use "./components/Tooltip.scss"; @use "./components/Dropdown.scss"; @use "./components/Slider.scss"; +@use "./components/Switch.scss"; @use "./controls/container/Container.scss"; @use "./controls/bar/ControlsBar.scss"; From 12a1efad0b070a754409c4f05050a0bef6794e60 Mon Sep 17 00:00:00 2001 From: Farhad Jay Date: Mon, 16 Feb 2026 12:45:35 -0800 Subject: [PATCH 2/5] Only display dev mode toggle in dev env --- build.js | 6 +++--- src/controls/settings/MainMenu.jsx | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/build.js b/build.js index cfdb0a6..817372c 100644 --- a/build.js +++ b/build.js @@ -111,6 +111,9 @@ const buildOptions = { }, }, ], + define: { + "process.env.NODE_ENV": JSON.stringify(env), + }, mainFields: ["browser", "module", "main"], conditions: ["production", "default"], }; @@ -119,9 +122,6 @@ if (env === "prod") { Object.assign(buildOptions, { minify: true, pure: ["console.debug"], // Remove all `console.debug()` calls in production builds. - define: { - "process.env.NODE_ENV": '"production"', - }, }); } diff --git a/src/controls/settings/MainMenu.jsx b/src/controls/settings/MainMenu.jsx index 0685216..b12427e 100644 --- a/src/controls/settings/MainMenu.jsx +++ b/src/controls/settings/MainMenu.jsx @@ -25,15 +25,17 @@ export default function MainMenu({ - { - e.preventDefault(); - }} - > - Dev Mode - - + {process.env.NODE_ENV === "dev" && ( + { + e.preventDefault(); + }} + > + Dev Mode + + + )} ); } From 4889110aaa50c9b603d27ec545c6e051bfbf84bf Mon Sep 17 00:00:00 2001 From: Farhad Jay Date: Mon, 16 Feb 2026 12:53:23 -0800 Subject: [PATCH 3/5] Improve readability of message logs --- src/controls/settings/useDevMode.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/controls/settings/useDevMode.js b/src/controls/settings/useDevMode.js index 606dbd7..0a1eda7 100644 --- a/src/controls/settings/useDevMode.js +++ b/src/controls/settings/useDevMode.js @@ -8,8 +8,21 @@ export function useDevMode(core) { return; } + // Sample IVS messages: + // { type: 12, arg: { key: "bufferedPosition", value: 2.334009 } } + // { type: 12, arg: { key: "statistics", value: { bitrate: 3109311, ... } } } + // { type: "PlayerQualityChanged", arg: { name: "720p60", ... } } const handler = (event) => { - console.debug("[Kickstiny][IVS]", event.data); + const data = event.data; + const type = data.type; + const detail = data.arg?.key || data.arg?.name; + + if (detail === "bufferedPosition") { + return; + } + + const label = detail ? `${type} (${detail})` : type; + console.debug(`[Kickstiny] IVS Message: ${label}`, data); }; core.worker.addEventListener("message", handler); From 55aa3d820bd63c7588c49cce3993f82c577f55ea Mon Sep 17 00:00:00 2001 From: Farhad Jay Date: Mon, 16 Feb 2026 12:55:54 -0800 Subject: [PATCH 4/5] Rename the setting to IVS Debug --- src/controls/settings/MainMenu.jsx | 8 ++++---- src/controls/settings/SettingsButton.jsx | 8 ++++---- .../settings/{useDevMode.js => useIvsDebug.js} | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) rename src/controls/settings/{useDevMode.js => useIvsDebug.js} (82%) diff --git a/src/controls/settings/MainMenu.jsx b/src/controls/settings/MainMenu.jsx index b12427e..76c59d6 100644 --- a/src/controls/settings/MainMenu.jsx +++ b/src/controls/settings/MainMenu.jsx @@ -6,8 +6,8 @@ import Switch from "../../components/Switch.jsx"; export default function MainMenu({ onNavigateQuality, selectedQuality, - isDevMode, - onDevModeChange, + isIvsDebug, + onIvsDebugChange, }) { return ( <> @@ -32,8 +32,8 @@ export default function MainMenu({ e.preventDefault(); }} > - Dev Mode - + IVS Debug + )} diff --git a/src/controls/settings/SettingsButton.jsx b/src/controls/settings/SettingsButton.jsx index 1596c72..383eb76 100644 --- a/src/controls/settings/SettingsButton.jsx +++ b/src/controls/settings/SettingsButton.jsx @@ -8,7 +8,7 @@ import MainMenu from "./MainMenu.jsx"; import QualityMenu from "./QualityMenu.jsx"; import { useSettings } from "./useSettings.js"; import { useQualitySelector } from "./useQualitySelector.js"; -import { useDevMode } from "./useDevMode.js"; +import { useIvsDebug } from "./useIvsDebug.js"; export default function SettingsButton({ core, container, shouldShow }) { const { @@ -23,7 +23,7 @@ export default function SettingsButton({ core, container, shouldShow }) { const { selectedQuality, qualityOptions, handleQualityChange } = useQualitySelector(core); - const { isDevMode, setIsDevMode } = useDevMode(core); + const { isIvsDebug, setIsIvsDebug } = useIvsDebug(core); return ( @@ -57,8 +57,8 @@ export default function SettingsButton({ core, container, shouldShow }) { )} diff --git a/src/controls/settings/useDevMode.js b/src/controls/settings/useIvsDebug.js similarity index 82% rename from src/controls/settings/useDevMode.js rename to src/controls/settings/useIvsDebug.js index 0a1eda7..442ca92 100644 --- a/src/controls/settings/useDevMode.js +++ b/src/controls/settings/useIvsDebug.js @@ -1,10 +1,10 @@ import { useState, useEffect } from "react"; -export function useDevMode(core) { - const [isDevMode, setIsDevMode] = useState(false); +export function useIvsDebug(core) { + const [isIvsDebug, setIsIvsDebug] = useState(false); useEffect(() => { - if (!isDevMode) { + if (!isIvsDebug) { return; } @@ -29,7 +29,7 @@ export function useDevMode(core) { return () => { core.worker.removeEventListener("message", handler); }; - }, [isDevMode, core]); + }, [isIvsDebug, core]); - return { isDevMode, setIsDevMode }; + return { isIvsDebug, setIsIvsDebug }; } From f62fae1bd5cab8bcc6caa449bc2558e11ecb05cb Mon Sep 17 00:00:00 2001 From: Farhad Jay Date: Mon, 16 Feb 2026 12:57:11 -0800 Subject: [PATCH 5/5] Save the IVS debug setting to preferences --- src/controls/settings/useIvsDebug.js | 5 +++-- src/controls/usePreferences.js | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/controls/settings/useIvsDebug.js b/src/controls/settings/useIvsDebug.js index 442ca92..4351a63 100644 --- a/src/controls/settings/useIvsDebug.js +++ b/src/controls/settings/useIvsDebug.js @@ -1,7 +1,8 @@ -import { useState, useEffect } from "react"; +import { useEffect } from "react"; +import { usePreferences } from "../usePreferences"; export function useIvsDebug(core) { - const [isIvsDebug, setIsIvsDebug] = useState(false); + const { isIvsDebug, setIsIvsDebug } = usePreferences(); useEffect(() => { if (!isIvsDebug) { diff --git a/src/controls/usePreferences.js b/src/controls/usePreferences.js index 0f15294..67741fc 100644 --- a/src/controls/usePreferences.js +++ b/src/controls/usePreferences.js @@ -2,6 +2,7 @@ import { useState, useCallback } from "react"; const QUALITY_STORAGE_KEY = "kickstiny.preference.quality"; const VOLUME_STORAGE_KEY = "kickstiny.preference.volume"; +const IVS_DEBUG_STORAGE_KEY = "kickstiny.preference.ivsDebug"; const DEFAULT_VOLUME = 100; @@ -25,6 +26,16 @@ export function usePreferences() { } }); + const [isIvsDebug, setIsIvsDebugState] = useState(() => { + try { + const stored = window.localStorage.getItem(IVS_DEBUG_STORAGE_KEY); + return stored === "true"; + } catch (err) { + console.log("[Kickstiny] Unable to read ivs debug preference", err); + return false; + } + }); + const setSavedQuality = useCallback((value) => { try { window.localStorage.setItem(QUALITY_STORAGE_KEY, value); @@ -43,10 +54,21 @@ export function usePreferences() { } }, []); + const setIsIvsDebug = useCallback((value) => { + try { + window.localStorage.setItem(IVS_DEBUG_STORAGE_KEY, value.toString()); + setIsIvsDebugState(value); + } catch (err) { + console.log("[Kickstiny] Unable to persist ivs debug preference", err); + } + }, []); + return { savedQuality, savedVolume, + isIvsDebug, setSavedQuality, setSavedVolume, + setIsIvsDebug, }; }