feat(viewer): bundle md + slug + conditional owner pill [v0.2.0]

Closes 3 of 6 UX-round1 fixes in one TDD cycle (49→56 tests).

drawer-bundle-md: TaskRecord.md_content fetched in build-time;
drawer reads recordsBySlug instead of fetch — 0 runtime hits to
git.kzntsv.site/raw (private repos 401'd browser fetches without token).
Fallback dl-block when md=null.

ux-show-slug: <code class="card-slug"> rendered under card-title in
both render.ts and board.js renderCard (re-render path).

ux-owner-conditional: distinctOwner(records) pre-scan. Single owner →
pill omitted on cards, header gets <span class="owner-summary">
Owner: X</span>. Multi-owner → pill restored, no header summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
vitya
2026-05-22 18:51:12 +03:00
parent 768167e376
commit fdca0b375a
8 changed files with 253 additions and 54 deletions

View File

@@ -26,7 +26,11 @@ _Updated: 2026-05-22_
describe('readBoard', () => {
test('merges parser output with project metadata and Gitea last-commit + raw URL', async () => {
const client = fakeClient({
getFile: vi.fn().mockResolvedValue(STATUS_MD_ONE),
getFile: vi.fn(async (_o: string, _r: string, path: string) => {
if (path === '.tasks/STATUS.md') return STATUS_MD_ONE;
if (path === '.tasks/task-a.md') return '# task-a\n\ndeep context body.';
return null;
}),
getLatestCommitIso: vi.fn().mockResolvedValue('2026-05-22T09:00:00Z'),
rawUrl: vi.fn().mockReturnValue(
'https://git.kzntsv.site/OpeItcLoc03/board-viewer/raw/branch/master/.tasks/task-a.md',
@@ -50,9 +54,40 @@ describe('readBoard', () => {
last_commit_iso: '2026-05-22T09:00:00Z',
raw_url:
'https://git.kzntsv.site/OpeItcLoc03/board-viewer/raw/branch/master/.tasks/task-a.md',
md_content: '# task-a\n\ndeep context body.',
});
});
test('per-task .md missing (404) → md_content is null, record still emitted', async () => {
const client = fakeClient({
getFile: vi.fn(async (_o: string, _r: string, path: string) => {
if (path === '.tasks/STATUS.md') return STATUS_MD_ONE;
return null;
}),
getLatestCommitIso: vi.fn().mockResolvedValue(null),
});
const records = await readBoard(client, [{ owner: 'o', repo: 'r' }]);
expect(records).toHaveLength(1);
expect(records[0]!.md_content).toBeNull();
});
test('fetches per-task .md alongside STATUS.md (.tasks/<slug>.md path)', async () => {
const getFile = vi.fn(async (_o: string, _r: string, path: string) => {
if (path === '.tasks/STATUS.md') return STATUS_MD_ONE;
return 'body';
});
const client = fakeClient({
getFile,
getLatestCommitIso: vi.fn().mockResolvedValue(null),
});
await readBoard(client, [{ owner: 'o', repo: 'r' }]);
expect(getFile).toHaveBeenCalledWith('o', 'r', '.tasks/task-a.md');
});
test('skips repo silently when STATUS.md is missing (returns no records for that repo)', async () => {
const client = fakeClient({
getFile: vi.fn().mockResolvedValue(null), // 404