verstak/docs/frontend-architecture.md

8.1 KiB

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:

function wailsCall(method, ...args) {
  // Returns Promise, rejects with 'Wails not connected: <method>' 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

  • <vault>/.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