Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
12 changes: 6 additions & 6 deletions frontend/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export default function SettingsPage() {
});

return (
<main className="page-surface pb-20 pt-8 sm:pt-10 lg:pb-24 lg:pt-12">
<main className="page-surface pb-16 pt-8 sm:pb-20 sm:pt-10 lg:pb-24 lg:pt-12">
<div className="mx-auto w-full max-w-6xl">
<header className="mb-7 border-b border-[color:var(--frost)] pb-5 sm:mb-8">
<header className="mb-6 border-b border-[color:var(--frost)] pb-6 sm:mb-8">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<div className="flex flex-wrap items-baseline gap-2">
Expand All @@ -63,7 +63,7 @@ export default function SettingsPage() {
Settings
</h1>
</div>
<p className="mt-2 text-sm text-[color:var(--silver)]">
<p className="mt-2 max-w-2xl text-sm leading-6 text-[color:var(--silver)]">
Appearance, local AI, privacy, and retention.
</p>
</div>
Expand All @@ -88,7 +88,7 @@ export default function SettingsPage() {
</div>
</header>

<div className="grid gap-8 lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-10">
<div className="grid gap-6 sm:gap-8 lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-10">
<aside className="lg:sticky lg:top-[calc(var(--nav-height)+2rem)] lg:self-start">
<nav
aria-label="Settings sections"
Expand All @@ -99,7 +99,7 @@ export default function SettingsPage() {
<a
key={href}
href={href}
className="flex min-w-max items-center gap-2 rounded-xl px-3 py-2.5 text-sm font-medium text-[color:var(--silver)] outline-none transition hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] lg:min-w-0"
className="flex min-h-11 min-w-max items-center gap-2 rounded-xl px-3 py-2.5 text-sm font-medium text-[color:var(--silver)] outline-none transition hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)] active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] lg:min-w-0"
>
<Icon aria-hidden="true" size={16} />
{label}
Expand Down Expand Up @@ -150,7 +150,7 @@ export default function SettingsPage() {
<button
type="button"
onClick={() => settingsQuery.refetch()}
className="mt-5 inline-flex h-10 items-center gap-2 rounded-xl bg-[color:var(--near-white)] px-4 text-sm font-semibold text-[color:var(--void)] outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
className="mt-5 inline-flex h-11 items-center gap-2 rounded-xl bg-[color:var(--near-white)] px-4 text-sm font-semibold text-[color:var(--void)] outline-none transition hover:opacity-90 active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] disabled:cursor-wait disabled:opacity-60"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>
<RefreshCw aria-hidden="true" size={15} />
Retry
Expand Down
32 changes: 24 additions & 8 deletions frontend/src/components/about-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { useQuery } from "@tanstack/react-query";
import { BadgeInfo, Check, ClipboardCopy } from "lucide-react";
import { BadgeInfo, Check, ClipboardCopy, LoaderCircle } from "lucide-react";
import { useState } from "react";
import { getAppConfig, getRuntimeConfig } from "@/lib/api";
import { buildSupportSummary, formatWorkerAge } from "@/lib/support-summary";

type CopyState = "idle" | "copied" | "error";
type CopyState = "idle" | "copying" | "copied" | "error";

const WORKER_LABEL: Record<string, string> = {
healthy: "Healthy",
Expand All @@ -17,9 +17,9 @@ const WORKER_LABEL: Record<string, string> = {

function Row({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-baseline justify-between gap-4 py-1.5">
<div className="flex flex-col gap-0.5 py-2 sm:flex-row sm:items-baseline sm:justify-between sm:gap-4">
<dt className="text-sm text-[color:var(--silver)]">{label}</dt>
<dd className="truncate text-sm font-medium text-[color:var(--near-white)]">
<dd className="truncate text-sm font-medium text-[color:var(--near-white)] sm:max-w-[60%] sm:text-right">
{value}
</dd>
</div>
Expand Down Expand Up @@ -56,6 +56,7 @@ export function AboutSettings() {
};

const onCopy = async () => {
setCopyState("copying");
try {
await navigator.clipboard.writeText(buildSupportSummary(summaryInput));
setCopyState("copied");
Expand All @@ -72,9 +73,15 @@ export function AboutSettings() {
aria-labelledby="about-heading"
>
<div className="flex gap-3">
<BadgeInfo className="mt-0.5 h-5 w-5 text-[color:var(--silver)]" />
<BadgeInfo
aria-hidden="true"
className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--silver)]"
/>
<div className="min-w-0 flex-1">
<h2 id="about-heading" className="font-semibold">
<h2
id="about-heading"
className="text-base font-semibold tracking-tight"
>
About this instance
</h2>
<p className="mt-1 text-sm text-[color:var(--silver)]">
Expand Down Expand Up @@ -133,9 +140,17 @@ export function AboutSettings() {
data-testid="copy-support-summary"
onClick={onCopy}
aria-label="Copy support summary to the clipboard"
className="inline-flex h-10 items-center gap-2 rounded-xl border border-[color:var(--frost)] px-4 text-sm font-medium text-[color:var(--silver)] outline-none transition hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
aria-busy={copyState === "copying"}
disabled={copyState === "copying"}
className="inline-flex h-11 items-center gap-2 rounded-xl border border-[color:var(--frost)] px-4 text-sm font-medium text-[color:var(--silver)] outline-none transition hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)] active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] disabled:cursor-wait disabled:opacity-60"
>
{copyState === "copied" ? (
{copyState === "copying" ? (
<LoaderCircle
aria-hidden="true"
className="animate-spin"
size={15}
/>
) : copyState === "copied" ? (
<Check aria-hidden="true" size={15} />
) : (
<ClipboardCopy aria-hidden="true" size={15} />
Expand All @@ -153,6 +168,7 @@ export function AboutSettings() {
: "text-[color:var(--silver)]"
}`}
>
{copyState === "copying" && "Copying support summary…"}
{copyState === "copied" && "Support summary copied."}
{copyState === "error" &&
"Couldn't copy. Select the details above and copy manually."}
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/components/ai-runtime-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,17 @@ export function AiRuntimeSettings({
aria-checked={checked}
disabled={enabled === undefined || pending}
onClick={() => onChange(!checked)}
className="relative h-7 w-12 shrink-0 rounded-full border border-[color:var(--frost-strong)] bg-[color:var(--surface-hover)] transition disabled:cursor-wait disabled:opacity-50 aria-checked:border-[color:var(--green)] aria-checked:bg-[color:var(--green)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
aria-busy={pending}
className="relative h-11 w-12 shrink-0 rounded-full border border-[color:var(--frost-strong)] bg-[color:var(--surface-hover)] transition hover:border-[color:var(--frost-strong)] active:scale-95 disabled:cursor-wait disabled:opacity-50 aria-checked:border-[color:var(--green)] aria-checked:bg-[color:var(--green)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
>
<span
aria-hidden="true"
className="absolute left-1 top-1 size-[1.125rem] rounded-full bg-white shadow transition-transform"
style={{ transform: checked ? "translateX(1.25rem)" : undefined }}
className="absolute left-1 top-1/2 size-5 rounded-full bg-[color:var(--void)] shadow transition-transform"
style={{
transform: checked
? "translate(1.25rem, -50%)"
: "translateY(-50%)",
}}
/>
</button>
</div>
Expand Down Expand Up @@ -122,7 +127,7 @@ export function AiRuntimeSettings({
onChange={(event) =>
onModeChange(event.target.value as "disabled" | "full" | "mock")
}
className="mt-3 h-11 w-full rounded-xl border border-[color:var(--frost)] bg-[color:var(--void)] px-3 text-sm text-[color:var(--near-white)] outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] sm:max-w-xs"
className="mt-3 h-11 w-full rounded-xl border border-[color:var(--frost)] bg-[color:var(--void)] px-3 text-sm text-[color:var(--near-white)] outline-none transition hover:border-[color:var(--frost-strong)] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] disabled:cursor-wait disabled:opacity-60 sm:max-w-xs"
>
{mode && !supportedModes.includes(mode) && (
<option value={mode} disabled>
Expand All @@ -142,7 +147,7 @@ export function AiRuntimeSettings({
</div>

<details className="border-t border-[color:var(--frost)] px-5 py-4">
<summary className="cursor-pointer text-sm font-semibold">
<summary className="cursor-pointer rounded-lg text-sm font-semibold outline-none transition hover:text-[color:var(--near-white)] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]">
Change the installed build
</summary>
<p className="mt-2 text-xs leading-5 text-[color:var(--silver)]">
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/appearance-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ export function AppearanceSettings() {
<section
id="appearance"
aria-labelledby="appearance-heading"
className="rounded-2xl border border-[color:var(--frost)] bg-[color:var(--surface-soft)] p-5 sm:p-6"
className="rounded-2xl border border-[color:var(--frost)] bg-[color:var(--surface-soft)] p-5 shadow-[0_18px_60px_rgba(0,0,0,0.08)] sm:p-6"
>
<h2 id="appearance-heading" className="text-base font-semibold">
<h2
id="appearance-heading"
className="text-base font-semibold tracking-tight"
>
Appearance
</h2>
<p className="mt-1 text-sm text-[color:var(--silver)]">
Expand All @@ -39,7 +42,7 @@ export function AppearanceSettings() {
{OPTIONS.map(({ value: option, label, icon: Icon }) => (
<label
key={option}
className={`flex cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2.5 text-sm font-medium transition ${value === option ? "bg-[color:var(--near-white)] text-[color:var(--void)] shadow-sm" : "text-[color:var(--silver)] hover:bg-[color:var(--surface-hover)]"}`}
className={`flex min-h-11 cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2.5 text-sm font-medium outline-none transition active:scale-[0.98] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[color:var(--blue)] ${value === option ? "bg-[color:var(--near-white)] text-[color:var(--void)] shadow-sm" : "text-[color:var(--silver)] hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)]"}`}
>
<input
className="sr-only"
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/hardware-accel-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function HardwareAccelSettings({
<label
key={mode.value}
data-testid={`accel-option-${mode.value}`}
className="group relative flex min-h-28 cursor-pointer flex-col gap-2 bg-[color:var(--void)]/90 p-4 transition hover:bg-[color:var(--surface-hover)] has-[:checked]:bg-[color:var(--blue-soft)] has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50"
className="group relative flex min-h-28 cursor-pointer flex-col gap-2 bg-[color:var(--void)]/90 p-4 outline-none transition hover:bg-[color:var(--surface-hover)] active:bg-[color:var(--blue-soft)] has-[:checked]:bg-[color:var(--blue-soft)] has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:focus-visible]:z-10 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[color:var(--blue)]"
title={
gpuNotInstalled
? "GPU runtime is not included in this installed build."
Expand Down Expand Up @@ -118,6 +118,8 @@ export function HardwareAccelSettings({
<p
className="px-5 py-4 text-sm text-[color:var(--silver)]"
data-testid="accel-loading"
role="status"
aria-live="polite"
>
Detecting hardware…
</p>
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/components/map-privacy-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,17 @@ export function MapPrivacySettings({
aria-checked={checked}
disabled={enabled === undefined || pending}
onClick={() => onChange(!checked)}
className="relative h-7 w-12 shrink-0 rounded-full border border-[color:var(--frost-strong)] bg-[color:var(--surface-hover)] transition disabled:cursor-wait disabled:opacity-50 aria-checked:border-[color:var(--near-white)] aria-checked:bg-[color:var(--near-white)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--void)]"
aria-busy={pending}
className="relative h-11 w-12 shrink-0 rounded-full border border-[color:var(--frost-strong)] bg-[color:var(--surface-hover)] transition hover:border-[color:var(--frost-strong)] active:scale-95 disabled:cursor-wait disabled:opacity-50 aria-checked:border-[color:var(--green)] aria-checked:bg-[color:var(--green)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--void)]"
>
<span
aria-hidden="true"
className="absolute left-1 top-1 size-[1.125rem] rounded-full bg-white shadow transition-transform"
style={{ transform: checked ? "translateX(1.25rem)" : undefined }}
className="absolute left-1 top-1/2 size-5 rounded-full bg-[color:var(--void)] shadow transition-transform"
style={{
transform: checked
? "translate(1.25rem, -50%)"
: "translateY(-50%)",
}}
/>
</button>
</div>
Expand Down Expand Up @@ -86,7 +91,7 @@ export function MapPrivacySettings({
{checked && (
<Link
href="/map"
className="shrink-0 rounded-lg px-2 py-1 text-xs font-semibold text-[color:var(--near-white)] hover:bg-[color:var(--surface-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
className="inline-flex min-h-11 shrink-0 items-center rounded-lg px-3 text-xs font-semibold text-[color:var(--near-white)] outline-none transition hover:bg-[color:var(--surface-hover)] active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
>
Open map
</Link>
Expand Down
19 changes: 13 additions & 6 deletions frontend/src/components/trash-retention-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@ export function TrashRetentionSettings({
>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
<Clock3 className="mt-0.5 h-5 w-5 text-[color:var(--silver)]" />
<Clock3
aria-hidden="true"
className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--silver)]"
/>
<div>
<div className="flex items-center gap-2">
<h2 id="trash-retention-heading" className="font-semibold">
<h2
id="trash-retention-heading"
className="text-base font-semibold tracking-tight"
>
Trash retention
</h2>
<button
type="button"
title="Expired items are permanently removed when Trash is opened. Set Never to require manual emptying."
aria-label="Expired items are permanently removed when Trash is opened. Set Never to require manual emptying."
className="rounded text-[color:var(--muted)] outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
className="inline-flex size-11 items-center justify-center rounded-lg text-[color:var(--muted)] outline-none transition hover:bg-[color:var(--surface-hover)] hover:text-[color:var(--near-white)] active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)]"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
>
<Info className="h-4 w-4" aria-hidden="true" />
</button>
Expand All @@ -53,7 +59,7 @@ export function TrashRetentionSettings({
disabled={pending}
aria-pressed={value === days}
onClick={() => onChange(days)}
className={`rounded-xl border px-4 py-2 text-sm transition ${
className={`min-h-11 rounded-xl border px-4 py-2 text-sm font-medium outline-none transition active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-[color:var(--blue)] disabled:cursor-wait disabled:opacity-60 ${
value === days
? "border-[color:var(--near-white)] bg-[color:var(--near-white)] text-[color:var(--void)]"
: "border-[color:var(--frost)] text-[color:var(--silver)] hover:bg-[color:var(--surface-hover)]"
Expand All @@ -62,7 +68,7 @@ export function TrashRetentionSettings({
{days === 0 ? "Never" : `${days} days`}
</button>
))}
<label className="flex items-center gap-2 rounded-xl border border-[color:var(--frost)] px-3 py-1.5 text-sm text-[color:var(--silver)]">
<label className="flex min-h-11 items-center gap-2 rounded-xl border border-[color:var(--frost)] px-3 py-1.5 text-sm text-[color:var(--silver)] transition focus-within:border-[color:var(--frost-strong)] focus-within:ring-2 focus-within:ring-[color:var(--blue)]">
Custom
<input
type="number"
Expand All @@ -79,7 +85,8 @@ export function TrashRetentionSettings({
onChange(days);
}
}}
className="w-20 bg-transparent text-right text-[color:var(--near-white)] outline-none"
aria-label="Custom retention period in days"
className="w-20 bg-transparent text-right text-[color:var(--near-white)] outline-none disabled:cursor-wait disabled:opacity-60"
/>
</label>
</div>
Expand Down