- Expand all RecordOp payloads with full entity data needed for reconstruction
- Add applyRemoteOp with handlers for node/note/file/action/worklog CRUD
- SyncNow() now applies pulled ops to real DB tables + sets LastSeenServerSeq
- SyncTestConnection uses /api/auth/test instead of PairDevice
- autoSyncLoop respects configured interval with lastSync timing
- Add helper functions: nodePayload, notePayload, filePayload, actionPayload, worklogPayload
- Add handleAuthTest endpoint that validates credentials without creating a device
- Fix resetPasswordHTML to use {TOKEN} placeholder instead of %s
- Remove fmt.Sprintf from admin dashboard (no format args needed)
BREAKING: replace legacy API keys with device tokens via pairing flow.
- Server: /api/client/pair, revoke, me endpoints; server_sequence + tombstones + idempotency
- Desktop client: PairDevice, GetMe, RevokeCurrent; auto-sync loop every 60s
- Config: device_token stored in separate file (0600), not config.yml
- Client DB: last_pull_seq migration for incremental pull
- Frontend (Svelte): settings modal with connect/disconnect/interval
- User dashboard (/dashboard): device list with status, revoke with password
- Admin dashboard (/admin/dashboard): devices table from /admin/api/devices
- CLI (cmd/verstak): updated for ServerSequence/GetState changes
- Fix: autoSyncLoop falls back to SQLite sync_state for server URL
- Fix: SyncSetInterval preserves server_url/device_id from SQLite
- internal/core/sync/: Service, Client, Blob packages
- RecordOp creates sync_ops entries for all mutations
- Client for push/pull/blob HTTP to server
- Blob SHA-256 hashing and local storage
- Wired into app.go alongside activity recording
- Device ID from config or fallback
- POST /api/v1/sync/push — accepts ops, assigns revisions, returns accepted list
- POST /api/v1/sync/pull — returns ops since given revision with server_revision
- POST /api/v1/blobs/ — multipart upload, stored as blobs/ab/cd/sha256
- GET /api/v1/blobs/{sha256} — download by hash
- server_blobs table for tracking stored blobs
TodayView now uses only activity_events (source of truth) + ListTodayNodes (ensure changed cases appear). Removed direct queries to nodes (notes) and files tables — those will come from activity_events going forward.
- ListTodayView() on backend: queries nodes created or updated today
using local timezone day boundaries
- todayBoundaries() helper returns start/end of current day in RFC3339
- Section validation: Create() rejects today and inbox as node sections
- validSections moved from repository.go to types.go with IsValidSection
and IsServiceSection helpers
- Frontend selectSection('today') calls ListTodayView instead of
ListNodesBySection('today')
- FAB (create node) hidden in today and inbox sections
- CreateNode in app.go guarded against today/inbox sections
- types.go: today/inbox defined as service sections (sidebar only)
Backend fixes:
- Wire search service in App struct, implement Search() bindings
- Fix OpenFile to use files.Service.Open() instead of stub
- Fix OpenFolder to open spaces/<slug>/ instead of vault root
- Remove unused imports and dead code in app.go
Frontend fixes:
- Add missing Svelte plugin to vite.config.js (blocking build error)
- Fix optional catch binding for compatibility
- Fix select dropdown rendering on Linux (appearance: none + custom arrow)
- Switch api/verstak.js to use generated Wails v2 bindings
- Include hand-written wailsjs bindings in repository
- Add build.sh to repository
Build:
cd frontend && npm run build
rm -rf cmd/verstak-gui/frontend-dist && cp -r frontend/dist cmd/verstak-gui/frontend-dist
go build -tags 'gui production webkit2_41' -o verstak-gui ./cmd/verstak-gui
Problem: UI appeared as narrow dark panel on white background with scrollbar.
Root causes:
- html/body had no reset — default browser margin/padding = white borders
- index.html referenced non-existent /style.css
- .app used height:100vh but no width:100vw or overflow:hidden
- sidebar used fixed width instead of flexbox
Fixed:
- index.html: added critical CSS reset (html/body/#app = 100%, margin:0, overflow:hidden, dark bg)
- index.html: removed /style.css ref, changed lang to ru, title to Верстак
- App.svelte: complete layout rewrite
- .app = flex, 100vw x 100vh, overflow:hidden
- sidebar = 260px width, full height, flex column
- main = flex:1, full height, flex column (header + content)
- sidebar nav with sections + nodes using <button> elements
- content area fills remaining space
- proper dark theme colors throughout
- Rebuilt frontend/dist and cmd/verstak-gui/frontend-dist
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.