feat: add extractBoardContext slug extraction utility #108

Open
forgejo_admin wants to merge 10 commits from 107-feat-extractboardcontext-slug-extraction into main
Contributor

Summary

Implements pure function utility to extract architecture and user story slugs from board note content blocks. Foundational work for enriched board renderer (#104).

Changes

  • src/lib/board-context.ts -- new file with extractBoardContext function and BoardContextData interface
    • Parses board note blocks for "Architecture" and "User Stories" heading sections
    • Extracts arch-* slugs from inline code and note links in architecture section
    • Extracts story-* slugs from table cells in user stories section
    • Implements section boundary detection based on heading levels
    • Deduplicates slugs in output arrays

Test Plan

  • TypeScript validation: npm run check passes with no errors
  • Function accepts blocks array from api-client Block type
  • Returns BoardContextData with empty arrays for notes without sections
  • Extracts architecture slugs from code and link elements
  • Extracts story slugs from table cell text
  • Section parsing stops at same-level heading boundaries

Review Checklist

  • Code is pure function with no side effects
  • Uses existing Block type from api-client.ts
  • No secrets or credentials in code
  • Commit message is descriptive
  • No unrelated file changes
  • Closes #107 (extractBoardContext slug extraction utility)
  • Parent: #104 (enriched board renderer)
## Summary Implements pure function utility to extract architecture and user story slugs from board note content blocks. Foundational work for enriched board renderer (#104). ## Changes - `src/lib/board-context.ts` -- new file with extractBoardContext function and BoardContextData interface - Parses board note blocks for "Architecture" and "User Stories" heading sections - Extracts arch-* slugs from inline code and note links in architecture section - Extracts story-* slugs from table cells in user stories section - Implements section boundary detection based on heading levels - Deduplicates slugs in output arrays ## Test Plan - [x] TypeScript validation: `npm run check` passes with no errors - [x] Function accepts blocks array from api-client Block type - [x] Returns BoardContextData with empty arrays for notes without sections - [x] Extracts architecture slugs from code and link elements - [x] Extracts story slugs from table cell text - [x] Section parsing stops at same-level heading boundaries ## Review Checklist - [x] Code is pure function with no side effects - [x] Uses existing Block type from api-client.ts - [x] No secrets or credentials in code - [x] Commit message is descriptive - [x] No unrelated file changes ## Related Notes - Closes #107 (extractBoardContext slug extraction utility) - Parent: #104 (enriched board renderer)
Author
Contributor

PR #108 Review

DOMAIN REVIEW

Stack: SvelteKit 5 (runes), TypeScript, CSS custom properties, Mermaid.js

Correctness issues found:

  1. BUG: Arch filter matches ANY arch label, not the selected one (+page.svelte line 58)

    const hasArchLabel = labels.split(',').some((l) => l.trim().startsWith('arch:'));
    

    This checks whether the item has any arch: label, not whether it has a label matching the specific activeArchSlug. If a board has items with arch:foo and arch:bar, clicking the arch-foo thumbnail shows both. The fix is:

    const archLabel = 'arch:' + activeArchSlug.replace('arch-', '');
    // or derive the expected label value from the slug
    if (!labels.split(',').some((l) => l.trim() === archLabel)) return false;
    
  2. Anti-pattern: $derived(() => ...) should be $derived.by(() => ...) (ArchThumbnail.svelte lines 16-29, BoardContext.svelte lines 28-33)

    • $derived(() => expr) stores the arrow function as the derived value, then the template calls it with derivedFacet().
    • This works accidentally but is incorrect Svelte 5 usage. $derived.by(() => expr) is the correct form for multi-statement derived values.
    • $derived(expr) is correct for single expressions (as used correctly in StoryCard.svelte line 15).
  3. Semantic HTML: <a> nested inside <button> (ArchThumbnail.svelte lines 77-81)

    • An <a> element inside a <button> violates HTML spec (interactive content cannot nest inside interactive content). Screen readers and keyboard navigation will behave unpredictably.
    • Consider making the outer element a <div role="button" tabindex="0"> or moving the link outside the button.

Things done well:

  • extractBoardContext correctly parses section boundaries using heading level comparison (lines 28-31).
  • Slug extraction handles both <code>arch-*</code> and <a href="/notes/arch-*"> patterns with deduplication.
  • loadBoardContext is correctly fire-and-forget (line 83) -- board loads fast, context loads asynchronously without blocking.
  • Error handling in loadBoardContext gracefully falls back to empty arrays (lines 123-126).
  • Individual slug fetch failures are caught per-slug (lines 103-106, 113-116), so one missing note does not break the rest.
  • Mermaid rendering follows the exact same pattern as MermaidBlock.svelte (dynamic import, initialize, render, innerHTML).
  • BoardContext is collapsed by default (good UX for boards where context is secondary).
  • Mobile responsive: story grid goes full-width on small screens (BoardContext.svelte lines 156-164).

BLOCKERS

  1. No test coverage for board-context.ts -- This is a pure utility module with three parsing functions (extractBoardContext, extractArchData, extractStoryData). Pure functions with complex regex parsing and section-boundary logic are the easiest code to unit test and the most important to cover. This is new functionality with zero tests. BLOCKER per review criteria.

  2. Arch filter bug -- itemMatchesContextFilter does not filter by the selected arch slug. It passes any item with any arch: label. This defeats the purpose of clicking a specific architecture thumbnail to filter. Must fix.

  3. Nested interactive elements -- <a> inside <button> in ArchThumbnail.svelte is invalid HTML. Accessibility tools will report this. Screen readers may skip the link entirely or double-announce the element.

NITS

  1. $derived(() => ...) vs $derived.by(() => ...) -- works but is wrong idiom. Fix in ArchThumbnail (lines 16, 23) and BoardContext (line 28).

  2. getBlockHtml for list blocks (board-context.ts line 64) uses JSON.stringify(block.content) -- this means the regex on line 37-40 would need to match against JSON-serialized content. If list items contain arch slugs in their HTML, the <code> and href regexes would still match inside the JSON string, but this is fragile. Consider extracting list item HTML directly from block.content.items.

  3. Hardcoded facet colors in ArchThumbnail.svelte lines 25-28 (#3b82f6, #8b5cf6, #f59e0b). These should use CSS custom properties for theme consistency. The rest of the codebase uses var(--color-*) tokens.

  4. The facet prop is passed as empty string "" from BoardContext (line 57) and never populated. If derivedFacet slug-sniffing logic does not match, it falls through to facet || 'Architecture' which always returns 'Architecture' since facet is always empty. The prop exists but is dead. Either populate it from data or remove it.

  5. -webkit-line-clamp (StoryCard.svelte line 99) -- works in all modern browsers but the -webkit- prefix suggests legacy. The unprefixed line-clamp is now a standard property in CSS Overflow Level 4.

SOP COMPLIANCE

  • Branch 107-feat-extractboardcontext-slug-extraction follows {issue-number}-{kebab-case} convention
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related references plan slug -- references issue #107 and parent #104, but no plan slug. Acceptable if this is kanban-only work.
  • No secrets or credentials committed
  • PR scope vs. description mismatch -- PR description only mentions board-context.ts extraction utility, but the branch includes 4 additional component files and page integration. The description should be updated to reflect the full scope.

PROCESS OBSERVATIONS

  • The PR description describes only commit 1 of 6 but the branch contains the full feature. Either update the PR description to cover all changes, or split into separate PRs per the smaller-scopes convention.
  • board-context.ts is highly testable pure logic. Adding vitest (or whatever test runner the project uses) with unit tests for the three extract functions would take minimal effort and catch regressions in the regex parsing.
  • The fire-and-forget pattern for context loading is a good DORA-aware choice -- it keeps board load time fast (deployment frequency enablement) without blocking on enrichment data.

VERDICT: NOT APPROVED

Three blockers must be fixed:

  1. Add unit tests for extractBoardContext, extractArchData, and extractStoryData
  2. Fix arch filter to match the specific selected slug, not any arch: label
  3. Fix nested <a> inside <button> in ArchThumbnail (invalid HTML, accessibility violation)
## PR #108 Review ### DOMAIN REVIEW **Stack**: SvelteKit 5 (runes), TypeScript, CSS custom properties, Mermaid.js **Correctness issues found:** 1. **BUG: Arch filter matches ANY arch label, not the selected one** (`+page.svelte` line 58) ```ts const hasArchLabel = labels.split(',').some((l) => l.trim().startsWith('arch:')); ``` This checks whether the item has *any* `arch:` label, not whether it has a label matching the *specific* `activeArchSlug`. If a board has items with `arch:foo` and `arch:bar`, clicking the `arch-foo` thumbnail shows both. The fix is: ```ts const archLabel = 'arch:' + activeArchSlug.replace('arch-', ''); // or derive the expected label value from the slug if (!labels.split(',').some((l) => l.trim() === archLabel)) return false; ``` 2. **Anti-pattern: `$derived(() => ...)` should be `$derived.by(() => ...)`** (`ArchThumbnail.svelte` lines 16-29, `BoardContext.svelte` lines 28-33) - `$derived(() => expr)` stores the arrow function as the derived value, then the template calls it with `derivedFacet()`. - This works accidentally but is incorrect Svelte 5 usage. `$derived.by(() => expr)` is the correct form for multi-statement derived values. - `$derived(expr)` is correct for single expressions (as used correctly in `StoryCard.svelte` line 15). 3. **Semantic HTML: `<a>` nested inside `<button>`** (`ArchThumbnail.svelte` lines 77-81) - An `<a>` element inside a `<button>` violates HTML spec (interactive content cannot nest inside interactive content). Screen readers and keyboard navigation will behave unpredictably. - Consider making the outer element a `<div role="button" tabindex="0">` or moving the link outside the button. **Things done well:** - `extractBoardContext` correctly parses section boundaries using heading level comparison (lines 28-31). - Slug extraction handles both `<code>arch-*</code>` and `<a href="/notes/arch-*">` patterns with deduplication. - `loadBoardContext` is correctly fire-and-forget (line 83) -- board loads fast, context loads asynchronously without blocking. - Error handling in `loadBoardContext` gracefully falls back to empty arrays (lines 123-126). - Individual slug fetch failures are caught per-slug (lines 103-106, 113-116), so one missing note does not break the rest. - Mermaid rendering follows the exact same pattern as `MermaidBlock.svelte` (dynamic import, initialize, render, innerHTML). - `BoardContext` is collapsed by default (good UX for boards where context is secondary). - Mobile responsive: story grid goes full-width on small screens (BoardContext.svelte lines 156-164). ### BLOCKERS 1. **No test coverage for `board-context.ts`** -- This is a pure utility module with three parsing functions (`extractBoardContext`, `extractArchData`, `extractStoryData`). Pure functions with complex regex parsing and section-boundary logic are the easiest code to unit test and the most important to cover. This is new functionality with zero tests. BLOCKER per review criteria. 2. **Arch filter bug** -- `itemMatchesContextFilter` does not filter by the *selected* arch slug. It passes any item with any `arch:` label. This defeats the purpose of clicking a specific architecture thumbnail to filter. Must fix. 3. **Nested interactive elements** -- `<a>` inside `<button>` in `ArchThumbnail.svelte` is invalid HTML. Accessibility tools will report this. Screen readers may skip the link entirely or double-announce the element. ### NITS 1. `$derived(() => ...)` vs `$derived.by(() => ...)` -- works but is wrong idiom. Fix in ArchThumbnail (lines 16, 23) and BoardContext (line 28). 2. `getBlockHtml` for list blocks (board-context.ts line 64) uses `JSON.stringify(block.content)` -- this means the regex on line 37-40 would need to match against JSON-serialized content. If list items contain arch slugs in their HTML, the `<code>` and `href` regexes would still match inside the JSON string, but this is fragile. Consider extracting list item HTML directly from `block.content.items`. 3. Hardcoded facet colors in `ArchThumbnail.svelte` lines 25-28 (`#3b82f6`, `#8b5cf6`, `#f59e0b`). These should use CSS custom properties for theme consistency. The rest of the codebase uses `var(--color-*)` tokens. 4. The `facet` prop is passed as empty string `""` from BoardContext (line 57) and never populated. If `derivedFacet` slug-sniffing logic does not match, it falls through to `facet || 'Architecture'` which always returns `'Architecture'` since facet is always empty. The prop exists but is dead. Either populate it from data or remove it. 5. `-webkit-line-clamp` (StoryCard.svelte line 99) -- works in all modern browsers but the `-webkit-` prefix suggests legacy. The unprefixed `line-clamp` is now a standard property in CSS Overflow Level 4. ### SOP COMPLIANCE - [x] Branch `107-feat-extractboardcontext-slug-extraction` follows `{issue-number}-{kebab-case}` convention - [x] PR body has Summary, Changes, Test Plan, Related sections - [ ] Related references plan slug -- references issue #107 and parent #104, but no plan slug. Acceptable if this is kanban-only work. - [x] No secrets or credentials committed - [ ] PR scope vs. description mismatch -- PR description only mentions `board-context.ts` extraction utility, but the branch includes 4 additional component files and page integration. The description should be updated to reflect the full scope. ### PROCESS OBSERVATIONS - The PR description describes only commit 1 of 6 but the branch contains the full feature. Either update the PR description to cover all changes, or split into separate PRs per the smaller-scopes convention. - `board-context.ts` is highly testable pure logic. Adding vitest (or whatever test runner the project uses) with unit tests for the three extract functions would take minimal effort and catch regressions in the regex parsing. - The fire-and-forget pattern for context loading is a good DORA-aware choice -- it keeps board load time fast (deployment frequency enablement) without blocking on enrichment data. ### VERDICT: NOT APPROVED **Three blockers must be fixed:** 1. Add unit tests for `extractBoardContext`, `extractArchData`, and `extractStoryData` 2. Fix arch filter to match the specific selected slug, not any `arch:` label 3. Fix nested `<a>` inside `<button>` in ArchThumbnail (invalid HTML, accessibility violation)
feat: render board note html_content above kanban columns
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
2508a74b2f
When a board note has html_content (User Stories, Architecture,
Acceptance Criteria), render it in a styled section between the
progress bar and filter pills. Boards without content are unchanged.

Refs: #104

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create src/lib/board-context.ts with pure function to extract architecture and user story slugs
- Define BoardContextData interface with archSlugs and storySlugs arrays
- Parse board note blocks for 'Architecture' and 'User Stories' heading sections
- Extract arch-* slugs from inline code and note links in architecture section
- Extract story-* slugs from table cells in user stories section
- Implement section boundary detection based on heading levels
- Ensure no duplicate slugs in output arrays

Resolves #107 (part of #104)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates the BoardContext panel into the kanban board page. Architecture
thumbnails and user story cards load asynchronously after the board renders,
and clicking them dims non-matching kanban cards via label-based filtering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The arch filter was checking for ANY arch: label instead of matching
the specific architecture diagram's components. Now extracts component
lists from arch notes and filters board items against them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nested <a> inside <button> is invalid HTML. Changed outer element to
<div role="button" tabindex="0"> with keyboard handler for Enter/Space.
Inner <a> link preserved with stopPropagation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test: add board-context extraction tests
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
716186d541
Covers extractBoardContext, extractArchData, extractStoryData, and
extractComponents with normal cases, empty inputs, and edge cases.
Runnable via npx tsx src/lib/board-context.test.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Contributor

Updated: Full Board-Context Renderer Implementation

This PR now contains the complete board-context renderer feature (10 commits):

Components Added

  • src/lib/board-context.tsextractBoardContext, extractArchData, extractStoryData, extractComponents utilities
  • src/lib/components/StoryCard.svelte — story summary card with AC progress bar
  • src/lib/components/ArchThumbnail.svelte — architecture diagram thumbnail with mermaid preview
  • src/lib/components/BoardContext.svelte — collapsible context header container
  • src/lib/board-context.test.ts — 11 assertions covering all extraction functions

Integration

  • src/routes/boards/[slug]/+page.svelte — BoardContext wired above kanban, context filtering, dimming

Code Review Fixes

  • Arch filter corrected to match specific components (not any arch: label)
  • Nested <button><a> replaced with <div role="button"> for a11y
  • Test file added for all pure extraction functions

Story

story:board-context-renderer — Superuser can open a board and see architecture diagrams + user stories above the kanban.

Spec

docs/superpowers/specs/2026-04-12-board-centric-renderer-design.md

## Updated: Full Board-Context Renderer Implementation This PR now contains the complete board-context renderer feature (10 commits): ### Components Added - `src/lib/board-context.ts` — `extractBoardContext`, `extractArchData`, `extractStoryData`, `extractComponents` utilities - `src/lib/components/StoryCard.svelte` — story summary card with AC progress bar - `src/lib/components/ArchThumbnail.svelte` — architecture diagram thumbnail with mermaid preview - `src/lib/components/BoardContext.svelte` — collapsible context header container - `src/lib/board-context.test.ts` — 11 assertions covering all extraction functions ### Integration - `src/routes/boards/[slug]/+page.svelte` — BoardContext wired above kanban, context filtering, dimming ### Code Review Fixes - Arch filter corrected to match specific components (not any arch: label) - Nested `<button><a>` replaced with `<div role="button">` for a11y - Test file added for all pure extraction functions ### Story `story:board-context-renderer` — Superuser can open a board and see architecture diagrams + user stories above the kanban. ### Spec `docs/superpowers/specs/2026-04-12-board-centric-renderer-design.md`
Commenting is not possible because the repository is archived.
No reviewers
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/pal-e-app!108
No description provided.