From 12252d1ebeb596d344a9ab0a9bc85e8bb711b213 Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 9 Jun 2026 08:45:14 -0500 Subject: [PATCH 1/3] refactor(ui): systematic design-system consistency pass Establish shared UI primitives and codified token scales, then migrate the app onto them. Every change reuses existing color tokens, so all 29 themes are preserved. Tokens (tailwind.config.js x3 + index.css @layer): - type scale with an 11px `text-2xs` floor + tuned line-heights - documented z-index scale (palette/modal/nested/popover/toast) - dialog-width scale (dialog-xs..3xl) - themed danger/success/warning colors (follow --z-red/green/yellow) - .form-label / .form-hint utilities (unify 3 prior label styles) Primitives (packages/app-core/src/components/ui/): - )) )} -
+
↑↓{' '} 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 e5cf6a8ae..157940e03 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 19ae77dba..6e29a5bb9 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 fd076b113..d23bf427b 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 e286157f0..93e976dbe 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 58a6e86de..d01e94b71 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 fe0a23acb..38d43047e 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 0c8e4ce25..5b6564657 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 56cf68ef8..23dc75455 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 06b344060..95a36b423 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 7f23bf773..cdacfbd65 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 d644f3932..098208155 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 a6a8a2491..3e887d556 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 2858c1f6c..edb86f669 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 877e5b7c9..4a9dee748 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 3e1358e36..366bb71b5 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 f434c28ed..12a890d19 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 { )}
-