- notes.Create(): .md files stored in parent node's fs_path folder
- files.CopyIntoVault/CreateEmptyFile/Duplicate: use parent fs_path
- files.AddPathCopy/AddPathLink: use parent fs_path, set folder fs_path
- files.DeleteNodeAndChildren: move physical folder to .verstak/trash
- UpdateFsPathRecursive: use SafeDisplayNameToPathSegment(child.Title)
- sync_apply.go note ops: use fs_path instead of spaces/
- internal/gui/server.go file upload: use n.FsPath instead of nodeSlug
- VaultCheck diagnostic: walk nodes/files, verify paths on disk
- Tests: create/rename/move/delete/name-conflict/vault-check all pass
cmd/verstak-server/server.go (2838→127 строк): разделён на 12 файлов
- config.go, tokens.go, schema.go
- server.go (только struct + NewServer + ListenAndServe)
- routes.go, middleware.go, smtp.go
- handlers_api.go, handlers_user.go, handlers_web_user.go, handlers_admin.go
- templates.go (конвертирован в функции с i18n.T())
frontend: все русские строки заменены на t() вызовы
- App.svelte, FileTreeRow.svelte, ConfirmModal.svelte
- FilePreviewModal.svelte, fileUtils.js
core: gofmt по всему проекту
Все сборки (CLI, server, gui, frontend), go vet, go test проходят.
check-i18n.sh: frontend чист, Go-файлы с кириллицей — только тесты/легаси.
Backend:
- Migration 004: add 'section' column to nodes table
(NULL=inbox, values: clients/projects/recipes/documents/archive)
- Create(parentID, type, title, section) — section stored on root nodes
- ListRoots(includeDeleted, section) — filters by section
(section='inbox' returns nodes with NULL section)
- GET /api/nodes?section=X filters root nodes by section
- POST /api/nodes accepts 'section' field in body
Frontend:
- Sidebar separates 'НАВИГАЦИЯ' (virtual sections) from 'ДЕЛА' (real nodes)
- Each section loads only its own nodes: GET /api/nodes?section=clients etc.
- Creating from a section sets the section automatically
- Inbox shows only nodes with no section
- selectBySearch(id) closes result dropdown after selection
- All types shown in Russian (Дело, Заметка, Папка, etc.)
Acceptance: go build pass, go test pass (all packages),
manual: Pro projects section shows only project-nodes,
clients only client-nodes, inbox only unsectioned nodes.
Root cause: single global 'cur' node ID was shared across all
sections. Switching between 'Клиенты'/'Проекты'/etc did not
change the rendered content because renderToday/renderInbox/
loadSection all loaded the same flat root-node list and
switchTab('ov') re-rendered selN(cur) regardless of which
section was active.
Fix: split selection into two distinct states:
sel = {kind:'section', section:'today'|'inbox'|'clients'|...}
or sel = {kind:'node', nodeId:'<uuid>'}
Each section renders its own content (title, quick-actions,
filtered items, empty state). Real nodes show their own
dashboard. Tab dispatch checks sel.kind first.
Sidebar separated into:
'НАВИГАЦИЯ' — virtual sections (today/inbox/7 categories)
'ДЕЛА' — real user nodes from API
Russian type labels everywhere (TL map). Section metadata
(SEC_META) provides per-section empty states and action types.
Known limitation: section content currently shows all root
nodes (backend has no section/group column yet). When section
assignment is added to the data model, filtering will wire
up without frontend changes — renderSectionList already
receives the section id.