# Frontend Architecture — Verstak GUI ## Tech Stack - **Framework**: Svelte 4 (runes-free, compiler-only) - **Build tool**: Vite 5 - **GUI shell**: Wails v2 (Go backend + WebKit frontend) - **Language**: Plain JavaScript (no TypeScript in Svelte files — `lang="ts"` is NOT used) ## Source Structure ``` frontend/src/ main.js # Entry: mounts App.svelte, global error handlers App.svelte # Root component: sidebar, header, modals, tab router FileTreeRow.svelte # Sidebar tree node row TreeNode.svelte # Recursive tree node lib/ AppHeader.svelte # Top bar with title, search, actions BrowserEvents.svelte # Browser extension events panel CalendarPluginPage.svelte # Plugin page host ConfirmModal.svelte # Reusable confirm dialog FileBreadcrumbs.svelte # File tab breadcrumb navigation FileIcon.svelte # Icon resolver for file types FilePreviewModal.svelte # File content preview modal FirstRun.svelte # First-run wizard GlobalSearch.svelte # Global search bar + results SettingsWindow.svelte # Settings modal container Settings*.svelte # Settings sub-sections (General, Sync, etc.) SyncStatus.svelte # Sync status badge TemplateIcon.svelte # Template type icon TodayScreen.svelte # Today dashboard VaultRecovery.svelte # Vault recovery wizard actionIcons.js # SVG icon strings fileUtils.js # File type helpers (canPreview, isMarkdown, etc.) i18n/ # Internationalization (en, ru) markdown/ # Markdown rendering + internal links util/ # Keyboard layout helper components/ notes/ MarkdownEditor.svelte # Markdown textarea with toolbar MarkdownPreview.svelte # Rendered markdown preview NoteEditorPanel.svelte # Editor + preview layout, public API: insertText() InternalLinkPicker.svelte # Object picker for verstak:// links ObjectPickerModal.svelte # Legacy object picker modal ``` ## Role of App.svelte `App.svelte` is the **root component**. It owns: 1. **Global UI state**: sidebar (system views, workspace tree), active tab, selected node/section 2. **Lifecycle**: startup checks (GetStartupStatus, VerstakVersion, ListWorkspaceTree), event listeners 3. **Top-level modals**: confirm, rename, import, worklog, inbox assign, link edit, settings, file preview 4. **Cross-cutting concerns**: navigation history (goBack, rememberNavigation), keyboard shortcuts, drag-and-drop orchestration, capture/inbox flow App.svelte is **NOT** responsible for: - Individual tab content (Overview, Notes, Files) — these are inlined but should be extracted - Note editor internals — `NoteEditorPanel.svelte` + `MarkdownEditor.svelte` handle this - Settings sections — each has its own `Settings*.svelte` ## State Ownership Rules ### App.svelte MUST own: - `selectedSection`, `selectedNode`, `activeTab` - `systemViews`, `workspaceTree`, `enabledTemplates` - `noteEditor` (the note being edited), `noteViewMode` - `showFirstRun`, `showRecovery`, `showSettings`, `loading` - `startupStatus`, `startupChecked` - Navigation state: `navHistory`, `restoringHistory` - Trash browser state: `trashInfo`, `trashCount`, `trashSelectedIds`, `trashFolderId`, `trashFolderStack` - Journal state: `journalRows`, `journalSummary`, filters - Worklog modal state: `showWorklogModal`, `wlModal*` - Inbox: `inboxNodes`, `localInboxNodes`, capture state - Links: `links` - Sync: `syncStatus` - `error` (global error banner) ### App.svelte must NOT directly own (after extraction): - Files tab internal state → `FilesTab.svelte` - Notes tab list UI state → `NotesTab.svelte` - Overview tab content → `OverviewTab.svelte` ## Component Communication ### Props (parent → child) Data flows down via Svelte `export let prop` ### Events (child → parent) Children dispatch events via `createEventDispatcher()` - Suffix `:` in event names means handler in template: `on:openNote={handler}`, NOT call on mount - All events should use `e.detail` to pass data ### Public API (bind:this) Parent gets imperative handle via `bind:this={ref}` and calls: - `ref.publicMethod(args)` — guard with optional chaining: `ref?.publicMethod?.(args)` - Methods are exposed via `export function` in child component ## Services (Wails API) All backend calls go through the `wailsCall()` helper in App.svelte: ```js function wailsCall(method, ...args) { // Returns Promise, rejects with 'Wails not connected: ' if backend unavailable } ``` Key backend methods: - **Startup**: `GetStartupStatus`, `VerstakVersion`, `ListSystemViewsWithPlugins`, `ListWorkspaceTree`, `ListEnabledTemplates` - **Nodes**: `GetNodeDetail`, `ListItems`, `ListWorkspaceChildren`, `CreateNodeFromTemplate`, `DeleteNode`, `MoveNode`, `RenameNode` - **Notes**: `ListNotes`, `CreateNote`, `ReadNote`, `SaveNote`, `DeleteNote`, `RenameNote` - **Files**: `ListFiles`, `DeleteFileOrFolder`, `CreateEmptyFile`, `DuplicateNode`, `OpenFile`, `OpenFolder`, `GetFileBase64`, `ReadFileText`, `PreviewImport`, `AddPathCopy`, `AddPathLink` - **Worklog**: `ListWorklog`, `CreateWorklogFull`, `UpdateWorklogEntry`, `DeleteWorklogEntry`, `AcceptSuggestionFull/With`, `GetSuggestions` - **Inbox**: `ListInboxNodes`, `Capture*WithContext`, `DeleteInboxNode`, `ResolveInboxNode` - **Sync**: `SyncStatus`, `SyncNow`, `TrashCount`, `ListTrash` - **Logging**: `FrontendLog(level, message, stack)`, `LogStartupStep(step, success, detail)` ## Logging & Diagnostics ### Frontend errors - Global `window.addEventListener('error', ...)` catches uncaught exceptions - Global `window.addEventListener('unhandledrejection', ...)` catches broken promises - Both log to `console.error` and forward to `FrontendLog()` backend binding - Fatal mount error: renders error message inline in `#app` div (blank window becomes diagnostic) ### Backend logs - Application log file: `~/.local/state/verstak/logs/verstak.log` (Linux) - Fallback: `~/.config/verstak/logs/verstak.log` - Format: `[timestamp] [level] message` - Go `log.Printf()` output goes to stderr (visible in dev console) ### Vault debug log - `/.verstak/debug.log` — written by `WriteDebugLog` binding (existing feature) ## Refactor Rules ### After each extraction step: 1. `grep` App.svelte for state that moved to child — must be zero hits or documented exceptions 2. `npm run build` — must pass 3. `go test ./...` — must pass 4. Manual GUI check: - Window is not blank/white - Sidebar shows (system views + workspace tree) - No infinite "Загрузка..." on welcome screen - Clicking system sections works - Opening a case shows tabs ### Forbidden patterns: - Child component state referenced directly in App.svelte after extraction - `lang="ts"` in Svelte files (svelte-preprocess not installed) - `[]string` passed directly to Wails bindings (must JSON-serialize) - Modal without `role="dialog" aria-modal="true"` and Escape handler - Buttons without `type="button"` in forms - `resize: none` on textareas that should be resizable (use `resize: vertical`) ## Troubleshooting ### Blank window, no errors in terminal 1. Check browser DevTools console (F12 on Windows/Linux, Cmd+Alt+I on macOS) 2. In Wails dev mode: right-click → Inspect → Console 3. Look for `[Frontend Error]` messages 4. Check application log: `~/.local/state/verstak/logs/verstak.log` ### Stuck on "Загрузка..." - `GetStartupStatus` likely failed or returned unexpected status - Check network calls in DevTools Network tab - Check backend log for startup errors ### Wails method not found - Ensure method is registered in `bindings*.go` files - Ensure method is exported (capitalized name) - Ensure `wailsCall()` helper is used (not direct window access) ### Child component state moved but App still references old variable - This causes a silent `undefined` reference or runtime crash - Run `grep -n 'oldStateName' App.svelte` after each extraction - Use `ref?.method?.()` for all public API calls on child components