From 6d2a6cdea4f7b18fda604f590fc1878e6686c6c3 Mon Sep 17 00:00:00 2001
From: GFrancV
Date: Wed, 22 Jul 2026 11:30:35 +0100
Subject: [PATCH] refactor: reorganize security settings by domain, not by
scroll order
Backups and vault-location actions were mixed into the security dialog
despite being data-management concerns, auto-lock was split between
the gear dropdown (timeout) and the modal (triggers), and Key file had
far less prominence than Change password despite similar criticality.
Moves Vault Location/Save copy/Backups into the gear dropdown, unifies
auto-lock timeout + triggers in one section, and groups Password and
Key file under a new Authentication section so both open their own
flow with equal visual weight.
Fixes #6
---
.../components/security-settings-dialog.tsx | 138 +++++++++---------
.../components/settings/KeyFileSetting.tsx | 15 +-
src/renderer/src/components/sidebar.tsx | 69 ++-------
3 files changed, 88 insertions(+), 134 deletions(-)
diff --git a/src/renderer/src/components/security-settings-dialog.tsx b/src/renderer/src/components/security-settings-dialog.tsx
index e43c820..46e897a 100644
--- a/src/renderer/src/components/security-settings-dialog.tsx
+++ b/src/renderer/src/components/security-settings-dialog.tsx
@@ -1,15 +1,22 @@
-import { type ReactNode } from 'react'
+import { useState, type ReactNode } from 'react'
-import { CheckIcon } from 'lucide-react'
-import { toast } from 'sonner'
+import { LockIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
-import { notvex } from '@/lib/ipc'
import { usePrefsStore } from '@/store/prefs.store'
+import { ChangePasswordDialog } from './change-password-dialog'
import { KeyFileSetting } from './settings/KeyFileSetting'
import { Button } from './ui/button'
import { Checkbox } from './ui/checkbox'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from './ui/select'
import { Separator } from './ui/separator'
interface Props {
@@ -17,20 +24,22 @@ interface Props {
onClose: () => void
}
-async function handleOpenBackupsFolder(): Promise {
- const res = await notvex.vault.openBackupsFolder()
- if (!res.success) toast.error(res.error)
-}
-
export function SecuritySettingsDialog({ open, onClose }: Props): ReactNode {
const { setPref } = usePrefsStore()
- const { lockOnMinimize, allowScreenCapture } = usePrefsStore(
+ const { autoLockMinutes, lockOnMinimize, allowScreenCapture } = usePrefsStore(
useShallow((s) => ({
+ autoLockMinutes: s.autoLockMinutes,
lockOnMinimize: s.lockOnMinimize,
allowScreenCapture: s.allowScreenCapture
}))
)
+ const [changePasswordOpen, setChangePasswordOpen] = useState(false)
+
+ const handleAutoLockChange = (minutes: string): void => {
+ void setPref('autoLockMinutes', Number(minutes))
+ }
+
const handleLockOnMinimize = (checked: boolean): void => {
void setPref('lockOnMinimize', checked)
}
@@ -58,6 +67,26 @@ export function SecuritySettingsDialog({ open, onClose }: Props): ReactNode {
Auto-lock
+
+
+
+
+
+
+
+ Never
+ After 5 minutes
+ After 15 minutes
+ After 30 minutes
+ After 1 hour
+
+
+
+
+
handleLockOnMinimize(v === true)}
/>
-
-
-
-
-
-
-
-
@@ -106,50 +119,51 @@ export function SecuritySettingsDialog({ open, onClose }: Props): ReactNode {
- {/* ── Key file ── */}
-
-
-
-
- {/* ── Backups ── */}
+ {/* ── Authentication ── */}
- Backups
-
-
- Plain point-in-time copies of your vault file, created automatically before
- format-changing updates. Safe to delete — not a restore feature, just browsable via
- your file explorer.
+ Authentication
- {
- void handleOpenBackupsFolder()
- }}
+
- Open backups folder
-
+ {
+ onClose()
+ setChangePasswordOpen(true)
+ }}
+ >
+
+ Change password
+
+
+
+
- {/* ── Unlock attempts ── */}
+ {/* ── Always-on protections ── */}
- Unlock attempts
+ Always-on protections
+
+
+ Notvex always locks the vault when the system sleeps or the screen locks, and applies
+ a progressive delay after repeated failed unlock attempts. These protections are built
+ in and cannot be turned off.
-
-
-
-
+
+ setChangePasswordOpen(false)}
+ />
)
}
@@ -157,29 +171,19 @@ export function SecuritySettingsDialog({ open, onClose }: Props): ReactNode {
function SettingRow({
label,
description,
- readonly = false,
children
}: {
label: string
description?: string
- readonly?: boolean
children: React.ReactNode
}): ReactNode {
return (
-
{label}
+
{label}
{description &&
{description}
}
{children}
)
}
-
-function ReadonlyCheck(): ReactNode {
- return (
-
-
-
- )
-}
diff --git a/src/renderer/src/components/settings/KeyFileSetting.tsx b/src/renderer/src/components/settings/KeyFileSetting.tsx
index 2df4fcc..2dec555 100644
--- a/src/renderer/src/components/settings/KeyFileSetting.tsx
+++ b/src/renderer/src/components/settings/KeyFileSetting.tsx
@@ -128,14 +128,13 @@ export function KeyFileSetting(): ReactNode {
return (
-
- Key file
-
-
-
- A key file adds a second factor to unlock your vault. You need both your password and the
- key file to access your notes. If you lose the key file, access is permanently lost.
-
+
+
Key file
+
+ A key file adds a second factor to unlock your vault. You need both your password and the
+ key file to access your notes. If you lose the key file, access is permanently lost.
+
+
{keyFileStep === 'idle' && (
<>
diff --git a/src/renderer/src/components/sidebar.tsx b/src/renderer/src/components/sidebar.tsx
index e6a2a56..6bd45f1 100644
--- a/src/renderer/src/components/sidebar.tsx
+++ b/src/renderer/src/components/sidebar.tsx
@@ -26,12 +26,10 @@ import { useCreateNote } from '@/hooks/use-create-note'
import { useIsDev } from '@/hooks/use-is-dev'
import { notvex } from '@/lib/ipc'
import { cn, truncatePath } from '@/lib/utils'
-import { usePrefsStore } from '@/store/prefs.store'
import { useUiStore } from '@/store/ui.store'
import { useVaultStore } from '@/store/vault.store'
import type { Tag } from '@shared/types'
import { AppLogo } from './AppLogo'
-import { ChangePasswordDialog } from './change-password-dialog'
import { AppVersionDialog } from './dialogs/AppVersionDialog'
import { SecuritySettingsDialog } from './security-settings-dialog'
import { TagCreateModal } from './tags/TagCreateModal'
@@ -49,14 +47,6 @@ import {
} from './ui/dropdown-menu'
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from './ui/input-group'
import { Kbd } from './ui/kbd'
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue
-} from './ui/select'
import {
Sidebar as ShadcnSidebar,
SidebarContent,
@@ -84,6 +74,11 @@ const handleSaveCopy = async (): Promise => {
if (result.data) toast.success('Copy saved successfully')
}
+const handleOpenBackupsFolder = async (): Promise => {
+ const res = await notvex.vault.openBackupsFolder()
+ if (!res.success) toast.error(res.error)
+}
+
export function Sidebar(): React.ReactNode {
const { loadTags, loadTagCounts, setStatus, setActiveNoteId, setNotes } = useVaultStore()
const { tags, tagCounts, notes, currentVaultPath } = useVaultStore(
@@ -107,9 +102,6 @@ export function Sidebar(): React.ReactNode {
}))
)
- const { setPref } = usePrefsStore()
- const autoLockMinutes = usePrefsStore((s) => s.autoLockMinutes)
-
const handleNewNote = useCreateNote()
const { isCopied, copyToClipboard } = useCopyToClipboard()
const isDev = useIsDev()
@@ -117,7 +109,6 @@ export function Sidebar(): React.ReactNode {
const searchContainerRef = useRef(null)
const [appVersionOpen, setAppVersionOpen] = useState(false)
const [settingsPopoverOpen, setSettingsPopoverOpen] = useState(false)
- const [changePasswordOpen, setChangePasswordOpen] = useState(false)
const [securitySettingsOpen, setSecuritySettingsOpen] = useState(false)
const [createModalOpen, setCreateModalOpen] = useState(false)
const [createModalKey, setCreateModalKey] = useState(0)
@@ -141,10 +132,6 @@ export function Sidebar(): React.ReactNode {
setNotes([])
}
- const handleAutoLockChange = (minutes: string): void => {
- void setPref('autoLockMinutes', Number(minutes))
- }
-
const allNotesCount = notes.filter((n) => !n.isTrashed).length
const pinnedCount = notes.filter((n) => n.isPinned && !n.isTrashed).length
const trashCount = notes.filter((n) => n.isTrashed).length
@@ -184,28 +171,8 @@ export function Sidebar(): React.ReactNode {
-
- Auto-lock
- e.preventDefault()}>
-
-
-
-
-
-
- Never
- After 5 minutes
- After 15 minutes
- After 30 minutes
- After 1 hour
-
-
-
-
-
{currentVaultPath && (
<>
-
Vault Location
@@ -221,29 +188,17 @@ export function Sidebar(): React.ReactNode {
-
+
Save a vault copy as...
+
+ Open backups folder
+
+
>
)}
-
- Security
- {
- setChangePasswordOpen(true)
- setSettingsPopoverOpen(false)
- }}
- >
- Change password
-
setAppVersionOpen(false)} />
- setChangePasswordOpen(false)}
- />
setSecuritySettingsOpen(false)}