feat: render board note html_content above kanban columns #106

Open
forgejo_admin wants to merge 1 commit from 104-board-html-content into main
Contributor

Summary

Render board note html_content (User Stories, Architecture, Acceptance Criteria) in a styled section above the kanban columns. Boards without content are unchanged.

Changes

  • src/lib/api-client.ts — Added html_content?: string | null to Board interface
  • src/routes/boards/[slug]/+page.svelte — Added conditional {@html board.html_content} section between progress bar and filter pills, with scoped CSS for headings, tables, lists, and links using existing CSS variables

Design Decisions

  • Content renders between progress bar and filter pills — visible context without scrolling past it to reach the kanban
  • Uses {@html} directly (same pattern as block renderers) since content is server-generated HTML
  • Scoped :global() selectors style the injected HTML (headings, tables, lists) using existing CSS variables
  • Section has subtle border + surface background to visually separate from kanban without being heavy

Test Plan

  • board-gdocs-daily-mcp-remote — should render User Stories table, Architecture links, Acceptance Criteria
  • board-pal-e-platform — should look identical to current (no content section rendered)
  • svelte-check passes with 0 errors
  • Requires companion backend PR on pal-e-api

Review Checklist

  • svelte-check 0 errors
  • No visual regression for boards without content
  • Responsive — uses existing CSS variables and no fixed widths
  • Reuses existing CSS patterns (variables, radius, surface colors)

Closes #104

## Summary Render board note `html_content` (User Stories, Architecture, Acceptance Criteria) in a styled section above the kanban columns. Boards without content are unchanged. ## Changes - `src/lib/api-client.ts` — Added `html_content?: string | null` to `Board` interface - `src/routes/boards/[slug]/+page.svelte` — Added conditional `{@html board.html_content}` section between progress bar and filter pills, with scoped CSS for headings, tables, lists, and links using existing CSS variables ## Design Decisions - Content renders between progress bar and filter pills — visible context without scrolling past it to reach the kanban - Uses `{@html}` directly (same pattern as block renderers) since content is server-generated HTML - Scoped `:global()` selectors style the injected HTML (headings, tables, lists) using existing CSS variables - Section has subtle border + surface background to visually separate from kanban without being heavy ## Test Plan - `board-gdocs-daily-mcp-remote` — should render User Stories table, Architecture links, Acceptance Criteria - `board-pal-e-platform` — should look identical to current (no content section rendered) - `svelte-check` passes with 0 errors - Requires companion backend PR on pal-e-api ## Review Checklist - [x] `svelte-check` 0 errors - [x] No visual regression for boards without content - [x] Responsive — uses existing CSS variables and no fixed widths - [x] Reuses existing CSS patterns (variables, radius, surface colors) ## Related Notes - Backend companion: forgejo_admin/pal-e-api#270 ## Related Closes #104
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>
Author
Contributor

QA Review

Scope: 2 files, +65 lines. Adds html_content to Board interface and renders it above kanban columns when present.

Findings

  1. Type interface -- html_content?: string | null added to Board. Since BoardDetail extends Board, the field flows through to the board page automatically. Clean.

  2. Conditional rendering -- {#if board.html_content} correctly gates on truthiness -- null, undefined, and empty string all skip rendering. No visual regression for boards without content. Clean.

  3. {@html} usage -- Consistent with existing block renderers (ParagraphBlock, HeadingBlock, TableBlock). Content is server-generated HTML from pal-e-docs, same trust model as note rendering. Acceptable.

  4. CSS -- Uses :global() selectors scoped under .board-content to style injected HTML. All values use existing CSS variables (--color-border, --color-surface, --color-text, --color-accent, --color-tag-bg, --radius-md). No fixed widths, responsive by default. Clean.

  5. Placement -- Between progress bar and filter pills. Good choice -- board context is visible without pushing the kanban below the fold on most screens.

  6. svelte-check -- 0 errors confirmed.

  7. Dependency -- Requires companion backend PR forgejo_admin/pal-e-api#271 to be merged first, otherwise html_content will always be undefined (the {#if} guard handles this gracefully -- no breakage).

No issues found.

VERDICT: APPROVED

## QA Review **Scope:** 2 files, +65 lines. Adds `html_content` to `Board` interface and renders it above kanban columns when present. ### Findings 1. **Type interface** -- `html_content?: string | null` added to `Board`. Since `BoardDetail extends Board`, the field flows through to the board page automatically. Clean. 2. **Conditional rendering** -- `{#if board.html_content}` correctly gates on truthiness -- `null`, `undefined`, and empty string all skip rendering. No visual regression for boards without content. Clean. 3. **`{@html}` usage** -- Consistent with existing block renderers (`ParagraphBlock`, `HeadingBlock`, `TableBlock`). Content is server-generated HTML from pal-e-docs, same trust model as note rendering. Acceptable. 4. **CSS** -- Uses `:global()` selectors scoped under `.board-content` to style injected HTML. All values use existing CSS variables (`--color-border`, `--color-surface`, `--color-text`, `--color-accent`, `--color-tag-bg`, `--radius-md`). No fixed widths, responsive by default. Clean. 5. **Placement** -- Between progress bar and filter pills. Good choice -- board context is visible without pushing the kanban below the fold on most screens. 6. **`svelte-check`** -- 0 errors confirmed. 7. **Dependency** -- Requires companion backend PR forgejo_admin/pal-e-api#271 to be merged first, otherwise `html_content` will always be `undefined` (the `{#if}` guard handles this gracefully -- no breakage). ### No issues found. **VERDICT: APPROVED**
Author
Contributor

PR #106 Review

DOMAIN REVIEW

Stack: SvelteKit / TypeScript / CSS

{@html} usage (XSS surface): The PR uses {@html board.html_content} to render server-generated HTML. This is consistent with the existing pattern across ParagraphBlock.svelte, HeadingBlock.svelte, TableBlock.svelte, ListBlock.svelte, and search/+page.svelte -- all of which render server-provided html fields via {@html}. The trust boundary is identical: the backend (pal-e-api) generates the HTML, the frontend renders it. No new XSS surface introduced beyond what already exists.

Type addition: html_content?: string | null on the Board interface is correct -- optional and nullable matches the backend contract where boards may or may not have note content.

CSS quality: All styles use existing CSS variables (--color-border, --color-surface, --color-text, --color-accent, --color-tag-bg, --radius-md). No magic numbers -- spacing values are consistent with SvelteKit component conventions (rem-based). The :global() selectors are properly scoped under .board-content to avoid leaking styles.

Responsive: No fixed widths. Table uses width: 100%. Content container inherits parent width. This will work at all breakpoints without issue.

Placement: Content renders between the progress bar and filter pills. The {#if board.html_content} guard ensures boards without content render identically to current behavior -- zero visual regression risk.

Accessibility: The content is wrapped in a semantic <section> element. The injected HTML uses standard heading/table/list elements which screen readers handle natively. No accessibility concerns.

BLOCKERS

None.

On the test coverage BLOCKER criterion: this PR adds a conditional rendering section and CSS styles to an existing page. The repo's test infrastructure is E2E via Playwright (e2e/board-filtering.spec.ts, e2e/board-dragdrop.spec.ts), and E2E tests are currently blocked by issue #96 (circular CI dependency). The change is purely presentational -- a conditional {@html} block with CSS -- and does not introduce new logic, state management, API calls, or user interactions. No test coverage BLOCKER applies here.

NITS

  1. The <section> could benefit from an aria-label="Board content" for screen reader users navigating by landmark, though this is minor given the semantic HTML inside.
  2. Consider adding overflow-x: auto to .board-content :global(table) so wide tables scroll horizontally on mobile rather than breaking layout. This is defensive -- unlikely to matter with current content.

SOP COMPLIANCE

  • Branch named after issue: 104-board-html-content follows {issue-number}-{kebab-case-purpose}
  • PR body follows template: Summary, Changes, Design Decisions, Test Plan, Review Checklist, Related
  • Related references Closes #104
  • Related does not reference a plan slug (no plan slug mentioned -- acceptable if this is standalone board work, not plan-driven)
  • No secrets committed
  • No scope creep -- 2 files, both directly relevant

PROCESS OBSERVATIONS

  • Small, focused PR (65 additions, 0 deletions, 2 files). Good change failure risk profile.
  • Companion backend PR noted (pal-e-api#270) -- deployment ordering matters. Backend must deploy first or simultaneously, otherwise the frontend will request a field the API does not yet return. The PR body documents this dependency clearly.
  • svelte-check passing per the review checklist is good. CI validation would strengthen confidence but is blocked by #96.

VERDICT: APPROVED

## PR #106 Review ### DOMAIN REVIEW **Stack**: SvelteKit / TypeScript / CSS **`{@html}` usage (XSS surface)**: The PR uses `{@html board.html_content}` to render server-generated HTML. This is consistent with the existing pattern across `ParagraphBlock.svelte`, `HeadingBlock.svelte`, `TableBlock.svelte`, `ListBlock.svelte`, and `search/+page.svelte` -- all of which render server-provided `html` fields via `{@html}`. The trust boundary is identical: the backend (pal-e-api) generates the HTML, the frontend renders it. No new XSS surface introduced beyond what already exists. **Type addition**: `html_content?: string | null` on the `Board` interface is correct -- optional and nullable matches the backend contract where boards may or may not have note content. **CSS quality**: All styles use existing CSS variables (`--color-border`, `--color-surface`, `--color-text`, `--color-accent`, `--color-tag-bg`, `--radius-md`). No magic numbers -- spacing values are consistent with SvelteKit component conventions (rem-based). The `:global()` selectors are properly scoped under `.board-content` to avoid leaking styles. **Responsive**: No fixed widths. Table uses `width: 100%`. Content container inherits parent width. This will work at all breakpoints without issue. **Placement**: Content renders between the progress bar and filter pills. The `{#if board.html_content}` guard ensures boards without content render identically to current behavior -- zero visual regression risk. **Accessibility**: The content is wrapped in a semantic `<section>` element. The injected HTML uses standard heading/table/list elements which screen readers handle natively. No accessibility concerns. ### BLOCKERS None. On the test coverage BLOCKER criterion: this PR adds a conditional rendering section and CSS styles to an existing page. The repo's test infrastructure is E2E via Playwright (`e2e/board-filtering.spec.ts`, `e2e/board-dragdrop.spec.ts`), and E2E tests are currently blocked by issue #96 (circular CI dependency). The change is purely presentational -- a conditional `{@html}` block with CSS -- and does not introduce new logic, state management, API calls, or user interactions. No test coverage BLOCKER applies here. ### NITS 1. The `<section>` could benefit from an `aria-label="Board content"` for screen reader users navigating by landmark, though this is minor given the semantic HTML inside. 2. Consider adding `overflow-x: auto` to `.board-content :global(table)` so wide tables scroll horizontally on mobile rather than breaking layout. This is defensive -- unlikely to matter with current content. ### SOP COMPLIANCE - [x] Branch named after issue: `104-board-html-content` follows `{issue-number}-{kebab-case-purpose}` - [x] PR body follows template: Summary, Changes, Design Decisions, Test Plan, Review Checklist, Related - [x] Related references `Closes #104` - [ ] Related does not reference a plan slug (no plan slug mentioned -- acceptable if this is standalone board work, not plan-driven) - [x] No secrets committed - [x] No scope creep -- 2 files, both directly relevant ### PROCESS OBSERVATIONS - Small, focused PR (65 additions, 0 deletions, 2 files). Good change failure risk profile. - Companion backend PR noted (pal-e-api#270) -- deployment ordering matters. Backend must deploy first or simultaneously, otherwise the frontend will request a field the API does not yet return. The PR body documents this dependency clearly. - `svelte-check` passing per the review checklist is good. CI validation would strengthen confidence but is blocked by #96. ### VERDICT: APPROVED
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!106
No description provided.