ui(settings): Quit/Uninstall as real buttons with icons and a busy spinner; code in row descriptions; dimmed unavailable rows; smoother switch

This commit is contained in:
2026-10-16 10:58:14 +07:00
parent 538aec3ed9
commit 5e0a9b526b
4 changed files with 151 additions and 45 deletions
+6 -3
View File
@@ -33,13 +33,16 @@ export function Switch({
// On: the solid accent (white knob clears 3:1 in every theme). Off: a // On: the solid accent (white knob clears 3:1 in every theme). Off: a
// border-light track with an inset fg-muted/80 ring (≥3:1 in every theme), so the control's edge // border-light track with an inset fg-muted/80 ring (≥3:1 in every theme), so the control's edge
// is visible on any surface (surface-4 alone was ~1:1 on a light card). // is visible on any surface (surface-4 alone was ~1:1 on a light card).
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50 ${ // Hover darkens the track (enabled only), so it reads as clickable.
checked ? "bg-accent-solid" : "bg-border-light ring-1 ring-inset ring-fg-muted/80" className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 ${
checked
? "bg-accent-solid enabled:hover:bg-accent-solid-hover"
: "bg-border-light ring-1 ring-inset ring-fg-muted/80 enabled:hover:ring-fg-secondary"
}`} }`}
> >
<span <span
aria-hidden aria-hidden
className={`inline-block h-4 w-4 rounded-full bg-white shadow transition-transform ${ className={`inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out ${
checked ? "translate-x-[18px]" : "translate-x-0.5" checked ? "translate-x-[18px]" : "translate-x-0.5"
}`} }`}
/> />
@@ -9,17 +9,24 @@
* boot/login while the switch is on. Between them, the status-area icon switch * boot/login while the switch is on. Between them, the status-area icon switch
* (`GET/PUT /api/settings/tray`, on by default — server/lib/tray.js). Last, * (`GET/PUT /api/settings/tray`, on by default — server/lib/tray.js). Last,
* **Uninstall AgentHub**: `POST /api/settings/uninstall` removes AgentHub from * **Uninstall AgentHub**: `POST /api/settings/uninstall` removes AgentHub from
* the machine, database included (scripts/uninstall.js). * the machine, database included (scripts/uninstall.js). Quit is a plain
* button and Uninstall a danger one, both the same width with an icon, and
* each spins while its request runs.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn> * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/ */
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Loader2, Power, Trash2 } from "lucide-react";
import { api, type AutostartStatus, type TrayStatus } from "../../lib/api"; import { api, type AutostartStatus, type TrayStatus } from "../../lib/api";
import { toast } from "../../lib/toast"; import { toast } from "../../lib/toast";
import { confirmDialog } from "../../lib/confirm"; import { confirmDialog } from "../../lib/confirm";
import { SettingsRow, SettingsSection, SettingsSwitchRow } from "./SettingsRow"; import { SettingsRow, SettingsSection, SettingsSwitchRow } from "./SettingsRow";
// The two action buttons share one width, so they line up under the switches.
const ACTION = "min-w-[6.5rem] justify-center text-xs";
const ICON = "h-3.5 w-3.5 shrink-0";
export function AutostartSettings() { export function AutostartSettings() {
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
const [status, setStatus] = useState<AutostartStatus | null>(null); const [status, setStatus] = useState<AutostartStatus | null>(null);
@@ -126,9 +133,11 @@ export function AutostartSettings() {
type="button" type="button"
data-testid="quit-agenthub" data-testid="quit-agenthub"
disabled={quitting} disabled={quitting}
aria-busy={quitting}
onClick={() => void quit()} onClick={() => void quit()}
className="btn-ghost text-xs text-status-danger disabled:opacity-40" className={`btn-secondary ${ACTION}`}
> >
{quitting ? <Loader2 className={ICON + " animate-spin"} /> : <Power className={ICON} />}
{t("quit.button")} {t("quit.button")}
</button> </button>
} }
@@ -141,9 +150,15 @@ export function AutostartSettings() {
type="button" type="button"
data-testid="uninstall-agenthub" data-testid="uninstall-agenthub"
disabled={uninstalling} disabled={uninstalling}
aria-busy={uninstalling}
onClick={() => void uninstall()} onClick={() => void uninstall()}
className="btn-ghost text-xs text-status-danger disabled:opacity-40" className={`btn-danger ${ACTION}`}
> >
{uninstalling ? (
<Loader2 className={ICON + " animate-spin"} />
) : (
<Trash2 className={ICON} />
)}
{t("uninstall.button")} {t("uninstall.button")}
</button> </button>
} }
+31 -4
View File
@@ -4,7 +4,8 @@
* switch row (`SettingsSwitchRow`) and a segmented radio group * switch row (`SettingsSwitchRow`) and a segmented radio group
* (`SettingsSegmented`, arrow keys + one roving tab stop). Rows divide * (`SettingsSegmented`, arrow keys + one roving tab stop). Rows divide
* themselves with a hairline, none after the last, so a section is just its * themselves with a hairline, none after the last, so a section is just its
* rows in order. * rows in order. A string description shows its `backticked` words as code,
* and a `disabled` row dims its label, so an unavailable control reads as one.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn> * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/ */
@@ -32,30 +33,55 @@ export function SettingsSection({
); );
} }
/** `text` with every `backticked` run drawn as inline code (a command to type). */
export function withCode(text: string): ReactNode {
const parts = text.split(/`([^`]+)`/);
if (parts.length === 1) return text;
return parts.map((p, i) =>
i % 2 ? (
<code
key={i}
className="whitespace-nowrap rounded border border-border bg-surface-2 px-1 py-px font-mono text-[11px] text-fg-secondary"
>
{p}
</code>
) : (
p
)
);
}
export function SettingsRow({ export function SettingsRow({
label, label,
description, description,
control, control,
alignTop = false, alignTop = false,
disabled = false,
id, id,
}: { }: {
label: ReactNode; label: ReactNode;
description?: ReactNode; description?: ReactNode;
control: ReactNode; control: ReactNode;
alignTop?: boolean; alignTop?: boolean;
/** The control cannot be used here: the label is dimmed with it. */
disabled?: boolean;
/** A deep-link anchor (`#id`) for one row inside a grouped section. */ /** A deep-link anchor (`#id`) for one row inside a grouped section. */
id?: string; id?: string;
}) { }) {
return ( return (
<div <div
id={id} id={id}
className={`${id ? "scroll-mt-24 " : ""}flex justify-between gap-4 border-b border-border/60 last:border-b-0 ${ className={`${id ? "scroll-mt-24 " : ""}flex justify-between gap-6 border-b border-border/60 last:border-b-0 ${
description ? "py-3" : "py-2" description ? "py-3" : "py-2"
} ${alignTop ? "items-start" : "items-center"}`} } ${alignTop ? "items-start" : "items-center"}`}
> >
<div className="min-w-0"> <div className="min-w-0">
<p className="text-sm text-fg-secondary">{label}</p> <p className={`text-sm ${disabled ? "text-fg-muted" : "text-fg-secondary"}`}>{label}</p>
{description && <p className="mt-0.5 text-xs text-fg-muted">{description}</p>} {description && (
<p className="mt-0.5 text-xs leading-relaxed text-fg-muted">
{typeof description === "string" ? withCode(description) : description}
</p>
)}
</div> </div>
<div className="shrink-0">{control}</div> <div className="shrink-0">{control}</div>
</div> </div>
@@ -82,6 +108,7 @@ export function SettingsSwitchRow({
id={id} id={id}
label={label} label={label}
description={description} description={description}
disabled={disabled}
control={<Switch checked={checked} onChange={onChange} label={label} disabled={disabled} />} control={<Switch checked={checked} onChange={onChange} label={label} disabled={disabled} />}
/> />
); );
@@ -7022,7 +7022,7 @@ exports[`screen snapshots > Settings — about 1`] = `
class="mt-2" class="mt-2"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7033,7 +7033,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Start AgentHub automatically Start AgentHub automatically
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
Run the dashboard as a systemd --user service, so it starts at boot without Claude Code being opened. Turning it off keeps the running server up. Run the dashboard as a systemd --user service, so it starts at boot without Claude Code being opened. Turning it off keeps the running server up.
</p> </p>
@@ -7044,19 +7044,19 @@ exports[`screen snapshots > Settings — about 1`] = `
<button <button
aria-checked="false" aria-checked="false"
aria-label="Start AgentHub automatically" aria-label="Start AgentHub automatically"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50 bg-border-light ring-1 ring-inset ring-fg-muted/80" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 bg-border-light ring-1 ring-inset ring-fg-muted/80 enabled:hover:ring-fg-secondary"
role="switch" role="switch"
type="button" type="button"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="inline-block h-4 w-4 rounded-full bg-white shadow transition-transform translate-x-0.5" class="inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out translate-x-0.5"
/> />
</button> </button>
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7067,7 +7067,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Show AgentHub icon in the status area Show AgentHub icon in the status area
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
An AgentHub icon beside the clock (top bar, menu bar or notification area) to open the dashboard, quit it, and start it again after a quit. An AgentHub icon beside the clock (top bar, menu bar or notification area) to open the dashboard, quit it, and start it again after a quit.
</p> </p>
@@ -7078,19 +7078,19 @@ exports[`screen snapshots > Settings — about 1`] = `
<button <button
aria-checked="true" aria-checked="true"
aria-label="Show AgentHub icon in the status area" aria-label="Show AgentHub icon in the status area"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50 bg-accent-solid" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 bg-accent-solid enabled:hover:bg-accent-solid-hover"
role="switch" role="switch"
type="button" type="button"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="inline-block h-4 w-4 rounded-full bg-white shadow transition-transform translate-x-[18px]" class="inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out translate-x-[18px]"
/> />
</button> </button>
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7101,25 +7101,51 @@ exports[`screen snapshots > Settings — about 1`] = `
Quit AgentHub Quit AgentHub
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
Stop the dashboard so nothing runs in the background. Opening Claude Code no longer starts it; run \`agenthub start\` to bring it back. Stop the dashboard so nothing runs in the background. Opening Claude Code no longer starts it; run
<code
class="whitespace-nowrap rounded border border-border bg-surface-2 px-1 py-px font-mono text-[11px] text-fg-secondary"
>
agenthub start
</code>
to bring it back.
</p> </p>
</div> </div>
<div <div
class="shrink-0" class="shrink-0"
> >
<button <button
class="btn-ghost text-xs text-status-danger disabled:opacity-40" aria-busy="false"
class="btn-secondary min-w-[6.5rem] justify-center text-xs"
data-testid="quit-agenthub" data-testid="quit-agenthub"
type="button" type="button"
> >
<svg
class="lucide lucide-power h-3.5 w-3.5 shrink-0"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2v10"
/>
<path
d="M18.4 6.6a9 9 0 1 1-12.77.04"
/>
</svg>
Quit Quit
</button> </button>
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7130,7 +7156,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Uninstall AgentHub Uninstall AgentHub
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
Remove AgentHub from this computer: the dashboard, its database and settings, its hooks in Claude Code, Codex and Antigravity, and the plugin. Your conversations, workspace folders and repositories are kept. Remove AgentHub from this computer: the dashboard, its database and settings, its hooks in Claude Code, Codex and Antigravity, and the plugin. Your conversations, workspace folders and repositories are kept.
</p> </p>
@@ -7139,10 +7165,45 @@ exports[`screen snapshots > Settings — about 1`] = `
class="shrink-0" class="shrink-0"
> >
<button <button
class="btn-ghost text-xs text-status-danger disabled:opacity-40" aria-busy="false"
class="btn-danger min-w-[6.5rem] justify-center text-xs"
data-testid="uninstall-agenthub" data-testid="uninstall-agenthub"
type="button" type="button"
> >
<svg
class="lucide lucide-trash2 h-3.5 w-3.5 shrink-0"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 6h18"
/>
<path
d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
/>
<path
d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
/>
<line
x1="10"
x2="10"
y1="11"
y2="17"
/>
<line
x1="14"
x2="14"
y1="11"
y2="17"
/>
</svg>
Uninstall Uninstall
</button> </button>
</div> </div>
@@ -7162,7 +7223,7 @@ exports[`screen snapshots > Settings — about 1`] = `
class="mt-2" class="mt-2"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7173,7 +7234,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Release Release
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
<span <span
class="text-status-warning/90" class="text-status-warning/90"
@@ -7193,7 +7254,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7215,7 +7276,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7240,7 +7301,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7262,7 +7323,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7284,7 +7345,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7295,7 +7356,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Live connections Live connections
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
Browser tabs and tools receiving live updates Browser tabs and tools receiving live updates
</p> </p>
@@ -7325,7 +7386,7 @@ exports[`screen snapshots > Settings — about 1`] = `
class="mt-2" class="mt-2"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7355,7 +7416,7 @@ exports[`screen snapshots > Settings — about 1`] = `
class="mt-2" class="mt-2"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7366,7 +7427,7 @@ exports[`screen snapshots > Settings — about 1`] = `
Database file Database file
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
<span <span
class="font-mono break-all" class="font-mono break-all"
@@ -7386,7 +7447,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -7408,7 +7469,7 @@ exports[`screen snapshots > Settings — about 1`] = `
</div> </div>
</div> </div>
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -8960,7 +9021,7 @@ exit 0
class="mt-2" class="mt-2"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-3 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-3 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -8971,7 +9032,7 @@ exit 0
Redact secrets before storing Redact secrets before storing
</p> </p>
<p <p
class="mt-0.5 text-xs text-fg-muted" class="mt-0.5 text-xs leading-relaxed text-fg-muted"
> >
API keys and tokens of known shapes, bearer headers, private keys and password=/TOKEN= values in event payloads and summaries become [redacted]. The transcripts on disk are the CLIs' own files and are not changed. API keys and tokens of known shapes, bearer headers, private keys and password=/TOKEN= values in event payloads and summaries become [redacted]. The transcripts on disk are the CLIs' own files and are not changed.
</p> </p>
@@ -8982,13 +9043,13 @@ exit 0
<button <button
aria-checked="false" aria-checked="false"
aria-label="Redact secrets before storing" aria-label="Redact secrets before storing"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50 bg-border-light ring-1 ring-inset ring-fg-muted/80" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 bg-border-light ring-1 ring-inset ring-fg-muted/80 enabled:hover:ring-fg-secondary"
role="switch" role="switch"
type="button" type="button"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="inline-block h-4 w-4 rounded-full bg-white shadow transition-transform translate-x-0.5" class="inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out translate-x-0.5"
/> />
</button> </button>
</div> </div>
@@ -9739,7 +9800,7 @@ exports[`screen snapshots > Settings — notifications 1`] = `
class="min-w-0 flex-1" class="min-w-0 flex-1"
> >
<div <div
class="flex justify-between gap-4 border-b border-border/60 last:border-b-0 py-2 items-center" class="flex justify-between gap-6 border-b border-border/60 last:border-b-0 py-2 items-center"
> >
<div <div
class="min-w-0" class="min-w-0"
@@ -9756,13 +9817,13 @@ exports[`screen snapshots > Settings — notifications 1`] = `
<button <button
aria-checked="false" aria-checked="false"
aria-label="Enable Browser Notifications" aria-label="Enable Browser Notifications"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-50 bg-border-light ring-1 ring-inset ring-fg-muted/80" class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 bg-border-light ring-1 ring-inset ring-fg-muted/80 enabled:hover:ring-fg-secondary"
role="switch" role="switch"
type="button" type="button"
> >
<span <span
aria-hidden="true" aria-hidden="true"
class="inline-block h-4 w-4 rounded-full bg-white shadow transition-transform translate-x-0.5" class="inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out translate-x-0.5"
/> />
</button> </button>
</div> </div>