From 6931a6995890cf16853616dff7fc86bcb13c95ea Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 2 Oct 2026 14:55:54 +0200 Subject: [PATCH 01/18] fix: restore Enter-to-submit in dialogs, drop dead Skeleton classes Four dialogs wrapped their fields in a
with no onsubmit handler and placed the submit button outside the form. Since our Button defaults to type="button", Enter fell through to a native GET submit and tore the dialog down. Worst case was the share-code redeem dialog, a single text input whose whole purpose is type-a-code-and-press-Enter. Also removes 21 leftover Skeleton UI classes (border-surface-*, modal-form, rounded-container-token) across 12 files. Nothing defines them under Tailwind v4, so they emitted no CSS: the sites that wanted a border already get the token color from the base layer, and the four forms never had one at all. --- .../PublicShareClassicControlModule.svelte | 2 +- .../ControlModules/SharedShockerControlModule.svelte | 2 +- src/lib/components/ControlModules/ShockerCard.svelte | 2 +- .../ControlModules/SimpleControlHeader.svelte | 2 +- src/routes/(app)/admin/users/dialog-user-edit.svelte | 2 +- .../(app)/settings/api-tokens/dialog-token-edit.svelte | 10 ++++++++-- src/routes/(app)/settings/api-tokens/new/+page.svelte | 2 +- .../api-tokens/shocker-control-settings.svelte | 2 +- .../public/[shareId=guid]/edit/SharedShocker.svelte | 2 +- .../[shareId=guid]/edit/dialog-add-shocker.svelte | 8 +++++++- .../(app)/shares/user/dialog-share-code-create.svelte | 8 +++++++- .../(app)/shares/user/dialog-share-code-redeem.svelte | 8 +++++++- 12 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/lib/components/ControlModules/PublicShareClassicControlModule.svelte b/src/lib/components/ControlModules/PublicShareClassicControlModule.svelte index 9fd26a17..75009a9a 100644 --- a/src/lib/components/ControlModules/PublicShareClassicControlModule.svelte +++ b/src/lib/components/ControlModules/PublicShareClassicControlModule.svelte @@ -62,7 +62,7 @@
diff --git a/src/lib/components/ControlModules/SharedShockerControlModule.svelte b/src/lib/components/ControlModules/SharedShockerControlModule.svelte index e83abdd8..4cad9b3a 100644 --- a/src/lib/components/ControlModules/SharedShockerControlModule.svelte +++ b/src/lib/components/ControlModules/SharedShockerControlModule.svelte @@ -64,7 +64,7 @@
diff --git a/src/lib/components/ControlModules/ShockerCard.svelte b/src/lib/components/ControlModules/ShockerCard.svelte index 9f464f38..b5cae4a1 100644 --- a/src/lib/components/ControlModules/ShockerCard.svelte +++ b/src/lib/components/ControlModules/ShockerCard.svelte @@ -36,7 +36,7 @@
diff --git a/src/lib/components/ControlModules/SimpleControlHeader.svelte b/src/lib/components/ControlModules/SimpleControlHeader.svelte index 67f2d645..7ffb180c 100644 --- a/src/lib/components/ControlModules/SimpleControlHeader.svelte +++ b/src/lib/components/ControlModules/SimpleControlHeader.svelte @@ -18,7 +18,7 @@
diff --git a/src/routes/(app)/admin/users/dialog-user-edit.svelte b/src/routes/(app)/admin/users/dialog-user-edit.svelte index eb365dac..e8efd985 100644 --- a/src/routes/(app)/admin/users/dialog-user-edit.svelte +++ b/src/routes/(app)/admin/users/dialog-user-edit.svelte @@ -49,7 +49,7 @@

Roles

-
+
{#each [RoleType.Support, RoleType.Staff, RoleType.Admin, RoleType.System] as role (role)} {role} {/each} diff --git a/src/routes/(app)/settings/api-tokens/dialog-token-edit.svelte b/src/routes/(app)/settings/api-tokens/dialog-token-edit.svelte index 84771b49..882462fc 100644 --- a/src/routes/(app)/settings/api-tokens/dialog-token-edit.svelte +++ b/src/routes/(app)/settings/api-tokens/dialog-token-edit.svelte @@ -83,7 +83,13 @@ Edit API Token - + { + e.preventDefault(); + void saveChanges(); + }} + >

Permissions

-
+
{#each permissionCategories as permission (permission.name)} {permission.name} {#each permission.perms as perm (perm.key)} diff --git a/src/routes/(app)/settings/api-tokens/new/+page.svelte b/src/routes/(app)/settings/api-tokens/new/+page.svelte index f22def0f..d00eb6d8 100644 --- a/src/routes/(app)/settings/api-tokens/new/+page.svelte +++ b/src/routes/(app)/settings/api-tokens/new/+page.svelte @@ -276,7 +276,7 @@

Permissions

-
+
{#each permissionCategories as permission (permission.name)} {capitalizeFirstLetter(permission.name)} {#each permission.perms as perm (perm.key)} diff --git a/src/routes/(app)/settings/api-tokens/shocker-control-settings.svelte b/src/routes/(app)/settings/api-tokens/shocker-control-settings.svelte index 2f5efd39..712feb06 100644 --- a/src/routes/(app)/settings/api-tokens/shocker-control-settings.svelte +++ b/src/routes/(app)/settings/api-tokens/shocker-control-settings.svelte @@ -57,7 +57,7 @@ {/each} {/snippet} -
+
diff --git a/src/routes/(app)/shares/public/[shareId=guid]/edit/SharedShocker.svelte b/src/routes/(app)/shares/public/[shareId=guid]/edit/SharedShocker.svelte index b7320f9b..835ea1b0 100644 --- a/src/routes/(app)/shares/public/[shareId=guid]/edit/SharedShocker.svelte +++ b/src/routes/(app)/shares/public/[shareId=guid]/edit/SharedShocker.svelte @@ -78,7 +78,7 @@
diff --git a/src/routes/(app)/shares/public/[shareId=guid]/edit/dialog-add-shocker.svelte b/src/routes/(app)/shares/public/[shareId=guid]/edit/dialog-add-shocker.svelte index 5ef5c3cd..bb20cc05 100644 --- a/src/routes/(app)/shares/public/[shareId=guid]/edit/dialog-add-shocker.svelte +++ b/src/routes/(app)/shares/public/[shareId=guid]/edit/dialog-add-shocker.svelte @@ -52,7 +52,13 @@ > - + { + e.preventDefault(); + void onFormSubmit(); + }} + > Create a Share Code or Invite - + { + e.preventDefault(); + void onFormSubmit(); + }} + > Enter the share code to redeem - + { + e.preventDefault(); + void onFormSubmit(); + }} + > Date: Fri, 2 Oct 2026 14:56:23 +0200 Subject: [PATCH 02/18] fix: confirm disruptive hub commands Reboot and the Wi-Fi hotspot toggles fired straight at the hub on a single dropdown click while Pair, Regenerate Token and Delete in the same menu all sat behind dialogs. A mis-click restarted hardware attached to a person. Both now route through dialog.confirm with copy that states the consequence. Emergency Stop is deliberately left instant. It is a safety control, so the cost of a delayed real stop outweighs the cost of an accidental one. --- .../(app)/hubs/data-table-actions.svelte | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/routes/(app)/hubs/data-table-actions.svelte b/src/routes/(app)/hubs/data-table-actions.svelte index 17f8d322..6a90ec1d 100644 --- a/src/routes/(app)/hubs/data-table-actions.svelte +++ b/src/routes/(app)/hubs/data-table-actions.svelte @@ -44,6 +44,35 @@ const copyId = () => copyToClipboard(hub.id, 'ID copied to clipboard'); + async function confirmHubCommand( + title: string, + desc: string, + confirmButtonText: string, + send: () => void + ) { + const result = await dialog.confirm({ title, desc, confirmButtonText }); + if (!result.confirmed) return; + send(); + } + + const rebootHub = () => + confirmHubCommand( + `Reboot ${hub.name}?`, + 'The hub will disconnect and restart. Any shockers paired to it stop responding until it comes back online.', + 'Reboot', + () => serializeRebootMessage(getConnection(), hub.id) + ); + + const setCaptivePortal = (enabled: boolean) => + confirmHubCommand( + enabled ? `Enable Wi-Fi hotspot on ${hub.name}?` : `Disable Wi-Fi hotspot on ${hub.name}?`, + enabled + ? 'The hub broadcasts its own setup network. It stays reachable on your Wi-Fi, but the hotspot is open to anyone in range until you turn it off.' + : 'The setup hotspot shuts down. If the hub is not already on your Wi-Fi you will need physical access to set it up again.', + enabled ? 'Enable' : 'Disable', + () => serializeCaptivePortalMessage(getConnection(), hub.id, enabled) + ); + async function editHub(name: string, close: () => void) { try { await devicesEditDevice({ path: { deviceId: hub.id }, body: { name } }); @@ -234,24 +263,15 @@ >Update - serializeRebootMessage(getConnection(), hub.id)} - > + Reboot - serializeCaptivePortalMessage(getConnection(), hub.id, true)} - > + setCaptivePortal(true)}> Enable Wi-Fi hotspot - serializeCaptivePortalMessage(getConnection(), hub.id, false)} - > + setCaptivePortal(false)}> Disable Wi-Fi hotspot From 0337493a6034ab232a0f55e1c68f236bc6094b8f Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 2 Oct 2026 14:56:23 +0200 Subject: [PATCH 03/18] fix: show an empty state on the hubs list The mobile branch had no {:else} on its {#each}, so a user with no hubs got a blank screen with no explanation and no call to action. Desktop only managed a bare "No hubs found." table cell. Both branches now share one EmptyState with an Add Hub action, hoisted above the responsive split. --- src/routes/(app)/hubs/+page.svelte | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/routes/(app)/hubs/+page.svelte b/src/routes/(app)/hubs/+page.svelte index 3786dce1..7ef770a2 100644 --- a/src/routes/(app)/hubs/+page.svelte +++ b/src/routes/(app)/hubs/+page.svelte @@ -15,7 +15,7 @@ import * as Dialog from '@openshock/svelte-core/components/ui/dialog'; import { TextInput } from '@openshock/svelte-core/components/input'; - import { PageHeader, Container } from '@openshock/svelte-core/components'; + import { PageHeader, Container, EmptyState } from '@openshock/svelte-core/components'; const isMobile = new IsMobile(); @@ -90,7 +90,15 @@ - {#if isMobile.current} + {#if data.length === 0} + + + + {:else if isMobile.current}
{#each data as hub (hub.id)}
@@ -139,10 +147,6 @@ - {:else} - - No hubs found. - {/each} From 29e795ac8989ec9c779bccada7f305582e8aa50a Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 2 Oct 2026 14:57:52 +0200 Subject: [PATCH 04/18] fix: size the app shell to svh so mobile stops over-scrolling h-screen is 100vh, the viewport with a phone's URL bar hidden, so the shell outgrew the visible page and left it scrollable by exactly that bar's height. Locking and unlocking body scroll around the mobile sidebar sheet's transition then shifted the whole page for a frame. h-svh matches the sidebar wrapper's min-h-svh. w-full for the same reason on the other axis, since 100vw counts the scrollbar. --- src/routes/+layout.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 15e631bf..49f2ba47 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -60,7 +60,13 @@ (sidebarOpen = v)}> -
+ +
{#if PUBLIC_DEVELOPMENT_BANNER}

From 09ec5e1bcee9700e7bf9f0b37f89876382221bc8 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 2 Oct 2026 15:35:15 +0200 Subject: [PATCH 05/18] chore: update svelte-core submodule Picks up the status tokens (--success/--warning/--info) that the palette sweep depends on, PageLoading and PageError, and ConfirmDeleteDialog's pending state and Cancel button. --- packages/svelte-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte-core b/packages/svelte-core index 5847ed4c..32d07f39 160000 --- a/packages/svelte-core +++ b/packages/svelte-core @@ -1 +1 @@ -Subproject commit 5847ed4c4b77e4aa59093a0287329acef3a72ae3 +Subproject commit 32d07f391d3a141451487646de0d6daaa2b1af53 From bc8ec2e6e8b8d1f5591578409b869d53c4858322 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Fri, 2 Oct 2026 15:02:25 +0200 Subject: [PATCH 06/18] refactor: replace raw palette classes with semantic tokens Sweeps about 230 raw Tailwind palette classes out of the app in favour of the theme's tokens, now that --success, --warning and --info exist alongside --destructive in svelte-core. Mappings: green -> success, yellow/amber/orange -> warning, blue -> info, red -> destructive, and the neutral ramps -> foreground/muted-foreground/ border/ring/background. Where a class was half of a light/dark pair the dark: override goes away, since the token already switches: 14 copies of "text-gray-600 dark:text-gray-300" on spinners collapse to text-muted-foreground, and CircleSlider loses five such pairs. Fixes several sites that were broken in one theme or the other: - report/api-tokens was light-only (bg-red-50, bg-gray-50, bg-green-200) and rendered as a white slab in dark mode - Footer used text-green-800/text-red-800 for connection state, near-invisible on a dark background - ring-slate-800 on three share items, likewise - admin/blacklists had a bg-gray-800 pill, unreadable in light mode Also drops hardcoded hex from Turnstile, Footer, SerialPortSelector, FirmwareChannelSelector and FirmwareFlasher. Deliberately left alone: WelcomeScreen and the marketing page (intentionally always-dark brand surfaces), CircleSlider's blue accent arc, the ActionButtons party animation, the LiveButton gradient, and the online-hubs chart palette, which is already theme-aware via its own light/dark pairs. --- .../ControlModules/ShockerCard.svelte | 9 ++------- .../ControlModules/SimpleControlHeader.svelte | 4 +--- .../ControlModules/impl/CircleSlider.svelte | 8 ++++---- .../ControlModules/impl/ShockerMenu.svelte | 2 +- .../components/FirmwareChannelSelector.svelte | 12 +++++------ src/lib/components/Turnstile.svelte | 6 ++---- .../shares/permission-tooltip.svelte | 2 +- .../components/shares/user-selector.svelte | 2 +- src/lib/components/svg/LiveControlIcon.svelte | 2 +- src/routes/(app)/+layout.svelte | 2 +- .../admin/automation-tokens/+page.svelte | 2 +- .../data-table-actions.svelte | 2 +- .../dialog-secret-reveal.svelte | 4 ++-- .../(app)/admin/blacklists/+page.svelte | 6 +++--- src/routes/(app)/admin/mail/+page.svelte | 8 ++++---- .../admin/mail/data-table-actions.svelte | 2 +- .../(app)/admin/online-gateways/+page.svelte | 4 ++-- .../(app)/admin/online-hubs/+page.svelte | 6 +++--- .../admin/users/[userId=guid]/+page.svelte | 4 ++-- .../[userId=guid]/user-api-tokens.svelte | 2 +- .../admin/users/data-table-actions.svelte | 2 +- src/routes/(app)/admin/webhooks/+page.svelte | 2 +- .../admin/webhooks/data-table-actions.svelte | 2 +- src/routes/(app)/home/+page.svelte | 4 ++-- src/routes/(app)/hubs/+page.svelte | 6 +++--- .../hubs/[hubId=guid]/update/+page.svelte | 20 +++++++++---------- .../(app)/hubs/data-table-actions.svelte | 6 +++--- .../(app)/settings/account/DangerZone.svelte | 4 ++-- .../(app)/settings/api-tokens/+page.svelte | 2 +- .../settings/api-tokens/new/+page.svelte | 6 +++--- .../settings/api-tokens/token-actions.svelte | 2 +- .../(app)/settings/connections/+page.svelte | 6 +++--- .../(app)/settings/sessions/+page.svelte | 2 +- .../settings/sessions/session-actions.svelte | 2 +- src/routes/(app)/shares/public/+page.svelte | 6 +++--- .../public/[shareId=guid]/edit/+page.svelte | 4 ++-- .../[shareId=guid]/edit/SharedShocker.svelte | 6 ++---- .../shares/public/data-table-actions.svelte | 2 +- .../user/dialog-share-code-redeem.svelte | 6 +++--- .../(app)/shares/user/incoming/+page.svelte | 4 ++-- .../user/incoming/incoming-share-item.svelte | 4 +--- .../(app)/shares/user/invites/+page.svelte | 4 ++-- .../user/invites/incoming-invite-item.svelte | 4 +--- .../user/invites/outgoing-invite-item.svelte | 4 +--- .../(app)/shares/user/outgoing/+page.svelte | 4 ++-- .../user/outgoing/user-share-item.svelte | 4 +--- src/routes/(app)/shockers/logs/+page.svelte | 4 ++-- src/routes/(app)/shockers/own/+page.svelte | 2 +- src/routes/Footer.svelte | 10 +++++----- src/routes/Header.svelte | 10 +++++----- src/routes/report/api-tokens/+page.svelte | 18 ++++++++++------- .../shares/public/[shareId=guid]/+page.svelte | 2 +- .../public/[shareId=guid]/ControlView.svelte | 6 +++--- src/routes/terminal/+page.svelte | 4 ++-- src/routes/terminal/FirmwareFlasher.svelte | 4 ++-- src/routes/terminal/HelpDialog.svelte | 2 +- .../terminal/RiskAcknowledgementModal.svelte | 6 +++--- src/routes/terminal/SerialPortSelector.svelte | 6 +++--- src/routes/terminal/SerialTerminal.svelte | 2 +- 59 files changed, 134 insertions(+), 149 deletions(-) diff --git a/src/lib/components/ControlModules/ShockerCard.svelte b/src/lib/components/ControlModules/ShockerCard.svelte index b5cae4a1..6cea0443 100644 --- a/src/lib/components/ControlModules/ShockerCard.svelte +++ b/src/lib/components/ControlModules/ShockerCard.svelte @@ -34,12 +34,7 @@ }: Props = $props(); -

+
@@ -47,7 +42,7 @@ {#if showHubBadge && hubName}
{hubName} diff --git a/src/lib/components/ControlModules/SimpleControlHeader.svelte b/src/lib/components/ControlModules/SimpleControlHeader.svelte index 7ffb180c..909ab217 100644 --- a/src/lib/components/ControlModules/SimpleControlHeader.svelte +++ b/src/lib/components/ControlModules/SimpleControlHeader.svelte @@ -17,9 +17,7 @@ }: Props = $props(); -
+
diff --git a/src/lib/components/ControlModules/impl/CircleSlider.svelte b/src/lib/components/ControlModules/impl/CircleSlider.svelte index 0db98170..cc07c6a6 100644 --- a/src/lib/components/ControlModules/impl/CircleSlider.svelte +++ b/src/lib/components/ControlModules/impl/CircleSlider.svelte @@ -150,7 +150,7 @@