Files
xboct-flow/docs/PLAN.md
T

10 KiB
Raw Blame History

xboct-flow — VS Code Extension

Хранение

Формат: Markdown + YAML frontmatter (один .task.md файл на задачу)

---
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...

Структура папок:

  • .vscode/tasks/ — проектные задачи
  • глобальная папка — настраивается (xboctFlow.globalPath)

Оба пути настраиваются через VS Code settings.

UI в основном окне (Split-редактор)

Через Custom Editor + WebView:

  1. Activity Bar — вкладка XBOCT flow
  2. Task Dashboard — WebView в editor area со split layout:
    • Левая панель — список задач с фильтрацией/группировкой
    • Правая панель — содержимое выбранной задачи (редактируемый markdown)
  3. TreeView — быстрый просмотр по статусу

Архитектура кода

Актуальная раскладка (после рефакторинга портов/адаптеров):

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 .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

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
  • neverthrowResult / 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-matterfront-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 — отдельные адаптеры