- Create TextFileEditor.svelte: full editor/viewer for text files
- Markdown: edit/preview/split modes with MarkdownEditor + MarkdownPreview
- Plain text: readonly monospace viewer
- Footer: external open button + save (markdown) + close
- Rewrite _openFile in FilesTab: text/markdown → TextFileEditor, images/PDF → FilePreviewModal
- Linked .md notes → note editor (via CheckFileAction)
- Unlinked .md / text files → TextFileEditor (not FilePreviewModal)
- Fix toggleMenu in FileTreeRow: position using getBoundingClientRect
- Expand codeNames/textMimes for all text file types
- Add debug logs for file open flow and menu positioning
Co-Authored-By: OWL (Hermes Agent) <hermes@nousresearch.com>
- Add reactive $: block in FilesTab to auto-load files on selectedNode change
- Copy .btn/.btn-primary/.btn-sm styles into NotesTab.svelte and FilesTab.svelte (scoped CSS)
- Fix FilePreviewModal: render .md with MarkdownPreview, text files in <pre><code>, external open in footer
- Expand codeNames/textMimes in fileUtils.js to cover all text file types
- Add fallback text read for unknown file types in _openPreview
- Fix toggleMenu in FileTreeRow: position menu near button using getBoundingClientRect
- Add debug logs for file open flow and menu positioning
- Add tabindex+keydown to MarkdownPreview div (a11y partial fix)
Co-Authored-By: OWL (Hermes Agent) <hermes@nousresearch.com>
- main.js: global error handlers (error + unhandledrejection), try/catch around App mount with inline fallback UI
- bindings_logging.go: FrontendLog() and LogStartupStep() backend bindings for persistent application logging
- App.js: FrontendLog() and LogStartupStep() Wails JS bindings
- docs/frontend-architecture.md: component architecture, state ownership rules, communication patterns, logging guide, troubleshooting
All existing code untouched. This is a pure additive step that enables
blank-window debugging before any App.svelte extraction begins.
Problem: MarkdownPreview dispatched DOM CustomEvent via link.dispatchEvent()
which doesn't propagate through Svelte's event system. The on:verstak-link
handler on <MarkdownPreview> in NoteEditorPanel only catches Svelte dispatch()
events, not DOM CustomEvents from {@html} content.
Fix: Replaced DOM CustomEvent dispatch with Svelte createEventDispatcher.
Now handleClick() in MarkdownPreview calls dispatch('verstak-link', {...})
which properly propagates through NoteEditorPanel → App.svelte chain.
Also: removed unused importInternalLink import (was unused after
InternalLinkPicker replaced the manual modal).
Root cause: DOMPurify afterSanitizeAttributes hook was treating verstak://
links as blocked because hash-based href didnt match ALLOWED_SCHEMES regex.
Fix:
1. afterSanitizeAttributes hook now checks data-verstak-href first and
returns early for internal links - they never get blocked
2. Changed href from hash-based to about:blank (safe value that
DOMPurify wont strip, unlike javascript:void(0))
3. Click handler already uses data-verstak-href, not href
Added unit test: markdown.test.js (27 tests for renderer.link output)
Unified search normalization across InternalLinkPicker and GlobalSearch:
1. GlobalSearch.svelte: multi-variant search (same as InternalLinkPicker)
- expandKeyboardVariants() for RU/EN layout swap
- Parallel Search queries with dedup by type+nodeId+targetId+title
- 180ms debounce preserved
2. Backend: fix LOWER() in SQL for links/actions
- Replace LOWER(column) LIKE with lowercased columns (title_lower, url_lower, etc.)
- Migration 020: add lowercased columns + indexes for links and actions
- BackfillLinksLower() + BackfillActionsLower() in storage.go
- Update INSERT in bindings_links.go and action.go to populate lowercased columns
3. FTS5 search: Unicode case-insensitive
- Index lowercased title/content/tags in search_index
- sanitizeFTS() now lowercases query before MATCH
- RebuildFTS() called after migrations
4. Case-insensitive search for nodes (already done in previous commit, verified):
- title_lower column with Go strings.ToLower
- Search() queries title_lower with lowercased query
All test suites PASS, full build OK.
Replace broken ObjectPickerModal and manual modal with proper
InternalLinkPicker component:
- Search field with debounced SearchNodes API calls
- Type tabs: Дело, Заметка, Файл, Секрет (disabled)
- Results list showing title + path, keyboard navigation
- Inserts [Title](verstak://type/id) at cursor position
- No layout breakage — picker is a normal modal via position:fixed
- Escape/Cancel close picker cleanly
- bind:this on NoteEditorPanel → MarkdownEditor.insertText()
Also:
- MarkdownEditor: added public insertText() method + bind:this
- NoteEditorPanel: added bind:this on MarkdownEditor + public insertText()
- Removed manual modal, insertInternalLinkMarkdown(), document.querySelector
Root cause: initVault() called Discover() but never called
SyncConfig(), InitRuntimes(), CallInitHooks(), or StartSchedulers().
On restart, plugins were discovered from disk but their config state
(Installed/Enabled) was not restored and no Lua VMs were created.
This caused:
- Settings → Plugins showing plugins as 'not installed' after restart
- Sidebar showing calendar item (from enabledSet config) but
GetPluginPanelHTML failing (only checked p.Active)
- Toggle working in-session but state lost on restart
- closeVault() not stopping plugin schedulers/shutdown/VM (leak)
Fixes:
- bindings_config.go: add SyncConfig(appCfg) after Discover()
- bindings_config.go: add InitRuntimes() + CallInitHooks() + StartSchedulers()
after watcher start
- bindings_config.go: add StopSchedulers + CallShutdownHooks + CloseRuntimes
to closeVault() (before db.Close since plugins use DB)
- bindings_plugins.go: GetPluginPanelHTML now checks enabledSet || p.Active,
consistent with ListSystemViewsWithPlugins and ListPlugins
Root cause: CallFunctionJSON used .String() on Lua return values, which
for tables produces 'table: 0x...' — not valid JSON. Frontend does
JSON.parse() on the result and silently caught the parse error.
Fix:
- runtime.go: convert Lua return value to JSON via luaValueToGo +
json.Marshal so tables become proper JSON arrays/objects
- main.lua: add backward compat in get_events() and update_event()
to accept both positional args (start, end) and table params
- CalendarPluginPage.svelte: show errors in UI instead of silent catch;
restructure template to always show iframe + error overlay
1. SetPluginEnabled(true): after DeactivatePlugin, also call Disable(name)
to rollback in-memory Enabled state (not just config).
2. on_init failure is now fatal for ActivatePlugin — returns error
and rolls back scheduler + VM (was incorrectly non-fatal).
3. TestSetPluginEnabled_BrokenPlugin_Rollback: end-to-end test with
broken plugin (invalid interval), verifies error + not Active +
not Enabled + not in config.
PluginPage.svelte использовал несуществующий Wails binding CallPluginAction.
Заменён на CallPluginFunction с правильным dotted path (calendar.get_events и т.д.),
что соответствует сигнатуре bindings_plugins.go.
Frontend пересобран, go build + go test ./... — всё зелёное.
Critical:
- bridge: AutoGenPort=false по умолчанию, не генерируем secret если пустой
→ extension и bridge совпадают на port 9786 и empty secret
- bridgeConfig: убрана авто-генерация secret, убран secret из BridgeInfo
High:
- extension/background.js + extension-firefox/background.js:
все chrome.* listeners вынесены в global scope (не внутри onInstalled/onStartup)
→ MV3 service worker корректно перезапускается
- UI: acceptBrowserEvent вызывает AcceptBrowserEvent, attachBrowserEvent вызывает
AttachBrowserEventToNode (к текущему selectedNode), а не DismissBrowserEvent
- watcher: при Create проверяется isUnderVault(absPath, vaultRoot) —
если файл уже в vault, используется AddExternal вместо CopyIntoVault
→ нет дублирования файлов с timestamp-суффиксом
Medium:
- bridge.Event: добавлено поле DeviceID, handleEvents обогащает events из batch.DeviceID
→ device_id сохраняется в DB как chrome-*/firefox-*, а не evt_*
- config: FileWatcher изменён на *bool — nil означает default true,
false = явно выключено → старые config.json без поля file_watcher получают true