diff --git a/.gitignore b/.gitignore index df7d56a..45cd856 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,3 @@ logs/ # Claude Code local agent state .claude/ - -# .freebuff local tool state (not for commit) -.freebuff/ -# graphify knowledge-graph output (local tool cache, not for commit) -graphify-out/ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 03950c8..6f1479c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,6 @@ "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tauri-apps/cli": "^2", - "@types/node": "^26.4.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", @@ -1309,16 +1308,6 @@ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1848,13 +1837,6 @@ "node": ">=14.17" } }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, "node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 62b0eb1..08d2563 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bos-settings-frontend", - "version": "0.8.2", + "version": "0.8.0", "description": "Frontend for BOS Settings", "type": "module", "scripts": { @@ -23,7 +23,6 @@ "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tauri-apps/cli": "^2", - "@types/node": "^26.4.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", diff --git a/frontend/src/lib/components/Embed.svelte b/frontend/src/lib/components/Embed.svelte deleted file mode 100644 index 40090e5..0000000 --- a/frontend/src/lib/components/Embed.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -{@render children()} diff --git a/frontend/src/lib/components/EmptyState.svelte b/frontend/src/lib/components/EmptyState.svelte index aba4339..357d48f 100644 --- a/frontend/src/lib/components/EmptyState.svelte +++ b/frontend/src/lib/components/EmptyState.svelte @@ -16,8 +16,8 @@ flex-direction: column; align-items: center; gap: 6px; - padding: 28px 8px; - color: var(--muted); + padding: var(--space-xl, 20px) 0; + opacity: 0.7; text-align: center; } diff --git a/frontend/src/lib/components/Group.svelte b/frontend/src/lib/components/Group.svelte index fd11cb0..5a6caae 100644 --- a/frontend/src/lib/components/Group.svelte +++ b/frontend/src/lib/components/Group.svelte @@ -22,35 +22,33 @@ display: flex; flex-direction: column; min-width: 0; - background: var(--surface); - border: 1px solid var(--line, color-mix(in srgb, var(--fg) 8%, transparent)); - border-radius: var(--radius, 14px); - padding: 16px 18px; } .group.wide { - width: 100%; + grid-column: 1 / -1; } .title { font-weight: 600; - font-size: 13px; - letter-spacing: 0.04em; - text-transform: uppercase; - color: var(--muted, color-mix(in oklab, var(--fg) 58%, transparent)); - margin: 0 0 12px; + font-size: 1.05em; + margin: 0 0 var(--space-sm, 8px); } .hint { - color: var(--muted, color-mix(in oklab, var(--fg) 58%, transparent)); + opacity: 0.75; font-size: var(--font-size-secondary, 12px); line-height: 1.4; - margin: -6px 0 12px; + margin: 0 0 var(--space-sm, 8px); } .rows { display: flex; flex-direction: column; + /* Flex children default to refusing to shrink below their content's + natural width (min-width: auto) — without this, a button/input + with enough text overflows past the grid column's actual pixel + width instead of wrapping, visually spilling into the next + column. */ min-width: 0; } diff --git a/frontend/src/lib/components/Hint.svelte b/frontend/src/lib/components/Hint.svelte index e86d531..450e2ad 100644 --- a/frontend/src/lib/components/Hint.svelte +++ b/frontend/src/lib/components/Hint.svelte @@ -6,9 +6,13 @@ diff --git a/frontend/src/lib/components/Hub.svelte b/frontend/src/lib/components/Hub.svelte deleted file mode 100644 index dd404a0..0000000 --- a/frontend/src/lib/components/Hub.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - - {#if tabs.length > 1} - ({ id, label }))} bind:value={tab} /> - {/if} - - {#key tab} - {#if Active} - - {/if} - {/key} - - diff --git a/frontend/src/lib/components/HyprColorField.svelte b/frontend/src/lib/components/HyprColorField.svelte index cc9f430..115d4d7 100644 --- a/frontend/src/lib/components/HyprColorField.svelte +++ b/frontend/src/lib/components/HyprColorField.svelte @@ -1,7 +1,7 @@
-
- {label} - {#if hint}{hint}{/if} -
+ {label}
{@render children()}
diff --git a/frontend/src/lib/components/SaveButton.svelte b/frontend/src/lib/components/SaveButton.svelte index faadab1..5dcd916 100644 --- a/frontend/src/lib/components/SaveButton.svelte +++ b/frontend/src/lib/components/SaveButton.svelte @@ -19,7 +19,7 @@
- + {status}
@@ -28,11 +28,28 @@ display: flex; align-items: center; gap: var(--space-md, 12px); - margin-top: 4px; + margin-top: var(--space-lg, 16px); + /* Always spans the full grid width, regardless of how many + Group columns the rest of the page laid out above it. */ + grid-column: 1 / -1; + } + + button { + background-color: var(--accent); + color: var(--on-accent); + border: none; + border-radius: var(--radius-primary, 8px); + padding: var(--space-sm, 8px) var(--space-lg, 16px); + cursor: pointer; + } + + button:disabled { + opacity: 0.5; + cursor: default; } .status { - color: var(--muted, color-mix(in oklab, var(--fg) 58%, transparent)); + opacity: 0.6; font-size: var(--font-size-secondary, 12px); } diff --git a/frontend/src/lib/components/SelectField.svelte b/frontend/src/lib/components/SelectField.svelte index feba79e..2dc50c6 100644 --- a/frontend/src/lib/components/SelectField.svelte +++ b/frontend/src/lib/components/SelectField.svelte @@ -1,25 +1,13 @@ - + @@ -30,8 +18,8 @@ background-color: var(--bg); color: var(--on-surface); border: 1px solid transparent; - border-radius: var(--radius-sm, 10px); - padding: 6px 10px; + border-radius: var(--radius-secondary, 6px); + padding: var(--space-xs, 4px) var(--space-sm, 8px); } option { diff --git a/frontend/src/lib/components/ServiceControl.svelte b/frontend/src/lib/components/ServiceControl.svelte index 9f3e8c0..1c81d29 100644 --- a/frontend/src/lib/components/ServiceControl.svelte +++ b/frontend/src/lib/components/ServiceControl.svelte @@ -30,7 +30,7 @@ function toggle() { if (active && critical) { - if (!confirm(`Stop ${unit}? Other bread apps may stall until it is restarted.`)) { + if (!confirm(`Stop ${unit}? This is a core part of the desktop's event handling — stopping it may affect other bread apps until it's restarted.`)) { return; } } @@ -57,7 +57,7 @@ {#if hasConfig} - + {/if} @@ -74,16 +74,16 @@ } button { - background-color: var(--surface-2, var(--bg)); + background-color: var(--surface); color: var(--on-surface); - border: 1px solid var(--line, transparent); - border-radius: var(--radius-sm, 10px); + border: none; + border-radius: var(--radius-primary, 8px); padding: var(--space-sm, 8px) var(--space-lg, 16px); cursor: pointer; } button:hover { - background-color: color-mix(in srgb, var(--on-surface) 10%, transparent); + background-color: color-mix(in srgb, var(--on-surface) 14%, transparent); } button:disabled { diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte index d7c67e8..95fb43e 100644 --- a/frontend/src/lib/components/Sidebar.svelte +++ b/frontend/src/lib/components/Sidebar.svelte @@ -1,130 +1,59 @@ diff --git a/frontend/src/lib/components/SliderField.svelte b/frontend/src/lib/components/SliderField.svelte deleted file mode 100644 index e3ee9a9..0000000 --- a/frontend/src/lib/components/SliderField.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - - - onChange?.()} /> - {shown}{suffix} - - - diff --git a/frontend/src/lib/components/Subnav.svelte b/frontend/src/lib/components/Subnav.svelte deleted file mode 100644 index acc782b..0000000 --- a/frontend/src/lib/components/Subnav.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/frontend/src/lib/components/Switch.svelte b/frontend/src/lib/components/Switch.svelte index c62300d..e2504d3 100644 --- a/frontend/src/lib/components/Switch.svelte +++ b/frontend/src/lib/components/Switch.svelte @@ -11,40 +11,33 @@ aria-label={ariaLabel} onclick={() => (value = !value)} > - + diff --git a/frontend/src/lib/components/SwitchField.svelte b/frontend/src/lib/components/SwitchField.svelte index a8e2b19..a49caaf 100644 --- a/frontend/src/lib/components/SwitchField.svelte +++ b/frontend/src/lib/components/SwitchField.svelte @@ -2,13 +2,9 @@ import Row from "./Row.svelte"; import Switch from "./Switch.svelte"; - let { - label, - hint, - value = $bindable(), - }: { label: string; hint?: string; value: boolean } = $props(); + let { label, value = $bindable() }: { label: string; value: boolean } = $props(); - + diff --git a/frontend/src/lib/components/TextField.svelte b/frontend/src/lib/components/TextField.svelte index 1711e56..62fa918 100644 --- a/frontend/src/lib/components/TextField.svelte +++ b/frontend/src/lib/components/TextField.svelte @@ -18,8 +18,8 @@ background-color: var(--bg); color: var(--on-surface); border: 1px solid transparent; - border-radius: var(--radius-sm, 10px); - padding: 6px 10px; + border-radius: var(--radius-secondary, 6px); + padding: var(--space-xs, 4px) var(--space-sm, 8px); } input:focus { diff --git a/frontend/src/lib/components/Titlebar.svelte b/frontend/src/lib/components/Titlebar.svelte deleted file mode 100644 index 78fe93a..0000000 --- a/frontend/src/lib/components/Titlebar.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - -
-
- - - -
-
Settings
-
- Ctrl K -
- - diff --git a/frontend/src/lib/components/ViewScaffold.svelte b/frontend/src/lib/components/ViewScaffold.svelte index 1eef417..bb27e82 100644 --- a/frontend/src/lib/components/ViewScaffold.svelte +++ b/frontend/src/lib/components/ViewScaffold.svelte @@ -1,64 +1,40 @@ -{#if embedded} -
+
+

{title}

+
{@render children()}
-{:else} -
-

{title}

- {#if lede} -

{lede}

- {/if} -
- {@render children()} -
-
-{/if} +
diff --git a/frontend/src/lib/debounce.ts b/frontend/src/lib/debounce.ts deleted file mode 100644 index 463b59b..0000000 --- a/frontend/src/lib/debounce.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function debounce(fn: () => void, ms = 280): () => void { - let t: ReturnType | undefined; - return () => { - clearTimeout(t); - t = setTimeout(fn, ms); - }; -} diff --git a/frontend/src/lib/embed.ts b/frontend/src/lib/embed.ts deleted file mode 100644 index 86ff3ab..0000000 --- a/frontend/src/lib/embed.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** When set, ViewScaffold renders inner groups only - used by hub tab panes. */ -export const EMBEDDED_KEY = "bos-settings-embedded"; diff --git a/frontend/src/lib/nav.svelte.ts b/frontend/src/lib/nav.svelte.ts deleted file mode 100644 index b7382c9..0000000 --- a/frontend/src/lib/nav.svelte.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { DEFAULT_PAGE, resolvePage } from "$lib/sidebar"; - -/** Shared navigation - module `$state` so sidebar/search/hubs all see the same page. */ -export const nav = $state({ - page: DEFAULT_PAGE, - tab: null as string | null, - searchNonce: 0, -}); - -export function go(page: string, tab?: string) { - const resolved = resolvePage(page); - nav.tab = tab ?? resolved.tab ?? ""; - nav.page = resolved.page; -} - -export type Navigate = (page: string, tab?: string) => void; -export const NAVIGATE_KEY = "bos-settings-navigate"; diff --git a/frontend/src/lib/search.ts b/frontend/src/lib/search.ts deleted file mode 100644 index 2b5f4e6..0000000 --- a/frontend/src/lib/search.ts +++ /dev/null @@ -1,120 +0,0 @@ -export interface SearchHit { - page: string; - tab?: string; - label: string; - keywords: string; -} - -export const SEARCH_INDEX: SearchHit[] = [ - { page: "home", label: "Home", keywords: "search overview status" }, - { - page: "network", - label: "Wi-Fi", - keywords: "wifi ssid password scan radio ethernet hotspot dns", - }, - { - page: "network", - tab: "vpn", - label: "VPN / WireGuard", - keywords: "vpn wireguard tailscale tunnel import conf", - }, - { - page: "network", - tab: "breadcrumbs", - label: "Saved networks", - keywords: "wifi profiles breadcrumbs known ssid tailscale", - }, - { page: "bluetooth", label: "Bluetooth", keywords: "bluetooth headphones pair scan mouse keyboard" }, - { - page: "displays", - label: "Displays", - keywords: "monitor scale resolution arrange rotate vrr hdr hyprland", - }, - { - page: "displays", - tab: "nightlight", - label: "Night light", - keywords: "night light hyprsunset temperature warmth sunset", - }, - { - page: "displays", - tab: "breadmon", - label: "Live arrange", - keywords: "breadmon mirror profile arrange", - }, - { - page: "appearance", - label: "Wallpaper", - keywords: "wallpaper theme palette pywal breadpaper accent", - }, - { - page: "appearance", - tab: "appearance", - label: "Windows", - keywords: "gaps blur rounding border shadow tiling dwindle", - }, - { page: "sound", label: "Sound", keywords: "volume mute microphone output input speaker pavucontrol" }, - { - page: "power", - label: "Power & battery", - keywords: "battery brightness charge tlp suspend idle lid", - }, - { - page: "input", - label: "Keyboard & mouse", - keywords: "keybind shortcut layout touchpad tap natural scroll follow mouse", - }, - { page: "input", tab: "ime", label: "Input method", keywords: "fcitx5 ime cjk chinese japanese korean" }, - { - page: "input", - tab: "accessibility", - label: "Accessibility", - keywords: "orca screen reader zoom magnifier sticky keys", - }, - { - page: "desktop", - label: "Bar", - keywords: "breadbar modules clock tray workspaces", - }, - { page: "desktop", tab: "breadbox", label: "Launcher", keywords: "breadbox launcher apps" }, - { page: "desktop", tab: "breadlock", label: "Lock screen", keywords: "lock greet breadlock idle" }, - { page: "desktop", tab: "breadshot", label: "Screenshots", keywords: "screenshot grim slurp breadshot" }, - { page: "desktop", tab: "autostart", label: "Startup apps", keywords: "autostart login startup" }, - { page: "desktop", tab: "breadclip", label: "Clipboard", keywords: "clipboard history breadclip" }, - { page: "desktop", tab: "breadpad", label: "Notes", keywords: "breadpad notes calendar caldav" }, - { page: "desktop", tab: "breadsearch", label: "File search", keywords: "breadsearch breadmill index" }, - { page: "desktop", tab: "bread", label: "Daemon", keywords: "breadd daemon adapters lua" }, - { page: "apps", label: "Default apps", keywords: "browser terminal pdf mime default" }, - { page: "apps", tab: "optional", label: "Optional software", keywords: "steam flatpak libreoffice" }, - { page: "apps", tab: "printing", label: "Printing", keywords: "cups printer" }, - { page: "privacy", label: "Firewall", keywords: "firewall ufw port allow" }, - { page: "privacy", tab: "users", label: "Users", keywords: "users password account" }, - { page: "system", label: "Updates", keywords: "update bakery pacman firmware nvidia" }, - { page: "system", tab: "packages", label: "Packages", keywords: "packages bakery install" }, - { page: "system", tab: "aur", label: "AUR", keywords: "aur yay" }, - { page: "system", tab: "snapshots", label: "Snapshots", keywords: "snapper snapshot rollback grub" }, - { page: "system", tab: "backup", label: "Backup", keywords: "restic backup restore" }, - { page: "system", tab: "channel", label: "Bakery channel", keywords: "track stable beta dev bakery" }, - { page: "about", label: "About", keywords: "hostname kernel cpu gpu about machine" }, - { page: "about", tab: "datetime", label: "Date & time", keywords: "timezone ntp clock date time" }, - { page: "desktop", tab: "breadhelp", label: "Help", keywords: "help breadhelp onboarding" }, -]; - -export function searchSettings(query: string, limit = 8): SearchHit[] { - const q = query.trim().toLowerCase(); - if (q.length < 1) return []; - const scored = SEARCH_INDEX.map((hit) => { - const hay = `${hit.label} ${hit.keywords} ${hit.page}`.toLowerCase(); - let score = 0; - if (hit.label.toLowerCase().startsWith(q)) score += 8; - if (hit.label.toLowerCase().includes(q)) score += 4; - if (hay.includes(q)) score += 2; - for (const part of q.split(/\s+/)) { - if (hay.includes(part)) score += 1; - } - return { hit, score }; - }) - .filter((x) => x.score > 0) - .sort((a, b) => b.score - a.score); - return scored.slice(0, limit).map((x) => x.hit); -} diff --git a/frontend/src/lib/sidebar.ts b/frontend/src/lib/sidebar.ts index f7d9d50..7d98ce6 100644 --- a/frontend/src/lib/sidebar.ts +++ b/frontend/src/lib/sidebar.ts @@ -1,112 +1,97 @@ +// Ports src/ui/sidebar.rs's declarative item lists verbatim. Grouped by task, +// not "app vs system internals" — a user thinks "I want to change my Wi-Fi", +// not "which of these is a bread-ecosystem app" (why breadcrumbs/Wi-Fi +// Profiles lives in System, not Personalization). + import type { Component } from "svelte"; -import House from "@lucide/svelte/icons/house"; import Wifi from "@lucide/svelte/icons/wifi"; +import Network from "@lucide/svelte/icons/network"; import Bluetooth from "@lucide/svelte/icons/bluetooth"; -import Monitor from "@lucide/svelte/icons/monitor"; +import Shield from "@lucide/svelte/icons/shield"; import Volume2 from "@lucide/svelte/icons/volume-2"; import BatteryFull from "@lucide/svelte/icons/battery-full"; -import Palette from "@lucide/svelte/icons/palette"; -import AppWindow from "@lucide/svelte/icons/app-window"; +import Clock from "@lucide/svelte/icons/clock"; +import Monitor from "@lucide/svelte/icons/monitor"; import Keyboard from "@lucide/svelte/icons/keyboard"; +import Rocket from "@lucide/svelte/icons/rocket"; +import Users from "@lucide/svelte/icons/users"; +import Palette from "@lucide/svelte/icons/palette"; +import Image from "@lucide/svelte/icons/image"; import LayoutGrid from "@lucide/svelte/icons/layout-grid"; -import Shield from "@lucide/svelte/icons/shield"; -import Download from "@lucide/svelte/icons/download"; +import Grid3x3 from "@lucide/svelte/icons/grid-3x3"; +import Clipboard from "@lucide/svelte/icons/clipboard"; +import NotebookPen from "@lucide/svelte/icons/notebook-pen"; +import Search from "@lucide/svelte/icons/search"; +import Cog from "@lucide/svelte/icons/cog"; +import Package from "@lucide/svelte/icons/package"; +import RefreshCw from "@lucide/svelte/icons/refresh-cw"; +import History from "@lucide/svelte/icons/history"; import Info from "@lucide/svelte/icons/info"; +import Lock from "@lucide/svelte/icons/lock"; +import Camera from "@lucide/svelte/icons/camera"; +import AppWindow from "@lucide/svelte/icons/app-window"; +import CircleHelp from "@lucide/svelte/icons/circle-help"; export interface SidebarItem { + /** Must match a key in the view component map (see routing in +page.svelte). */ id: string; label: string; + /** Dim second line — the underlying binary/config name, for items whose + * human label doesn't already make that obvious. */ + sublabel?: string; icon: Component; } +export const SYSTEM_ITEMS: SidebarItem[] = [ + { id: "network", label: "Network", icon: Wifi }, + { id: "breadcrumbs", label: "Wi-Fi Profiles", sublabel: "breadcrumbs", icon: Network }, + { id: "bluetooth", label: "Bluetooth", icon: Bluetooth }, + { id: "firewall", label: "Firewall", icon: Shield }, + { id: "sound", label: "Sound", icon: Volume2 }, + { id: "power", label: "Power", icon: BatteryFull }, + { id: "datetime", label: "Date & Time", icon: Clock }, + { id: "hyprland", label: "Display", sublabel: "monitors.json", icon: Monitor }, + { id: "breadmon", label: "Monitors", sublabel: "breadmon", icon: AppWindow }, + { id: "breadlock", label: "Lock & greet", sublabel: "breadlock", icon: Lock }, + { id: "keybinds", label: "Keybinds", sublabel: "binds.json", icon: Keyboard }, + { id: "breadshot", label: "Screenshots", sublabel: "breadshot", icon: Camera }, + { id: "autostart", label: "Startup Apps", sublabel: "autostart.json", icon: Rocket }, + { id: "users", label: "Users", icon: Users }, +]; + +export const PERSONALIZATION_ITEMS: SidebarItem[] = [ + { id: "appearance", label: "Appearance", sublabel: "settings.json", icon: Palette }, + { id: "breadpaper", label: "Wallpaper", sublabel: "breadpaper", icon: Image }, + { id: "breadbar", label: "Bar", sublabel: "breadbar", icon: LayoutGrid }, + { id: "breadbox", label: "Launcher", sublabel: "breadbox", icon: Grid3x3 }, + { id: "breadclip", label: "Clipboard", sublabel: "breadclipd", icon: Clipboard }, + { id: "breadpad", label: "Notes", sublabel: "breadpad", icon: NotebookPen }, + { id: "breadsearch", label: "File Search", sublabel: "breadsearch", icon: Search }, + { id: "bread", label: "Daemon", sublabel: "breadd", icon: Cog }, +]; + +export const MAINTENANCE_ITEMS: SidebarItem[] = [ + { id: "packages", label: "Packages", icon: Package }, + { id: "aur", label: "AUR", icon: Search }, + { id: "firmware", label: "Firmware", icon: RefreshCw }, + { id: "snapshots", label: "Snapshots", icon: History }, +]; + +export const ABOUT_ITEMS: SidebarItem[] = [ + { id: "breadhelp", label: "Help", sublabel: "breadhelp", icon: CircleHelp }, + { id: "about", label: "About", icon: Info }, +]; + export interface SidebarSection { title: string | null; items: SidebarItem[]; } export const SIDEBAR_SECTIONS: SidebarSection[] = [ - { title: "Overview", items: [{ id: "home", label: "Home", icon: House }] }, - { - title: "Devices", - items: [ - { id: "network", label: "Wi-Fi & internet", icon: Wifi }, - { id: "bluetooth", label: "Bluetooth", icon: Bluetooth }, - { id: "displays", label: "Displays", icon: Monitor }, - { id: "sound", label: "Sound", icon: Volume2 }, - { id: "power", label: "Power & battery", icon: BatteryFull }, - ], - }, - { - title: "Desktop", - items: [ - { id: "appearance", label: "Appearance", icon: Palette }, - { id: "desktop", label: "Bar & apps", icon: AppWindow }, - { id: "input", label: "Keyboard & mouse", icon: Keyboard }, - { id: "apps", label: "Default apps", icon: LayoutGrid }, - ], - }, - { - title: "System", - items: [ - { id: "privacy", label: "Privacy & users", icon: Shield }, - { id: "system", label: "Updates & backup", icon: Download }, - { id: "about", label: "About", icon: Info }, - ], - }, + { title: "System", items: SYSTEM_ITEMS }, + { title: "Personalization", items: PERSONALIZATION_ITEMS }, + { title: "Maintenance", items: MAINTENANCE_ITEMS }, + { title: null, items: ABOUT_ITEMS }, ]; -/** Old sidebar ids (screenshot CLI, in-app jumps) → hub page + optional tab. */ -export const PAGE_ALIASES: Record = { - home: { page: "home" }, - network: { page: "network" }, - vpn: { page: "network", tab: "vpn" }, - breadcrumbs: { page: "network", tab: "breadcrumbs" }, - bluetooth: { page: "bluetooth" }, - displays: { page: "displays" }, - hyprland: { page: "displays", tab: "hyprland" }, - nightlight: { page: "displays", tab: "nightlight" }, - breadmon: { page: "displays", tab: "breadmon" }, - sound: { page: "sound" }, - power: { page: "power" }, - appearance: { page: "appearance" }, - breadpaper: { page: "appearance", tab: "breadpaper" }, - desktop: { page: "desktop" }, - breadbar: { page: "desktop", tab: "breadbar" }, - breadbox: { page: "desktop", tab: "breadbox" }, - breadlock: { page: "desktop", tab: "breadlock" }, - breadshot: { page: "desktop", tab: "breadshot" }, - autostart: { page: "desktop", tab: "autostart" }, - breadclip: { page: "desktop", tab: "more" }, - breadpad: { page: "desktop", tab: "more" }, - breadsearch: { page: "desktop", tab: "more" }, - bread: { page: "desktop", tab: "more" }, - breadhelp: { page: "desktop", tab: "more" }, - more: { page: "desktop", tab: "more" }, - input: { page: "input" }, - keybinds: { page: "input", tab: "keybinds" }, - ime: { page: "input", tab: "ime" }, - accessibility: { page: "input", tab: "accessibility" }, - apps: { page: "apps" }, - defaults: { page: "apps" }, - optional: { page: "apps", tab: "optional" }, - printing: { page: "apps", tab: "printing" }, - privacy: { page: "privacy" }, - firewall: { page: "privacy" }, - users: { page: "privacy", tab: "users" }, - system: { page: "system" }, - updates: { page: "system" }, - packages: { page: "system", tab: "packages" }, - aur: { page: "system", tab: "aur" }, - firmware: { page: "system", tab: "firmware" }, - snapshots: { page: "system", tab: "snapshots" }, - backup: { page: "system", tab: "backup" }, - channel: { page: "system", tab: "channel" }, - about: { page: "about" }, - datetime: { page: "about", tab: "datetime" }, -}; - -export const DEFAULT_PAGE = "home"; - -export function resolvePage(id: string): { page: string; tab?: string } { - return PAGE_ALIASES[id] ?? { page: id }; -} +export const DEFAULT_PAGE = "about"; diff --git a/frontend/src/lib/streaming.ts b/frontend/src/lib/streaming.ts index bf78f05..daaeee4 100644 --- a/frontend/src/lib/streaming.ts +++ b/frontend/src/lib/streaming.ts @@ -1,5 +1,5 @@ // Frontend half of the event-streaming command pattern (see -// src/src/commands/streaming.rs) - listens for `cmd-output` lines from a +// src/src/commands/streaming.rs) — listens for `cmd-output` lines from a // typed Tauri command that runs a hardcoded program, then resolves once // the process exits. diff --git a/frontend/src/lib/styles/app.css b/frontend/src/lib/styles/app.css deleted file mode 100644 index a626e8e..0000000 --- a/frontend/src/lib/styles/app.css +++ /dev/null @@ -1,121 +0,0 @@ -:root { - --bg-2: color-mix(in oklab, var(--bg) 82%, var(--surface)); - --surface-2: color-mix(in oklab, var(--surface) 72%, var(--overlay)); - --muted: color-mix(in oklab, var(--fg) 58%, transparent); - --faint: color-mix(in oklab, var(--fg) 38%, transparent); - --line: color-mix(in srgb, var(--fg) 8%, transparent); - --accent-soft: color-mix(in oklab, var(--accent) 18%, transparent); - --radius: 14px; - --radius-sm: 10px; -} - -* { - box-sizing: border-box; -} - -html, -body { - margin: 0; - border: none; - outline: none; - height: 100%; - color-scheme: dark; - background-color: var(--bg, #12161c); - color: var(--fg); - font-family: var(--font-family, sans-serif); - font-size: var(--font-size-base, 14px); -} - -#svelte { - height: 100%; -} - -button, -input, -select, -textarea { - font: inherit; - color: inherit; -} - -:focus { - outline: none; -} - -:focus-visible { - outline: 2px solid color-mix(in oklab, var(--accent) 70%, white); - outline-offset: 2px; -} - -input[type="range"] { - accent-color: var(--accent); -} - -.btn { - background: var(--surface-2, var(--surface)); - border: 1px solid var(--line); - padding: 8px 14px; - border-radius: var(--radius-sm, 10px); - font-size: 13px; - cursor: pointer; - color: var(--fg); -} - -.btn:hover { - background: color-mix(in oklab, var(--surface-2, var(--surface)) 80%, var(--fg)); -} - -.btn.primary { - background: var(--accent); - color: var(--on-accent); - border-color: transparent; - font-weight: 600; -} - -.btn.primary:hover { - filter: brightness(1.06); -} - -.btn.danger { - color: var(--red); -} - -.btn:disabled { - opacity: 0.5; - cursor: default; -} - -.kbd { - font-size: 11px; - letter-spacing: 0.04em; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--line); - padding: 2px 7px; - border-radius: 6px; - color: var(--muted); -} - -.pills { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.pill { - display: inline-flex; - align-items: center; - padding: 6px 11px; - border-radius: 999px; - font-size: 12px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--line); - color: var(--fg); - cursor: pointer; -} - -.pill.on { - background: var(--accent); - color: var(--on-accent); - border-color: transparent; - font-weight: 600; -} diff --git a/frontend/src/lib/theme/index.ts b/frontend/src/lib/theme/index.ts index 87e4b17..a358759 100644 --- a/frontend/src/lib/theme/index.ts +++ b/frontend/src/lib/theme/index.ts @@ -1,6 +1,6 @@ // Bridges bread-theme's pywal-derived palette into the webview. Mirrors // bread_theme::gtk::apply_shared()'s two-phase pattern: fetch once at -// startup, then keep it live via a backend-pushed event - see +// startup, then keep it live via a backend-pushed event — see // src-tauri/src/commands/theme.rs for the file-watch side of this. import { invoke } from "@tauri-apps/api/core"; diff --git a/frontend/src/lib/views/About.svelte b/frontend/src/lib/views/About.svelte index 43c0e56..894aca2 100644 --- a/frontend/src/lib/views/About.svelte +++ b/frontend/src/lib/views/About.svelte @@ -24,7 +24,7 @@ // The backend reports the GPU as a raw lspci device string, e.g. // "Advanced Micro Devices, Inc. [AMD/ATI] Krackan [Radeon 840M / 860M Graphics] (rev c2)". // That's too technical for a general settings page, so extract just the - // marketing model name. Heuristic (not exhaustive - just needs to read + // marketing model name. Heuristic (not exhaustive — just needs to read // well for common vendors): drop the trailing "(rev ..)", prefer the // text inside the last [...] bracket group (usually the model name), // collapse "840M / 860M" style multi-model lists to the last variant, diff --git a/frontend/src/lib/views/AboutHub.svelte b/frontend/src/lib/views/AboutHub.svelte deleted file mode 100644 index b4f7a2e..0000000 --- a/frontend/src/lib/views/AboutHub.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/frontend/src/lib/views/Accessibility.svelte b/frontend/src/lib/views/Accessibility.svelte deleted file mode 100644 index ed4094b..0000000 --- a/frontend/src/lib/views/Accessibility.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - - - - {#if !st} - - {:else if !st.orca_installed} - - - {:else} -
- Orca - -
- {/if} -
- - - {#if st} - - - - {#if !st.kmag_installed} - - - {:else} - - {/if} - {/if} - - - - {#if st} -
- Sticky keys - -
-
- Slow keys - -
- - {/if} -
- {#if message}{/if} - -
- - diff --git a/frontend/src/lib/views/Appearance.svelte b/frontend/src/lib/views/Appearance.svelte index 2ef5fe2..b9d7541 100644 --- a/frontend/src/lib/views/Appearance.svelte +++ b/frontend/src/lib/views/Appearance.svelte @@ -5,12 +5,13 @@ import Group from "$lib/components/Group.svelte"; import SwitchField from "$lib/components/SwitchField.svelte"; import SelectField from "$lib/components/SelectField.svelte"; - import SliderField from "$lib/components/SliderField.svelte"; + import NumberField from "$lib/components/NumberField.svelte"; import HyprColorField from "$lib/components/HyprColorField.svelte"; + import Row from "$lib/components/Row.svelte"; import Hint from "$lib/components/Hint.svelte"; - import { debounce } from "$lib/debounce"; + import SaveButton from "$lib/components/SaveButton.svelte"; - // Common XKB layout codes. Not exhaustive - if the config holds + // Common XKB layout codes. Not exhaustive — if the config holds // something else, it's merged in below so it's never dropped from the // dropdown. const COMMON_KB_LAYOUTS = [ @@ -24,51 +25,13 @@ // window under the cursor; 2 = focus follows the cursor, but clicking a // window keeps keyboard focus there until the mouse moves again; 3 = // cursor and keyboard focus are fully independent. - const FOLLOW_MOUSE_MODES: { value: string; label: string }[] = [ - { value: "0", label: "Click to focus" }, - { value: "1", label: "Follow mouse" }, - { value: "2", label: "Follow, keep focus on click" }, - { value: "3", label: "Mouse and keys independent" }, + const FOLLOW_MOUSE_MODES: { value: number; label: string }[] = [ + { value: 0, label: "Off — click to focus" }, + { value: 1, label: "Follow mouse" }, + { value: 2, label: "Follow mouse, detach on click" }, + { value: 3, label: "Fully detached from keyboard focus" }, ]; - const KB_LABELS: Record = { - us: "English (US)", - gb: "English (UK)", - de: "German", - fr: "French", - es: "Spanish", - it: "Italian", - pt: "Portuguese", - nl: "Dutch", - se: "Swedish", - no: "Norwegian", - dk: "Danish", - fi: "Finnish", - pl: "Polish", - cz: "Czech", - sk: "Slovak", - hu: "Hungarian", - ro: "Romanian", - gr: "Greek", - tr: "Turkish", - ru: "Russian", - ua: "Ukrainian", - jp: "Japanese", - kr: "Korean", - cn: "Chinese", - br: "Portuguese (Brazil)", - ca: "English (Canada)", - ch: "German (Switzerland)", - be: "Belgian", - at: "German (Austria)", - ie: "English (Ireland)", - }; - - const LAYOUT_LABELS: Record = { - dwindle: "Tiling", - master: "Master stack", - }; - interface Appearance { gaps_in: number; gaps_out: number; @@ -90,73 +53,76 @@ } let cfg = $state(null); - let loaded = $state(false); - let followMouse = $state("1"); let kbLayoutOptions = $derived( cfg && !COMMON_KB_LAYOUTS.includes(cfg.kb_layout) ? [...COMMON_KB_LAYOUTS, cfg.kb_layout] : COMMON_KB_LAYOUTS, ); - const persist = debounce(() => { - if (!cfg) return; - invoke("save_appearance", { appearance: { ...cfg, follow_mouse: Number(followMouse) } }); - }, 450); - onMount(async () => { cfg = await invoke("get_appearance"); - followMouse = String(cfg.follow_mouse); - loaded = true; }); - let primed = false; - $effect(() => { - if (!loaded || !cfg) return; - JSON.stringify(cfg); - void followMouse; - if (!primed) { - primed = true; - return; - } - persist(); - }); + async function save() { + await invoke("save_appearance", { appearance: cfg }); + } {#if cfg} - - - - - - - - + + + + + + + + - + - {#if cfg.blur_enabled} - - - {/if} - - {#if cfg.shadow_enabled} - - {/if} + + + + + - - - m.value)} - labels={Object.fromEntries(FOLLOW_MOUSE_MODES.map((m) => [m.value, m.label]))} - /> - + + + + + + - + + {/if} + + diff --git a/frontend/src/lib/views/AppearanceHub.svelte b/frontend/src/lib/views/AppearanceHub.svelte deleted file mode 100644 index 692f3ea..0000000 --- a/frontend/src/lib/views/AppearanceHub.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/frontend/src/lib/views/AppsHub.svelte b/frontend/src/lib/views/AppsHub.svelte deleted file mode 100644 index 5b5c900..0000000 --- a/frontend/src/lib/views/AppsHub.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/lib/views/Aur.svelte b/frontend/src/lib/views/Aur.svelte index ecf2ae0..11c035f 100644 --- a/frontend/src/lib/views/Aur.svelte +++ b/frontend/src/lib/views/Aur.svelte @@ -34,7 +34,7 @@
diff --git a/frontend/src/lib/views/Autostart.svelte b/frontend/src/lib/views/Autostart.svelte index 914bf12..5bc5248 100644 --- a/frontend/src/lib/views/Autostart.svelte +++ b/frontend/src/lib/views/Autostart.svelte @@ -41,7 +41,7 @@ {#if entries} {#each entries as entry, i (i)} diff --git a/frontend/src/lib/views/Backup.svelte b/frontend/src/lib/views/Backup.svelte deleted file mode 100644 index 32cc11b..0000000 --- a/frontend/src/lib/views/Backup.svelte +++ /dev/null @@ -1,254 +0,0 @@ - - - - - {#if !st} - - {:else if !st.restic_installed} - - - {:else} - - - - - {/if} - - - -
- - - -
- {#if message}{/if} -
- - - -
- - -
-
- - - {#if snapshots.length === 0} - - {:else} -
- {#each snapshots as s (s.id)} - - {/each} -
- {/if} -
- -
- - diff --git a/frontend/src/lib/views/Bluetooth.svelte b/frontend/src/lib/views/Bluetooth.svelte index 73bad27..49d5962 100644 --- a/frontend/src/lib/views/Bluetooth.svelte +++ b/frontend/src/lib/views/Bluetooth.svelte @@ -75,10 +75,7 @@ } - + {#if powered === null} {dev.name}{dev.connected ? " (connected)" : ""} - +
{/each} @@ -110,7 +107,7 @@
{#if scanResults === null} @@ -147,13 +144,9 @@ display: flex; align-items: center; gap: var(--space-sm, 8px); - padding: 10px 2px; - border-top: 1px solid var(--line); - } - - .row:first-child { - border-top: none; - padding-top: 0; + background-color: var(--surface); + border-radius: var(--radius-secondary, 6px); + padding: var(--space-sm, 8px) var(--space-md, 12px); } .name { diff --git a/frontend/src/lib/views/Bread.svelte b/frontend/src/lib/views/Bread.svelte index a0de5cf..15fa6a9 100644 --- a/frontend/src/lib/views/Bread.svelte +++ b/frontend/src/lib/views/Bread.svelte @@ -34,7 +34,7 @@ let cfg = $state(null); // The daemon's 4 compiled-in modules plus every *.lua file actually - // sitting in the configured module directory - real installed modules, + // sitting in the configured module directory — real installed modules, // not a guess, so picking one to disable is a click. let knownModules = $state([]); diff --git a/frontend/src/lib/views/Breadbar.svelte b/frontend/src/lib/views/Breadbar.svelte index 7748928..ee844ef 100644 --- a/frontend/src/lib/views/Breadbar.svelte +++ b/frontend/src/lib/views/Breadbar.svelte @@ -3,10 +3,10 @@ import { invoke } from "@tauri-apps/api/core"; import ViewScaffold from "$lib/components/ViewScaffold.svelte"; import Group from "$lib/components/Group.svelte"; + import Row from "$lib/components/Row.svelte"; import TextField from "$lib/components/TextField.svelte"; - import SliderField from "$lib/components/SliderField.svelte"; - import Hint from "$lib/components/Hint.svelte"; - import { debounce } from "$lib/debounce"; + import NumberField from "$lib/components/NumberField.svelte"; + import SaveButton from "$lib/components/SaveButton.svelte"; import ChevronDown from "@lucide/svelte/icons/chevron-down"; import ChevronRight from "@lucide/svelte/icons/chevron-right"; @@ -23,33 +23,20 @@ } let style = $state(null); - let loaded = $state(false); let advancedOpen = $state(false); let css = $state(""); let cssStatus = $state(""); let cssSaving = $state(false); - const persist = debounce(() => { - if (!style) return; - invoke("save_breadbar_style", { style }).then(() => invoke("get_breadbar_css").then((c) => (css = c))); - }, 320); - onMount(async () => { style = await invoke("get_breadbar_style"); css = await invoke("get_breadbar_css"); - loaded = true; }); - let primed = false; - $effect(() => { - if (!loaded || !style) return; - JSON.stringify(style); - if (!primed) { - primed = true; - return; - } - persist(); - }); + async function saveStyle() { + await invoke("save_breadbar_style", { style }); + css = await invoke("get_breadbar_css"); + } async function saveCss() { cssSaving = true; @@ -68,37 +55,36 @@ {#if style} - + - + - - + + - - - + + + +
+ + {Math.round(style.workspace_inactive_opacity * 100)}% +
+
- - - - + + + + - + {/if} - + - {/each} -
- + {/if}
- {#if live} - - - {#if live.available_modes.length > 0} - - {:else} - {live.mode} - {/if} - - -
- {#each SCALES as s (s.value)} - - {/each} + + + + + + {#if rules} + + {#each rules as rule, i (i)} +
+ + + + +
- - -
- {#each TURNS as t (t.value)} - - {/each} -
-
- - {live.x}, {live.y} - - {#if message} - - {/if} + {/each} + + +
{/if} diff --git a/frontend/src/lib/views/DisplaysHub.svelte b/frontend/src/lib/views/DisplaysHub.svelte deleted file mode 100644 index 3e86d77..0000000 --- a/frontend/src/lib/views/DisplaysHub.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/lib/views/Firewall.svelte b/frontend/src/lib/views/Firewall.svelte index aba18bf..9bbdca0 100644 --- a/frontend/src/lib/views/Firewall.svelte +++ b/frontend/src/lib/views/Firewall.svelte @@ -1,5 +1,4 @@ - + - {/each} -
- {:else} -
- - - -
- -
- {#each tiles as tile (tile.id + (tile.tab ?? ""))} - - {/each} -
- {/if} -
- - diff --git a/frontend/src/lib/views/InputHub.svelte b/frontend/src/lib/views/InputHub.svelte deleted file mode 100644 index e9a7664..0000000 --- a/frontend/src/lib/views/InputHub.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/lib/views/InputMethod.svelte b/frontend/src/lib/views/InputMethod.svelte deleted file mode 100644 index c485eb5..0000000 --- a/frontend/src/lib/views/InputMethod.svelte +++ /dev/null @@ -1,166 +0,0 @@ - - - - - {#if !st} - - {:else} -
- Enable fcitx5 for this session - -
- - - {/if} - {#if message}{/if} -
- - - {#if st} -
    - {#each st.packages as p (p.name)} -
  • {p.name}{p.installed ? "" : " (missing)"}
  • - {/each} -
- {/if} - {#if missing.length} - - {/if} -
- -
- - diff --git a/frontend/src/lib/views/Keybinds.svelte b/frontend/src/lib/views/Keybinds.svelte index 07b7b70..dc710cf 100644 --- a/frontend/src/lib/views/Keybinds.svelte +++ b/frontend/src/lib/views/Keybinds.svelte @@ -9,7 +9,7 @@ import SaveButton from "$lib/components/SaveButton.svelte"; import Switch from "$lib/components/Switch.svelte"; - // Mirrors src-tauri/src/commands/keybinds.rs's `Bind` - `action`/`key`/ + // Mirrors src-tauri/src/commands/keybinds.rs's `Bind` — `action`/`key`/ // `mods` are the only fields every bind has; everything else (`command`, // `direction`, `workspace`, `options`, breadhelp's `label`/`category`/ // `demo_cmd`, ...) round-trips through serde's `#[serde(flatten)]` as @@ -35,7 +35,7 @@ } // Every action actually seen in real binds.json files (BOS's shipped - // flat schema and this app's own dev MultiLayout config) - an `action` + // flat schema and this app's own dev MultiLayout config) — an `action` // dropdown beats free-typing a dispatcher name from memory. "Custom…" // keeps anything not in this list reachable without blocking on it. const KNOWN_ACTIONS = [ @@ -56,7 +56,7 @@ ] as const; // The editable, per-row UI state a bind gets flattened into on load and - // reconstructed back into a `Bind` on save - same trade-off the GTK + // reconstructed back into a `Bind` on save — same trade-off the GTK // version made with individual `Entry` widgets per field. interface EditRow { action: string; @@ -64,7 +64,7 @@ mods: string; // Whether `mods` has been explicitly set (present in the loaded JSON, // or touched by the user since). `None`/absent means "fall back to - // default_mods"; `Some([])` - an explicitly *empty* mods list - means + // default_mods"; `Some([])` — an explicitly *empty* mods list — means // "use no modifiers at all, even though default_mods exists" (real // BOS binds rely on this for e.g. bare media keys). Collapsing both // cases to "omit when empty" would silently turn an explicit @@ -77,123 +77,12 @@ // key they don't know about (label/category/demo_cmd, or an action // shape this editor has no dedicated fields for) survives untouched. extraValue: Record; - // Raw-JSON view of `extraValue`, kept in sync both directions - + // Raw-JSON view of `extraValue`, kept in sync both directions — // only actually shown when `advancedOpen` is true, or for an action // not in `KNOWN_ACTIONS` (nothing dedicated to show instead). extraText: string; extraError: boolean; advancedOpen: boolean; - capturing: boolean; - } - - const ACTION_LABELS: Record = { - exec: "Run command", - close: "Close window", - fullscreen: "Fullscreen", - float: "Float window", - pseudo: "Fake fullscreen", - resize: "Resize", - focus: "Go to workspace", - focus_last: "Last workspace", - move: "Move window to workspace", - move_dir: "Move window", - resize_dir: "Resize window", - layout: "Layout", - drag: "Mouse drag", - exit: "Exit Hyprland", - }; - - const KEY_LABELS: Record = { - RETURN: "Enter", - SPACE: "Space", - ESCAPE: "Esc", - BACKSPACE: "Backspace", - TAB: "Tab", - SUPER: "Super", - CTRL: "Ctrl", - ALT: "Alt", - SHIFT: "Shift", - XF86AudioRaiseVolume: "Vol +", - XF86AudioLowerVolume: "Vol -", - XF86AudioMute: "Mute", - XF86AudioMicMute: "Mic mute", - XF86MonBrightnessUp: "Bright +", - XF86MonBrightnessDown: "Bright -", - XF86AudioNext: "Next", - XF86AudioPrev: "Prev", - XF86AudioPlay: "Play", - Print: "Print", - }; - - const CODE_TO_HYPR: Record = { - Space: "SPACE", - Enter: "RETURN", - Escape: "ESCAPE", - Backspace: "BACKSPACE", - Tab: "TAB", - AudioVolumeUp: "XF86AudioRaiseVolume", - AudioVolumeDown: "XF86AudioLowerVolume", - AudioVolumeMute: "XF86AudioMute", - AudioMicMute: "XF86AudioMicMute", - BrightnessUp: "XF86MonBrightnessUp", - BrightnessDown: "XF86MonBrightnessDown", - MediaTrackNext: "XF86AudioNext", - MediaTrackPrevious: "XF86AudioPrev", - MediaPlayPause: "XF86AudioPlay", - PrintScreen: "Print", - }; - - function keyLabel(k: string): string { - return KEY_LABELS[k] ?? k; - } - - function bindTitle(row: EditRow): string { - if (row.action === "exec") { - const cmd = String(row.extraValue.command ?? ""); - if (cmd.includes("set-volume") && cmd.includes("+")) return "Volume up"; - if (cmd.includes("set-volume") && cmd.includes("-")) return "Volume down"; - if (cmd.includes("set-mute") && cmd.includes("SINK")) return "Mute"; - if (cmd.includes("set-mute") && cmd.includes("SOURCE")) return "Mute mic"; - if (cmd.includes("brightnessctl") && cmd.includes("+")) return "Brightness up"; - if (cmd.includes("brightnessctl") && cmd.includes("-")) return "Brightness down"; - if (cmd.includes("playerctl next")) return "Next track"; - if (cmd.includes("playerctl previous")) return "Previous track"; - if (cmd.includes("playerctl play-pause")) return "Play/pause"; - if (cmd) return cmd.split(/\s+/)[0].split("/").pop() ?? "Command"; - } - return ACTION_LABELS[row.action] ?? row.action; - } - - function keyChips(row: EditRow): string[] { - const mods = textToMods(row.mods); - const key = row.key.trim(); - return [...mods, key].filter(Boolean).map(keyLabel); - } - - function hyprFromEvent(e: KeyboardEvent): { mods: string[]; key: string } | null { - if (["Shift", "Control", "Alt", "Meta"].includes(e.key)) return null; - const mods: string[] = []; - if (e.metaKey) mods.push("SUPER"); - if (e.ctrlKey) mods.push("CTRL"); - if (e.altKey) mods.push("ALT"); - if (e.shiftKey) mods.push("SHIFT"); - let key = CODE_TO_HYPR[e.code] ?? CODE_TO_HYPR[e.key]; - if (!key) { - if (e.code.startsWith("Key") && e.code.length === 4) key = e.code.slice(3); - else if (e.code.startsWith("Digit")) key = e.code.slice(5); - else if (e.code.startsWith("F") && /^F\d+$/.test(e.code)) key = e.code; - else key = e.key.length === 1 ? e.key.toUpperCase() : e.key; - } - return { mods, key }; - } - - function allRows(): EditRow[] { - return [...globalsRows, ...commonRows, ...bindingsRows, ...Object.values(layoutRows).flat()]; - } - - function beginCapture(row: EditRow) { - for (const r of allRows()) r.capturing = false; - row.capturing = true; } function syncExtraText(row: EditRow) { @@ -225,7 +114,7 @@ } // Hyprland workspace refs mix bare integers ("1") and relative tokens - // ("e+1", "e-1") in the same field - keep whichever shape the user typed + // ("e+1", "e-1") in the same field — keep whichever shape the user typed // instead of forcing everything through one type. function parseWorkspaceValue(s: string): string | number | undefined { const trimmed = s.trim(); @@ -254,14 +143,13 @@ extraText: "", extraError: false, advancedOpen: false, - capturing: false, }; syncExtraText(row); return row; } function rowToBind(r: EditRow): Bind { - // `drag` binds are only meaningful as a mouse bind - there's no + // `drag` binds are only meaningful as a mouse bind — there's no // dedicated field for `options.mouse` (nothing to configure, it's // always true), so pin it here rather than exposing a checkbox // whose only correct state is "on". @@ -305,7 +193,6 @@ extraText: "", extraError: false, advancedOpen: false, - capturing: false, }; } @@ -321,32 +208,13 @@ let newLayoutName = $state(""); let loadError = $state(""); - onMount(() => { - const onKey = (e: KeyboardEvent) => { - const row = allRows().find((r) => r.capturing); - if (!row) return; - const mapped = hyprFromEvent(e); - if (!mapped) return; - e.preventDefault(); - row.mods = mapped.mods.join(", "); - row.modsTouched = true; - row.key = mapped.key; - row.capturing = false; - }; - window.addEventListener("keydown", onKey); - void loadKeybinds(); - // Register cleanup synchronously so Svelte types the onMount - // callback as returning a function (not a Promise-of-function). - return () => window.removeEventListener("keydown", onKey); - }); - - async function loadKeybinds() { + onMount(async () => { try { const p = await invoke("get_keybinds"); kind = p.kind; // Rust's `#[serde(skip_serializing_if = ...)]` on every one of // these fields means an empty one is OMITTED from the JSON - // entirely, not sent as `[]`/`{}`/`""` - every field here needs + // entirely, not sent as `[]`/`{}`/`""` — every field here needs // a `??` fallback, not just the ones that are "usually" empty. activeLayout = p.file.active_layout ?? ""; defaultMods = modsToText(p.file.default_mods ?? []); @@ -361,7 +229,7 @@ } catch (e) { loadError = String(e); } - } + }); function addLayout() { const name = newLayoutName.trim(); @@ -380,7 +248,7 @@ // A row whose "extra" column currently holds text that doesn't parse as // JSON keeps its *last successfully parsed* value in `extraValue` (see // `onExtraInput`) rather than losing it on every keystroke while the - // user is mid-edit - but that means saving while such a row is still + // user is mid-edit — but that means saving while such a row is still // showing invalid/incomplete text would silently write that stale (or, // for a brand new row, empty) value instead of what's actually on // screen. Block save entirely until every row's extra JSON is valid, so @@ -481,7 +349,7 @@ oninput={(e) => setExtra(row, "layout", e.currentTarget.value)} /> {:else if row.action === "drag"} - Mouse-drag bind. + Mouse-drag bind — nothing else to set. {:else if row.action === "close" || row.action === "fullscreen" || row.action === "float" || row.action === "pseudo" || row.action === "resize" || row.action === "focus_last" || row.action === "exit"} No extra options for this action. {:else} @@ -507,19 +375,30 @@ {#each rows as row, i (i)}
- {bindTitle(row)} - + (row.modsTouched = true)} + /> + + + {#if !KNOWN_ACTIONS.includes(row.action as (typeof KNOWN_ACTIONS)[number])} + + {/if}
@@ -557,7 +436,7 @@ {:else if kind === "flat"} @@ -574,7 +453,7 @@ {:else} {#if layoutOrder.length > 0} @@ -633,13 +512,9 @@ display: flex; flex-direction: column; gap: var(--space-xs, 4px); - padding: 10px 2px; - border-top: 1px solid var(--line); - } - - .bind-card:first-child { - border-top: none; - padding-top: 0; + background-color: var(--surface); + border-radius: var(--radius-secondary, 6px); + padding: var(--space-sm, 8px) var(--space-md, 12px); } .bind-top, @@ -650,34 +525,6 @@ flex-wrap: wrap; } - .what { - flex: 1; - min-width: 12ch; - } - - .capture { - display: flex; - align-items: center; - gap: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--line); - border-radius: 10px; - padding: 6px 10px; - color: inherit; - cursor: pointer; - min-height: 32px; - } - - .capture.listening { - border-color: var(--accent); - color: var(--accent); - } - - .plus { - opacity: 0.4; - padding: 0 1px; - } - .bind-card input, .bind-card select { background-color: var(--bg); @@ -697,6 +544,26 @@ border-color: var(--red); } + .mods { + width: 14ch; + flex-shrink: 0; + } + + .key { + width: 9ch; + flex-shrink: 0; + } + + .action-select { + width: 11ch; + flex-shrink: 0; + } + + .action-custom { + width: 11ch; + flex-shrink: 0; + } + .extra-wide { flex: 1; min-width: 16ch; diff --git a/frontend/src/lib/views/Network.svelte b/frontend/src/lib/views/Network.svelte index b8e2f1b..0a8e788 100644 --- a/frontend/src/lib/views/Network.svelte +++ b/frontend/src/lib/views/Network.svelte @@ -3,8 +3,9 @@ import { invoke } from "@tauri-apps/api/core"; import ViewScaffold from "$lib/components/ViewScaffold.svelte"; import Group from "$lib/components/Group.svelte"; - import SwitchField from "$lib/components/SwitchField.svelte"; + import Row from "$lib/components/Row.svelte"; import InfoRow from "$lib/components/InfoRow.svelte"; + import Hint from "$lib/components/Hint.svelte"; import Wifi from "@lucide/svelte/icons/wifi"; import WifiOff from "@lucide/svelte/icons/wifi-off"; import Lock from "@lucide/svelte/icons/lock"; @@ -25,6 +26,12 @@ let pendingSsid = $state(null); let password = $state(""); + // The backend reports the first `nmcli`-visible device of TYPE=ethernet, + // which on a machine running containers/VMs can be a virtual interface + // (docker/podman veth pairs, libvirt bridges, VPN tuns) rather than a + // real NIC — nmcli doesn't distinguish these from physical ethernet. + // Names like "vethYCF3ZA: unmanaged" are meaningless to a non-technical + // user, so hide the card rather than show raw interface jargon. const VIRTUAL_IFACE_PREFIXES = ["veth", "docker", "br-", "virbr", "vnet", "tun", "tap", "vmnet", "podman"]; const ETHERNET_STATE_LABELS: Record = { connected: "Connected", @@ -40,14 +47,11 @@ if (!iface || VIRTUAL_IFACE_PREFIXES.some((prefix) => iface.startsWith(prefix))) return null; return ETHERNET_STATE_LABELS[rawState ?? ""] ?? "Not connected"; }); - let activeNet = $derived(networks?.find((n) => n.active) ?? null); - let nearby = $derived(networks?.filter((n) => !n.active) ?? []); onMount(async () => { const info = await invoke<{ radio_enabled: boolean; ethernet: string | null }>("get_network_info"); radioEnabled = info.radio_enabled; ethernet = info.ethernet; - if (info.radio_enabled) await scan(); }); async function toggleRadio(enabled: boolean) { @@ -57,12 +61,10 @@ async function scan() { scanning = true; - try { - networks = await invoke("scan_wifi"); - } finally { - scanning = false; - status = ""; - } + status = "Scanning…"; + networks = await invoke("scan_wifi"); + status = `Found ${networks.length} network(s)`; + scanning = false; } async function connect(ssid: string, secured: boolean, known: boolean) { @@ -74,9 +76,8 @@ try { await invoke("connect_wifi", { ssid, password: null }); status = `Connected to ${ssid}`; - await scan(); } catch (e) { - status = `Failed: ${e}`; + status = `Failed to connect to ${ssid}: ${e}`; } } @@ -89,76 +90,54 @@ try { await invoke("connect_wifi", { ssid, password: pw }); status = `Connected to ${ssid}`; - await scan(); } catch { - status = "Wrong password?"; + status = `Failed to connect to ${ssid}: wrong password?`; } } - function bars(signal: number): number { - if (signal >= 75) return 4; - if (signal >= 50) return 3; - if (signal >= 25) return 2; - return 1; + function signalLabel(signal: number): string { + return `${Math.min(100, Math.max(0, signal))}%`; } - - - radioEnabled, (v) => toggleRadio(v)} /> - {#if ethernetLabel} - - {/if} + + + + + - {#if activeNet} - -
-
- {#each [1, 2, 3, 4] as n (n)} - = n}> - {/each} -
-
- {activeNet.ssid} - {activeNet.secured ? "Secured" : "Open"} · {activeNet.signal}% -
- Connected -
+ {#if ethernetLabel} + + {/if} - +
- {#if scanning && networks === null} + {#if networks === null}
- - Scanning… + + Not scanned yet + Press Scan to see nearby networks.
- {:else if networks === null} + {:else if networks.length === 0}
- - No scan yet -
- {:else if nearby.length === 0 && !activeNet} -
- + No networks found + Try Scan again, or check Wi-Fi radio is on.
{:else} - {#each nearby as net (net.ssid)} -
-
- {#each [1, 2, 3, 4] as n (n)} - = n}> - {/each} -
-
- {net.ssid} - {net.known ? "Saved" : net.secured ? "Secured" : "Open"} -
+ {#each networks as net (net.ssid)} +
+ {net.ssid}{net.active ? " (connected)" : ""} {#if net.secured}{/if} - + {signalLabel(net.signal)} + {#if !net.active} + + {/if}
{/each} {/if} @@ -166,28 +145,52 @@ {#if pendingSsid}
- - + +
{/if} {#if status} {status} {/if} - + + - - + + diff --git a/frontend/src/lib/views/NetworkHub.svelte b/frontend/src/lib/views/NetworkHub.svelte deleted file mode 100644 index da2b0fd..0000000 --- a/frontend/src/lib/views/NetworkHub.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/lib/views/NightLight.svelte b/frontend/src/lib/views/NightLight.svelte deleted file mode 100644 index 8f07737..0000000 --- a/frontend/src/lib/views/NightLight.svelte +++ /dev/null @@ -1,85 +0,0 @@ - - - - - {#if !st} - - {:else if !st.installed} - - - {:else} - st!.enabled, (v) => { st!.enabled = v; applyNow(); }} - /> - {#if st.enabled} - - {/if} - {/if} - {#if message}{/if} - - - diff --git a/frontend/src/lib/views/Optional.svelte b/frontend/src/lib/views/Optional.svelte deleted file mode 100644 index 882d537..0000000 --- a/frontend/src/lib/views/Optional.svelte +++ /dev/null @@ -1,149 +0,0 @@ - - - - - {#if !st} - - {:else} - {#each st.items as item (item.id)} -
-
- {item.title} -

{item.detail}

- {item.via}{item.installed ? " · installed" : ""} -
- {#if item.installed} - Installed - {:else} - - {/if} -
- {/each} - {/if} - {#if message}{/if} -
- -
- - diff --git a/frontend/src/lib/views/Packages.svelte b/frontend/src/lib/views/Packages.svelte index ec20f80..d28509f 100644 --- a/frontend/src/lib/views/Packages.svelte +++ b/frontend/src/lib/views/Packages.svelte @@ -85,7 +85,7 @@ diff --git a/frontend/src/lib/views/Power.svelte b/frontend/src/lib/views/Power.svelte index dcb50dd..d81ea9c 100644 --- a/frontend/src/lib/views/Power.svelte +++ b/frontend/src/lib/views/Power.svelte @@ -40,10 +40,7 @@ } - + {#if info} {#each info.battery as [label, value] (label)} @@ -66,22 +63,20 @@ {#if info.charge_start !== null && info.charge_end !== null} - - setChargeThreshold("start", chargeStart)} /> - {chargeStart}% + + setChargeThreshold("start", chargeStart)} /> - - setChargeThreshold("end", chargeEnd)} /> - {chargeEnd}% + + setChargeThreshold("end", chargeEnd)} /> {/if} @@ -93,6 +88,15 @@ width: 180px; } + input[type="number"] { + width: 8ch; + background-color: var(--bg); + color: var(--on-surface); + border: 1px solid transparent; + border-radius: var(--radius-secondary, 6px); + padding: var(--space-xs, 4px) var(--space-sm, 8px); + } + .pct { margin-left: var(--space-sm, 8px); font-size: var(--font-size-secondary, 12px); diff --git a/frontend/src/lib/views/Printing.svelte b/frontend/src/lib/views/Printing.svelte deleted file mode 100644 index 2fc3ac5..0000000 --- a/frontend/src/lib/views/Printing.svelte +++ /dev/null @@ -1,147 +0,0 @@ - - - - - {#if !status} - - {:else if status.error} - - {:else if status.printers.length === 0} - - {:else} -
- {#each status.printers as p (p.name)} -
-
- {p.name}{p.is_default ? " (default)" : ""} - {p.enabled ? p.status : "disabled"} -
- -
- {/each} -
- {/if} -
- - -
- {#if message}{/if} -
- - - - - - -
- - diff --git a/frontend/src/lib/views/PrivacyHub.svelte b/frontend/src/lib/views/PrivacyHub.svelte deleted file mode 100644 index e0f7ef1..0000000 --- a/frontend/src/lib/views/PrivacyHub.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/frontend/src/lib/views/Snapshots.svelte b/frontend/src/lib/views/Snapshots.svelte index 592f82a..8687a00 100644 --- a/frontend/src/lib/views/Snapshots.svelte +++ b/frontend/src/lib/views/Snapshots.svelte @@ -25,7 +25,7 @@ } catch (e) { const msg = `${e}`.toLowerCase(); if (msg.includes("no permission")) { - errorHint = "This user is not in ALLOW_USERS for snapper."; + errorHint = "This user isn't allowed to run snapper. Check ALLOW_USERS in /etc/snapper/configs/root — it should list your username."; } else if (msg.includes("unknown config") || msg.includes("no such file")) { errorHint = "No snapper config exists for root yet, so nothing is being snapshotted. This should be set up automatically at install."; } else { @@ -42,7 +42,7 @@ if (!selected) return; if ( confirm( - `Reboot to pick snapshot #${selected} in GRUB? BOS boots snapshots from the GRUB “BOS snapshots” submenu. This does not run snapper rollback.`, + `Boot into snapshot #${selected}? Snapshots on BOS are booted directly from the GRUB menu (under "BOS snapshots"), not rolled back in place. Reboot now and pick this snapshot there.`, ) ) { invoke("reboot_system"); @@ -62,10 +62,9 @@ -
@@ -76,11 +75,6 @@ {:else if snapshots.length === 0} {:else} - {#each snapshots as snap (snap.number)} - +
@@ -127,15 +121,7 @@ min-width: 0; } - .row.header { - background: transparent; - cursor: default; - opacity: 0.55; - font-size: var(--font-size-secondary, 12px); - padding-top: 0; - } - - .row:hover:not(.header) { + .row:hover { background-color: color-mix(in srgb, var(--surface), var(--on-surface) 8%); } diff --git a/frontend/src/lib/views/Sound.svelte b/frontend/src/lib/views/Sound.svelte index 9c887a0..b863d45 100644 --- a/frontend/src/lib/views/Sound.svelte +++ b/frontend/src/lib/views/Sound.svelte @@ -24,154 +24,107 @@ let output = $state(null); let input = $state(null); - function pick(devices: SoundDevice[], defaultName: string | null): number { - const i = devices.findIndex((d) => d.name === defaultName); - return i >= 0 ? i : 0; - } - async function loadSection(kind: "sinks" | "sources", title: string): Promise { - const section = await invoke<{ devices: SoundDevice[]; default_name: string | null }>("get_sound_section", { - kind, - }); - return { kind, title, devices: section.devices, selected: pick(section.devices, section.default_name) }; + const section = await invoke<{ devices: SoundDevice[]; default_name: string | null }>("get_sound_section", { kind }); + const selected = Math.max(0, section.devices.findIndex((d) => d.name === section.default_name)); + return { kind, title, devices: section.devices, selected }; } onMount(async () => { - try { - output = await loadSection("sinks", "Output"); - input = await loadSection("sources", "Input"); - } catch (e) { - console.error(e); - output = { kind: "sinks", title: "Output", devices: [], selected: 0 }; - input = { kind: "sources", title: "Input", devices: [], selected: 0 }; - } + output = await loadSection("sinks", "Output"); + input = await loadSection("sources", "Input"); }); - function current(section: DeviceSection): SoundDevice | null { - return section.devices[section.selected] ?? section.devices[0] ?? null; - } - async function selectDevice(section: DeviceSection, index: number) { - const device = section.devices[index]; - if (!device) return; section.selected = index; - await invoke("set_default_sound_device", { kind: section.kind, name: device.name }); + await invoke("set_default_sound_device", { kind: section.kind, name: section.devices[index].name }); } async function setVolume(section: DeviceSection, percent: number) { - const device = current(section); - if (!device) return; + const device = section.devices[section.selected]; device.percent = percent; - await invoke("set_sound_volume", { kind: section.kind, name: device.name, percent: Math.round(percent) }); + await invoke("set_sound_volume", { kind: section.kind, name: device.name, percent }); } async function setMute(section: DeviceSection, mute: boolean) { - const device = current(section); - if (!device) return; + const device = section.devices[section.selected]; device.mute = mute; await invoke("set_sound_mute", { kind: section.kind, name: device.name, mute }); } -{#snippet sectionCard(section: DeviceSection)} - - {#if section.devices.length === 0} - - {:else} - {#each section.devices as d, i (d.name + i)} - - {/each} - {#if current(section)} +{#snippet deviceSection(section: DeviceSection | null)} + {#if section} + + {#if section.devices.length === 0} + + {:else} + + + setVolume(section, Number(e.currentTarget.value))} /> - {Math.round(current(section)!.percent)}% + {section.devices[section.selected].percent}% current(section)!.mute, (v) => setMute(section, v)} + bind:value={ + () => section.devices[section.selected].mute, + (v) => setMute(section, v) + } /> {/if} - {/if} - + + {/if} {/snippet} - - {#if output} - {@render sectionCard(output)} - {/if} - {#if input} - {@render sectionCard(input)} - {/if} + + {@render deviceSection(output)} + {@render deviceSection(input)} - - + + diff --git a/frontend/src/lib/views/Users.svelte b/frontend/src/lib/views/Users.svelte index 5a74504..08868f5 100644 --- a/frontend/src/lib/views/Users.svelte +++ b/frontend/src/lib/views/Users.svelte @@ -69,7 +69,7 @@ - +
{#if accounts} {#each accounts as acc (acc.username)} diff --git a/frontend/src/lib/views/Vpn.svelte b/frontend/src/lib/views/Vpn.svelte deleted file mode 100644 index 1973575..0000000 --- a/frontend/src/lib/views/Vpn.svelte +++ /dev/null @@ -1,148 +0,0 @@ - - - - - {#if !status} - - {:else if status.error} - - {:else if status.connections.length === 0} - - {:else} -
- {#each status.connections as c (c.name)} -
-
- {c.name} - {c.kind}{c.active ? " · connected" : ""}{c.autoconnect ? " · autoconnect" : ""} -
- {#if c.active} - - {:else} - - {/if} -
- {/each} -
- {/if} - - {#if message}{/if} -
- - - - - -
- - diff --git a/frontend/src/lib/views/registry.ts b/frontend/src/lib/views/registry.ts index d36e136..ee0e5e4 100644 --- a/frontend/src/lib/views/registry.ts +++ b/frontend/src/lib/views/registry.ts @@ -1,30 +1,63 @@ +// Maps a sidebar page id to its view component. Every sidebar id has a +// real view — +page.svelte's Placeholder is only a safety net for typos. + import type { Component } from "svelte"; -import AboutHub from "./AboutHub.svelte"; -import AppearanceHub from "./AppearanceHub.svelte"; -import AppsHub from "./AppsHub.svelte"; -import Bluetooth from "./Bluetooth.svelte"; -import DesktopHub from "./DesktopHub.svelte"; -import DisplaysHub from "./DisplaysHub.svelte"; -import Home from "./Home.svelte"; -import InputHub from "./InputHub.svelte"; -import NetworkHub from "./NetworkHub.svelte"; -import Power from "./Power.svelte"; -import PrivacyHub from "./PrivacyHub.svelte"; +import About from "./About.svelte"; +import Breadclip from "./Breadclip.svelte"; +import Bread from "./Bread.svelte"; +import Breadbar from "./Breadbar.svelte"; +import Breadbox from "./Breadbox.svelte"; +import Breadpad from "./Breadpad.svelte"; +import Breadpaper from "./Breadpaper.svelte"; +import Breadsearch from "./Breadsearch.svelte"; +import Breadcrumbs from "./Breadcrumbs.svelte"; +import Appearance from "./Appearance.svelte"; +import Autostart from "./Autostart.svelte"; +import Display from "./Display.svelte"; +import Keybinds from "./Keybinds.svelte"; import Sound from "./Sound.svelte"; -import SystemHub from "./SystemHub.svelte"; +import DateTime from "./DateTime.svelte"; +import Power from "./Power.svelte"; +import Network from "./Network.svelte"; +import Bluetooth from "./Bluetooth.svelte"; +import Firewall from "./Firewall.svelte"; +import Users from "./Users.svelte"; +import Packages from "./Packages.svelte"; +import Aur from "./Aur.svelte"; +import Firmware from "./Firmware.svelte"; +import Snapshots from "./Snapshots.svelte"; +import Breadlock from "./Breadlock.svelte"; +import Breadshot from "./Breadshot.svelte"; +import Breadmon from "./Breadmon.svelte"; +import Breadhelp from "./Breadhelp.svelte"; export const VIEWS: Record = { - home: Home, - network: NetworkHub, - bluetooth: Bluetooth, - displays: DisplaysHub, + about: About, + breadclip: Breadclip, + bread: Bread, + breadbar: Breadbar, + breadbox: Breadbox, + breadpad: Breadpad, + breadpaper: Breadpaper, + breadsearch: Breadsearch, + breadcrumbs: Breadcrumbs, + appearance: Appearance, + autostart: Autostart, + hyprland: Display, + keybinds: Keybinds, sound: Sound, + datetime: DateTime, power: Power, - appearance: AppearanceHub, - desktop: DesktopHub, - input: InputHub, - apps: AppsHub, - privacy: PrivacyHub, - system: SystemHub, - about: AboutHub, + network: Network, + bluetooth: Bluetooth, + firewall: Firewall, + users: Users, + packages: Packages, + aur: Aur, + firmware: Firmware, + snapshots: Snapshots, + breadlock: Breadlock, + breadshot: Breadshot, + breadmon: Breadmon, + breadhelp: Breadhelp, }; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 7916d15..22b7a6c 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,66 +1,65 @@ -
- -
- -
- {#key nav.page} - {#if ActiveView} - - {:else} - - {/if} - {/key} -
-
+
+ +
+ {#if ActiveView} + + {:else} + + {/if} +