@schlessera/brain-ui-react 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/chat/chat-page.js +4 -4
  2. package/dist/components/chat/chat-page.js.map +1 -1
  3. package/dist/components/layout/mobile-tab-bar.d.ts.map +1 -1
  4. package/dist/components/layout/mobile-tab-bar.js +41 -4
  5. package/dist/components/layout/mobile-tab-bar.js.map +1 -1
  6. package/dist/components/layout/side-rail.js +3 -3
  7. package/dist/components/layout/side-rail.js.map +1 -1
  8. package/dist/components/settings/models-tab.d.ts +11 -0
  9. package/dist/components/settings/models-tab.d.ts.map +1 -0
  10. package/dist/components/settings/models-tab.js +107 -0
  11. package/dist/components/settings/models-tab.js.map +1 -0
  12. package/dist/components/settings/passkey-tab.d.ts +13 -0
  13. package/dist/components/settings/passkey-tab.d.ts.map +1 -0
  14. package/dist/components/settings/{passkey-panel.js → passkey-tab.js} +14 -11
  15. package/dist/components/settings/passkey-tab.js.map +1 -0
  16. package/dist/components/settings/settings-panel.d.ts +10 -0
  17. package/dist/components/settings/settings-panel.d.ts.map +1 -0
  18. package/dist/components/settings/settings-panel.js +24 -0
  19. package/dist/components/settings/settings-panel.js.map +1 -0
  20. package/dist/lib/api-client.d.ts +7 -1
  21. package/dist/lib/api-client.d.ts.map +1 -1
  22. package/dist/lib/api-client.js +9 -0
  23. package/dist/lib/api-client.js.map +1 -1
  24. package/dist/stores/ui-store.d.ts +9 -3
  25. package/dist/stores/ui-store.d.ts.map +1 -1
  26. package/dist/stores/ui-store.js +6 -3
  27. package/dist/stores/ui-store.js.map +1 -1
  28. package/dist/styles.css +1 -1
  29. package/package.json +2 -2
  30. package/src/components/chat/chat-page.tsx +6 -6
  31. package/src/components/layout/mobile-tab-bar.tsx +95 -8
  32. package/src/components/layout/side-rail.tsx +6 -6
  33. package/src/components/settings/models-tab.tsx +205 -0
  34. package/src/components/settings/{passkey-panel.tsx → passkey-tab.tsx} +74 -79
  35. package/src/components/settings/settings-panel.tsx +61 -0
  36. package/src/lib/api-client.ts +15 -0
  37. package/src/stores/ui-store.ts +18 -7
  38. package/dist/components/settings/passkey-panel.d.ts +0 -10
  39. package/dist/components/settings/passkey-panel.d.ts.map +0 -1
  40. package/dist/components/settings/passkey-panel.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Eye, EyeOff, Loader2, RefreshCw } from "lucide-react";
3
+ import type {
4
+ ModelCatalogEntry,
5
+ ModelCatalogResponse,
6
+ } from "@schlessera/brain-ui-sdk/protocol";
7
+ import { api } from "../../lib/api-client.js";
8
+ import { cn } from "../../lib/utils.js";
9
+
10
+ /**
11
+ * The model picker's contents, and which of them to show.
12
+ *
13
+ * The roster comes from the server (Anthropic model discovery plus any
14
+ * host-declared profiles), so this tab never hardcodes model names. Hiding is
15
+ * presentation only — a session already pinned to a hidden model keeps running.
16
+ */
17
+ export function ModelsTab({ active }: { active: boolean }) {
18
+ const [catalog, setCatalog] = useState<ModelCatalogResponse | null>(null);
19
+ const [loading, setLoading] = useState(false);
20
+ const [refreshing, setRefreshing] = useState(false);
21
+ const [error, setError] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ if (!active) return;
25
+ let cancelled = false;
26
+ setLoading(true);
27
+ setError(null);
28
+ api
29
+ .models()
30
+ .then((data) => {
31
+ if (!cancelled) setCatalog(data);
32
+ })
33
+ .catch((err: unknown) => {
34
+ if (!cancelled) {
35
+ setError(err instanceof Error ? err.message : "Could not load models");
36
+ }
37
+ })
38
+ .finally(() => {
39
+ if (!cancelled) setLoading(false);
40
+ });
41
+ return () => {
42
+ cancelled = true;
43
+ };
44
+ }, [active]);
45
+
46
+ async function toggleHidden(entry: ModelCatalogEntry) {
47
+ if (!catalog) return;
48
+ const previous = catalog;
49
+ const hidden = catalog.models
50
+ .filter((model) =>
51
+ model.id === entry.id ? !entry.hidden : model.hidden
52
+ )
53
+ .map((model) => model.id);
54
+
55
+ // Optimistic: the list is the user's own click, so reflect it immediately
56
+ // and roll back if the write fails.
57
+ setCatalog({
58
+ ...catalog,
59
+ models: catalog.models.map((model) =>
60
+ model.id === entry.id ? { ...model, hidden: !model.hidden } : model
61
+ ),
62
+ });
63
+ setError(null);
64
+ try {
65
+ setCatalog(await api.setHiddenModels(hidden));
66
+ } catch (err) {
67
+ setCatalog(previous);
68
+ setError(err instanceof Error ? err.message : "Could not save");
69
+ }
70
+ }
71
+
72
+ async function onRefresh() {
73
+ if (refreshing) return;
74
+ setRefreshing(true);
75
+ setError(null);
76
+ try {
77
+ setCatalog(await api.refreshModels());
78
+ } catch (err) {
79
+ setError(err instanceof Error ? err.message : "Refresh failed");
80
+ } finally {
81
+ setRefreshing(false);
82
+ }
83
+ }
84
+
85
+ const visibleCount = catalog?.models.filter((m) => !m.hidden).length ?? 0;
86
+
87
+ return (
88
+ <div className="flex h-full flex-col">
89
+ <div className="flex-1 overflow-y-auto p-4">
90
+ <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
91
+ Models
92
+ </h3>
93
+ <p className="mt-1 text-xs text-muted-foreground">
94
+ The list is read from the provider and refreshes on its own. Hide the
95
+ ones you never pick — running sessions are unaffected.
96
+ </p>
97
+
98
+ {loading ? (
99
+ <div className="mt-6 flex justify-center">
100
+ <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
101
+ </div>
102
+ ) : (
103
+ <ul className="mt-4 flex flex-col gap-2">
104
+ {catalog?.models.map((entry) => (
105
+ <ModelRow
106
+ key={entry.id}
107
+ entry={entry}
108
+ onToggle={() => toggleHidden(entry)}
109
+ />
110
+ ))}
111
+ {catalog?.models.length === 0 && (
112
+ <li className="rounded-lg border border-dashed border-border-subtle p-4 text-center text-xs text-muted-foreground">
113
+ No models available.
114
+ </li>
115
+ )}
116
+ </ul>
117
+ )}
118
+
119
+ {error && (
120
+ <p role="alert" className="mt-3 text-xs text-destructive">
121
+ {error}
122
+ </p>
123
+ )}
124
+
125
+ {catalog?.discovery.error && (
126
+ <p className="mt-3 text-xs text-muted-foreground">
127
+ Last refresh failed ({catalog.discovery.error}). Showing the last
128
+ known list.
129
+ </p>
130
+ )}
131
+ {catalog && !catalog.discovery.enabled && (
132
+ <p className="mt-3 text-xs text-muted-foreground">
133
+ Discovery is disabled (BRAIN_UI_MODEL_DISCOVERY). Only configured
134
+ profiles are listed.
135
+ </p>
136
+ )}
137
+ </div>
138
+
139
+ <div className="flex items-center justify-between gap-3 border-t border-border p-4">
140
+ <span className="text-[11px] text-muted-foreground">
141
+ {visibleCount} in picker · {formatRefreshed(catalog?.refreshedAt ?? null)}
142
+ </span>
143
+ <button
144
+ onClick={onRefresh}
145
+ disabled={refreshing}
146
+ className="flex items-center gap-2 rounded-lg border border-border-subtle bg-surface px-3 py-2 text-xs font-medium text-foreground transition-colors hover:border-primary hover:text-primary disabled:opacity-50"
147
+ >
148
+ <RefreshCw className={cn("h-3.5 w-3.5", refreshing && "animate-spin")} />
149
+ Refresh
150
+ </button>
151
+ </div>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ function ModelRow({
157
+ entry,
158
+ onToggle,
159
+ }: {
160
+ entry: ModelCatalogEntry;
161
+ onToggle: () => void;
162
+ }) {
163
+ const Icon = entry.hidden ? EyeOff : Eye;
164
+ return (
165
+ <li
166
+ className={cn(
167
+ "flex items-center gap-3 rounded-lg border border-border-subtle bg-surface p-3",
168
+ entry.hidden && "opacity-50"
169
+ )}
170
+ >
171
+ <div className="min-w-0 flex-1">
172
+ <p className="truncate text-sm text-foreground">{entry.label}</p>
173
+ <p className="truncate text-[11px] text-muted-foreground">
174
+ {entry.id}
175
+ {entry.contextWindow ? ` · ${formatTokens(entry.contextWindow)}` : ""}
176
+ {entry.source === "declared" ? " · configured" : ""}
177
+ </p>
178
+ </div>
179
+ <button
180
+ onClick={onToggle}
181
+ title={entry.hidden ? "Show in picker" : "Hide from picker"}
182
+ aria-pressed={!entry.hidden}
183
+ className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-surface-raised hover:text-foreground"
184
+ >
185
+ <Icon className="h-4 w-4" />
186
+ </button>
187
+ </li>
188
+ );
189
+ }
190
+
191
+ function formatTokens(tokens: number): string {
192
+ if (tokens >= 1_000_000) return `${tokens / 1_000_000}M context`;
193
+ if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K context`;
194
+ return `${tokens} context`;
195
+ }
196
+
197
+ function formatRefreshed(refreshedAt: number | null): string {
198
+ if (refreshedAt === null) return "never refreshed";
199
+ const minutes = Math.round((Date.now() - refreshedAt) / 60_000);
200
+ if (minutes < 1) return "refreshed just now";
201
+ if (minutes < 60) return `refreshed ${minutes}m ago`;
202
+ const hours = Math.round(minutes / 60);
203
+ if (hours < 24) return `refreshed ${hours}h ago`;
204
+ return `refreshed ${Math.round(hours / 24)}d ago`;
205
+ }
@@ -13,21 +13,18 @@ import {
13
13
  import type { PasskeySummary } from "@schlessera/brain-ui-sdk/protocol";
14
14
  import { api } from "../../lib/api-client.js";
15
15
  import { isUserCancel, registerPasskey, supportsPasskeys } from "../../lib/passkeys.js";
16
- import { SlidePanel } from "../layout/slide-panel.js";
17
16
  import { cn } from "../../lib/utils.js";
18
17
 
19
18
  /**
20
- * Passkey management + sign out. Registration requires the current session
21
- * (routes are behind the auth guard); credentials are RP-scoped, so entries
22
- * registered on another hostname are badged and only useful there.
19
+ * Passkey management + sign out — the Security tab of the settings panel.
20
+ * Registration requires the current session (routes are behind the auth guard);
21
+ * credentials are RP-scoped, so entries registered on another hostname are
22
+ * badged and only useful there.
23
+ *
24
+ * `active` is "this tab is on screen": the list reloads when it becomes visible
25
+ * rather than on mount, so switching tabs picks up changes made elsewhere.
23
26
  */
24
- export function PasskeyPanel({
25
- open,
26
- onClose,
27
- }: {
28
- open: boolean;
29
- onClose: () => void;
30
- }) {
27
+ export function PasskeyTab({ active }: { active: boolean }) {
31
28
  const [credentials, setCredentials] = useState<PasskeySummary[]>([]);
32
29
  const [loading, setLoading] = useState(false);
33
30
  const [busy, setBusy] = useState(false);
@@ -52,11 +49,11 @@ export function PasskeyPanel({
52
49
  }
53
50
 
54
51
  useEffect(() => {
55
- if (open) {
52
+ if (active) {
56
53
  setError(null);
57
54
  void refresh();
58
55
  }
59
- }, [open]);
56
+ }, [active]);
60
57
 
61
58
  async function onAdd() {
62
59
  if (busy) return;
@@ -106,79 +103,77 @@ export function PasskeyPanel({
106
103
  }
107
104
 
108
105
  return (
109
- <SlidePanel open={open} onClose={onClose} title="Security">
110
- <div className="flex h-full flex-col">
111
- <div className="flex-1 overflow-y-auto p-4">
112
- <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
113
- Passkeys
114
- </h3>
115
- <p className="mt-1 text-xs text-muted-foreground">
116
- Sign in with Face ID, fingerprint, or a security key instead of the
117
- password. Passkeys only work on the site they were added on.
118
- </p>
106
+ <div className="flex h-full flex-col">
107
+ <div className="flex-1 overflow-y-auto p-4">
108
+ <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
109
+ Passkeys
110
+ </h3>
111
+ <p className="mt-1 text-xs text-muted-foreground">
112
+ Sign in with Face ID, fingerprint, or a security key instead of the
113
+ password. Passkeys only work on the site they were added on.
114
+ </p>
119
115
 
120
- {loading ? (
121
- <div className="mt-6 flex justify-center">
122
- <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
123
- </div>
124
- ) : (
125
- <ul className="mt-4 flex flex-col gap-2">
126
- {credentials.map((credential) => (
127
- <PasskeyRow
128
- key={credential.id}
129
- credential={credential}
130
- onDelete={() => onDelete(credential.id)}
131
- onRename={(label) => onRename(credential.id, label)}
132
- />
133
- ))}
134
- {credentials.length === 0 && passkeyMode && (
135
- <li className="rounded-lg border border-dashed border-border-subtle p-4 text-center text-xs text-muted-foreground">
136
- No passkeys yet.
137
- </li>
138
- )}
139
- </ul>
140
- )}
141
-
142
- {error && (
143
- <p role="alert" className="mt-3 text-xs text-destructive">
144
- {error}
145
- </p>
146
- )}
116
+ {loading ? (
117
+ <div className="mt-6 flex justify-center">
118
+ <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
119
+ </div>
120
+ ) : (
121
+ <ul className="mt-4 flex flex-col gap-2">
122
+ {credentials.map((credential) => (
123
+ <PasskeyRow
124
+ key={credential.id}
125
+ credential={credential}
126
+ onDelete={() => onDelete(credential.id)}
127
+ onRename={(label) => onRename(credential.id, label)}
128
+ />
129
+ ))}
130
+ {credentials.length === 0 && passkeyMode && (
131
+ <li className="rounded-lg border border-dashed border-border-subtle p-4 text-center text-xs text-muted-foreground">
132
+ No passkeys yet.
133
+ </li>
134
+ )}
135
+ </ul>
136
+ )}
147
137
 
148
- {passkeyMode && webAuthnSupported ? (
149
- <button
150
- onClick={onAdd}
151
- disabled={busy}
152
- className="mt-4 flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:brightness-110 disabled:opacity-50"
153
- >
154
- {busy ? (
155
- <Loader2 className="h-4 w-4 animate-spin" />
156
- ) : (
157
- <Plus className="h-4 w-4" />
158
- )}
159
- Add a passkey
160
- </button>
161
- ) : (
162
- <p className="mt-4 text-xs text-muted-foreground">
163
- {passkeyMode
164
- ? "This browser does not support passkeys."
165
- : "Passkeys need password auth mode on the server."}
166
- </p>
167
- )}
168
- </div>
138
+ {error && (
139
+ <p role="alert" className="mt-3 text-xs text-destructive">
140
+ {error}
141
+ </p>
142
+ )}
169
143
 
170
- {/* Sign out */}
171
- <div className="border-t border-border p-4">
144
+ {passkeyMode && webAuthnSupported ? (
172
145
  <button
173
- onClick={onSignOut}
174
- className="flex w-full items-center justify-center gap-2 rounded-lg border border-border-subtle bg-surface px-4 py-2 text-sm font-medium text-foreground transition-colors hover:border-destructive hover:text-destructive"
146
+ onClick={onAdd}
147
+ disabled={busy}
148
+ className="mt-4 flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:brightness-110 disabled:opacity-50"
175
149
  >
176
- <LogOut className="h-4 w-4" />
177
- Sign out
150
+ {busy ? (
151
+ <Loader2 className="h-4 w-4 animate-spin" />
152
+ ) : (
153
+ <Plus className="h-4 w-4" />
154
+ )}
155
+ Add a passkey
178
156
  </button>
179
- </div>
157
+ ) : (
158
+ <p className="mt-4 text-xs text-muted-foreground">
159
+ {passkeyMode
160
+ ? "This browser does not support passkeys."
161
+ : "Passkeys need password auth mode on the server."}
162
+ </p>
163
+ )}
164
+ </div>
165
+
166
+ {/* Sign out */}
167
+ <div className="border-t border-border p-4">
168
+ <button
169
+ onClick={onSignOut}
170
+ className="flex w-full items-center justify-center gap-2 rounded-lg border border-border-subtle bg-surface px-4 py-2 text-sm font-medium text-foreground transition-colors hover:border-destructive hover:text-destructive"
171
+ >
172
+ <LogOut className="h-4 w-4" />
173
+ Sign out
174
+ </button>
180
175
  </div>
181
- </SlidePanel>
176
+ </div>
182
177
  );
183
178
  }
184
179
 
@@ -0,0 +1,61 @@
1
+ import { KeyRound, SlidersHorizontal } from "lucide-react";
2
+ import { useUIStore, type SettingsTab } from "../../stores/ui-store.js";
3
+ import { SlidePanel } from "../layout/slide-panel.js";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { ModelsTab } from "./models-tab.js";
6
+ import { PasskeyTab } from "./passkey-tab.js";
7
+
8
+ const TABS: Array<{ id: SettingsTab; label: string; icon: typeof KeyRound }> = [
9
+ { id: "models", label: "Models", icon: SlidersHorizontal },
10
+ { id: "security", label: "Security", icon: KeyRound },
11
+ ];
12
+
13
+ /**
14
+ * The settings surface: one panel, one entry point in the menus, a tab per
15
+ * area. Only the selected tab is mounted — each fetches its own data on becoming
16
+ * active, so switching tabs (or reopening the panel) always shows current state.
17
+ */
18
+ export function SettingsPanel({
19
+ open,
20
+ onClose,
21
+ }: {
22
+ open: boolean;
23
+ onClose: () => void;
24
+ }) {
25
+ const tab = useUIStore((s) => s.settingsTab);
26
+ const setTab = useUIStore((s) => s.setSettingsTab);
27
+
28
+ return (
29
+ <SlidePanel open={open} onClose={onClose} title="Settings">
30
+ <div className="flex h-full flex-col">
31
+ <div className="flex shrink-0 gap-1 border-b border-border px-2 pt-2">
32
+ {TABS.map(({ id, label, icon: Icon }) => (
33
+ <button
34
+ key={id}
35
+ onClick={() => setTab(id)}
36
+ aria-selected={tab === id}
37
+ role="tab"
38
+ className={cn(
39
+ "flex items-center gap-1.5 rounded-t-lg px-3 py-2 text-xs font-medium transition-colors",
40
+ tab === id
41
+ ? "border-b-2 border-primary text-foreground"
42
+ : "text-muted-foreground hover:text-foreground"
43
+ )}
44
+ >
45
+ <Icon className="h-3.5 w-3.5" />
46
+ {label}
47
+ </button>
48
+ ))}
49
+ </div>
50
+
51
+ <div className="min-h-0 flex-1">
52
+ {tab === "models" ? (
53
+ <ModelsTab active={open && tab === "models"} />
54
+ ) : (
55
+ <PasskeyTab active={open && tab === "security"} />
56
+ )}
57
+ </div>
58
+ </div>
59
+ </SlidePanel>
60
+ );
61
+ }
@@ -6,6 +6,7 @@ import type {
6
6
  PronunciationOverride,
7
7
  ProviderInfo,
8
8
  PasskeySummary,
9
+ ModelCatalogResponse,
9
10
  } from "@schlessera/brain-ui-sdk/protocol";
10
11
  import type {
11
12
  AuthenticationResponseJSON,
@@ -119,6 +120,20 @@ export const api = {
119
120
  backends?: Record<string, BackendInfo>;
120
121
  }>("/providers"),
121
122
 
123
+ /** Full model catalog for the settings screen — hidden entries included. */
124
+ models: () => fetchJson<ModelCatalogResponse>("/models"),
125
+
126
+ /** Replace the hidden set (full list, not a delta); returns the new catalog. */
127
+ setHiddenModels: (hidden: string[]) =>
128
+ fetchJson<ModelCatalogResponse>("/models/hidden", {
129
+ method: "PUT",
130
+ body: JSON.stringify({ hidden }),
131
+ }),
132
+
133
+ /** Force a discovery refresh, bypassing the TTL. */
134
+ refreshModels: () =>
135
+ fetchJson<ModelCatalogResponse>("/models/refresh", { method: "POST" }),
136
+
122
137
  /** Password-mode login. Resolves on success; throws the server error otherwise. */
123
138
  login: (password: string) =>
124
139
  fetchJson<{ ok: true }>("/auth/login", {
@@ -1,22 +1,29 @@
1
1
  import { create } from "zustand";
2
2
 
3
+ /** Which tab the settings panel opens on. */
4
+ export type SettingsTab = "models" | "security";
5
+
3
6
  interface UIState {
4
7
  sessionPanelOpen: boolean;
5
8
  syncPanelOpen: boolean;
6
9
  whatsupPanelOpen: boolean;
7
10
  filePanelOpen: boolean;
8
- securityPanelOpen: boolean;
11
+ settingsPanelOpen: boolean;
12
+ settingsTab: SettingsTab;
9
13
  toggleSessionPanel: () => void;
10
14
  toggleSyncPanel: () => void;
11
15
  toggleWhatsupPanel: () => void;
12
16
  toggleFilePanel: () => void;
13
- toggleSecurityPanel: () => void;
17
+ toggleSettingsPanel: () => void;
14
18
  closeAllPanels: () => void;
15
19
  setSyncPanelOpen: (open: boolean) => void;
16
20
  setWhatsupPanelOpen: (open: boolean) => void;
17
21
  setSessionPanelOpen: (open: boolean) => void;
18
22
  setFilePanelOpen: (open: boolean) => void;
19
- setSecurityPanelOpen: (open: boolean) => void;
23
+ setSettingsPanelOpen: (open: boolean) => void;
24
+ /** Open settings straight onto a tab (menu entries, deep links). */
25
+ openSettings: (tab: SettingsTab) => void;
26
+ setSettingsTab: (tab: SettingsTab) => void;
20
27
  }
21
28
 
22
29
  const CLOSED = {
@@ -24,11 +31,12 @@ const CLOSED = {
24
31
  syncPanelOpen: false,
25
32
  whatsupPanelOpen: false,
26
33
  filePanelOpen: false,
27
- securityPanelOpen: false,
34
+ settingsPanelOpen: false,
28
35
  };
29
36
 
30
37
  export const useUIStore = create<UIState>((set) => ({
31
38
  ...CLOSED,
39
+ settingsTab: "models",
32
40
  toggleSessionPanel: () =>
33
41
  set((s) => ({ ...CLOSED, sessionPanelOpen: !s.sessionPanelOpen })),
34
42
  toggleSyncPanel: () =>
@@ -37,12 +45,15 @@ export const useUIStore = create<UIState>((set) => ({
37
45
  set((s) => ({ ...CLOSED, whatsupPanelOpen: !s.whatsupPanelOpen })),
38
46
  toggleFilePanel: () =>
39
47
  set((s) => ({ ...CLOSED, filePanelOpen: !s.filePanelOpen })),
40
- toggleSecurityPanel: () =>
41
- set((s) => ({ ...CLOSED, securityPanelOpen: !s.securityPanelOpen })),
48
+ toggleSettingsPanel: () =>
49
+ set((s) => ({ ...CLOSED, settingsPanelOpen: !s.settingsPanelOpen })),
42
50
  closeAllPanels: () => set({ ...CLOSED }),
43
51
  setSyncPanelOpen: (open) => set({ syncPanelOpen: open }),
44
52
  setWhatsupPanelOpen: (open) => set({ whatsupPanelOpen: open }),
45
53
  setSessionPanelOpen: (open) => set({ sessionPanelOpen: open }),
46
54
  setFilePanelOpen: (open) => set({ filePanelOpen: open }),
47
- setSecurityPanelOpen: (open) => set({ securityPanelOpen: open }),
55
+ setSettingsPanelOpen: (open) => set({ settingsPanelOpen: open }),
56
+ openSettings: (tab) =>
57
+ set({ ...CLOSED, settingsPanelOpen: true, settingsTab: tab }),
58
+ setSettingsTab: (tab) => set({ settingsTab: tab }),
48
59
  }));
@@ -1,10 +0,0 @@
1
- /**
2
- * Passkey management + sign out. Registration requires the current session
3
- * (routes are behind the auth guard); credentials are RP-scoped, so entries
4
- * registered on another hostname are badged and only useful there.
5
- */
6
- export declare function PasskeyPanel({ open, onClose, }: {
7
- open: boolean;
8
- onClose: () => void;
9
- }): import("react").JSX.Element;
10
- //# sourceMappingURL=passkey-panel.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"passkey-panel.d.ts","sourceRoot":"","sources":["../../../src/components/settings/passkey-panel.tsx"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAyJA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"passkey-panel.js","sourceRoot":"","sources":["../../../src/components/settings/passkey-panel.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EACL,KAAK,EACL,KAAK,EACL,WAAW,EACX,OAAO,EACP,MAAM,EACN,MAAM,EACN,IAAI,EACJ,MAAM,EACN,CAAC,GACF,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,IAAI,EACJ,OAAO,GAIR;IACC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAmB,EAAE,CAAC,CAAC;IACrE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAErD,MAAM,iBAAiB,GAAG,gBAAgB,EAAE,CAAC;IAE7C,KAAK,UAAU,OAAO;QACpB,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,cAAc,CAAC,KAAK,CAAC,CAAC;YACtB,cAAc,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,EAAE,CAAC;YACT,QAAQ,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,KAAK,UAAU,KAAK;QAClB,IAAI,IAAI;YAAE,OAAO;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,eAAe,EAAE,CAAC;YACxB,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,KAAK,UAAU,QAAQ,CAAC,EAAU;QAChC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,2CAA2C,CAAC;YAAE,OAAO;QACzE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YAC5B,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,KAAK,UAAU,QAAQ,CAAC,EAAU,EAAE,KAAa;QAC/C,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACnC,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE,CACtB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAC7D,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,KAAK,UAAU,SAAS;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,CACL,KAAC,UAAU,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAC,UAAU,YACxD,eAAK,SAAS,EAAC,sBAAsB,aACnC,eAAK,SAAS,EAAC,4BAA4B,aACzC,aAAI,SAAS,EAAC,mEAAmE,yBAE5E,EACL,YAAG,SAAS,EAAC,oCAAoC,iJAG7C,EAEH,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,0BAA0B,YACvC,KAAC,OAAO,IAAC,SAAS,EAAC,4CAA4C,GAAG,GAC9D,CACP,CAAC,CAAC,CAAC,CACF,cAAI,SAAS,EAAC,0BAA0B,aACrC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAC/B,KAAC,UAAU,IAET,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,EACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,IAH9C,UAAU,CAAC,EAAE,CAIlB,CACH,CAAC,EACD,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,IAAI,CAC1C,aAAI,SAAS,EAAC,oGAAoG,iCAE7G,CACN,IACE,CACN,EAEA,KAAK,IAAI,CACR,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,+BAA+B,YACtD,KAAK,GACJ,CACL,EAEA,WAAW,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAClC,kBACE,OAAO,EAAE,KAAK,EACd,QAAQ,EAAE,IAAI,EACd,SAAS,EAAC,2LAA2L,aAEpM,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,OAAO,IAAC,SAAS,EAAC,sBAAsB,GAAG,CAC7C,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,SAAS,EAAC,SAAS,GAAG,CAC7B,qBAEM,CACV,CAAC,CAAC,CAAC,CACF,YAAG,SAAS,EAAC,oCAAoC,YAC9C,WAAW;gCACV,CAAC,CAAC,yCAAyC;gCAC3C,CAAC,CAAC,iDAAiD,GACnD,CACL,IACG,EAGN,cAAK,SAAS,EAAC,4BAA4B,YACzC,kBACE,OAAO,EAAE,SAAS,EAClB,SAAS,EAAC,iNAAiN,aAE3N,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,GAAG,gBAEvB,GACL,IACF,GACK,CACd,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,UAAU,EACV,QAAQ,EACR,QAAQ,GAKT;IACC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,SAAS,GACb,OAAO,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAEhF,MAAM,WAAW,GACf,UAAU,CAAC,KAAK;QAChB,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,kBAAkB,EAAE,EAAE,CAAC;IAErE,SAAS,MAAM;QACb,UAAU,CAAC,KAAK,CAAC,CAAC;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,UAAU,CAAC,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,CACL,cAAI,SAAS,EAAC,uDAAuD,aACnE,eAAK,SAAS,EAAC,yBAAyB,aACtC,KAAC,WAAW,IAAC,SAAS,EAAC,+BAA+B,GAAG,EACxD,OAAO,CAAC,CAAC,CAAC,CACT,gBAAM,SAAS,EAAC,gCAAgC,aAC9C,gBACE,SAAS,QACT,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACzC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;oCACf,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;wCAAE,MAAM,EAAE,CAAC;oCAChC,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;wCAAE,UAAU,CAAC,KAAK,CAAC,CAAC;gCAC5C,CAAC,EACD,SAAS,EAAC,gIAAgI,GAC1I,EACF,iBAAQ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAC,MAAM,EAAC,SAAS,EAAC,iDAAiD,YAC/F,KAAC,KAAK,IAAC,SAAS,EAAC,aAAa,GAAG,GAC1B,EACT,iBAAQ,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,iDAAiD,YAClH,KAAC,CAAC,IAAC,SAAS,EAAC,aAAa,GAAG,GACtB,IACJ,CACR,CAAC,CAAC,CAAC,CACF,eAAM,SAAS,EAAC,yCAAyC,YAAE,WAAW,GAAQ,CAC/E,EACA,CAAC,OAAO,IAAI,CACX,8BACE,iBACE,OAAO,EAAE,GAAG,EAAE;oCACZ,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;oCAC3B,UAAU,CAAC,IAAI,CAAC,CAAC;gCACnB,CAAC,EACD,KAAK,EAAC,QAAQ,EACd,SAAS,EAAC,mEAAmE,YAE7E,KAAC,MAAM,IAAC,SAAS,EAAC,aAAa,GAAG,GAC3B,EACT,iBACE,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAC,QAAQ,EACd,SAAS,EAAC,oEAAoE,YAE9E,KAAC,MAAM,IAAC,SAAS,EAAC,aAAa,GAAG,GAC3B,IACR,CACJ,IACG,EACN,eAAK,SAAS,EAAC,2FAA2F,aACvG,UAAU,CAAC,QAAQ,IAAI,CACtB,gBAAM,SAAS,EAAC,yBAAyB,aACvC,KAAC,KAAK,IAAC,SAAS,EAAC,SAAS,GAAG,eACxB,CACR,EACA,SAAS,IAAI,CACZ,eACE,SAAS,EAAE,EAAE,CACX,wEAAwE,CACzE,EACD,KAAK,EAAE,iBAAiB,UAAU,CAAC,IAAI,sBAAsB,YAE5D,UAAU,CAAC,IAAI,GACX,CACR,EACD,yBACG,UAAU,CAAC,UAAU;4BACpB,CAAC,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,kBAAkB,EAAE,EAAE;4BACrE,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,kBAAkB,EAAE,EAAE,GAC7D,IACH,IACH,CACN,CAAC;AACJ,CAAC"}