meta(tasks): migrate .tasks to v2 (global numbering 16 tasks)
This commit is contained in:
47
.tasks/done/2026-05-22-00198-board-viewer-card-meta-fix.md
Normal file
47
.tasks/done/2026-05-22-00198-board-viewer-card-meta-fix.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# board-viewer-card-meta-fix
|
||||
|
||||
## Goal
|
||||
|
||||
Card meta сейчас содержит **title + project + age**. Acceptance criteria `board-viewer-html-render.md` строка 19-24 явно перечисляет 5 полей:
|
||||
|
||||
- title (труб 80 chars) ✅
|
||||
- project badge ✅
|
||||
- **owner (если есть) — pill** ❌ (поле `project_owner` есть в TaskRecord и JSON, но не выводится)
|
||||
- age (relative) ✅
|
||||
- **last-commit-marker — короткий hash или дата** ❌ (есть только relative age от `last_commit_iso`, абсолютной даты/хэша нет)
|
||||
|
||||
html-render таска закрылась без этих полей — gap. Не было замечено при self-review (потому и нужен independent reviewer).
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts:111-127` — `renderCard()` (server-side первая рендер-проходка)
|
||||
- `static/board.js:76-89` — `renderCard()` (client-side rerender при toggle-grouping)
|
||||
- `tests/render.test.ts` — добавить assertions для двух новых полей
|
||||
- `static/board.css` — стиль для `.badge.owner` если нужно отличать визуально
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Card showing `project_owner` as a pill (CSS class `.badge.owner`), скрыт если поле пустое.
|
||||
- Card showing last-commit-marker: minimum — ISO date `YYYY-MM-DD` (короткая дата, NOT relative). Hash недоступен из API без extra call → opt for date.
|
||||
- Возможность размещения: либо как третий meta-span после age, либо как tooltip на age (`title="<iso>"`); решение имплементера.
|
||||
- 2+ TDD tests:
|
||||
- card with `project_owner` shows owner pill containing the value
|
||||
- card with `last_commit_iso` shows date (e.g. `2026-05-22`) — separate from relative age
|
||||
- Обе ветки (server-side `src/render.ts` + client-side `static/board.js`) синхронны.
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: задача из review-чекпойнта; severity=major (acceptance gap, не self-replied).
|
||||
- Open: owner pill всегда показывать или только при variability (когда есть >1 owner в выборке)? — рекомендация: всегда показывать когда поле не null (нет смысла фильтровать на rendering этапе).
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] RED+GREEN: 4 теста для owner pill + date span (renders/omits)
|
||||
- [x] `src/render.ts` renderCard: owner pill + date span (short YYYY-MM-DD + tooltip с full ISO)
|
||||
- [x] `static/board.js` renderCard синхронно
|
||||
- [x] `static/board.css`: `.badge.owner` (transparent + border) + `.date` (small muted tabular-nums)
|
||||
- [x] smoke-verified в браузере: top cards показывают `board-viewer` + `OpeItcLoc03` + `2026-05-22` + `25m`
|
||||
|
||||
## Notes
|
||||
|
||||
TDD obligatory — render логика, не visual. Тестируется через DOM-property assertions (как в `render.test.ts`).
|
||||
61
.tasks/done/2026-05-22-00199-board-viewer-cron-deploy.md
Normal file
61
.tasks/done/2026-05-22-00199-board-viewer-cron-deploy.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# board-viewer-cron-deploy
|
||||
|
||||
## Goal
|
||||
|
||||
Деплой статической HTML-доски на VDS под доменом `board.kzntsv.site`. Periodic regen через systemd timer ~5 мин. Traefik label для роутинга. DNS A-запись. Basic-auth через traefik (не in-app).
|
||||
|
||||
**TDD posture:** под carve-out «infrastructure / declarative» из `tdd-criteria`. Acceptance проверяется по факту deploy + curl, не unit-тестами. systemd unit-файлы и traefik labels — декларативные конфиги, не код.
|
||||
|
||||
## Key files
|
||||
|
||||
- `deploy/docker-compose.yml` — двухсервисный стек (build + web) на `proxy` network
|
||||
- `deploy/Dockerfile.build` — build-image (node:22-alpine + cron-loop entrypoint)
|
||||
- `deploy/nginx.conf` — static serving с Cache-Control
|
||||
- `deploy/auth.toml.example` — шаблон Gitea token + board_viewer_repos
|
||||
- `deploy/.env.example` — шаблон BOARD_VIEWER_USERS с правилом `$$` escape
|
||||
- `deploy/README.md` — DNS / image build / bootstrap / smoke / DR steps
|
||||
|
||||
**Замечание:** acceptance изначально упоминал systemd timer, но user выбрал docker-compose stack (consistent с существующим VDS pattern Gitea/registry/etc.). Mechanism: `while true; do node cli.ts; sleep 300; done` внутри `board-viewer-build` контейнера. Логи через `docker compose logs`, не journalctl. Эффект тот же — каждые 5 мин regen.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- DNS `board.kzntsv.site` → VDS IP (Cloudflare / namecheap / wherever DNS живёт).
|
||||
- Traefik route: `board.kzntsv.site` → static file serving из `/var/www/board-viewer/`.
|
||||
- Basic-auth на traefik (middleware), креды из `.htpasswd`.
|
||||
- systemd timer крутится каждые 5 мин, успешно дёргает reader + render, пишет в `/var/www/board-viewer/index.html`.
|
||||
- Логи systemd видны через `journalctl -u board-viewer.service`.
|
||||
- Health check: `curl -u user:pass https://board.kzntsv.site/` → HTML с актуальной доской.
|
||||
- README.md описывает install steps (для disaster-recovery: «как поднять с нуля на новой машине»).
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: task создан промоушеном; carve-out из TDD под инфра (declarative configs).
|
||||
- 2026-05-22: auth = traefik basic-auth, не in-app (фиксировано в дизайне).
|
||||
- 2026-05-22: deploy-mechanism = docker-compose stack (рекомендация принята); systemd timer заменён на cron-loop внутри build container'а. Гомогенно с existing VDS pattern (Gitea, registry, ntfy).
|
||||
- 2026-05-22: basicauth.users inline через env_file, `$$` escape для compose interpolation. Не нужно касаться traefik stack'а (mounting htpasswd в traefik потребовало бы изменения соседнего стека).
|
||||
|
||||
## Open questions
|
||||
|
||||
- [x] Host: VDS Rusonyx (тот же, где Gitea+traefik). Network `proxy`, путь `/opt/stacks/board-viewer/` — verified via vds-ops inspect of gitea container.
|
||||
- [x] Multi-user basic-auth: схема поддерживает (comma-separated), но MVP — single user.
|
||||
- [x] Rate-limit: 4 repos × ~2 calls/repo (STATUS.md fetch + commit log per task slug) каждые 5 мин = ~24-100 calls. Token'изированный Gitea не упрётся.
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] docker-compose.yml с двумя сервисами и traefik labels
|
||||
- [x] Dockerfile.build для cron-loop image
|
||||
- [x] nginx.conf для static serving
|
||||
- [x] auth.toml.example + .env.example + README.md с полным install path
|
||||
- [x] .gitignore исключает реальные `auth.toml` и `.env`
|
||||
- [x] compose syntax validated via `docker compose config`
|
||||
|
||||
## Pending (user-side actions)
|
||||
|
||||
- [ ] DNS A-record `board.kzntsv.site` → VDS IP
|
||||
- [ ] `docker build && docker push` на dev-машине → `registry.kzntsv.site/board-viewer-build:latest`
|
||||
- [ ] Bootstrap `/opt/stacks/board-viewer/` на VDS + заполнить `auth.toml` и `.env`
|
||||
- [ ] `docker compose up -d` + smoke `curl -u user:pass https://board.kzntsv.site/`
|
||||
|
||||
## Notes
|
||||
|
||||
Зависит от `board-viewer-html-render` — нужен артефакт для деплоя (хотя бы placeholder). Можно стартовать параллельно с DNS-записью (она прогревается).
|
||||
@@ -0,0 +1,43 @@
|
||||
# board-viewer-drawer-bundle-md
|
||||
|
||||
## Goal
|
||||
|
||||
Drawer падает с `TypeError: Failed to fetch` при клике на любую карточку. Root-cause: `rawUrl` указывает на `git.kzntsv.site/<owner>/<repo>/raw/branch/<branch>/<path>` — приватные репо требуют auth, в браузере токена нет (он только в build-контейнере для статической генерации, `auth.toml` ro-mounted). Browser fetch без `Authorization` → Gitea отдаёт 401 / 302→login HTML → fetch воспринимает как failure → TypeError.
|
||||
|
||||
Решение: **bundle per-task md-content в build-time**. Build-контейнер уже знает где per-task `.md` файлы (он их fetch'ит для парсинга), у него есть Gitea token. Расширить read-фазу: вместе с STATUS.md тянуть и per-task .md (если существует), складывать в `dist/tasks-md.json` или встраивать в `<script type="application/json" id="tasks-md">…</script>` в `index.html`. Drawer читает из этого локального data вместо fetch.
|
||||
|
||||
## TDD posture
|
||||
|
||||
**Mode = TDD** (project CLAUDE.md имеет `follow tdd-criteria`). Failing test первым:
|
||||
|
||||
- Unit: reader получает per-task md content параллельно с STATUS.md, кладёт в `TaskRecord.md_content?: string`
|
||||
- Render: каждая карточка имеет `data-md-encoded` или используется shared JSON-bundle с `data-md-key={slug}`
|
||||
- Drawer test (board.js): открывает draweр по data-key, читает из bundled JSON, рендерит через `marked.parse` — без fetch
|
||||
- E2E smoke: открыть локально dist/index.html в browser-driver (или mocked DOM), кликнуть карту → drawer показывает md content, не «Failed to load»
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/gitea.ts` — добавить метод `getRawContent(owner, repo, path)` через API (`/api/v1/repos/.../contents/`+base64-decode или `/api/v1/repos/.../raw/...` с токеном)
|
||||
- `src/reader.ts` — расширить чтобы для каждого `TaskRecord` пулить per-task `.md` (если file exists)
|
||||
- `src/render.ts:136` — добавить data-md-key вместо data-raw-url, либо встроить JSON-bundle в html
|
||||
- `static/board.js:200-215` — `openDrawer` читает из localStorage / global var вместо fetch
|
||||
- `tests/` — fixture для per-task md, новые тесты
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Все 49 текущих тестов всё ещё pass + новые тесты для md-bundle pipeline
|
||||
- E2E smoke на real board.kzntsv.site (live): клик на любую карту → drawer заполняется per-task content без network-запросов на git.kzntsv.site
|
||||
- DevTools Network: открыть карту → 0 запросов исходящих к git.kzntsv.site/raw
|
||||
- Размер `index.html` или `tasks-md.json` reasonable (предположительно <500 KB на 80 records — markdown текстовый, gzip сильно сожмёт)
|
||||
- Карты без per-task `.md` файла (status=ready без deep-context) — drawer показывает только title + status block, без «Failed to load»
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: создана из user UX-feedback round1 — drawer ломается с TypeError. Root-cause grounded: приватные репо + no browser token. Импл-направление = bundle, не proxy (proxy потребовал бы nginx с auth_request к board-viewer-build или Gitea token в nginx — переусложнение). Bundle deterministic + no runtime network.
|
||||
|
||||
## Notes
|
||||
|
||||
- Bundle approach впишется в существующий cron-loop pattern (build every 300s регенирует index.html + tasks-md). Никаких runtime-зависимостей от Gitea API.
|
||||
- Файлы могут быть rotated/архивированы (см. сестринскую таску `board-viewer-ux-done-cutoff`).
|
||||
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
|
||||
69
.tasks/done/2026-05-22-00201-board-viewer-gitea-reader.md
Normal file
69
.tasks/done/2026-05-22-00201-board-viewer-gitea-reader.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# board-viewer-gitea-reader
|
||||
|
||||
## Goal
|
||||
|
||||
Модуль, который через Gitea API читает `.tasks/STATUS.md` + per-task `<slug>.md` файлы из всех релевантных репо (`OpeItcLoc03/agenda` + per-project репо из `~/.config/projects-mcp/auth.toml::gitea_owners`) и нормализует в типизированную структуру для рендера.
|
||||
|
||||
Это первая импл-таска. Остальные импл-таски (`board-viewer-html-render`, `board-viewer-cron-deploy`) зависят от типов, которые этот модуль публикует.
|
||||
|
||||
**TDD-mode:** обязательно по `follow tdd-criteria` триггеру в `.wiki/CLAUDE.md`. Сначала тесты на нормализацию пар фикстур (STATUS.md + per-task.md) → потом реализация. Не модифицировать assert'ы во время реализации без `[test-modify: ...]` маркера в commit subject.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/parser.ts` — STATUS.md → `StatusBlock[]` (pure)
|
||||
- `src/gitea.ts` — Gitea HTTP client (DI'd fetch, `getFile` / `getLatestCommitIso` / `rawUrl`)
|
||||
- `src/reader.ts` — `readBoard(client, repos) → TaskRecord[]`; orchestrates parser+client
|
||||
- `src/config.ts` — loads `~/.config/projects-mcp/auth.toml` → `{ baseUrl, token, repos }`
|
||||
- `tests/fixtures/` — `status-single-block.md`, `status-multi.md`, real STATUS.md from books / claude-skills / projects-meta-mcp
|
||||
- `tests/parser.test.ts` (9), `tests/gitea.test.ts` (7), `tests/reader.test.ts` (5), `tests/config.test.ts` (4) — 25 tests total
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- **Output contract:** массив объектов с полями:
|
||||
```
|
||||
{
|
||||
slug: string,
|
||||
project: string, // repo name (e.g. "board-viewer", "books")
|
||||
project_owner: string, // gitea owner (e.g. "OpeItcLoc03")
|
||||
status: "open" | "in_progress" | "paused" | "blocked" | "done",
|
||||
status_emoji: "⚪" | "🔴" | "🟡" | "🔵" | "🟢",
|
||||
title: string, // from STATUS.md block H2 after slug
|
||||
where_stopped: string | null,
|
||||
next_action: string | null,
|
||||
blocker: string | null,
|
||||
branch: string | null,
|
||||
last_commit_iso: string | null, // from Gitea API, latest commit touching the task file
|
||||
raw_url: string, // Gitea raw URL to per-task <slug>.md for drawer lazy-fetch
|
||||
}
|
||||
```
|
||||
- **Inputs:** список репо из конфига (env var или TOML).
|
||||
- **Auth:** Gitea token из `~/.config/projects-mcp/auth.toml::gitea_token` (admin scope, читает любой репо).
|
||||
- **Robustness:** репо без `.tasks/` → пропустить молча (не ошибка). `.tasks/STATUS.md` без emoji-блоков → пустой массив для этого проекта.
|
||||
- **Tests:** фикстуры из ≥3 реальных репо (board-viewer, books, .workshop), assertions покрывают: нормальный case, status без per-task.md (только STATUS.md block), per-task без записи в STATUS.md (legacy), blocked-таски с `Blocker:` строкой.
|
||||
- **Single language:** один из TS / Python. Выбор — за имплементером (TS легче переиспользует с HTML-рендером в одном процессе; Python проще читать конфиг).
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: task создан промоушеном; контракт фиксирован — это публичный API для html-render.
|
||||
- 2026-05-22: emoji-словарь выравнен под реальные `.tasks/STATUS.md` (using-tasks skill): ⚪ open / 🔴 in_progress / 🟡 paused / 🔵 blocked / 🟢 done. Раньше дизайн-док и acceptance путали 🔴/🔵 — исправлено.
|
||||
- 2026-05-22: язык — TypeScript / Node (vitest, ESM, Node 22). Repo discovery — whitelist `board_viewer_repos` в auth.toml. Без кэша Gitea API на MVP.
|
||||
- 2026-05-22 (post-review): "per-task без записи в STATUS.md (legacy)" из acceptance criteria — intentionally not supported. STATUS.md = index of record per design (`.wiki/concepts/board-viewer.md` строка 44: "SoT не меняется: `.tasks/<slug>.md` + `STATUS.md`"). Orphan per-task `.md` файлы без блока в STATUS.md — out of scope reader'а. Если потребуется — отдельная таска `reader-list-orphan-tasks`.
|
||||
|
||||
## Open questions
|
||||
|
||||
- [x] TS (Node 22, vitest, ESM) — выбор подтверждён юзером 2026-05-22.
|
||||
- [x] Whitelist `board_viewer_repos` в `auth.toml` — выбран как явный и контролируемый.
|
||||
- [x] Без кэширования на MVP — Gitea на том же VDS, latency negligible.
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] парсер STATUS.md (`src/parser.ts`) с маппингом ⚪🔴🟡🔵🟢 → open/in_progress/paused/blocked/done
|
||||
- [x] Gitea HTTP client (`src/gitea.ts`): getFile / getLatestCommitIso / rawUrl, DI fetch
|
||||
- [x] reader orchestrator (`src/reader.ts`): объединяет parser+client → TaskRecord[]
|
||||
- [x] TOML config loader (`src/config.ts`) с whitelist `board_viewer_repos`
|
||||
- [x] 25 тестов: 9 парсера (включая 3 real-repo фикстуры), 7 gitea, 5 reader, 4 config
|
||||
- [x] typecheck clean
|
||||
|
||||
## Notes
|
||||
|
||||
NB: исходный буфер упоминал `mcp__projects-meta__tasks_aggregate` как возможный источник — **это ложная развилка**, MCP недоступен HTTP-сервису. Только прямой Gitea API. См. `.wiki/concepts/board-viewer.md` секцию «Implementation note».
|
||||
60
.tasks/done/2026-05-22-00202-board-viewer-html-render.md
Normal file
60
.tasks/done/2026-05-22-00202-board-viewer-html-render.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# board-viewer-html-render
|
||||
|
||||
## Goal
|
||||
|
||||
Рендер 5-колоночного kanban-board из нормализованной структуры (продукт `board-viewer-gitea-reader`). Статический HTML + минимальный клиент-сайд JS для фильтра / drawer / refresh-таймера. Без write-side, без realtime.
|
||||
|
||||
**TDD-mode:** обязательно по `follow tdd-criteria`. Снапшот-тесты на HTML-output из фиксированных reader-фикстур. Не модифицировать assert'ы без `[test-modify: ...]` маркера. Визуальная отделка (CSS) — под carve-out «visual CSS», но логика рендера (распределение по колонкам, age-расчёт, drawer-привязка) — TDD.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts` — `renderBoard()`, `formatAge()`, `partitionByStatus()` (pure)
|
||||
- `src/cli.ts` — entry: load config → readBoard → renderBoard → `dist/index.html` + copy `static/`
|
||||
- `static/board.css` — raw CSS, system font, light/dark, ~190 LOC
|
||||
- `static/board.js` — ESM module: filter, drawer (lazy-fetch + `marked@14` from esm.sh), toggle-archived, toggle-grouping (full rerender into per-project columns)
|
||||
- `tests/render.test.ts` — 17 tests on render logic (no snapshots — semantic property assertions, more robust to harmless markup tweaks)
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- **5 колонок** по статусам: ⚪ open / 🔴 in-progress / 🟡 paused / 🔵 blocked / 🟢 done. Названия колонок — emoji + слово.
|
||||
- **Карточка:**
|
||||
- title (truncate на 80 chars в default-view),
|
||||
- project badge (e.g. `books`, `board-viewer`),
|
||||
- owner (если есть) — pill,
|
||||
- age — относительное (`3d`, `2w`, `5mo`) от `last_commit_iso`,
|
||||
- last-commit-marker — короткий hash или дата.
|
||||
- **Группировка по проекту** — toggle вверху: «по статусам» (5 колонок × проекты внутри) vs «по проектам» (per-project колонки × статусы внутри).
|
||||
- **Поиск/фильтр** — клиент-сайд input: substring по `slug`, `title`, `project`; multi-select по статусам.
|
||||
- **Per-card drawer** — клик по карточке → side panel с full markdown из per-task `<slug>.md` (lazy-fetch из `raw_url`). Markdown рендерится клиент-сайд (e.g. `marked` для TS, `markdown-it` для Py-side-rendered).
|
||||
- **Refresh-таймер** видно в header: «refreshed Xm ago, next in Ym» (data attribute из generation timestamp).
|
||||
- **Output:** один `index.html` файл + sidecar `static/`. Открывается локально без сервера для smoke-test.
|
||||
- **Tests:** snapshot-тесты на 3 reader-фикстуры (пустая доска / 1-2 таски / N≥10 тасок). Snapshots checked-in.
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: task создан промоушеном; зависит от `board-viewer-gitea-reader` контракта.
|
||||
- 2026-05-22: emoji-set acceptance выравнен под reader контракт (⚪🔴🟡🔵🟢).
|
||||
- 2026-05-22: client-side `marked@14` via esm.sh — lazy-load на первое открытие drawer'а.
|
||||
- 2026-05-22: raw CSS, no framework — соответствует «whitespace + типографика» intent task notes.
|
||||
- 2026-05-22: done >14 дней архивируются (hidden by default, toggle «show archived»).
|
||||
- 2026-05-22: snapshot tests заменены 17 semantic-property тестами — устойчивее к косметическим изменениям markup'а.
|
||||
|
||||
## Open questions
|
||||
|
||||
- [x] Markdown-renderer → client-side `marked` via esm.sh (lazy на drawer).
|
||||
- [x] CSS → raw, no framework.
|
||||
- [x] Done archive → >14d hidden, toggle for show.
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] `formatAge` (m/h/d/w/mo/y, calendar-month arithmetic) — pure, TDD
|
||||
- [x] `partitionByStatus` — canonical 5-col order
|
||||
- [x] `renderBoard` — HTML with escaped fields, JSON embed XSS-safe (`\uXXXX` escapes)
|
||||
- [x] static/board.css — raw, light/dark
|
||||
- [x] static/board.js — filter, drawer, archived/grouping toggles, refresh-info
|
||||
- [x] src/cli.ts — wires config+reader+render+fs
|
||||
- [x] 17 render tests + smoke-verified e2e (78 records, 4 repos, browser-tested both toggle modes)
|
||||
|
||||
## Notes
|
||||
|
||||
Стиль: минимализм без аватарок/гифок/анимаций. Это инструмент глядеть на доску, не дашборд для презентации. Whitespace + типографика.
|
||||
58
.tasks/done/2026-05-22-00203-board-viewer-pointers.md
Normal file
58
.tasks/done/2026-05-22-00203-board-viewer-pointers.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# board-viewer-pointers
|
||||
|
||||
## Goal
|
||||
|
||||
Pre-impl bootstrap: заполнить `.wiki/CLAUDE.md` секцию «Domain conventions» pointer-блоком на спецификацию. Дизайн не лежит в этом репо целиком — только pointer-stub. Без этой таски следующий агент попадёт в дыру: dense `where_stopped` one-liner + пустой Domain conventions stub = угадывание архитектуры вместо чтения готовых решений.
|
||||
|
||||
**Кто делает:** любой следующий агент в этом проекте. Это **первая** по приоритету таска промоушена — все импл-таски ссылаются на pointers через `.wiki/CLAUDE.md`.
|
||||
|
||||
## Key files
|
||||
|
||||
- `.wiki/CLAUDE.md` — Domain conventions section
|
||||
- `.wiki/concepts/board-viewer.md` — canonical design (target of pointer)
|
||||
- `~/projects/.workshop/.archive/2026-05-22-board-viewer.md` — brainstorm rationale (target of pointer)
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- `.wiki/CLAUDE.md` содержит блок «Mandatory: read design context before implementation» в Domain conventions (см. Next action ниже — дословный текст).
|
||||
- Коммит с message `wiki(claude): add design-context pointers for board-viewer`.
|
||||
- Push в `origin/master`.
|
||||
|
||||
## Next action (готовый блок для копирования в `.wiki/CLAUDE.md` Domain conventions)
|
||||
|
||||
Заменить существующий комментарий-stub в секции `## Domain conventions` на следующий блок:
|
||||
|
||||
```markdown
|
||||
### Mandatory: read design context before implementation
|
||||
|
||||
Before picking up any task in `.tasks/`, load the full design context. It does **not** live in this repo fully — only pointers do. Sources, in order:
|
||||
|
||||
1. **Canonical design:** `.wiki/concepts/board-viewer.md`. Architecture decisions, scope, MVP feature list, anti-patterns (hermes-style, Gitea Projects), trade-offs.
|
||||
2. **Brainstorm process trace (rationale):** `~/projects/.workshop/.archive/2026-05-22-board-viewer.md`. Why each decision was made, what was rejected and why, clarifying note that `mcp__projects-meta__*` is not callable from HTTP service.
|
||||
3. **Local `overview.md`** — quick orientation summary; never source of truth.
|
||||
|
||||
Do **not** invent thresholds, taxonomies, container topology, or pipeline stages from task `where_stopped` lines alone — those are pointers, not specifications.
|
||||
|
||||
### TDD posture
|
||||
|
||||
Per `follow tdd-criteria` trigger in this CLAUDE.md: impl-tasks `board-viewer-gitea-reader` and `board-viewer-html-render` are TDD. `board-viewer-cron-deploy` falls under the infrastructure carve-out (declarative systemd / traefik / DNS — verify by deploy + curl, not unit tests). Mark each impl commit accordingly.
|
||||
```
|
||||
|
||||
После вставки: коммит, push.
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: pointers task создан промоушеном из `.workshop/.brainstorm/board-viewer.md` (workshop-promote-brainstorm).
|
||||
- 2026-05-22: pointer-блок вставлен в `.wiki/CLAUDE.md` Domain conventions дословно из Next action; closed.
|
||||
|
||||
## Open questions
|
||||
|
||||
- [ ] (нет — pointer-блок самодостаточен)
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] вставлен блок «Mandatory: read design context before implementation» + «TDD posture» в `.wiki/CLAUDE.md`
|
||||
|
||||
## Notes
|
||||
|
||||
Pointer-таска намеренно тривиальная — её существование защищает следующего агента от чтения stub'а вместо спецификации. Не объединять с импл-тасками.
|
||||
60
.tasks/done/2026-05-22-00204-board-viewer-polish.md
Normal file
60
.tasks/done/2026-05-22-00204-board-viewer-polish.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# board-viewer-polish
|
||||
|
||||
## Goal
|
||||
|
||||
Bundle of minor / nitpick findings from `board-viewer-review` checkpoint. Each — small, individually below the task-creation threshold; collectively worth doing in one sweep.
|
||||
|
||||
## Items
|
||||
|
||||
### M1. Document parser's `**Status:**` field neglect
|
||||
|
||||
- **Location:** `src/parser.ts` (in `parseOneBlock`)
|
||||
- **Problem:** `**Status:**` text field is matched by `FIELD_RE` but never stored in `StatusBlock` (emoji is SoT). Future maintainer reads parser and wonders why.
|
||||
- **Fix:** comment `// **Status:** text field intentionally ignored — emoji is canonical` near where it's matched, OR `delete fields['Status']` after parse to avoid the orphan field in local var. Choose the comment.
|
||||
|
||||
### M2. `formatAge` future-timestamp behavior is undocumented
|
||||
|
||||
- **Location:** `src/render.ts:31` + `static/board.js:49`
|
||||
- **Problem:** `Math.max(0, …)` silently clamps future ISO to "0m". With server-clock drift на VDS vs Gitea это маскирует ошибку синхронизации.
|
||||
- **Fix:** add one test asserting `formatAge(futureIso, now) === '0m'` to lock behavior, and add inline comment "// clamps to '0m' on future iso — see test".
|
||||
|
||||
### M3. `gitea-reader` acceptance "per-task без записи в STATUS.md (legacy)" is impossible by design
|
||||
|
||||
- **Location:** `.tasks/board-viewer-gitea-reader.md` Decisions log
|
||||
- **Problem:** Acceptance line 42 promises tests for "per-task без записи в STATUS.md (legacy)" case, but the reader iterates STATUS.md blocks only — orphan per-task files are invisible by design (matches spec: STATUS.md is the index).
|
||||
- **Fix:** append a decisions-log entry to `board-viewer-gitea-reader.md` clarifying this is intentional; STATUS.md is the entry point, orphan per-task files are out of scope.
|
||||
|
||||
### M4. Client filter doesn't match status in by-status mode; "multi-select по статусам" not implemented
|
||||
|
||||
- **Location:** `static/board.js:139-147` (`applyFilter`)
|
||||
- **Problem:** Filter reads `.badge.status` / `.badge.project` text from DOM. In by-status mode there's no status badge → typing "blocked" matches nothing. Также acceptance в `board-viewer-html-render.md:27` обещает multi-select по статусам — не реализован.
|
||||
- **Fix:** rebuild `applyFilter` to query the underlying `records` array (status always available regardless of view mode). Decide on multi-select: either ship a status-pill filter row, or amend acceptance criteria. Recommendation: add multi-select pills below the filter input — simple `<button data-status="…" class="status-filter">` toggle.
|
||||
|
||||
### M5. Build container failures show only `echo` output, no Docker healthcheck
|
||||
|
||||
- **Location:** `deploy/Dockerfile.build:14`
|
||||
- **Problem:** `... || echo 'render failed; will retry next tick'` swallows non-zero exit. Operator must `docker compose logs build` to notice. No Docker healthcheck signals unhealthy.
|
||||
- **Fix:** emit failure to stderr (`>&2 echo`), and add a `HEALTHCHECK` to Dockerfile that checks `[ -f /output/index.html ]` AND `[ $(date +%s) - $(stat -c %Y /output/index.html) -lt 600 ]` (file exists AND fresher than 10 min). Compose `web` service can `depends_on` health.
|
||||
|
||||
### M6. `getLatestCommitIso` called serially, N=80+ per tick
|
||||
|
||||
- **Location:** `src/reader.ts:36-37`
|
||||
- **Problem:** `for...of await` serializes 80+ HTTP calls. Within budget for now but a Gitea hiccup serializes through all of them.
|
||||
- **Fix:** parallelize per-repo or globally with `Promise.all(blocks.map(...))`. Cap concurrency at ~10 if politeness is a concern.
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: bundle создан промоушеном из review-чекпойнта; severity варьируется minor → nitpick; bundled, чтобы не плодить 6 микро-тасок.
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] M1: comment в `parser.ts` объясняющий что `**Status:**` text field intentionally ignored (emoji canonical)
|
||||
- [x] M2: тест clamps future timestamps to "0m" + inline comment в formatAge
|
||||
- [x] M3: decisions-log entry в gitea-reader.md: orphan per-task без блока в STATUS.md — out of scope by design
|
||||
- [x] M4: filter rebuilt to query underlying records (status always known); 5 status-filter pill buttons добавлены в header; click toggles active set; CSS `.status-filter.active` filled. Smoke-verified: click "blocked" → только 1 blocked задача видна.
|
||||
- [x] M5: HEALTHCHECK в Dockerfile.build (index.html exists AND fresher than 2×tick); failures redirected to stderr с timestamp
|
||||
- [x] M6: `getLatestCommitIso` parallelized via Promise.all per-repo (preserves block order)
|
||||
|
||||
## Notes
|
||||
|
||||
Каждый item — TDD по тому, что есть testable. M1/M3 — comment/docs only ([skip-tdd: visual]). M2 — pure test. M4/M5/M6 — TDD logic.
|
||||
@@ -0,0 +1,30 @@
|
||||
# board-viewer-readme-emoji-fix
|
||||
|
||||
## Goal
|
||||
|
||||
Fix stale emoji legend в `README.md`. README — первое, что видит человек открывая repo. Сейчас он указывает старый emoji-набор `⚪ 🟡 🟣 🔴 🟢` (с фиолетовым и красным=blocked), который был исправлен mid-stream на корректный `⚪ 🔴 🟡 🔵 🟢` (everywhere else в код-базе).
|
||||
|
||||
Найдено в review-checkpoint (`board-viewer-review`).
|
||||
|
||||
## Key files
|
||||
|
||||
- `README.md` строка 5 — "About" с emoji-словарём
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- README.md строка 5 содержит корректный emoji-словарь: `⚪ open / 🔴 in-progress / 🟡 paused / 🔵 blocked / 🟢 done`
|
||||
- Заодно — заполнить TBD блок "Quick start" одной командой: `npx vitest run` + `node --experimental-strip-types src/cli.ts <auth-toml-path>`
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: findings зафайлены в review-чекпойнте; severity=major (стартовое впечатление, рассинхрон со всем кодом).
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] README.md emoji legend ⚪🔴🟡🔵🟢 (with correct labels)
|
||||
- [x] Quick start блок заполнен: npm install → npm test → npm run typecheck → cli.ts → python -m http.server smoke
|
||||
- [x] Добавлен Deploy + Architecture секции (бонус — README был анемичный)
|
||||
|
||||
## Notes
|
||||
|
||||
Trivially small fix — `[skip-tdd: visual]` (docs).
|
||||
62
.tasks/done/2026-05-22-00206-board-viewer-review.md
Normal file
62
.tasks/done/2026-05-22-00206-board-viewer-review.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# board-viewer-review
|
||||
|
||||
## Goal
|
||||
|
||||
Code-review checkpoint для брейнсторма `board-viewer` (промоушен 2026-05-22).
|
||||
|
||||
**Спецификация:** `.wiki/concepts/board-viewer.md`.
|
||||
**Pre-impl bootstrap:** `board-viewer-pointers` (заполнил `.wiki/CLAUDE.md` Domain conventions — без него review бы читал stub).
|
||||
**Импл-таски (review против их acceptance criteria):** `board-viewer-gitea-reader`, `board-viewer-html-render`, `board-viewer-cron-deploy`.
|
||||
|
||||
**Кто делает:** **не имплементер.** Следующая сессия в этом проекте (другая модель / другой день / другой агент) поднимает таску с чистым контекстом. «Я только что это написал» bias = главный риск.
|
||||
|
||||
## Key files
|
||||
|
||||
- `.wiki/concepts/board-viewer.md` — canonical design
|
||||
- `.tasks/board-viewer-*.md` — acceptance criteria per impl-task
|
||||
- `~/projects/.workshop/.archive/2026-05-22-board-viewer.md` — brainstorm rationale
|
||||
|
||||
## Acceptance criteria (для самого ревью)
|
||||
|
||||
- Прочитана спецификация целиком.
|
||||
- `git log --oneline` shipped-коммитов (по slug или scope в commit-message) сверен с acceptance criteria каждой импл-таски.
|
||||
- Для каждой импл-таски: прогнан соответствующий тест-suite, проверено что тесты реально доходят до своих веток (не coverage-illusion).
|
||||
- Сверены дизайн-decisions со shipped-кодом: контракт reader → render, статика + cron на VDS, отсутствие write-side.
|
||||
- Findings зафайлены как follow-up tasks (`board-viewer-<gap>-fix` или подобное) через `mcp__projects-meta__tasks_create`, либо ревьюер подтвердил «нет findings» в close-note.
|
||||
|
||||
## Чек-лист ревью (poll-выполнения)
|
||||
|
||||
- [ ] reader: контракт публичных полей сохранён (не сужен, не расширен молча)
|
||||
- [ ] reader: фикстуры покрывают edge cases (legacy без per-task.md, blocked с blocker-строкой)
|
||||
- [ ] render: snapshot-тесты обновлены вместе с изменениями (не закоммичен код без апдейта snapshot)
|
||||
- [ ] render: визуально не AI-generic, минимализм соблюдён
|
||||
- [ ] cron-deploy: systemd unit + timer проверены `systemctl status`, не только запущены
|
||||
- [ ] cron-deploy: basic-auth проверена `curl` с верными / неверными кредами
|
||||
- [ ] auth scope: пушится ли токен в публичный репо случайно? (`grep -r "02a14e" deploy/` должен быть пуст)
|
||||
- [ ] disaster recovery: README.md в deploy/ достаточен чтобы поднять с нуля?
|
||||
|
||||
## Decisions log
|
||||
|
||||
- 2026-05-22: review-task создан промоушеном; status=blocked, blocker = все 4 импл-таски.
|
||||
- 2026-05-22: review выполнен subagent'ом (general-purpose, clean context). 43/43 тестов pass, TDD-immutability clean (no `[test-modify:]` markers needed — diffs append-only), secret hygiene OK (`02a14e` grep clean across `deploy/`). 2 Major findings + 6 minor/nitpick — зафайлены: `board-viewer-readme-emoji-fix`, `board-viewer-card-meta-fix`, `board-viewer-polish` (бандл).
|
||||
- 2026-05-22: findings зафайлены LOCAL в `.tasks/` (project-discipline Rule 1 override), не через `mcp__projects-meta__tasks_create` (это локальные таски, не cross-project).
|
||||
|
||||
## Open questions
|
||||
|
||||
- [x] (нет — все findings зафайлены)
|
||||
|
||||
## Completed steps
|
||||
|
||||
- [x] спецификация прочитана subagent'ом
|
||||
- [x] `git log --oneline` сверен с acceptance criteria каждой импл-таски
|
||||
- [x] test suite прогнан (43/43 pass)
|
||||
- [x] design ↔ shipped contract проверен
|
||||
- [x] TDD-immutability audit (clean)
|
||||
- [x] secret hygiene grep (clean)
|
||||
- [x] findings зафайлены follow-up тасками
|
||||
|
||||
## Notes
|
||||
|
||||
**Закрытие:** только когда все findings зафайлены ИЛИ ревьюер подтвердил «нет findings» в close-note.
|
||||
|
||||
**TDD-immutability:** если ревью обнаружит модифицированные assert'ы без `[test-modify: ...]` маркера в commit subject — это нарушение `follow tdd-criteria`, finding обязателен.
|
||||
40
.tasks/done/2026-05-22-00207-board-viewer-ux-done-cutoff.md
Normal file
40
.tasks/done/2026-05-22-00207-board-viewer-ux-done-cutoff.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# board-viewer-ux-done-cutoff
|
||||
|
||||
## Goal
|
||||
|
||||
User: «чаще нужна архивная чистка — очень длинный хвост в done». Колонка 🟢 done растёт без ограничения; visual noise. Нужна фильтрация / cutoff / collapse.
|
||||
|
||||
## Design open questions — РЕШИТЬ ДО ИМПЛА
|
||||
|
||||
1. **Что архивная чистка должна делать на UI-стороне** (источник = STATUS.md остаётся неизменным, чистка — render-time)?
|
||||
- Hide-by-default + toggle «show done» (текущий `toggleArchived` уже близко к этому, но done ≠ archived)
|
||||
- Last-N cutoff: показать последние N done (например 10) per column, остальные за «N more…»
|
||||
- Date-cutoff: показать done за последние 7 / 14 / 30 дней, остальные скрыты
|
||||
- Combined: hide by default, expander «show older»
|
||||
2. **Что считается «done для скрытия»?** Все 🟢 или только те у которых close-date старше N дней?
|
||||
3. **Должен ли source (STATUS.md) фактически archive'ить старые** (физически переносить в отдельный `.tasks/_archive/<date>.md`)? Это уже не UI а layout-вопрос. Recommend: **нет**, source неизменен, всё на UI-стороне — иначе любая повторная сборка ломается при reorganization.
|
||||
4. **Default:** hide-done by default или show-all? Если hide — user тыкает один раз чтобы увидеть, плюс — чистый default-вид. Recommend hide-by-default + видимый toggle в шапке.
|
||||
|
||||
## Pre-impl
|
||||
|
||||
- Brainstorm перед импл. Если решения 4-х вопросов выше bundle'ятся в один очевидный recommendation — стартовать impl сразу с design-комментарием в decisions log. Если возникает дилемма — `.workshop/.brainstorm/board-viewer-done-cutoff.md`.
|
||||
|
||||
## TDD posture
|
||||
|
||||
**Mode = TDD** (после resolve design). Failing test: render с N done > cutoff показывает только cutoff штук + «N more» link/button. Цикл RED→GREEN.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts` (column-rendering — добавить cutoff + expander)
|
||||
- `static/board.js` (expander click handler)
|
||||
- `static/board.css` (styling collapsed state)
|
||||
- `tests/render.test.ts`
|
||||
|
||||
## Acceptance
|
||||
|
||||
- В колонке 🟢 done видны только последние N (число согласовано в design) — остальные скрыты под expander
|
||||
- Toggle / expander работает (DevTools click test)
|
||||
- Source STATUS.md не модифицируется (verify через `git status` after smoke)
|
||||
- Тесты pass
|
||||
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
|
||||
@@ -0,0 +1,30 @@
|
||||
# board-viewer-ux-full-datetime
|
||||
|
||||
## Goal
|
||||
|
||||
Сейчас карточка показывает `YYYY-MM-DD` + tooltip с full ISO + относительное `7h` (per `board-viewer-card-meta-fix`). User: «хочется видеть дату и время, а не только дату в непонятном форматировании».
|
||||
|
||||
Заменить short ISO на `YYYY-MM-DD HH:MM` (timezone-suffix явный — `МСК` / `UTC`, чтобы не было ambiguity). Relative «7h» оставить (компактный signal recency) или убрать — зависит от ответа user'а в impl-сессии.
|
||||
|
||||
## Design open questions
|
||||
|
||||
- TZ: МСК (UTC+3) или UTC? User в МСК, но board может смотреться из других мест. Recommend МСК с явным суффиксом.
|
||||
- Format: `2026-05-22 14:35 МСК` или ISO `2026-05-22T14:35+03:00`? Recommend первое — читаемее.
|
||||
- Relative «7h» — сохранить или убрать? Recommend сохранить как тоненькую вторую строку под датой.
|
||||
|
||||
## TDD posture
|
||||
|
||||
**Mode = TDD**. Failing test для нового format'а в `render.test.ts` (или эквивалент в card-meta-fix tests). Impl в `src/render.ts`.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts` (formatLastCommitIso или эквивалент функция)
|
||||
- `tests/render.test.ts`
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Карточка показывает `2026-05-22 14:35 МСК` (или согласованный format) — не только дату
|
||||
- Tooltip остаётся (full ISO с секундами для копи-паста)
|
||||
- Тесты pass с новым форматом
|
||||
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
|
||||
@@ -0,0 +1,29 @@
|
||||
# board-viewer-ux-owner-conditional
|
||||
|
||||
## Goal
|
||||
|
||||
User: «`OpeItcLoc03` — почему везде один и тот же owner? Зачем он вообще?» Owner-pill добавлен `board-viewer-card-meta-fix` как differentiator для cross-owner whitelist'а, но в текущем VDS-конфиге все 5 репо в одном namespace (`OpeItcLoc03/*`) → pill дублируется на каждой карточке, visual noise.
|
||||
|
||||
Рендерить owner **условно**: если на текущем рендере все видимые карточки имеют один и тот же owner — pill скрыть, owner один раз показать в шапке доски (например «Owner: OpeItcLoc03 (5 repos)»). При ≥2 distinct owners — pill вернуть на карточки.
|
||||
|
||||
## TDD posture
|
||||
|
||||
**Mode = TDD**. Failing test: render с фикстурой single-owner → cards без `.owner-pill`, шапка содержит owner-summary. Render с фикстурой multi-owner → cards содержат `.owner-pill`, шапка без owner-summary (или с list). Затем impl.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts` — pre-render scan distinct owners, conditional output
|
||||
- `static/board.css` — стиль для header owner-summary
|
||||
- `tests/render.test.ts` — два теста (single-owner / multi-owner fixtures)
|
||||
|
||||
## Acceptance
|
||||
|
||||
- При single-owner whitelist карточки чище (без duplicate pill); owner один раз в шапке
|
||||
- При multi-owner — pill возвращается, шапка не дублирует
|
||||
- Тесты pass
|
||||
|
||||
## Notes
|
||||
|
||||
- Связанная ops-проверка (см. `.admin/[board-viewer-redeploy-ux-round1]`): верифицировать что `board_viewer_repos` в `/opt/stacks/board-viewer/auth.toml` корректно — `books` по memory `Gitea owner namespace` должен быть `victor/books`, не `OpeItcLoc03/books`. Если auth.toml корректен и whitelist single-owner — UI conditional работает как задумано.
|
||||
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
|
||||
25
.tasks/done/2026-05-22-00211-board-viewer-ux-show-slug.md
Normal file
25
.tasks/done/2026-05-22-00211-board-viewer-ux-show-slug.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# board-viewer-ux-show-slug
|
||||
|
||||
## Goal
|
||||
|
||||
Карточка не показывает slug визуально. Юзер видит только description (первая строка после `## 🟢 [<slug>] — <desc>`) и не понимает «какая это таска по имени». `data-slug` атрибут есть, но в header карточки не рендерится.
|
||||
|
||||
Добавить slug как видимый sub-heading или badge в верхней части карточки (под/над description-line, в моноширинном шрифте чтобы выделялся).
|
||||
|
||||
## TDD posture
|
||||
|
||||
**Mode = TDD** (project `follow tdd-criteria`). Сначала failing render-test: ожидать что output `renderCard(record)` содержит `<code class="card-slug">board-viewer-pointers</code>` (или эквивалент). Затем impl в `src/render.ts`.
|
||||
|
||||
## Key files
|
||||
|
||||
- `src/render.ts` (рендер карточки — добавить slug-element)
|
||||
- `static/board.css` (стиль `.card-slug` — monospace, dim color, маленький)
|
||||
- `tests/render.test.ts` — новый тест
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Slug виден на каждой карточке (любой статус)
|
||||
- Тест: html-output карточки содержит slug в parseable form (не только в data-атрибуте)
|
||||
- Smoke: визуально на board.kzntsv.site после redeploy — slug читается, не сливается с description
|
||||
|
||||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
|
||||
Reference in New Issue
Block a user