8.9 KiB
project-tasks — VS Code Extension
Хранение
Формат: Markdown + YAML frontmatter (один .task.md файл на задачу)
---
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:
- Activity Bar — вкладка Project Tasks
- Task Dashboard — WebView в editor area со split layout:
- Левая панель — список задач с фильтрацией/группировкой
- Правая панель — содержимое выбранной задачи (редактируемый markdown)
- 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
Зависимости
front-matter— разбор YAML frontmatter (body + attributes)js-yaml— сериализация frontmatter обратно в YAML (dump)uuid— генерация IDneverthrow—Result/ResultAsyncдля use-casesvitest/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 — отдельные адаптеры