Files
xboct-flow/docs/PLAN.md
T

191 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# XBOCTuK Flow — VS Code Extension
## Хранение
Формат: Markdown + YAML frontmatter (один `.xflow.md` файл на задачу)
```markdown
---
id: "..."
title: "Пофиксить баг логина"
status: todo # backlog | todo | in-progress | done | cancelled
priority: high # low | medium | high | critical
tags: [bug, frontend]
assignee: ""
created: 2026-07-12T10:00:00Z
updated: 2026-07-12T10:00:00Z
---
Описание задачи в markdown...
```
**Структура папок:**
- `.xflow` — проектные задачи
- глобальная папка — настраивается (`xboctukFlow.globalPath`)
Оба пути настраиваются через VS Code settings.
## UI в основном окне (Split-редактор)
Через **Custom Editor** + **WebView**:
1. **Activity Bar** — вкладка XBOCTuK Flow
2. **Task Dashboard** — WebView в editor area со split layout:
- Левая панель — список задач с фильтрацией/группировкой
- Правая панель — содержимое выбранной задачи (редактируемый markdown)
3. **TreeView** — быстрый просмотр по статусу
## Архитектура кода
Актуальная раскладка (после рефакторинга портов/адаптеров):
```txt
src/
├── extension.ts # composition root (DI, register)
├── ports.ts # IFileSystem, ITaskRepository, IConfigProvider
├── error.ts # AppErrorVariant + AppError { variant, message }
├── model/task.ts
├── storage/ # infrastructure adapters
├── commands/ # use-cases (no vscode)
├── dashboard/ # pure view-model helpers
├── ui/ # thin vscode adapters (input/quickpick/errors)
│ ├── presentError.ts
│ ├── resolveTaskId.ts
│ ├── taskLabels.ts
│ └── taskCommands.ts # register + run* handlers
├── views/
│ ├── taskTreeItem.ts
│ ├── taskTreeProvider.ts
│ ├── taskDashboardPanel.ts # WebviewPanel host
│ └── taskDashboardHtml.ts # split layout document
└── utils/
tests/helpers/InMemoryFileSystem.ts
```
Слои: use-cases + ports — без vscode; `ui/*` — только ввод/вывод host API;
`extension.ts` — сборка зависимостей и регистрация.
## План реализации
| Фаза | Что делаем |
| ------------------------ | ---------------------------------------------------------------------- |
| **1. Scaffold** | Extension, TypeScript, сборка, package.json |
| **2. Storage** | CRUD `.xflow.md`, парсинг frontmatter, unit-тесты storage |
| **3. TreeView** | sidebar по статусу, file watcher |
| **4. Команды** | createTask, deleteTask, changeStatus, openTask (use-cases → wiring UI) |
| **5. Task Dashboard** | WebView split layout |
| **6. Глобальные задачи** | использование globalPath |
| **7. Kanban** | drag-n-drop |
| **8. Публикация** | vsce publish |
### Фаза 4 — команды (use-cases + UI wiring)
| Команда | Use-case | UI adapter |
| -------------- | -------------------------------------------------------------- | ------------------------------------------- |
| `createTask` | title + folder → defaults todo/medium; empty title / no folder | InputBox → use-case → tree.refresh |
| `deleteTask` | folder + id → delete; not found | confirm + TreeItem/QuickPick → use-case |
| `changeStatus` | folder + id + status → update; not found | status QuickPick → use-case |
| `openTask` | folder + id → path + task | TreeItem click / command → showTextDocument |
Ошибки use-case → `presentError` (`AppError.message`). Cancel UI ≠ `AppError`.
### Фаза 5 — Task Dashboard
Split layout: список (filter/group) + detail (editable markdown body).
| Модуль | Поведение |
| ------------------------------ | -------------------------------------------------------------------------------- |
| `filterTasks` | statuses / priorities / query (title+body) / tags (AND); пустой = all |
| `groupTasks` | `none` \| `status` \| `priority`; пустые группы не отдаём; фиксированный порядок |
| `buildDashboardView` | filter → group; `selected` по id из полного списка (даже если отфильтрован) |
| `parseDashboardInboundMessage` | ready / selectTask / setFilter / setGroupBy / saveDescription / refresh |
| `loadDashboard` | folder → `{ tasks }`; `AppError.NO_FOLDER` |
| `saveTaskDescription` | folder + id + description → update body; `NOT_FOUND` / validation |
**Host (сделано, unit-тестами не покрываем):**
- `TaskDashboardPanel` — create/reveal/dispose, state, inbound/outbound
- HTML split: filter/group/list | detail + save description
- `openDashboard` + status bar; watcher / command mutations → `refreshIfOpen`
**По-прежнему только smoke руками:** визуал, CSP, postMessage round-trip
### Фаза 6 — globalPath
| Модуль | Поведение |
| ------------------------------ | ---------------------------------------------------------- |
| `TaskLocation` / `LocatedTask` | runtime scope + folderPath (не в frontmatter) |
| `resolveTaskLocations` | project и/или global из config |
| `listLocatedTasks` | list всех locations → `LocatedTask[]`; empty → `NO_FOLDER` |
| Tree | Project / Global → status → task (location на item) |
| Create | QuickPick scope, если оба path заданы |
| Dashboard | multi-folder list; save по location из host cache |
| Watchers | на каждый active folder |
### Фаза 7 — Kanban
| Модуль | Поведение |
| --------------------------- | ------------------------------------------------------------------- |
| `buildKanbanBoard` | 4 колонки status (пустые остаются); tasks by status; `updated` desc |
| `parseKanbanInboundMessage` | ready / refresh / moveTask |
| Host | `TaskKanbanPanel`; move → `changeStatus` + location cache |
| UI | HTML5 DnD; command `openKanban` |
## Зависимости
- `front-matter` — разбор YAML frontmatter (body + attributes)
- `js-yaml` — сериализация frontmatter обратно в YAML (`dump`)
- `uuid` — генерация ID
- `neverthrow``Result` / `ResultAsync` для use-cases
- `vitest` / `eslint` — dev
## История изменений (решения, не чеклист)
Краткая летопись смен относительно **изначального** наброска плана. Детали
рефакторинга storage/тестов — в архиве `docs/REFACTOR-AND-TEST.md` (документ
закрыт, не ведём).
### Изначальный набросок (v0)
- Парсинг: **`gray-matter`**
- Storage: `taskStore.ts` (CRUD), `configStore.ts` (settings)
- Сборка/скелет: `yo code`-ориентированный scaffold
- Дерево модулей: storage + views (tree/list/dashboard) + commands + utils
### Рефакторинг storage / чистая архитектура
- `taskStore` / `configStore`**порты** (`IFileSystem`, `ITaskRepository`,
`IConfigProvider`) и адаптеры:
- `FsTaskRepository`, `NodeFileSystem`, `VscodeConfigProvider`
- `extension.ts` — composition root (DI), tree зависит от портов
- Unit-тесты: Vitest, colocation, `InMemoryFileSystem` в `tests/helpers/`
### `gray-matter` → `front-matter` + `js-yaml`
- **Было в плане:** один пакет `gray-matter` на parse (+ stringify при
необходимости).
- **Стало:** `front-matter` для чтения attributes/body; `js-yaml` (`dump`) для
записи frontmatter в `serializeTask`.
- **Зачем:** `gray-matter` даёт лишние возможности парсинга (eval для JS и
прочая «магия»), которые для задач не нужны; Vite на eval ругается, с точки
зрения безопасности это нежелательно. Поэтому parse через `front-matter`,
serialize через `js-yaml` (`dump`). Формат файла тот же: Markdown + YAML
frontmatter.
### neverthrow + `AppError`
- Use-cases возвращают `ResultAsync<T, AppError>`.
- `AppErrorVariant` — const-object кодов (`NOT_FOUND: "not-found"`).
- `AppError``{ variant, message }`; `message` из шаблона с `{placeholders}`
через `appError(variant, params?)`.
### Прочее относительно v0
- Сборка: Vite (extension bundle), не классический `tsc`-only из yo code
- В модели/файле задачи зафиксирован **`id`** в frontmatter (нужен для
list/get/update/delete)
- Фаза 4: use-cases в `src/commands/*` тестируются без vscode; UI — отдельные
адаптеры