Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6931a69
fix: restore Enter-to-submit in dialogs, drop dead Skeleton classes
hhvrc Oct 2, 2026
4c4828d
fix: confirm disruptive hub commands
hhvrc Oct 2, 2026
0337493
fix: show an empty state on the hubs list
hhvrc Oct 2, 2026
29e795a
fix: size the app shell to svh so mobile stops over-scrolling
hhvrc Oct 2, 2026
09ec5e1
chore: update svelte-core submodule
hhvrc Oct 2, 2026
bc8ec2e
refactor: replace raw palette classes with semantic tokens
hhvrc Oct 2, 2026
3bc7633
refactor: put every page on one header template
hhvrc Oct 2, 2026
dab6fec
refactor: share the loading, error and row-action patterns
hhvrc Oct 2, 2026
5d3ca47
refactor: settle button hierarchy, dialog footers and copy
hhvrc Oct 2, 2026
f832c32
fix: correct four Tailwind classes that did nothing or clipped
hhvrc Oct 2, 2026
7d5f72e
refactor: give the form dialogs a single submit path
hhvrc Oct 2, 2026
5059c65
Update send function to return Promise and handle errors
hhvrc Oct 2, 2026
6cd5427
fix: correct colour regressions from the semantic token sweep
hhvrc Oct 2, 2026
d963693
fix: report hub command failures and gate the hubs empty state on load
hhvrc Oct 2, 2026
b85a4ba
fix: keep dialogs open on failure and guard against repeat submits
hhvrc Oct 2, 2026
709f4f2
fix: restore the share editor layout and finish the page header migra…
hhvrc Oct 2, 2026
d562337
fix: drop the nested form in UserSelector and ignore late redeem results
hhvrc Oct 2, 2026
8346349
refactor: handle delete errors inside each dialog's confirm handler
hhvrc Oct 2, 2026
d6a4154
Merge develop into ui-ux-review-fixes
hhvrc Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/svelte-core
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</script>

<div
class="border-surface-400-500-token flex flex-col items-center justify-center gap-2 overflow-hidden rounded-md border p-2"
class="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-md border p-2"
class:opacity-50={isPaused}
>
<!-- Title -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
</script>

<div
class="border-surface-400-500-token flex flex-col items-center justify-center gap-2 overflow-hidden rounded-md border p-2"
class="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-md border p-2"
class:opacity-50={isPaused}
>
<!-- Title -->
Expand Down
9 changes: 2 additions & 7 deletions src/lib/components/ControlModules/ShockerCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,15 @@
}: Props = $props();
</script>

<div
class={cn(
'border-surface-400-500-token bg-card flex w-80 flex-col overflow-hidden rounded-md border',
className
)}
>
<div class={cn('bg-card flex w-80 flex-col overflow-hidden rounded-md border', className)}>
<!-- Header -->
<div class="border-border/60 flex items-center gap-2 border-b px-3 py-2">
<div class="flex min-w-0 flex-1 flex-col">
<h2 class="truncate text-sm leading-tight font-semibold" title={name}>{name}</h2>
{#if showHubBadge && hubName}
<div class="text-muted-foreground flex items-center gap-1.5 text-[11px]">
<span
class="size-1.5 shrink-0 rounded-full {hubOnline ? 'bg-green-400' : 'bg-red-500'}"
class="size-1.5 shrink-0 rounded-full {hubOnline ? 'bg-success' : 'bg-destructive'}"
title={hubOnline ? 'Online' : 'Offline'}
></span>
<span class="truncate" title={hubName}>{hubName}</span>
Expand Down
4 changes: 1 addition & 3 deletions src/lib/components/ControlModules/SimpleControlHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
}: Props = $props();
</script>

<div
class="border-surface-400-500-token flex flex-row items-center justify-center gap-16 rounded-md border px-8 py-2"
>
<div class="flex flex-row items-center justify-center gap-16 rounded-md border px-8 py-2">
<!-- Sliders -->
<div class="grid flex-1 grid-cols-[24px_auto_40px] items-center gap-1 text-center">
<Zap />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</script>

<Dialog.Header>
<Dialog.Title>Add Shocker</Dialog.Title>
<Dialog.Title>Add shocker</Dialog.Title>
<Dialog.Description>Register a new shocker to one of your hubs.</Dialog.Description>
</Dialog.Header>
<div class="flex flex-col gap-4 py-2">
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/ControlModules/impl/CircleSlider.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
<!-- background arc -->
<path
d={calcSvgPathData(angleEnd)}
class="cursor-pointer fill-none stroke-neutral-200 stroke-20 dark:stroke-neutral-800"
class="stroke-muted-foreground/20 cursor-pointer fill-none stroke-20"
stroke-linecap="round"
aria-hidden="true"
onpointerdown={startTracking}
Expand All @@ -177,7 +177,7 @@
aria-valuenow={value}
aria-valuemax={max}
aria-labelledby={labelId}
class="cursor-move fill-white stroke-neutral-400 drop-shadow-md outline-none focus:ring-2 focus:ring-blue-500/60 dark:fill-neutral-900 dark:stroke-neutral-700"
class="fill-background stroke-muted-foreground/50 focus-visible:stroke-ring cursor-move drop-shadow-md outline-none focus-visible:stroke-3"
/>
</svg>

Expand All @@ -191,14 +191,14 @@
{max}
{step}
aria-label="Value"
class="hide-spinners absolute top-1/2 left-1/2 w-10 -translate-1/2 border-none bg-transparent text-center text-xl font-bold text-gray-900 focus:outline-none dark:text-gray-100"
class="hide-spinners text-foreground absolute top-1/2 left-1/2 w-10 -translate-1/2 border-none bg-transparent text-center text-xl font-bold focus:outline-none"
/>

<!-- gauge label -->
<label
id={labelId}
for={inputId}
class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/10 text-center text-neutral-600 dark:text-neutral-300"
class="text-muted-foreground absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/10 text-center"
>
{name}
</label>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ControlModules/impl/ShockerMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
Copy ID
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="cursor-pointer text-red-500" onclick={deleteShocker}>
<DropdownMenu.Item class="text-destructive cursor-pointer" onclick={deleteShocker}>
<Trash2 class="size-4" />
Delete
</DropdownMenu.Item>
Expand Down
12 changes: 6 additions & 6 deletions src/lib/components/FirmwareChannelSelector.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@

<div class="flex flex-row items-center justify-start gap-2 pl-2">
{#if channel === 'stable'}
<CircleCheckBig color="#22c55e" />
<p class="text-green-500">This is the recommended channel.</p>
<CircleCheckBig class="text-success" />
<p class="text-success">This is the recommended channel.</p>
<!--
{:else if channel === 'beta'}
<TriangleAlert color="#eab308" />
<p class="text-yellow-500">This channel might contain bugs.</p>
<TriangleAlert class="text-warning" />
<p class="text-warning">This channel might contain bugs.</p>
-->
{:else}
<TriangleAlert color="#ef4444" />
<p class="font-bold text-red-500" role="alert">
<TriangleAlert class="text-destructive" />
<p class="text-destructive font-bold" role="alert">
DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING
</p>
{/if}
Expand Down
56 changes: 56 additions & 0 deletions src/lib/components/Table/RowActions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script lang="ts" module>
import type { Component } from 'svelte';

export interface RowAction {
label: string;
/** Lucide icon component. Rendered at size-4 to match the rest of the menu. */
icon?: Component;
onclick?: () => void;
disabled?: boolean;
/** Tints the item with the destructive token. */
destructive?: boolean;
/** Draws a separator above this item. */
separatorBefore?: boolean;
}
</script>

<script lang="ts">
import { TableActionMenu } from '@openshock/svelte-core/components';
import * as DropdownMenu from '@openshock/svelte-core/components/ui/dropdown-menu';

interface Props {
/** Group heading, e.g. "Hub". Omit for an unlabelled menu. */
label?: string;
actions: RowAction[];
}

let { label, actions }: Props = $props();
</script>

<TableActionMenu>
{#if label}
<DropdownMenu.Label>{label}</DropdownMenu.Label>
{/if}
<DropdownMenu.Group>
{#each actions as action (action.label)}
{#if action.separatorBefore}
<DropdownMenu.Separator />
{/if}
<DropdownMenu.Item
class={action.disabled
? undefined
: action.destructive
? 'text-destructive cursor-pointer'
: 'cursor-pointer'}
disabled={action.disabled}
onclick={action.onclick}
>
{#if action.icon}
{@const Icon = action.icon}
<Icon class="size-4" />
{/if}
{action.label}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Group>
</TableActionMenu>
6 changes: 2 additions & 4 deletions src/lib/components/Turnstile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@
>
{#if !mounted}
<!-- Turnstile placeholder -->
<div
class="flex h-full items-center justify-center gap-3 border border-[#e0e0e0] bg-[#fafafa] p-3 select-none dark:border-[#666] dark:bg-[#222]"
>
<div class="bg-muted flex h-full items-center justify-center gap-3 border p-3 select-none">
{#if dev || disabled}
<Bug />
<span> Turnstile disabled </span>
Expand All @@ -102,7 +100,7 @@
<span> Loading... </span>
{/if}
<a
class="mb-auto ml-auto h-7 w-auto text-[#666] dark:text-[#999]"
class="text-muted-foreground mb-auto ml-auto h-7 w-auto"
href="https://www.cloudflare.com/products/turnstile/?utm_source=turnstile&utm_campaign=widget"
target="_blank"
rel="noopener noreferrer"
Expand Down
15 changes: 10 additions & 5 deletions src/lib/components/shares/permission-tooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@

function permClass(enabled: boolean): ClassValue {
let base = 'p-1 rounded-lg ';
return base + (enabled ? 'bg-green-500' : 'bg-red-500');
return (
base +
(enabled
? 'bg-success text-success-foreground'
: 'bg-destructive text-destructive-foreground')
);
}

let pausedReason = $derived(pauseState ? getPauseReason(pauseState) : null);
Expand All @@ -31,25 +36,25 @@
class={permClass(shockerShare.permissions.shock)}
title={shockerShare.permissions.shock ? 'Shock enabled' : 'Shock disabled'}
>
<Zap color="white" />
<Zap />
</span>
<span
class={permClass(shockerShare.permissions.vibrate)}
title={shockerShare.permissions.vibrate ? 'Vibrate enabled' : 'Vibrate disabled'}
>
<Waves color="white" />
<Waves />
</span>
<span
class={permClass(shockerShare.permissions.sound)}
title={shockerShare.permissions.sound ? 'Sound enabled' : 'Sound disabled'}
>
<Volume2 color="white" />
<Volume2 />
</span>
<span
class={permClass(shockerShare.permissions.live!)}
title={shockerShare.permissions.live ? 'Live enabled' : 'Live disabled'}
>
<ChartNoAxesGantt color="white" />
<ChartNoAxesGantt />
</span>
</div>
<div class="flex justify-center gap-2">
Expand Down
26 changes: 19 additions & 7 deletions src/lib/components/shares/user-selector.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
let userInput = $state('');
let { fetchedUser = $bindable(null) }: Props = $props();

function check(event: Event) {
event.preventDefault();
function check() {
usersGetByName({ path: { username: userInput } })
.then((user) => {
fetchedUser = user;
Expand All @@ -32,21 +31,34 @@
}

let inputModified = $derived(fetchedUser?.name !== userInput);

// Not a <form>: this sits inside the share dialog's form, and nested forms are invalid HTML.
// Enter runs the lookup while the name is unconfirmed, and otherwise falls through to the outer form.
function onKeydown(event: KeyboardEvent) {
if (event.key !== 'Enter' || !inputModified) return;
event.preventDefault();
check();
}
</script>

<form class="flex items-center gap-2" onsubmit={check}>
<Avatar.Root class={(fetchedUser ? 'border-3 border-green-500' : '') + ' h-15 w-15'}>
<div class="flex items-center gap-2">
<Avatar.Root class={(fetchedUser ? 'border-success border-3' : '') + ' h-15 w-15'}>
<Avatar.Image
src={fetchedUser?.image}
alt={fetchedUser ? `${fetchedUser.name}'s avatar` : 'User avatar'}
/>
<Avatar.Fallback>?</Avatar.Fallback>
</Avatar.Root>
<Input bind:value={userInput} placeholder="Enter user name" aria-label="Username to search" />
<Input
bind:value={userInput}
onkeydown={onKeydown}
placeholder="Enter user name"
aria-label="Username to search"
/>
<Button
onclick={check}
disabled={!inputModified}
type="submit"
type="button"
title={inputModified ? 'Search user' : 'User found'}
>
{#if inputModified}
Expand All @@ -55,4 +67,4 @@
<Check />
{/if}
</Button>
</form>
</div>
2 changes: 1 addition & 1 deletion src/lib/components/svg/LiveControlIcon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<svg
role="img"
class={cn('fill-black dark:fill-white', className)}
class={cn('fill-current', className)}
width={size}
height={size}
viewBox="0 0 50 50"
Expand Down
9 changes: 9 additions & 0 deletions src/lib/state/hubs-state.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export function notifyHubPaired(hubId: string) {
hubPairedSignals.set(hubId, (hubPairedSignals.get(hubId) ?? 0) + 1);
}

// Whether ownHubs reflects the server yet, so an empty map can be told apart from "not fetched".
export const ownHubsStatus = $state<{ loaded: boolean; failed: boolean }>({
loaded: false,
failed: false,
});

export async function refreshOwnHubs() {
try {
const { data } = await shockerListShockers();
Expand All @@ -52,7 +58,10 @@ export async function refreshOwnHubs() {
for (const d of data) {
ownHubs.set(d.id, d);
}
ownHubsStatus.loaded = true;
ownHubsStatus.failed = false;
} catch (error) {
ownHubsStatus.failed = true;
handleApiError(error);
}
}
2 changes: 1 addition & 1 deletion src/routes/(app)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

{#if authState.status !== AuthStatus.Authenticated}
<Container>
<Spinner class="size-20 text-gray-600 dark:text-gray-300" />
<Spinner class="text-muted-foreground size-20" />
</Container>
{:else}
{@render children()}
Expand Down
12 changes: 7 additions & 5 deletions src/routes/(app)/admin/automation-tokens/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<script lang="ts">
import { adminListAutomationTokens } from '#lib/api/index.js';
import { Bot, Plus, RotateCcw } from '@lucide/svelte';
import { Container, EmptyState, PageHeader } from '@openshock/svelte-core/components';
import {
Container,
EmptyState,
PageHeader,
PageLoading,
} from '@openshock/svelte-core/components';
import DataTable from '#lib/components/Table/DataTableTemplate.svelte';
import { Button } from '@openshock/svelte-core/components/ui/button';
import { Spinner } from '@openshock/svelte-core/components/ui/spinner';
import { handleApiError } from '#lib/errorhandling/apiErrorHandling.js';
import { registerBreadcrumbs } from '#lib/state/breadcrumbs-state.svelte.js';
import { onMount } from 'svelte';
Expand Down Expand Up @@ -55,9 +59,7 @@

<div class="flex min-h-0 w-full min-w-0 flex-1 flex-col gap-6">
{#if !hasLoaded}
<div class="flex h-64 w-full items-center justify-center">
<Spinner class="size-8 text-gray-600 dark:text-gray-300" />
</div>
<PageLoading />
{:else if data.length === 0}
<EmptyState
icon={Bot}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
class="cursor-pointer text-red-500"
class="text-destructive cursor-pointer"
onclick={() => (deleteDialogOpen = true)}
>
<Trash2 class="size-4" />
Expand Down
Loading
Loading