152 lines
11 KiB
Markdown
152 lines
11 KiB
Markdown
# Verstak project rules
|
||
|
||
## Project identity
|
||
|
||
Verstak is a local-first workbench for clients, projects, notes, files, tasks, activity and sync.
|
||
It must remain practical, simple, and filesystem-aware.
|
||
|
||
## Stack
|
||
|
||
- Backend: Go
|
||
- Storage: SQLite
|
||
- GUI: Wails v2
|
||
- Frontend: Svelte 4
|
||
- Build tooling: Vite 5
|
||
- Do not migrate to Wails v3, Svelte 5, or Vite 8 unless explicitly asked.
|
||
|
||
## Architecture rules
|
||
|
||
- Keep local-first behavior.
|
||
- Do not turn the project into SaaS.
|
||
- Do not replace SQLite with another database.
|
||
- Do not introduce cloud storage assumptions.
|
||
- Preserve recursive folder import semantics.
|
||
- Preserve stable node IDs.
|
||
- Do not duplicate nodes when moving items.
|
||
- Do not create parallel state systems for the same entity.
|
||
|
||
## UI rules
|
||
|
||
- Fix GUI behavior at root cause.
|
||
- Do not redesign the whole interface unless explicitly asked.
|
||
- Preserve active tab state correctly.
|
||
- Context menus must open near the cursor.
|
||
- Drag-and-drop must show clear visual target feedback.
|
||
- Moving nodes must never duplicate the same ID in two places.
|
||
- Nested selection must not collapse the parent unexpectedly.
|
||
|
||
## Files
|
||
|
||
- File view is not a tree.
|
||
- Sidebar shows logical hierarchy.
|
||
- Vault filesystem layout must remain human-readable without the app.
|
||
- Drag-and-drop folders must perform real recursive copy/move into the vault.
|
||
- Do not fake folder support with external links.
|
||
|
||
## Sync
|
||
|
||
- Sync settings belong in Settings.
|
||
- Main UI may keep only manual sync/status controls.
|
||
- Existing URL + login/password device registration flow should be preserved unless explicitly changed.
|
||
- Secrets must not be logged.
|
||
|
||
## Verification
|
||
|
||
For backend changes:
|
||
- Run `go test ./...` if possible.
|
||
|
||
For frontend changes:
|
||
- Run the relevant frontend build/check command if available.
|
||
- If unsure, inspect package scripts first.
|
||
|
||
For GUI bugs:
|
||
- Add targeted tests only where practical.
|
||
- If manual GUI clicking is required and unavailable, state exact manual verification steps for the user.
|
||
|
||
|
||
# Session summary
|
||
|
||
## Bugs fixed (this session)
|
||
1. **webkit2_41 build tag** — binary wouldn't start without it. Added to build instructions.
|
||
2. **Sidebar refresh** — `reloadTreePreservingExpanded` patches children in-place so expand/collapse state stays intact.
|
||
3. **Context menu off-screen** — changed to `position: fixed` with cursor coordinates.
|
||
4. **"Show in explorer" only for folder types** — `OpenFolder` in backend falls back to file record path for `TypeFile` nodes.
|
||
5. **Context menu not closing on action** — `handleShowInFolder` calls `closeMenu()`.
|
||
6. **Wrong folder when opening file's parent folder** — `OpenFolder` checks `n.FsPath == ""` for TypeFile and uses first file record path.
|
||
7. **Tab highlight not updating visually** — was using `class={tabClass(tab.id)}` which didn't trigger reactive class updates in Svelte. Switched to `class="tab" class:active={activeTab === tab.id}`.
|
||
8. **Journal table expand/collapse** — added explicit ▸/▾ toggle column so it's clear rows are expandable.
|
||
9. **Per-node worklog entries** — made entries expandable with ▸/▾, showing details + billable/approximate tags.
|
||
10. **Manual worklog entry form** — converted inline form to modal dialog ("+ Добавить запись") with all fields: date, summary, minutes, details, billable, approximate.
|
||
11. **"С подзадачами" → "Учитывать вложенные дела"** — renamed, now hidden when no node selected.
|
||
12. **Filter/export layout** — split into separate "Фильтры" and "Экспорт отчёта" sections with headings.
|
||
13. **Suggestion events** — added "Показать в проводнике" button for file-type events in suggestion detail.
|
||
14. **Removed duplicate i18n keys** in `ru.js` (worklog.suggestions, worklog.apply).
|
||
15. **Removed unused CSS** (`.journal-filters`, `.wl-meta`, `.worklog-form`).
|
||
16. **Added `openNodeFolder(nodeOrId)`** — accepts both string ID and node object.
|
||
17. **Added `resetJournalFilters()`** — resets all filters and reloads.
|
||
18. **Source field** — added `worklog_entries.source` column (migration 014). Values: manual, suggestion. Old entries default to 'unknown'.
|
||
19. **Suggestions now use worklog_entry_events** instead of `HasTodayEntries` — only events already linked to worklog entries are excluded. Repeated activity on the same node today now produces new suggestions.
|
||
20. **Activity target navigation** — clicking activity events for notes opens the note tab and loads the specific note. File events open the files tab.
|
||
21. **Source display** — detail sections now show accurate source: "Ручная запись", "Из предложения", "Из предложения, но связанные события отсутствуют", or "Источник неизвестен".
|
||
22. **Wails `[]string` marshalling bug** — Wails v2.12.0 silently drops `[]string` positional args from JS→Go. **Fix**: pass all string arrays as `JSON.stringify()` → `string` → `json.Unmarshal` on Go side.
|
||
23. **Event link validation** — `AcceptSuggestionWith` pre-checks each eventID against `activity_events`, uses plain `INSERT` (not `INSERT OR IGNORE`), and verifies with JOIN `COUNT(*)` after commit.
|
||
24. **GetWorklogEntryEvents column fix** — query used `e.details_json` but the column is `e.metadata`. Fixed to `COALESCE(e.metadata,'')`.
|
||
25. **"Посмотреть" button** — `openActivityTarget(ev)` navigates to the specific target: note tab + open note for `targetType=note`, files tab + `OpenFolder(targetPath)` for `file/folder`.
|
||
26. **End-to-end test** — `TestAcceptSuggestionWithEndToEnd` creates node, 3 activity events, accepts suggestion, verifies all 3 linked via `worklog_entry_events` + JOIN.
|
||
27. **WriteDebugLog binding** — `bindings_debug.go` writes frontend logs to `<vault>/.verstak/debug.log` for production GUI debugging.
|
||
28. **Journal regression tests** — `TestJournalFullRegression`, `TestSuggestionOnRepeatedActivity`, `TestManualWorklogEntry`.
|
||
29. **resolveActivityTarget helper** — pure function returning `{ nodeId, tab, noteId/fileId/targetPath }`, used by `openActivityTarget`.
|
||
30. **First-run flow** — no auto-vault creation. New `GetStartupStatus` binding returns `first_run`/`recovery`/`ready`. Frontend shows FirstRun.svelte or VaultRecovery.svelte accordingly.
|
||
31. **Global config.json** — moved vault path, sync settings, templates, theme, language from implicit CLI args to `~/.config/verstak/config.json` (`AppConfig` struct).
|
||
32. **Sync settings in Settings** — extracted sync modal into Settings → Sync section. Removed all inline sync form fields from `App.svelte`. Added `SyncStatus.svelte` widget replacing navbar sync button.
|
||
33. **Settings window** — modal with sidebar (8 sections: General, Workspace, Templates, Plugins, Files, Activity, Sync, Backup). ESC to close. Lazy-loaded content panels.
|
||
34. **Template enable/disable** — `AllTemplates` + `SetTemplateEnabled` bindings propagate to `appCfg.EnabledTemplates`. `initVault` applies filter to registry.
|
||
35. **Vault recovery screen** — when vault path exists but vault is missing, shows VaultRecovery.svelte with choose/create/quit options.
|
||
|
||
## Bugs fixed (this session)
|
||
|
||
1. **Trash preview "trash file not found" for TypeFile nodes** — `resolveTrashPath` only searched `<nodeID>_*` in trash dir, but TypeFile node files are moved by file record ID (`<recordID>_<filename>`). Added file record fallback via `ListTrashedByNode` + `ReadTrashFile(trashFsPath)` binding.
|
||
2. **Trash restore creates empty files** — `DeleteToTrash` permanently `DELETE`'d file records from DB and `restoreTrashPath` silently `return nil` for `FsPath=""` nodes. Changed `deleteFileRecords` to `trashRecord` (sets `missing=1`, keeps record). `restoreTrashPath` now restores file records: moves file back from trash, sets `missing=0`.
|
||
3. **`resolveTrashPath` inner loop corrupts `anc` variable** — `anc = child` inside the inner loop caused wrong path computation for nesting depth > 2. Replaced with direct `chain[0].FsPath` prefix computation.
|
||
4. **`ListTrash` missing `TrashFsPath` for TypeFile nodes** — Phase 1 only checked `<nodeID>_*` entries. Added `ListTrashedByNode` fallback to set `TrashFsPath` for TypeFile nodes.
|
||
5. **`ListByNode` returning trashed records** — Added `AND missing != 1` filter to exclude trashed file records from active node file listings.
|
||
|
||
## Key patterns (this session)
|
||
|
||
- **TypeFile node trash**: files are moved by file record ID (`<recordID>_<filename>`), NOT by node ID. Never search by `<nodeID>_*` alone — always fall back to file records via `ListTrashedByNode`.
|
||
- **File record soft-trash**: use `UPDATE files SET missing=1` instead of `DELETE` to keep records restorable. Restore via `UPDATE ... SET missing=0` + `os.Rename` from trash.
|
||
- `ReadTrashFile(trashFsPath)` is preferred over `ReadTrashFileContent(nodeID)` — frontend has `trashFsPath` precomputed by `ListTrash`.
|
||
- **`restoreTrashPath` for TypeFile nodes**: when `fsPath == ""`, find file records with `missing=1` and restore each one.
|
||
- **Full test coverage**: `TestTrashTypeFilePreviewAndRestore`, `TestTrashTypeFileInsideFolderRestorePreservesContent`, `TestTrashTypeFileMultipleRecords`.
|
||
|
||
## Key patterns
|
||
- Always use explicit toggle icons (▸/▾) on expandable rows.
|
||
- `CreateWorklogFull` supports all fields: nodeID, summary, details, date, minutes, approximate, billable.
|
||
- `openNodeFolder(id)` accepts a string ID or a node object.
|
||
- `GetSuggestions` filters out only events already in `worklog_entry_events`, not entire nodes.
|
||
- New worklog entries get `source=manual` via `Add`/`AddWithDate`; suggestion entries get `source=suggestion` via `AcceptSuggestionWith`.
|
||
- **NEVER pass `[]string` through Wails v2 bindings** — always JSON-serialize to `string` first. Wails v2.12.0 silently drops slice arguments.
|
||
- **Always wrap create-entry + link-events in a transaction** with pre-validation and post-commit verification to prevent orphan entries.
|
||
- Frontend debug logs in production: use `wailsCall('WriteDebugLog', msg)` → writes to `<vault>/.verstak/debug.log`.
|
||
- `AppConfig` stores all global settings in `~/.config/verstak/config.json`. Vault-specific config stays in `.verstak/config.yml`.
|
||
- Use `GetStartupStatus` to determine first-run vs recovery vs normal startup.
|
||
- Settings window uses a sidebar with 8 sections; each section is a separate Svelte component imported lazily.
|
||
- Template enable/disable state is stored in `appCfg.EnabledTemplates` and applied to the registry during `initVault`.
|
||
|
||
# Build instructions
|
||
|
||
## GUI binary (Wails v2)
|
||
|
||
```bash
|
||
# From project root:
|
||
cp -r frontend/dist/* cmd/verstak-gui/frontend-dist/
|
||
go build -tags "webkit2_41 desktop production" -ldflags="-s -w" -o build/verstak-gui-linux-amd64 ./cmd/verstak-gui/
|
||
```
|
||
|
||
## Server binary
|
||
|
||
```bash
|
||
go build -ldflags="-s -w" -o build/verstak-server-linux-amd64 ./cmd/verstak-server/
|
||
```
|
||
|