diff --git a/src/lib/components/NavigationSelector.svelte b/src/lib/components/NavigationSelector.svelte new file mode 100644 index 0000000..f73cb11 --- /dev/null +++ b/src/lib/components/NavigationSelector.svelte @@ -0,0 +1,197 @@ + + + +
Where the app opens (on the last-used environment).
+Where a tile click goes.
+
{#if $licenseStore.isEnterprise}
diff --git a/src/lib/server/nav-preferences-core.ts b/src/lib/server/nav-preferences-core.ts
new file mode 100644
index 0000000..46d47db
--- /dev/null
+++ b/src/lib/server/nav-preferences-core.ts
@@ -0,0 +1,39 @@
+/**
+ * nav-preferences-core.ts — the PURE parts of the navigation preferences: the page allowlist
+ * and the incoming-patch validation. No DB import, so it is unit-testable directly (importing
+ * nav-preferences.ts pulls in db/drizzle -> better-sqlite3, which bun's test runner can't load).
+ * nav-preferences.ts re-exports these so callers have one import site.
+ */
+
+export interface NavPreferences {
+ landingPage: string | null; // 'dashboard' | 'containers' | ... | null (=inherit/unset)
+ envClickPage: string | null; // where a Dashboard env-tile click goes
+}
+
+// The single list of pages the landing/env-click dropdowns can pick — the sidebar routes.
+// ONE source of truth: the API validation allowlist and the NavigationSelector UI both use
+// this, so a new page can't drift between "offered in the UI" and "accepted on save".
+export const PAGE_SLUGS = [
+ 'dashboard', 'containers', 'logs', 'terminal', 'stacks', 'images', 'volumes', 'networks',
+ 'templates', 'registry', 'activity', 'backups', 'schedules', 'audit'
+] as const;
+
+/** Coerce + validate an incoming nav-prefs patch (pure). A field set to '' / null clears it
+ * (inherit/none). Throws on an invalid page/env so the API returns 400. */
+export function parseNavPatch(data: any): Partial