Files
xboct-flow/docs/PLAN.md
T

168 lines
8.9 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.
# project-tasks — VS Code Extension
## Хранение
Формат: Markdown + YAML frontmatter (один `.task.md` файл на задачу)
```markdown
---
id: "..."
title: "Пофиксить баг логина"
status: todo # 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...
```
**Структура папок:**
- `.vscode/tasks/` — проектные задачи
- глобальная папка — настраивается (`projectTasks.globalPath`)
Оба пути настраиваются через VS Code settings.
## UI в основном окне (Split-редактор)
Через **Custom Editor** + **WebView**:
1. **Activity Bar** — вкладка Project Tasks
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
└── utils/
tests/helpers/InMemoryFileSystem.ts
```
Слои: use-cases + ports — без vscode; `ui/*` — только ввод/вывод host API;
`extension.ts` — сборка зависимостей и регистрация.
## План реализации
| Фаза | Что делаем |
| ------------------------ | ---------------------------------------------------------------------- |
| **1. Scaffold** | Extension, TypeScript, сборка, package.json |
| **2. Storage** | CRUD `.task.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 |
**Unit-тестами не покрываем** (нужен Extension Host / DOM WebView):
- `WebviewPanel` create/dispose, HTML/CSS split layout, CSP / local resources
- `postMessage` wiring host ↔ webview (кроме parse inbound payload)
- регистрация `projectTasks.openDashboard`, status bar → open panel
- live refresh watcher → push в webview
- визуальный рендер списка/редактора в editor area
## Зависимости
- `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 — отдельные
адаптеры