diff --git a/apps/desktop/src/main/login-shell-path.test.ts b/apps/desktop/src/main/login-shell-path.test.ts new file mode 100644 index 00000000..3490dfc3 --- /dev/null +++ b/apps/desktop/src/main/login-shell-path.test.ts @@ -0,0 +1,28 @@ +import path from 'node:path' +import { describe, expect, it } from 'vitest' +import { resolveCommandViaLoginShell } from './login-shell-path' + +const onPosix = process.platform !== 'win32' + +describe('resolveCommandViaLoginShell', () => { + // The core of issue #73: GUI apps inherit a minimal PATH, so a bare command + // name can't be resolved. A login shell sources the user's profile and + // returns an absolute path. `sh` is guaranteed present on POSIX systems. + it.skipIf(!onPosix)('resolves a ubiquitous command to an absolute path', async () => { + const resolved = await resolveCommandViaLoginShell('sh') + expect(resolved).toBeTruthy() + expect(path.isAbsolute(resolved as string)).toBe(true) + expect(path.basename(resolved as string)).toBe('sh') + }) + + it('returns null for a command that does not exist', async () => { + expect(await resolveCommandViaLoginShell('zen-not-a-real-binary-9f3a2b')).toBeNull() + }) + + it('rejects unsafe command names without spawning a shell', async () => { + expect(await resolveCommandViaLoginShell('rg; rm -rf /')).toBeNull() + expect(await resolveCommandViaLoginShell('$(touch /tmp/zen-pwned)')).toBeNull() + expect(await resolveCommandViaLoginShell('rg fzf')).toBeNull() + expect(await resolveCommandViaLoginShell('')).toBeNull() + }) +}) diff --git a/apps/desktop/src/main/login-shell-path.ts b/apps/desktop/src/main/login-shell-path.ts new file mode 100644 index 00000000..ef06b6f9 --- /dev/null +++ b/apps/desktop/src/main/login-shell-path.ts @@ -0,0 +1,73 @@ +import { execFile } from 'node:child_process' +import { promises as fsp, constants as fsConstants } from 'node:fs' +import path from 'node:path' +import { promisify } from 'node:util' + +const execFileAsync = promisify(execFile) + +// A login shell can be slow if the user's profile is heavy; cap it so a stuck +// shell never blocks search. Resolution is memoized, so this runs rarely. +const LOGIN_SHELL_TIMEOUT_MS = 5_000 +// Memoize resolved locations so we don't spawn a shell on every capability +// check / search. Short enough to self-heal after a tool is installed mid-run. +const RESOLUTION_TTL_MS = 60_000 + +// Binary names are interpolated into a shell `command -v` invocation, so only +// ever accept bare tokens — never anything that could break out of the word. +const SAFE_COMMAND = /^[A-Za-z0-9._-]+$/ + +const cache = new Map() + +/** + * Resolve a command to an absolute path using the user's login shell. + * + * GUI apps launched from Finder/Dock on macOS (and similar elsewhere) inherit + * only a minimal PATH (e.g. `/usr/bin:/bin:/usr/sbin:/sbin`), so tools + * installed by Homebrew, cargo, npm, nix, etc. aren't resolvable by their bare + * name. Spawning a *login* shell (`$SHELL -lc 'command -v '`) queries the + * PATH the user actually has configured — the same approach the CLI-install and + * Raycast integrations already use to locate their tools. + * + * Returns the absolute path, or `null` when the command can't be resolved — + * including on Windows, where GUI apps already inherit the full PATH and the + * POSIX login-shell trick doesn't apply (callers should fall back to the bare + * command name there). + */ +export async function resolveCommandViaLoginShell(command: string): Promise { + if (!SAFE_COMMAND.test(command)) return null + if (process.platform === 'win32') return null + + const cached = cache.get(command) + if (cached && Date.now() - cached.at < RESOLUTION_TTL_MS) return cached.value + + const value = await queryLoginShell(command) + cache.set(command, { at: Date.now(), value }) + return value +} + +async function queryLoginShell(command: string): Promise { + // Try the user's own shell first, then the standard POSIX shells. On + // macOS/Linux those still pick up the login PATH from the system/user + // profile even when the user's interactive shell is something exotic (fish, + // nu) whose `command -v` syntax differs from POSIX. + const shells = Array.from( + new Set([process.env.SHELL, '/bin/zsh', '/bin/bash', '/bin/sh'].filter(Boolean)) + ) as string[] + + for (const shellPath of shells) { + try { + await fsp.access(shellPath, fsConstants.X_OK) + const { stdout } = await execFileAsync(shellPath, ['-lc', `command -v ${command}`], { + encoding: 'utf8', + timeout: LOGIN_SHELL_TIMEOUT_MS, + maxBuffer: 1024 * 1024, + windowsHide: true + }) + const resolved = String(stdout).trim().split(/\r?\n/)[0]?.trim() + if (resolved && path.isAbsolute(resolved)) return resolved + } catch { + /* shell missing here, or command not found in it — try the next one */ + } + } + return null +} diff --git a/apps/desktop/src/main/vault.ts b/apps/desktop/src/main/vault.ts index 5ad972a1..85fe7b78 100644 --- a/apps/desktop/src/main/vault.ts +++ b/apps/desktop/src/main/vault.ts @@ -5,6 +5,7 @@ import path from 'node:path' import { promisify } from 'node:util' import { app } from 'electron' import { recordMainPerf } from './perf' +import { resolveCommandViaLoginShell } from './login-shell-path' import { DEFAULT_DAILY_NOTES_DIRECTORY, DEFAULT_WEEKLY_NOTES_DIRECTORY, @@ -1300,7 +1301,16 @@ async function searchExecutable( paths: Required ): Promise { const configured = kind === 'ripgrep' ? paths.ripgrepPath : paths.fzfPath - if (!configured) return kind === 'ripgrep' ? 'rg' : 'fzf' + if (!configured) { + // Auto mode. GUI apps launched from Finder/Dock inherit only a minimal + // PATH (/usr/bin:/bin:/usr/sbin:/sbin), so the bare command name often + // isn't resolvable even when rg/fzf are installed in a non-standard place + // (Homebrew, cargo, nix, …). Resolve through the user's login shell so the + // absolute path flows into both detection and execution; fall back to the + // bare name so an already-correct PATH (and Windows) keeps working. (#73) + const command = kind === 'ripgrep' ? 'rg' : 'fzf' + return (await resolveCommandViaLoginShell(command)) ?? command + } if (!path.isAbsolute(configured)) return null const normalized = path.resolve(configured) diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index c4774227..966e5100 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -25,7 +25,10 @@ module.exports = { DEFAULT: 'rgb(var(--z-accent) / )', soft: 'rgb(var(--z-accent-soft) / )', muted: 'rgb(var(--z-accent-muted) / )' - } + }, + danger: 'rgb(var(--z-red) / )', + success: 'rgb(var(--z-green) / )', + warning: 'rgb(var(--z-yellow) / )' }, fontFamily: { sans: [ @@ -43,6 +46,26 @@ module.exports = { panel: '0 1px 0 0 rgb(var(--z-shadow) / 0.04), 0 8px 28px -12px rgb(var(--z-shadow) / 0.18)', float: '0 20px 60px -20px rgb(var(--z-shadow) / 0.28)' + }, + fontSize: { + '2xs': ['0.6875rem', { lineHeight: '1rem' }] + }, + zIndex: { + dropdown: '40', + palette: '50', + modal: '70', + nested: '75', + popover: '80', + toast: '90' + }, + maxWidth: { + 'dialog-xs': '420px', + 'dialog-sm': '440px', + 'dialog-md': '560px', + 'dialog-lg': '720px', + 'dialog-xl': '900px', + 'dialog-2xl': '1120px', + 'dialog-3xl': '1360px' } } }, diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 9538f269..8654e1ae 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -25,7 +25,10 @@ export default { DEFAULT: 'rgb(var(--z-accent) / )', soft: 'rgb(var(--z-accent-soft) / )', muted: 'rgb(var(--z-accent-muted) / )' - } + }, + danger: 'rgb(var(--z-red) / )', + success: 'rgb(var(--z-green) / )', + warning: 'rgb(var(--z-yellow) / )' }, fontFamily: { sans: [ @@ -43,6 +46,26 @@ export default { panel: '0 1px 0 0 rgb(var(--z-shadow) / 0.04), 0 8px 28px -12px rgb(var(--z-shadow) / 0.18)', float: '0 20px 60px -20px rgb(var(--z-shadow) / 0.28)' + }, + fontSize: { + '2xs': ['0.6875rem', { lineHeight: '1rem' }] + }, + zIndex: { + dropdown: '40', + palette: '50', + modal: '70', + nested: '75', + popover: '80', + toast: '90' + }, + maxWidth: { + 'dialog-xs': '420px', + 'dialog-sm': '440px', + 'dialog-md': '560px', + 'dialog-lg': '720px', + 'dialog-xl': '900px', + 'dialog-2xl': '1120px', + 'dialog-3xl': '1360px' } } }, diff --git a/packages/app-core/src/App.tsx b/packages/app-core/src/App.tsx index 0f3115d2..edcc7976 100644 --- a/packages/app-core/src/App.tsx +++ b/packages/app-core/src/App.tsx @@ -209,7 +209,7 @@ function AppUpdateNotice({ {label} {updateState?.phase === 'downloading' && ( - + {Math.round(updateState.progressPercent ?? 0)}% )} diff --git a/packages/app-core/src/components/ArchiveView.tsx b/packages/app-core/src/components/ArchiveView.tsx index 8de71709..cd030d94 100644 --- a/packages/app-core/src/components/ArchiveView.tsx +++ b/packages/app-core/src/components/ArchiveView.tsx @@ -399,7 +399,7 @@ export function ArchiveView(): JSX.Element {
{filtered.length === 0 ? (
@@ -445,12 +445,12 @@ export function ArchiveView(): JSX.Element {
{note.title} - + {formatDate(note.updatedAt)}
-
{note.path}
-
+
{note.path}
+
{note.excerpt || 'Empty note'}
@@ -461,7 +461,7 @@ export function ArchiveView(): JSX.Element { e.stopPropagation() void openNote(note.path) }} - className="inline-flex items-center gap-1.5 rounded-lg bg-paper-100/85 px-2.5 py-1 text-[11px] font-medium text-ink-700 transition-colors hover:bg-paper-200 hover:text-ink-900" + className="inline-flex items-center gap-1.5 rounded-lg bg-paper-100/85 px-2.5 py-1 text-xs font-medium text-ink-700 transition-colors hover:bg-paper-200 hover:text-ink-900" > Open @@ -472,7 +472,7 @@ export function ArchiveView(): JSX.Element { e.stopPropagation() void unarchiveNote(note) }} - className="inline-flex items-center gap-1.5 rounded-lg bg-paper-100/85 px-2.5 py-1 text-[11px] font-medium text-ink-700 transition-colors hover:bg-paper-200 hover:text-ink-900" + className="inline-flex items-center gap-1.5 rounded-lg bg-paper-100/85 px-2.5 py-1 text-xs font-medium text-ink-700 transition-colors hover:bg-paper-200 hover:text-ink-900" > Unarchive @@ -483,7 +483,7 @@ export function ArchiveView(): JSX.Element { e.stopPropagation() void moveNoteToTrash(note) }} - className="inline-flex items-center gap-1.5 rounded-lg bg-red-500/10 px-2.5 py-1 text-[11px] font-medium text-[rgb(var(--z-red))] transition-colors hover:bg-red-500/16" + className="inline-flex items-center gap-1.5 rounded-lg bg-red-500/10 px-2.5 py-1 text-xs font-medium text-[rgb(var(--z-red))] transition-colors hover:bg-red-500/16" > Trash diff --git a/packages/app-core/src/components/BufferPalette.tsx b/packages/app-core/src/components/BufferPalette.tsx index 7a1d161d..fb445f1d 100644 --- a/packages/app-core/src/components/BufferPalette.tsx +++ b/packages/app-core/src/components/BufferPalette.tsx @@ -26,6 +26,7 @@ import { isTrashTabPath } from '@shared/trash' import { isQuickNotesTabPath } from '@shared/quick-notes' import { resolveSystemFolderLabels, type SystemFolderLabels } from '../lib/system-folder-labels' import { focusEditorNormalMode } from '../lib/editor-focus' +import { Modal } from './ui/Modal' interface BufferEntry { path: string @@ -274,15 +275,8 @@ export function BufferPalette(): JSX.Element { } return ( -
-
e.stopPropagation()} - > -
+ +
• )} - + {entry.virtual ? 'virtual' : entry.subtitle} - + {entry.badge} )) )}
-
+
↑↓{' '} Ctrl+N/P move @@ -358,7 +352,6 @@ export function BufferPalette(): JSX.Element { esc close
-
-
+ ) } diff --git a/packages/app-core/src/components/CalendarPanel.tsx b/packages/app-core/src/components/CalendarPanel.tsx index e5cf6a8a..157940e0 100644 --- a/packages/app-core/src/components/CalendarPanel.tsx +++ b/packages/app-core/src/components/CalendarPanel.tsx @@ -360,7 +360,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element {
-
+
Calendar
@@ -393,7 +393,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { @@ -402,7 +402,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { @@ -412,14 +412,14 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element {
{showWeekNumbers && ( -
+
W
)} {dayLabels.map((label, i) => (
{label}
@@ -443,7 +443,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { onMouseLeave={clearHover} title={`Open ${weeklyNoteTitle(monday)}`} className={[ - 'relative flex flex-col items-center rounded py-1 text-[11px] leading-tight transition-colors', + 'relative flex flex-col items-center rounded py-1 text-xs leading-tight transition-colors', isActiveWeek ? 'bg-accent font-semibold text-white' : 'text-ink-400 hover:bg-paper-200', @@ -467,7 +467,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { ) : (
{weekNum}
@@ -494,7 +494,7 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { disabled={!dailyEnabled} title={iso} className={[ - 'relative flex flex-col items-center rounded py-1 text-[11px] leading-tight transition-colors', + 'relative flex flex-col items-center rounded py-1 text-xs leading-tight transition-colors', !dailyEnabled ? inMonth ? 'cursor-default text-ink-600' @@ -539,11 +539,11 @@ export function CalendarPanel({ note }: { note: NoteContent }): JSX.Element { >
{hover.meta.title}
{hover.meta.excerpt ? ( -
+
{hover.meta.excerpt}
) : ( -
Empty note
+
Empty note
)}
)} diff --git a/packages/app-core/src/components/CollectionViewHeader.tsx b/packages/app-core/src/components/CollectionViewHeader.tsx index 19ae77db..6e29a5bb 100644 --- a/packages/app-core/src/components/CollectionViewHeader.tsx +++ b/packages/app-core/src/components/CollectionViewHeader.tsx @@ -25,19 +25,19 @@ export function CollectionViewHeader({ actions?: ReactNode }): JSX.Element { return ( -
+
-
+
{badgeIcon} {badge}

{title}

- + {count} note{count === 1 ? '' : 's'}
diff --git a/packages/app-core/src/components/CommandPalette.tsx b/packages/app-core/src/components/CommandPalette.tsx index fd076b11..d23bf427 100644 --- a/packages/app-core/src/components/CommandPalette.tsx +++ b/packages/app-core/src/components/CommandPalette.tsx @@ -19,6 +19,7 @@ import { type VaultSwitcherEntry } from '../lib/vault-switcher' import { focusEditorNormalMode } from '../lib/editor-focus' +import { Modal } from './ui/Modal' type Mode = 'main' | 'theme' | 'vault' @@ -263,16 +264,9 @@ export function CommandPalette(): JSX.Element { : 'Pick a vault' return ( -
closePalette()} - > -
e.stopPropagation()} - > - {mode !== 'main' && ( -
+ closePalette()} closeOnEsc={false}> + {mode !== 'main' && ( +
- -
-
-
, - document.body + + + + + + ) } diff --git a/packages/app-core/src/components/ConnectionsPanel.tsx b/packages/app-core/src/components/ConnectionsPanel.tsx index e286157f..93e976db 100644 --- a/packages/app-core/src/components/ConnectionsPanel.tsx +++ b/packages/app-core/src/components/ConnectionsPanel.tsx @@ -234,7 +234,7 @@ export function ConnectionsPanel({ note }: { note: NoteContent }): JSX.Element { >
-
+
Connections
@@ -339,7 +339,7 @@ export function ConnectionsPanel({ note }: { note: NoteContent }): JSX.Element {
{showKeyboardHints && ( -
+
{isHoverPreviewFocused ? 'Esc returns to Connections. Esc again returns to the note.' : 'Use p to focus the hover preview without leaving the keyboard.'} @@ -428,20 +428,20 @@ function ConnectionRow({
{note.title}
-
+
{note.path}
hover
-
+
{summary}
{active && ( @@ -486,20 +486,20 @@ function MissingConnectionRow({
{target}
-
+
{suggestedPath}
create
-
+
No note resolves this wikilink yet. Click to create it.
{active && ( @@ -539,13 +539,13 @@ function ConnectionKeyHint({ return ( - {keyLabel} + {keyLabel} {label} ) diff --git a/packages/app-core/src/components/ContextMenu.tsx b/packages/app-core/src/components/ContextMenu.tsx index 58a6e86d..d01e94b7 100644 --- a/packages/app-core/src/components/ContextMenu.tsx +++ b/packages/app-core/src/components/ContextMenu.tsx @@ -188,7 +188,7 @@ export function ContextMenu({ x, y, items, onClose }: Props): JSX.Element { onContextMenu={(e) => e.preventDefault()} > {query && ( -
+
filter {query} {visible.length} @@ -239,7 +239,7 @@ export function ContextMenu({ x, y, items, onClose }: Props): JSX.Element { {item.hint && ( diff --git a/packages/app-core/src/components/EditorPane.tsx b/packages/app-core/src/components/EditorPane.tsx index fe0a23ac..38d43047 100644 --- a/packages/app-core/src/components/EditorPane.tsx +++ b/packages/app-core/src/components/EditorPane.tsx @@ -3289,7 +3289,7 @@ function EmptyPaneState({ > Show sidebar - + ⌘1 @@ -3325,7 +3325,7 @@ function IconBtn({ ].join(' ')} > {children} - + {title} diff --git a/packages/app-core/src/components/EmptyVault.tsx b/packages/app-core/src/components/EmptyVault.tsx index 0c8e4ce2..5b656465 100644 --- a/packages/app-core/src/components/EmptyVault.tsx +++ b/packages/app-core/src/components/EmptyVault.tsx @@ -1,4 +1,5 @@ import { useStore } from '../store' +import { Button } from './ui/Button' import appIcon from '../assets/zennotes-app-icon.png' export function EmptyVault(): JSX.Element { @@ -17,7 +18,7 @@ export function EmptyVault(): JSX.Element { ZenNotes app icon

Welcome to ZenNotes

@@ -42,19 +43,23 @@ export function EmptyVault(): JSX.Element { )}
- + {canConnectRemote && ( - + )}
{workspaceSetupError && ( diff --git a/packages/app-core/src/components/ExternalFileApp.tsx b/packages/app-core/src/components/ExternalFileApp.tsx index 56cf68ef..23dc7545 100644 --- a/packages/app-core/src/components/ExternalFileApp.tsx +++ b/packages/app-core/src/components/ExternalFileApp.tsx @@ -241,7 +241,7 @@ export function ExternalFileApp(): JSX.Element { className="h-1.5 w-1.5 shrink-0 rounded-full bg-accent/80" /> )} - Not in a vault + Not in a vault
{moving ? 'Moving…' : 'Move to Vault'} -
+
{(['edit', 'preview'] as const).map((m) => ( - ) - })} -
-
- -
-
-
, - document.body + return ( + + + Select a sidebar icon for{' '} + {targetLabel}. + + } + /> + + {FOLDER_ICON_OPTIONS.map((option) => { + const active = option.id === currentIconId + return ( + + ) + })} + + + + + ) } diff --git a/packages/app-core/src/components/HelpView.tsx b/packages/app-core/src/components/HelpView.tsx index 06b34406..95a36b42 100644 --- a/packages/app-core/src/components/HelpView.tsx +++ b/packages/app-core/src/components/HelpView.tsx @@ -343,13 +343,13 @@ export function HelpView(): JSX.Element { >
-
+
ZenNotes Manual
@@ -449,7 +449,7 @@ export function HelpView(): JSX.Element {
{!hasMatches && ( -
+

No help topics matched.

Clear the filter to see the full manual again. @@ -511,7 +511,7 @@ export function HelpView(): JSX.Element { {shortcutSections.map((section) => (

{section.title}

{section.description}

@@ -538,7 +538,7 @@ export function HelpView(): JSX.Element { subtitle="Short aliases, curated commands, and keyboard-first editor behavior." >
-
+
Curated ex commands @@ -597,7 +597,7 @@ export function HelpView(): JSX.Element { {group.commands.map((command) => (
@@ -608,7 +608,7 @@ export function HelpView(): JSX.Element { {command.when && }
- + {command.id}
@@ -650,7 +650,7 @@ export function HelpView(): JSX.Element { {settingsSections.map((section) => (

{section.title}

@@ -709,7 +709,7 @@ function renderRichText(text: string): React.ReactNode { return ( {code} @@ -730,9 +730,9 @@ function renderRichText(text: string): React.ReactNode { function InfoCard({ title, body }: { title: string; body: string }): JSX.Element { return ( -
-

{title}

-
{renderRichText(body)}
+
+

{title}

+
{renderRichText(body)}
) } @@ -767,7 +767,7 @@ function Keycap({ return ( + {label} ) @@ -817,12 +817,12 @@ function CalloutCard({ body: string }): JSX.Element { return ( -
+
{icon} {title}
-
{renderRichText(body)}
+
{renderRichText(body)}
) } diff --git a/packages/app-core/src/components/NoteHoverPreview.tsx b/packages/app-core/src/components/NoteHoverPreview.tsx index 7f23bf77..cdacfbd6 100644 --- a/packages/app-core/src/components/NoteHoverPreview.tsx +++ b/packages/app-core/src/components/NoteHoverPreview.tsx @@ -156,15 +156,15 @@ export function NoteHoverPreview({
-
+
Hover Preview
{note.title}
-
{note.path}
+
{note.path}
{interactive && ( - - esc + + esc back )} diff --git a/packages/app-core/src/components/NoteList.tsx b/packages/app-core/src/components/NoteList.tsx index d644f393..09820815 100644 --- a/packages/app-core/src/components/NoteList.tsx +++ b/packages/app-core/src/components/NoteList.tsx @@ -10,6 +10,7 @@ import { } from './icons' import { ContextMenu, type ContextMenuItem } from './ContextMenu' import { ResizeHandle } from './ResizeHandle' +import { Button, IconButton } from './ui/Button' import { confirmMoveToTrash } from '../lib/confirm-trash' import { buildMoveNotePrompt, parseMoveNoteTarget } from '../lib/move-note' import { extractTags } from '../lib/tags' @@ -723,7 +724,7 @@ export function NoteList(): JSX.Element {

{heading}

- + {view.kind === 'assets' ? assetFiles.length : orderedFolderEntries.length}
@@ -746,29 +747,22 @@ export function NoteList(): JSX.Element { ))}
) : view.kind === 'folder' && view.folder === 'trash' && filtered.length > 0 && ( - + )} {view.kind !== 'assets' && ( - + )} - +
@@ -780,7 +774,7 @@ export function NoteList(): JSX.Element { > {view.kind === 'assets' ? ( assetFiles.length === 0 ? ( -
+
No files yet. Files anywhere inside the vault show up here.
) : assetLayout === 'grid' ? ( @@ -834,7 +828,7 @@ export function NoteList(): JSX.Element {
) ) : orderedFolderEntries.length === 0 ? ( -
+
{view.kind === 'folder' && view.folder === 'trash' ? `${folderLabels.trash} is empty.` : 'No files here yet.'} @@ -966,7 +960,7 @@ function NoteRow({ >
{note.title} - {formatDate(note.updatedAt)} + {formatDate(note.updatedAt)}
{note.excerpt || 'Empty note'} @@ -1021,7 +1015,7 @@ function FolderAssetRow({ loading="lazy" /> ) : ( - + {asset.kind} )} @@ -1030,7 +1024,7 @@ function FolderAssetRow({
{asset.name}
{asset.path}
-
+
{extension || formatBytes(asset.size)}
@@ -1073,14 +1067,14 @@ function AssetCard({ loading="lazy" /> ) : ( -
+
{asset.kind}
)}
{asset.name}
-
+
{asset.path} {formatBytes(asset.size)}
@@ -1120,14 +1114,14 @@ function AssetRow({ loading="lazy" /> ) : ( - {asset.kind} + {asset.kind} )}
{asset.name}
{asset.path}
-
+
{formatBytes(asset.size)}
diff --git a/packages/app-core/src/components/OnboardingWizard.tsx b/packages/app-core/src/components/OnboardingWizard.tsx index a6a8a249..3e887d55 100644 --- a/packages/app-core/src/components/OnboardingWizard.tsx +++ b/packages/app-core/src/components/OnboardingWizard.tsx @@ -7,6 +7,7 @@ import { type VaultSettings } from '@shared/ipc' import { normalizeDailyNotesDirectory } from '../lib/vault-layout' +import { Button } from './ui/Button' import appIcon from '../assets/zennotes-app-icon.png' type StepId = 'welcome' | 'vim' | 'theme' | 'vault' | 'layout' | 'done' @@ -274,13 +275,9 @@ export function OnboardingWizard(): JSX.Element {
- +
@@ -346,7 +343,7 @@ function StepRail({ /> -
- {eyebrow} -
+
{eyebrow}

{title}

{subtitle &&

{subtitle}

}
@@ -403,29 +398,21 @@ function StepFooter({ {hideBack ? ( ) : ( - + )}
{hint && {hint}} - +
) @@ -438,7 +425,7 @@ function WelcomeStep({ onNext }: { onNext: () => void }): JSX.Element { ZenNotes app icon

Welcome to ZenNotes

@@ -466,9 +453,7 @@ function WelcomeStep({ onNext }: { onNext: () => void }): JSX.Element { function Tile({ label, value }: { label: string; value: string }): JSX.Element { return (
-
- {label} -
+
{label}
{value}
) @@ -599,9 +584,7 @@ function ThemeStep({ subtitle="Each family has light + dark variants. Auto follows your system." />
-
- Mode -
+
Mode
{(['light', 'dark', 'auto'] as ThemeMode[]).map((mode) => (
-
- Family -
+
Family
{FAMILY_DESCRIPTORS.map((d) => ( 1 && (
-
- Variant -
+
Variant
{variants.map((variant) => { const selected = variant.id === themeId @@ -700,7 +679,7 @@ function ThemeFamilyTile({ className="flex h-12 w-full items-center justify-between overflow-hidden rounded-lg px-2" style={{ background: swatch.bg }} > - + Aa @@ -742,21 +721,23 @@ function VaultStep({ />
- + {canConnectRemote && ( - + )}
@@ -846,9 +827,7 @@ function LayoutStep({
-
- Primary notes location -
+
Primary notes location
-
- Daily notes -
+
Daily notes
+
{hasVault && ( - + )} - +
diff --git a/packages/app-core/src/components/OutlinePalette.tsx b/packages/app-core/src/components/OutlinePalette.tsx index 2858c1f6..edb86f66 100644 --- a/packages/app-core/src/components/OutlinePalette.tsx +++ b/packages/app-core/src/components/OutlinePalette.tsx @@ -18,6 +18,7 @@ import { isTasksTabPath } from '@shared/tasks' import { isTrashTabPath } from '@shared/trash' import { isQuickNotesTabPath } from '@shared/quick-notes' import { focusEditorNormalMode } from '../lib/editor-focus' +import { Modal } from './ui/Modal' function isVirtualPath(path: string | null): boolean { if (!path) return true @@ -78,15 +79,8 @@ export function OutlinePalette(): JSX.Element { } return ( -
-
e.stopPropagation()} - > -
+ +
- + H{item.level} {item.text} - L{item.line} + L{item.line} )) )}
-
+
↑↓{' '} Ctrl+N/P move @@ -155,7 +149,6 @@ export function OutlinePalette(): JSX.Element { esc close
-
-
+ ) } diff --git a/packages/app-core/src/components/OutlinePanel.tsx b/packages/app-core/src/components/OutlinePanel.tsx index 877e5b7c..4a9dee74 100644 --- a/packages/app-core/src/components/OutlinePanel.tsx +++ b/packages/app-core/src/components/OutlinePanel.tsx @@ -60,7 +60,7 @@ export function OutlinePanel({ note, activeLine, onJump }: Props): JSX.Element { >
-
+
Outline
@@ -106,7 +106,7 @@ export function OutlinePanel({ note, activeLine, onJump }: Props): JSX.Element { > diff --git a/packages/app-core/src/components/PinnedReferencePane.tsx b/packages/app-core/src/components/PinnedReferencePane.tsx index 3e1358e3..366bb71b 100644 --- a/packages/app-core/src/components/PinnedReferencePane.tsx +++ b/packages/app-core/src/components/PinnedReferencePane.tsx @@ -409,7 +409,7 @@ export function PinnedReferencePane(): JSX.Element | null {
{!isAsset && ( -
+
{(['edit', 'preview'] as const).map((m) => ( - ) - })} -
-
- )} - {error && ( -
- {error} -
- )} -
-
- - -
+
Suggestions
+
+ {filteredSuggestions.map((suggestion, index) => { + const active = index === activeSuggestion + return ( + + ) + })} +
+
+ )} + {error &&
{error}
}
-
, - document.body + + + + + ) } diff --git a/packages/app-core/src/components/QuickCaptureApp.tsx b/packages/app-core/src/components/QuickCaptureApp.tsx index f434c28e..12a890d1 100644 --- a/packages/app-core/src/components/QuickCaptureApp.tsx +++ b/packages/app-core/src/components/QuickCaptureApp.tsx @@ -569,7 +569,7 @@ export function QuickCaptureApp(): JSX.Element { {mode.kind === 'existing' && ( {mode.note.folder} @@ -626,7 +626,7 @@ export function QuickCaptureApp(): JSX.Element { )}
-