feat: board filtering + status view #17

Merged
forgejo_admin merged 1 commit from 15-feat-board-filtering-status-view into main 2026-03-14 23:12:09 +00:00

Summary

  • Add client-side board filtering (type pills, hide-done toggle, collapsible columns) with URL-persisted state
  • Add board summary card with column distribution bar and type breakdown
  • Add mini board status bar on project pages linking to the full board

Changes

  • src/routes/boards/[slug]/+page.svelte: Board summary card, type filter pills, hide-done toggle, collapsible columns, URL persistence via SvelteURLSearchParams, consolidated card colors to use typeColor() from $lib/colors
  • src/routes/projects/[slug]/+page.server.ts: Fetch boards list in parallel to find project board
  • src/routes/projects/[slug]/+page.svelte: Mini board distribution bar, clickable to navigate to full board

Test Plan

  • npm run build passes
  • npm run check passes (0 errors)
  • npm run lint passes (0 new errors)
  • Board page shows summary card with distribution bar
  • Type filter pills toggle correctly, column counts update
  • Hide done toggle removes done column
  • Column headers collapse/expand on click
  • Drag-and-drop works with filters active
  • URL params persist filter state across refresh
  • Project pages with boards show mini status bar

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #15
  • plan-2026-03-13-pal-e-frontend
## Summary - Add client-side board filtering (type pills, hide-done toggle, collapsible columns) with URL-persisted state - Add board summary card with column distribution bar and type breakdown - Add mini board status bar on project pages linking to the full board ## Changes - `src/routes/boards/[slug]/+page.svelte`: Board summary card, type filter pills, hide-done toggle, collapsible columns, URL persistence via SvelteURLSearchParams, consolidated card colors to use typeColor() from $lib/colors - `src/routes/projects/[slug]/+page.server.ts`: Fetch boards list in parallel to find project board - `src/routes/projects/[slug]/+page.svelte`: Mini board distribution bar, clickable to navigate to full board ## Test Plan - [ ] `npm run build` passes - [ ] `npm run check` passes (0 errors) - [ ] `npm run lint` passes (0 new errors) - [ ] Board page shows summary card with distribution bar - [ ] Type filter pills toggle correctly, column counts update - [ ] Hide done toggle removes done column - [ ] Column headers collapse/expand on click - [ ] Drag-and-drop works with filters active - [ ] URL params persist filter state across refresh - [ ] Project pages with boards show mini status bar ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #15 - `plan-2026-03-13-pal-e-frontend`
feat: add board filtering, collapsible columns, and project status view
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
e9f6c11bed
Add type filter pills, hide-done toggle, collapsible columns, board
summary card with distribution bar, and URL-persisted filter state to
the board detail page. Add mini board status bar to project pages.

Closes #15

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

Review

Verdict: APPROVE

Checked

  • Build passes (npm run build -- zero errors)
  • Type check passes (npm run check -- 0 errors, 0 warnings)
  • Lint passes (npm run lint -- 0 new errors, only 3 pre-existing in other files)
  • No secrets committed
  • No unnecessary file changes (3 files, all scoped to the feature)
  • Drag-and-drop integrity preserved -- filtering is view-only, underlying itemsByColumn data untouched
  • URL persistence uses replaceState to avoid polluting browser history
  • Uses proper Svelte 5 reactivity patterns (SvelteSet, SvelteURLSearchParams, mutation over reassignment)
  • Card colors consolidated to typeColor() from $lib/colors (was duplicated local map)
  • Project page server fetches boards in parallel (no waterfall)

Nits (non-blocking)

  1. COLUMN_COLORS map is duplicated between board page and project page. Could be extracted to $lib/colors.ts in a follow-up.
  2. The $effect for URL initialization and updateUrl/goto could theoretically cause a single extra reactivity cycle on filter changes (set mutated -> goto -> URL change -> effect re-fires -> set re-populated with same values -> settles). Not a bug, just a note for future optimization if needed.
## Review **Verdict: APPROVE** ### Checked - [x] Build passes (`npm run build` -- zero errors) - [x] Type check passes (`npm run check` -- 0 errors, 0 warnings) - [x] Lint passes (`npm run lint` -- 0 new errors, only 3 pre-existing in other files) - [x] No secrets committed - [x] No unnecessary file changes (3 files, all scoped to the feature) - [x] Drag-and-drop integrity preserved -- filtering is view-only, underlying `itemsByColumn` data untouched - [x] URL persistence uses `replaceState` to avoid polluting browser history - [x] Uses proper Svelte 5 reactivity patterns (`SvelteSet`, `SvelteURLSearchParams`, mutation over reassignment) - [x] Card colors consolidated to `typeColor()` from `$lib/colors` (was duplicated local map) - [x] Project page server fetches boards in parallel (no waterfall) ### Nits (non-blocking) 1. `COLUMN_COLORS` map is duplicated between board page and project page. Could be extracted to `$lib/colors.ts` in a follow-up. 2. The `$effect` for URL initialization and `updateUrl`/`goto` could theoretically cause a single extra reactivity cycle on filter changes (set mutated -> goto -> URL change -> effect re-fires -> set re-populated with same values -> settles). Not a bug, just a note for future optimization if needed.
Author
Owner

PR #17 Review

BLOCKERS

None.

NITS

  1. COLUMN_COLORS duplicated across two files. The COLUMN_COLORS map is defined identically in both src/routes/boards/[slug]/+page.svelte (line 20) and src/routes/projects/[slug]/+page.svelte (line 18). This is the same class of duplication that was just cleaned up for TYPE_COLORS (which correctly now lives in $lib/colors.ts). Consider extracting COLUMN_COLORS to $lib/colors.ts as well to keep a single source of truth.

  2. COLUMNS constant re-declared in project page. src/routes/projects/[slug]/+page.svelte (line 7) declares a local COLUMNS array identical to the one already exported from $lib/api.ts (line 18). Should import from $lib/api instead of redeclaring.

  3. columnDisplayName() duplicated. The same helper function exists in both src/routes/boards/[slug]/+page.svelte (line 167) and src/routes/projects/[slug]/+page.svelte (line 56). Could be extracted to a shared module.

  4. URL sync effect cycles on every goto. The $effect at src/routes/boards/[slug]/+page.svelte line 86 reads page.url.searchParams, which means every call to updateUrl() (via goto) will re-trigger the effect, clearing and re-populating activeTypes/collapsedColumns with the same values. This converges and does not cause an infinite loop, but it does cause a redundant clear-and-repopulate cycle on every filter toggle. A guard (e.g., a flag to skip the effect when the URL change was self-initiated) would eliminate the extra work.

  5. SvelteURLSearchParams in updateUrl() may be unnecessary. At src/routes/boards/[slug]/+page.svelte line 106, new SvelteURLSearchParams() is used but the params object is local and ephemeral -- it is not reactive state. A plain URLSearchParams() would suffice and avoid importing an unnecessary Svelte-specific class for non-reactive use.

ACCEPTANCE CRITERIA EVALUATION

# Criterion Status
1 Type filter pills work (click to toggle, show count, correct colors from typeColor()) PASS -- pills use typeColor() from $lib/colors, show typeCounts[t], toggle via activeTypes SvelteSet
2 Multiple filter pills active = show items matching ANY selected type PASS -- getFilteredItems() uses activeTypes.has(item.item_type), which is OR/union semantics
3 Click active pill to deactivate PASS -- toggleType() deletes from the set if already present
4 "Hide done" toggle hides the done column PASS -- visibleColumns filters out 'done' when hideDone is true
5 Click column header to collapse (shows header + count only) PASS -- collapsedColumns SvelteSet, collapsed view shows vertical header with count badge
6 Board summary card at top: total items, column distribution bar, type breakdown PASS -- distribution bar uses columnCounts, legend shows non-zero columns, type breakdown with typeColor()
7 Project page: mini board view with column distribution, clickable to full board PASS -- +page.server.ts fetches boards in parallel, +page.svelte renders mini bar with <a href="/boards/{board.slug}">
8 URL persistence for filter state (?type=phase&hide_done=true) PASS -- updateUrl() serializes to query params, $effect reads on load. Also persists collapsed state.
9 CRITICAL: Drag-and-drop still works with filters active PASS -- getFilteredItems() filters the view but doMoveItem() operates on itemsByColumn (the underlying data). Drag/touch/tap-to-move handlers all call doMoveItem() which mutates the source of truth. Drop targets remain active on all visible columns including collapsed ones.
10 Filtered items hidden, not removed -- underlying data intact for moves PASS -- getFilteredItems() returns a filtered copy without mutating itemsByColumn. Comment at line 144 explicitly documents this: "does NOT mutate underlying data".

CODE QUALITY

  • Dark theme consistency: Card backgrounds use #0e0e18, borders use #1a1a2e -- consistent with existing patterns.
  • typeColor() used correctly: The old inline TYPE_COLORS map was removed from the board page and replaced with typeColor() from $lib/colors. No duplicated color mappings for note types.
  • Client-side filtering uses $derived: visibleColumns, typeCounts, sortedTypes, allItems, columnCounts are all $derived. getFilteredItems() is a function that reads from $state/$derived sources. Correct Svelte 5 pattern.
  • No regression to drag-and-drop: Desktop HTML5 drag, mobile touch (long-press), and tap-to-move all preserved. The rendering loop changed from board.columns to visibleColumns and from getColumnItems() to getFilteredItems(), but all event handlers remain intact.
  • Scope is clean: 3 files changed, all directly related to the feature. No unrelated modifications.
  • No secrets or credentials committed.

SOP COMPLIANCE

  • Branch named after issue (15-feat-board-filtering-status-view maps to Issue #15)
  • PR body has: Summary, Changes, Test Plan, Related sections
  • Related section references plan slug (plan-2026-03-13-pal-e-frontend) and Closes #15
  • No secrets, .env files, or credentials committed
  • No unnecessary file changes (3 files, all in scope)
  • Commit messages are descriptive

VERDICT: APPROVED

## PR #17 Review ### BLOCKERS None. ### NITS 1. **COLUMN_COLORS duplicated across two files.** The `COLUMN_COLORS` map is defined identically in both `src/routes/boards/[slug]/+page.svelte` (line 20) and `src/routes/projects/[slug]/+page.svelte` (line 18). This is the same class of duplication that was just cleaned up for `TYPE_COLORS` (which correctly now lives in `$lib/colors.ts`). Consider extracting `COLUMN_COLORS` to `$lib/colors.ts` as well to keep a single source of truth. 2. **COLUMNS constant re-declared in project page.** `src/routes/projects/[slug]/+page.svelte` (line 7) declares a local `COLUMNS` array identical to the one already exported from `$lib/api.ts` (line 18). Should import from `$lib/api` instead of redeclaring. 3. **`columnDisplayName()` duplicated.** The same helper function exists in both `src/routes/boards/[slug]/+page.svelte` (line 167) and `src/routes/projects/[slug]/+page.svelte` (line 56). Could be extracted to a shared module. 4. **URL sync effect cycles on every `goto`.** The `$effect` at `src/routes/boards/[slug]/+page.svelte` line 86 reads `page.url.searchParams`, which means every call to `updateUrl()` (via `goto`) will re-trigger the effect, clearing and re-populating `activeTypes`/`collapsedColumns` with the same values. This converges and does not cause an infinite loop, but it does cause a redundant clear-and-repopulate cycle on every filter toggle. A guard (e.g., a flag to skip the effect when the URL change was self-initiated) would eliminate the extra work. 5. **`SvelteURLSearchParams` in `updateUrl()` may be unnecessary.** At `src/routes/boards/[slug]/+page.svelte` line 106, `new SvelteURLSearchParams()` is used but the params object is local and ephemeral -- it is not reactive state. A plain `URLSearchParams()` would suffice and avoid importing an unnecessary Svelte-specific class for non-reactive use. ### ACCEPTANCE CRITERIA EVALUATION | # | Criterion | Status | |---|-----------|--------| | 1 | Type filter pills work (click to toggle, show count, correct colors from `typeColor()`) | PASS -- pills use `typeColor()` from `$lib/colors`, show `typeCounts[t]`, toggle via `activeTypes` SvelteSet | | 2 | Multiple filter pills active = show items matching ANY selected type | PASS -- `getFilteredItems()` uses `activeTypes.has(item.item_type)`, which is OR/union semantics | | 3 | Click active pill to deactivate | PASS -- `toggleType()` deletes from the set if already present | | 4 | "Hide done" toggle hides the done column | PASS -- `visibleColumns` filters out `'done'` when `hideDone` is true | | 5 | Click column header to collapse (shows header + count only) | PASS -- `collapsedColumns` SvelteSet, collapsed view shows vertical header with count badge | | 6 | Board summary card at top: total items, column distribution bar, type breakdown | PASS -- distribution bar uses `columnCounts`, legend shows non-zero columns, type breakdown with `typeColor()` | | 7 | Project page: mini board view with column distribution, clickable to full board | PASS -- `+page.server.ts` fetches boards in parallel, `+page.svelte` renders mini bar with `<a href="/boards/{board.slug}">` | | 8 | URL persistence for filter state (`?type=phase&hide_done=true`) | PASS -- `updateUrl()` serializes to query params, `$effect` reads on load. Also persists `collapsed` state. | | 9 | CRITICAL: Drag-and-drop still works with filters active | PASS -- `getFilteredItems()` filters the view but `doMoveItem()` operates on `itemsByColumn` (the underlying data). Drag/touch/tap-to-move handlers all call `doMoveItem()` which mutates the source of truth. Drop targets remain active on all visible columns including collapsed ones. | | 10 | Filtered items hidden, not removed -- underlying data intact for moves | PASS -- `getFilteredItems()` returns a filtered copy without mutating `itemsByColumn`. Comment at line 144 explicitly documents this: "does NOT mutate underlying data". | ### CODE QUALITY - **Dark theme consistency**: Card backgrounds use `#0e0e18`, borders use `#1a1a2e` -- consistent with existing patterns. - **`typeColor()` used correctly**: The old inline `TYPE_COLORS` map was removed from the board page and replaced with `typeColor()` from `$lib/colors`. No duplicated color mappings for note types. - **Client-side filtering uses `$derived`**: `visibleColumns`, `typeCounts`, `sortedTypes`, `allItems`, `columnCounts` are all `$derived`. `getFilteredItems()` is a function that reads from `$state`/`$derived` sources. Correct Svelte 5 pattern. - **No regression to drag-and-drop**: Desktop HTML5 drag, mobile touch (long-press), and tap-to-move all preserved. The rendering loop changed from `board.columns` to `visibleColumns` and from `getColumnItems()` to `getFilteredItems()`, but all event handlers remain intact. - **Scope is clean**: 3 files changed, all directly related to the feature. No unrelated modifications. - **No secrets or credentials committed.** ### SOP COMPLIANCE - [x] Branch named after issue (`15-feat-board-filtering-status-view` maps to Issue #15) - [x] PR body has: Summary, Changes, Test Plan, Related sections - [x] Related section references plan slug (`plan-2026-03-13-pal-e-frontend`) and `Closes #15` - [x] No secrets, .env files, or credentials committed - [x] No unnecessary file changes (3 files, all in scope) - [x] Commit messages are descriptive ### VERDICT: APPROVED
forgejo_admin deleted branch 15-feat-board-filtering-status-view 2026-03-14 23:12:09 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
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
forgejo_admin/pal-e-docs-app!17
No description provided.