core: Milestone 7b — Files explorer and Default Editor improvements

- Files plugin: richer explorer with breadcrumbs, selection, toolbar actions,
  rename/trash, filter, sorting, hidden/reserved entries filtered
- Default Editor: line numbers, Ctrl+S, markdown toolbar, Edit/Preview/Split,
  markdown preview, Reload/Revert
- E2E tests: 39 passed for files + editor
- Workspace model: correction, naming alignment, compatibility wrappers
- Updated docs: NOTES_FILES_PLUGIN_PLAN.md, PLUGIN_RUNTIME.md
This commit is contained in:
2026-06-20 19:20:13 +08:00
parent 4de5a74a55
commit 0ac473d720
18 changed files with 1956 additions and 1161 deletions
+11 -12
View File
@@ -15,7 +15,7 @@ Already available:
- Plugin discovery, lifecycle, settings, capabilities, bundled commands, and
bundled frontend events.
- Workspace tree APIs for `space`, `case`, and `folder`.
- Workspace lifecycle APIs for top-level physical folders under the vault root.
- Plugin-owned internal storage directories:
`.verstak/plugin-data/<pluginId>`, `.verstak/plugin-settings/<pluginId>`, and
`.verstak/plugin-cache/<pluginId>`.
@@ -54,11 +54,11 @@ Canonical rules:
Canonical scoped paths:
- Workspace/root overview notes live under `<workspace-node-path>/Notes/`.
- Case/project/folder scoped notes live under `<workspace-node-path>/Notes/`.
- The default overview note is `<workspace-node-path>/Notes/Overview.md`.
- `workspace-node-path` is a normal vault-relative folder path stored on the
workspace node. Files plugin workspace views are scoped to this path.
- Workspace overview notes live under `<workspace>/Notes/`.
- The default overview note is `<workspace>/Notes/Overview.md`.
- `<workspace>` is the top-level physical folder name under the vault root.
- Files plugin workspace views are scoped with `workspaceRootPath`, which is the
selected top-level workspace folder name.
Visibility requirements:
@@ -67,10 +67,9 @@ Visibility requirements:
- External file managers must show the same `.md` files.
- Outside Verstak, the files must remain useful as normal Markdown.
The workspace tree can remain `space`/`case`/`folder`. Adding `note` as a
workspace node type is not part of the next milestone because it would require a
schema migration. The Notes service can index and manage Markdown files inside
canonical `Notes/` folders without changing workspace node types.
There is no canonical metadata workspace tree. Adding `note` as a workspace node
type is not part of the next milestone. The Notes service can index and manage
Markdown files inside canonical `Notes/` folders under each top-level workspace.
## Title To Filename Contract
@@ -201,8 +200,8 @@ Files owns safe raw vault file access. Notes owns note semantics.
The same physical note must be visible through both APIs:
- Files sees `SomeCase/Notes/Overview.md` as a file.
- Notes sees `SomeCase/Notes/Overview.md` as a note with title `Overview`.
- Files sees `Project/Notes/Overview.md` as a file.
- Notes sees `Project/Notes/Overview.md` as a note with title `Overview`.
There must be no duplicate note content stored in plugin settings, plugin data,
or a separate `.verstak` note database. Indexes and caches may exist later, but
+103 -62
View File
@@ -750,51 +750,104 @@ Vault plugin state хранится **внутри vault** в `.verstak/plugins.
- `./scripts/smoke-platform.sh` — ✅ (enable/disable/plugins.json)
- `./scripts/build.sh` — ✅
## Workspace / Cases Core Capability
## Workspace Core Capability
Workspace — центральная модель Верстака вокруг "дел". Это НЕ notes/files — это фундамент.
### Ноды
| Поле | Тип | Описание |
|------|-----|----------|
| `id` | UUID | Стабильный идентификатор |
| `parentId` | string | ID родителя (пусто для root) |
| `type` | space/case/folder | Тип ноды |
| `title` | string | Название |
| `path` | string | Vault-relative папка ноды |
| `status` | active/sleeping/archived | Жизненный цикл |
| `tags` | string[] | Теги |
| `order` | int | Порядок среди siblings |
| `createdAt` | RFC3339Nano | Создан |
| `updatedAt` | RFC3339Nano | Обновлён |
### Хранение
`<vault>/.verstak/workspace.json` — атомарная запись metadata (temp + rename).
Каждая workspace node также имеет user-visible folder inside vault. `path`
хранит canonical vault-relative folder path. Имена папок читаемые: берутся из
title, очищаются от запрещённых символов, сохраняют Unicode/кириллицу, а при
коллизии получают suffix ` (2)`, ` (3)`, ...
Workspace — это физическая папка верхнего уровня внутри vault root. Filesystem
является source of truth для списка workspaces.
Пример:
```
<vault>/
My Workspace/
Test/
test/
Workspace/
Notes/
Overview.md
Project/
ClientA/
.verstak/
```
Нет единого `<vault>/Workspace/` контейнера для всех workspaces. Папка
`Workspace/` может быть обычным workspace, но `Project/` и `ClientA/` являются
такими же workspace на том же уровне.
### Хранение
Workspace existence/list хранится только в filesystem:
- `ListWorkspaces()` читает top-level directories из vault root.
- `.verstak`, reserved/internal directories, top-level files и symlinks не
считаются workspaces.
- `.verstak/workspace*.json` не является source of truth для списка workspaces.
- Нет persisted workspace path mapping и нет virtual workspace tree, которое
мапится на произвольные папки.
`.verstak` может хранить только metadata, которая не заменяет filesystem:
- UI state: selected workspace, expanded folders, sort/pin state, preferences.
- Semantic snapshot: applied template snapshot, enabled feature areas, folder
conventions.
Template snapshot копируется в metadata при создании workspace. Workspace
identity при этом остаётся именем top-level folder; `metadata.workspaceName`
является presentation/snapshot field, not canonical identity. Если сохранённое
значение расходится с именем папки, runtime возвращает canonical `workspaceName`
равным имени папки без filesystem side effects.
```json
{
"workspaceName": "Project",
"createdFromTemplate": {
"templateId": "client-project",
"templateName": "Client Project",
"templateVersion": 1,
"appliedAt": "2026-06-19T12:00:00Z"
},
"features": {
"notes": true,
"files": true,
"secrets": true,
"activity": false
},
"folders": {
"notes": "Notes",
"files": "Files",
"secrets": "Secrets"
}
}
```
Если original template удалён или изменён позже, существующий workspace
открывается по сохранённому snapshot и не мутирует автоматически. Template
update/migration может быть только явной future feature. Если metadata
отсутствует, workspace открывается как generic workspace минимум с `files: true`.
### API
- `GetWorkspaceTree()`полное дерево
- `CreateWorkspaceNode(parentID, type, title)` — создать
- `RenameWorkspaceNode(id, title)` — переименовать
- `MoveWorkspaceNode(id, newParentID)` переместить
- `ArchiveWorkspaceNode(id)` — архивировать
- `SetCurrentWorkspaceNode(id)` — выбрать текущую
- `GetCurrentWorkspaceNode()` — получить текущую
- `ListWorkspaces()`список top-level physical folders.
- `CreateWorkspace(name, templateId?)` — создать `<vault>/<name>/`, применить
template один раз, сохранить snapshot metadata.
- `RenameWorkspace(oldName, newName)` — физически переименовать top-level folder
и обновить metadata key/name.
- `TrashWorkspace(name)` — перенести весь top-level workspace folder в internal
trash policy.
- `GetWorkspaceMetadata(name)` — прочитать metadata или вернуть generic fallback.
- `UpdateWorkspaceMetadata(name, patch)` — обновить metadata без влияния на
существование workspace.
Deprecated compatibility APIs:
- `GetWorkspaceTree()` — flat view, derived from top-level folders. Не дерево.
- `CreateWorkspaceNode(...)` — wrapper over `CreateWorkspace`.
- `RenameWorkspaceNode(...)` — wrapper over `RenameWorkspace`.
- `ArchiveWorkspaceNode(...)` — wrapper over `TrashWorkspace`.
- `MoveWorkspaceNode(...)` — unsupported; old nested/mapped moves are rejected.
- `GetCurrentWorkspaceNode()` / `SetCurrentWorkspaceNode(...)` — wrappers over
selected top-level workspace UI state.
Эти методы существуют только для постепенного frontend/Wails cleanup. Они не
должны создавать или сохранять nested workspace tree и не должны восстанавливать
`WorkspaceNode.path` mapping.
### Capability
@@ -802,28 +855,16 @@ title, очищаются от запрещённых символов, сохр
### Правила
- Root node создаётся при создании vault
- Для каждой node создаётся обычная папка внутри vault
- WorkspaceItems получают выбранную node и `workspaceRootPath`; Files plugin
показывает именно эту папку, а не общий root vault
- Порядок children стабилен (sort by order)
- Нельзя переместить ноду в себя или в своего потомка
- `MoveWorkspaceNode` переносит physical folder subtree and updates descendant
paths
- `RenameWorkspaceNode` меняет display title; physical folder rename/UI для этого
остаётся отдельным действием
- Архивирование — soft delete (status = archived)
- Corrupt JSON → backup + defaults
### Типы нод
| Тип | Назначение |
|-----|-----------|
| `space` | Рабочее пространство (root) |
| `case` | Дело |
| `folder` | Папка |
НЕ добавляются: note, file, action, secret, worklog, link — это плагины.
- Workspace name — один safe folder name, не path.
- Reject: empty, slash, backslash, absolute-looking paths, `..`, null byte,
`.verstak`, reserved/internal names, symlink workspaces, conflicts.
- WorkspaceItems получают `workspaceRootPath`, равный имени top-level папки
(`Project`, `ClientA`, etc). Files plugin показывает именно эту папку.
- Files API остаётся raw vault-relative API: `Project/Notes/Overview.md`,
`Project/docs/file.md`, `Test/readme.md`.
- Notes are ordinary Markdown files under `<workspace>/Notes/`; нет
`.verstak/notes`, UUID note storage или второго source of truth для note
content.
### Lifecycle Events
@@ -837,11 +878,11 @@ title, очищаются от запрещённых символов, сохр
### UI
WorkspaceTree в sidebar:
- Дерево с expand/collapse
- Создание case/folder
- Выбор текущей ноды
- Индикатор статуса (active/archived/sleeping)
Workspace list в sidebar:
- Flat list of top-level workspace folders.
- Create workspace, rename workspace, trash workspace.
- Selection is stored as selected workspace name.
- No expand/collapse workspace tree and no case/folder node creation in core.
---