Imported from spreadsheetsusa/nvAux (
AGENTS.md). Install upstream withnpx skills add spreadsheetsusa/nvAux. Copyright stays with the author.
nvAux
Core Principles and Rules to follow
- We compile down to a single html file for deployment. binary is base64'd into the file. Be resourceful and clever.
- We do not use tailwind, but we think like tailwind. Optimize accordingly.
- When i mention a tailwind-like class and it doesn't existm add it to style.css and use that instead of on-offs.
- Aim for platform-agnostic code and architecture. We want to work in all common browsers and devices.
nvAux is a browser-based note-taking PWA (Svelte 5 + Vite 8). The core app is client-side — notes live in IndexedDB via RxDB/Dexie. Note name/body are always encrypted at rest with RxDB encryption-crypto-js (device key in localStorage, auto-seeded; Settings can regenerate). Optional paid cloud sync is a separate stack (nvaux-backend + nvaux-infra), not required to use the PWA.
Services
| Service | Command | Port |
|---|---|---|
| Vite dev server | bun dev |
5173 (binds 0.0.0.0 via --host) |
Key commands
- Install deps:
bun i(lockfile:bun.lock) - Dev server:
bun dev - Build:
bun run build(produces a single-filedist/index.htmlviavite-plugin-singlefile) - Preview prod build:
bun run preview
Notes
- No lint or test scripts are configured in
package.json. Prettier config (.prettierrc) exists for formatting. - No environment variables or external services are needed; everything runs client-side.
Learned User Preferences
- Prefer simple, foundational upgrades that keep the app working; avoid broad rewrites when migrating packages or Svelte APIs. Prefer splitting oversized components into logical subcomponents and grouping related files in subfolders without over-abstraction; preserve behavior and styling.
- When upgrading Svelte, migrate toward runes mode (
$derived/$props/ etc.) rather than leaving legacy$:syntax in place. - Prefer Bun as the package manager (
bun install,bun.lock,bun run) when switching tooling off npm; Amplify CI (amplify.yml) installs Bun inpreBuild, then runsbun i --frozen-lockfileandbun run build. - Prefer Tailwind-inspired utility
class=""compositions in Svelte markup; keep component<style>blocks only for multi-use/DRY rules, and put shared cross-component styles instyle.css(not a Tailwind install). Secondary buttons/controls should stay grayscale to match surrounding UI; only major CTAs use accent. Icons live under$lib/components/icons(filenames without anIconprefix) and load through$lib/components/Icon.sveltevia anameprop; keep unused icon assets for later reuse. - Drag/resize (sidebar splitter, note list/detail splitter, Windowed main window, Demo edge-resize, and note popups) should feel smooth, stable, and 1:1 with shared active styling; focused Windowed windows/popups raise to front (macOS-like z-order). Dropdown/context menus should stay viewport-safe via shared fixed positioning with flip/clamp (
positionFixedMenu) and dismiss on outside click/blur. - Selecting a note or inline-renaming from the list ellipsis must not overwrite Omnibar
omniTextor filter mode; keep the user's current filtering/search position. Note-list ellipsis Close should clear that note from NoteDetail only when it is the open note. While NoteList is focused, ArrowUp/ArrowDown should move selection note-by-note (not only scroll); ArrowDown from the Omnibar should select the first list item, and ArrowUp from the first list item should return focus to the Omnibar. - Keep list/detail and window chrome layout-stable: note body length must not shift chrome above the note list; NoteList itself should stay flush (no outer margin/radius); when the window shrinks (esp. Windowed), clamp
noteListHeightso NoteDetail keeps a minimum visible height and is never pushed past the fold; dragging the list/detail splitter past the min threshold should snap NoteList fully hidden while keeping a recover handle/dragger. Long blocking error banners (e.g. DB open failure) should scroll internally so recovery actions like Reset Database stay reachable. - Prefer minimal, cross-browser thin scrollbar styling on scrollable panes (sidebar, note list, note detail) instead of default OS scrollbars.
- Markdown preview may stay on while browsing normal notes (hide/off for empty and settings); prefer Obsidian-like editable preview with
[[wiki link]]autocomplete, keeping raw Edit for true source text. - Life Calendar life↔week view transitions should stay scoped to the sidebar (avoid full-window flashes) and feel smooth when moving between weeks; selecting a note (NoteList / Graph) should accent-outline that note's week in the life grid; prefer not highlighting the open note inside week-note lists (NoteList already shows selection).
- Media player session is user-controlled (Play Now / Play Next / Add Last); switching notes must not reset or auto-queue tracks; markdown
[label](url)text drives playlist/player titles asNote · Trackwith a smooth overflow marquee; an active session should keep playing until closed. - On mobile (
$isMobile): sidebar drawer must respect the visual viewport and leave a dismiss peek so the backdrop stays tappable; NoteList vertical ellipsis stays visible while the relative-time column moves into that menu as a readonly muted label; hide AudioPlayer volume control.
Learned Workspace Facts
- Default open is Demo Mode (site chrome with hero/tagline). Settings note toggles App Mode (
fullScreen) and nested Windowed (windowed). Windowed is Settings-only. Omnibar toggles Demo ↔ App Fullscreen (from Windowed it enters App Fullscreen; minimize returns to Demo, not Windowed). Demo ↔ Fullscreen should keep the original smooth size tween and demo-chrome fade (avoid jarring placement jumps on minimize). In Demo Mode the page scrolls like a landing page with marketing sections under the app card (src/lib/components/marketing/); the Demo shell usesh-screen overflow-y-auto(not onlymin-h-screen) so content can scroll inside thebodyoverflow clip. Demo stage top-aligns hero+app;demo-windowis taller on desktop when space allows and stays compact on mobile; hero tagline (HeroTagline) randomly picks on load and click-cycles with a slide transition; marketing section headings usetext-pretty. Demo supports parent-clamped edge resize viademoWindowResize(centered/symmetric growth, not draggable). App/Windowed stay non-scrolling viewport chrome and are unaffected by demo-stage layout. - Omnibar toggles a slidable sidebar; when Demo or App Windowed, opening the sidebar grows the main window width. Default expand width is 443px (
sidebarWidth). The drawer layers under the main app content (not over it). On mobile, the drawer width tracksvisualViewport(fallbackinnerWidth) and leaves a ~36px peek (DRAWER_PEEK_PX) for backdrop dismiss. - Source layout is domain-grouped under
src/lib/:components/chrome(OmniBar, Sidebar, ResizeHandle, StatusBar),components/marketing,components/icons,notes/(list/detail/toolbar/wiki/unlock + sharedmarkdownPreview.js),life/(LifeCalendar + grid/week extracts),graph/(NoteGraphView),media/(AudioPlayer, MediaViewer, mediaSession),windowed/(NotePopupWindow, StickyNote),settings/(Settings + section components),noteTypes/, plusstore.js(prefs/UI APIs) anddb.js(RxDB open/seed/migrate/reset; re-exported fromstore.js). Prefer$lib/...imports across folders. - Windowed App Mode has no demo hero; the floating window uses fixed left/top positioning for Photoshop-style corner resize (opposite corner stays put) and threshold drag from Life Calendar / Omnibar regions (
windowFrame). Main window geometry persists asappWindowFramein localStorage and restores (clamped) when re-entering Windowed / on viewport resize. Entering Windowed from Fullscreen tweens down to the restored or default Demo-sized card. Move/drag is App Windowed only; Demo gets constrained centered edge-resize (demoWindowResize) but is not draggable. In App Windowed, note-list ellipsis or double-click opens session-onlynotePopupsfloating editors ($lib/windowed/NotePopupWindow.svelte, drag/resize); leaving Windowed closes all popups; main window and popups share focus z-order. Ellipsis Rename inline-edits the title (Enter/blur save, Escape cancel). Markdown notes can be sticky via toolbar + frontmatter (sticky: true,color: yellow|pink|blue); sticky notes always float at the app root (any mode — Demo/Fullscreen/Windowed) as fixed-size colored post-its ($lib/windowed/StickyNote.svelte); in Windowed they replace the normal popup until sticky is turned off; positions persist instickyNoteFrames; title hover shows three color swatches; X dismisses for the session only. - Omnibar tray (upper-right): Settings cog opens the settings note; fullscreen control and clock sit beside it. Accent color is a Settings preference (color picker + five presets) applied via a shared CSS variable; it seeds a soft desaturated page gradient (
accentGradient.js→--grad-1..4) that fades between accents (default cold gray-blue, not green). Demo hero exposes the five presets for quick picking.showStatusBarandshowClockboth default off. Settings also holds a global 6-digit Lock PIN (lockPin) and Lock timeout (lockTimeoutSeconds; 0 = never; UI shows seconds when under one minute); locking a note before a PIN is set should open Settings and scroll to that section with a dismissible inline notice. - Sidebar width and note-list height are user-resizable and persisted via
sidebarWidth/noteListHeightinstore.js(localStorage). DefaultnoteListHeightis ~220 when unset; persisted0means intentionally collapsed; values1..60are treated as unset (layout-race clamp) so the list does not open collapsed by mistake. Dragging the list/detail splitter past the open min (~60px) snapsnoteListHeightto0; the splitter remains the reveal affordance. - Life Calendar (
$lib/life/LifeCalendar.svelte, withLifeYearGrid/WeekStreamView/lifeCalendarModel.js) renders in the sidebar as week squares in year rows; Profile settingsbirthDateandexpectedLongevitydrive its span. Absent weeks stay dimmed. Title button click-cycleslifeCalendarStat(default Life Calendar); week-number gutter at sidebar width ≥712px. Week cells zoom into a sidebar-scoped week stream (sticky Year/Month/Week pins, Now control; double-click Now zooms out). Week notes open in NoteDetail and scroll into view in NoteList; selecting a note (NoteList / Graph) accent-outlines that note's week in the life grid and, while week view is open, scrolls the week stream to that note'screatedAtweek without overriding a fresh week zoom-in. Note toolbar calendar control can adjust a note's creation date for timeline placement. - Note toolbar (
$lib/notes/NoteToolbar.svelte+ media/created-at/pin extracts) hosts media queue actions (Play Now primary accent on the left; Play Next / Add Last secondary), creation-date editor, sticky pin (markdown only), note lock, Preview/Edit, and a rightmost note-type properties ellipsis when the type registers one (noteTypeProperties→ e.g. Kanban board prefs, Music BPM/clear); keep type-specific meta menus in the toolbar (not in-board chrome) so future note types share one place. Plain markdown defaults to Edit and rich types (kanban, music) default to Preview; prefs persist per kind (markdownPreviewPlain/markdownPreviewRich) so toggles do not cross kinds (hidden/off for empty and settings only in the UI); preview uses shared$lib/notes/markdownPreview.js(marked+ highlight.js); preview/edit content should share the same top spacing. Locked notes use frontmatterlocked: true, unlock via the global PIN with session activity + timeout (NoteUnlockPanel), and NoteList preview shows🔒 Locked. - Wiki-style
[[Note Title]]links: editor autocomplete + preview click-to-open by exactname(create-and-open if missing) viaopenNoteByNamewithout changing Omnibar filter. Graph View is a tall, bottom-pinned sidebar footer ($lib/graph/NoteGraphView.svelte) under Life Calendar: open from the calendar title-row graph icon, close via header X withslidetransition; height-draggable (graphViewHeight) and zoom (graphViewZoom) persist; vertical zoom slider; SVG +d3-forcefrom[[wiki links]](ghost nodes, collide, clustering, drag-with-reheat); click opens note without changing Omnibar filter. Prefer lean deps for the single-file build. - Note types are body-driven (YAML-ish frontmatter
type: …), not a schema field:resolveNoteTyperoutes Settings / kanban / music / markdown. Frontmatter may also carry sticky/color/locked for markdown notes (notyperequired). Kanban Preview is the board UI; Music Preview is the drum/step-sequencer DAW (noteTypes/music/, Tone.js + remote S3 kit samples — host must send CORSAccess-Control-Allow-Origin); Edit is raw source for both. SoundCloud, YouTube, and image/video file URLs queue into a persistent$lib/media/mediaSessionplaylist (skipped for rich note types); markdown[label](url)text is preferred for titles (else oEmbed/URL);$lib/media/AudioPlayer.svelteunder the Omnibar slides in/out withNote · Trackmarquee (double-click opens the source note), editable playlist, andmediaPlayerHeight; visual tracks slideMediaViewerabove the player bar. - Default seeded notes are Welcome, nvAux Settings,
🎧 The Gentleman's Techno League - EP1(SoundCloud demo),🎥 Video Link Example(YouTube demo),📋 Sample Kanban, and🥁 Sample Drum Machine(music/step sequencer); fresh DBs must get all six (db()indb.js, re-exported fromstore.js, shares one in-flight open so seeding is not raced away). Settings noteupdatedAtbumps when preferences change. Reset Database uses inline confirm and restores only those seeded notes; Settings also offers Hard Refresh (service-worker/cache bust) with inline confirm beside Reset. Settings UI is$lib/settings/Settings.sveltecomposed of section components (General, Profile, Lock, Encryption, Import/Export, Danger Zone). - Notes DB is
nvauxdb18with always-on field encryption (encrypted: ['name', 'body']viawrappedKeyEncryptionCryptoJsStorage); password is auto-generated intolocalStorage.dbPasswordon first open. Legacynvauxdb17plaintext DBs migrate once into the encrypted DB. Encrypted fields are not IndexedDB-queryable — Omnibar/list search and exact name lookup filter in memory after decrypt. Settings → Encryption shows status/fingerprint and Regenerate encryption key (re-encrypts via remove + restore). ZIP export/import stays plaintext by design. Optional paid cloud sync is a separate control/data plane:nvaux-backend(SvelteKit + SQLite for now, Stripe billing, REST-only to the PWA; own repo eventually) andnvaux-infra(CouchDB, Caddy, systemd, AWS Ubuntu deploy docs). Client-held keys; service must not read note contents. DB open failures surface a scrollable error banner so Reset remains reachable.