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
- Новый экран 'Сегодня' разбит на 4 вкладки: Лента, Предложения,
В работе, Захвачено
- Лента отображает события за сегодня с кликабельными сущностями
- Предложения вынесены в отдельную вкладку (только предложения)
- В работе: изменённые файлы/заметки/действия за сегодня с сортировками
- Захвачено: захваченные элементы за сегодня с сортировками
- Неразобранное: сортировка по дате/имени/типу с направлением
- Неразобранное: переключатель 'Группировать по месту захвата'
- TodayScreen.svelte: новый компонент с 4 вкладками
- Новые i18n ключи для вкладок и сортировок
- Backend: ListTodayInProgress, ListTodayCaptures bindings
- Все переходы из вкладок ведут в соответствующее место программы