closes board-viewer-polish
- M1 src/parser.ts: comment explaining why **Status:** text field is intentionally
ignored (emoji is canonical, prevents future-reader head-scratch)
- M2 src/render.ts + tests: clamp future ISO to '0m' (server clock drift safety)
+ inline comment + test
- M3 .tasks/board-viewer-gitea-reader.md: decisions-log clarifying orphan
per-task files are out of scope by design (STATUS.md = index of record)
- M4 static/board.{js,css} + src/render.ts: filter rebuilt to query records
(not DOM badge text); 5 status-filter pill buttons in header with active
toggle state; smoke-verified blocked filter
- M5 deploy/Dockerfile.build: HEALTHCHECK (file fresher than 2×tick) and
stderr redirect for failures with timestamp
- M6 src/reader.ts: parallelize getLatestCommitIso per-repo via Promise.all
(preserves block order)
49 tests pass (was 43 pre-polish), typecheck clean, smoke against real Gitea
returns 81 records from 4 repos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
61 lines
4.7 KiB
Markdown
61 lines
4.7 KiB
Markdown
# 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.
|