# 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`. - `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 — отдельные адаптеры