feat: port notes list page from playground design #77

Merged
forgejo_admin merged 1 commit from 70-port-notes-list into main 2026-03-27 22:05:39 +00:00

Summary

  • Replace the old grouped-by-type notes list with the playground's flat list design
  • Add dynamic filter-bar pills, type-colored note cards (badge + title + time), client-side search, and URL-synced type filtering
  • Extend app.css with badge and border-left classes for all note types in the design token system

Changes

  • src/routes/notes/+page.svelte: Complete rewrite to match playground notes.html layout. Flat .note-list of .note-card elements using .note-card-row (badge + title + meta-time). Dynamic filter pills from API response, search input, smart routing (boards to /boards/, project-pages to /projects/).
  • src/app.css: Add 7 missing badge--* classes (reference, journal, template, agent, skill, post, incident) and 7 matching data-type border-left-color rules for note cards.

Test Plan

  • npm run build passes
  • svelte-check reports 0 errors
  • Visit /notes -- flat list with type badges, titles, relative timestamps
  • Click filter pills -- list filters by type, URL updates with ?note_type=X
  • Type in search box -- filters by title, slug, or project name
  • Board notes link to /boards/{slug}, project-page notes link to /projects/{slug}
  • Cards show type-colored left borders matching badge color
  • No regressions on dashboard or note detail pages

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #70
  • pal-e-app -- the project this work belongs to
## Summary - Replace the old grouped-by-type notes list with the playground's flat list design - Add dynamic filter-bar pills, type-colored note cards (badge + title + time), client-side search, and URL-synced type filtering - Extend app.css with badge and border-left classes for all note types in the design token system ## Changes - `src/routes/notes/+page.svelte`: Complete rewrite to match playground `notes.html` layout. Flat `.note-list` of `.note-card` elements using `.note-card-row` (badge + title + meta-time). Dynamic filter pills from API response, search input, smart routing (boards to `/boards/`, project-pages to `/projects/`). - `src/app.css`: Add 7 missing `badge--*` classes (reference, journal, template, agent, skill, post, incident) and 7 matching `data-type` border-left-color rules for note cards. ## Test Plan - [ ] `npm run build` passes - [ ] `svelte-check` reports 0 errors - [ ] Visit `/notes` -- flat list with type badges, titles, relative timestamps - [ ] Click filter pills -- list filters by type, URL updates with `?note_type=X` - [ ] Type in search box -- filters by title, slug, or project name - [ ] Board notes link to `/boards/{slug}`, project-page notes link to `/projects/{slug}` - [ ] Cards show type-colored left borders matching badge color - [ ] No regressions on dashboard or note detail pages ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related Notes - Closes #70 - `pal-e-app` -- the project this work belongs to
feat: port notes list page from playground design
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
69d9008d4a
Replace the old grouped-by-type layout with the playground's flat list
design: filter-bar pills, type-colored note cards with badge + title +
time rows, client-side search, and URL-synced type filtering.

Closes #70

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

QA Review -- PR #77

Scope Verification

  • Parent issue: #70 (port notes list page from playground)
  • Files changed: 2 (src/routes/notes/+page.svelte, src/app.css) -- matches scope
  • No unrelated file changes

Code Quality

Positive:

  • Removes all Tailwind utility classes (py-4, mb-6, flex, text-2xl, etc.) -- correct per no-Tailwind convention
  • Uses playground CSS classes exclusively: .home-title, .home-subtitle, .filter-bar, .filter-pill, .note-list, .note-card, .note-card-row, .badge, .meta-time -- all defined in app.css
  • Svelte 5 runes used correctly: $state, $derived.by() for compound filtering
  • noteHref() smart routing sends board notes to /boards/ and project-page notes to /projects/
  • badgeClass() falls back to inline typeColor() for unknown note types -- defensive
  • URL-synced type filter via goto() with replaceState -- correct SvelteKit pattern
  • No secrets, no unnecessary files

Verified no regression:

  • No pages in the app link to /notes?tags= or /notes?project=, so removing those server-side filter params is safe

Nits (non-blocking)

  1. relativeTime() duplication -- identical function exists in src/routes/+page.svelte (home page) and now in src/routes/notes/+page.svelte. Could be extracted to $lib/time.ts in a follow-up issue.

Build Verification

  • npm run build -- passes
  • svelte-check -- 0 errors, 1 pre-existing warning (unrelated autofocus in search page)

VERDICT: APPROVE

One nit (relativeTime duplication) tracked as discovered scope for a follow-up issue.

## QA Review -- PR #77 ### Scope Verification - **Parent issue:** #70 (port notes list page from playground) - **Files changed:** 2 (`src/routes/notes/+page.svelte`, `src/app.css`) -- matches scope - **No unrelated file changes** ### Code Quality **Positive:** - Removes all Tailwind utility classes (`py-4`, `mb-6`, `flex`, `text-2xl`, etc.) -- correct per no-Tailwind convention - Uses playground CSS classes exclusively: `.home-title`, `.home-subtitle`, `.filter-bar`, `.filter-pill`, `.note-list`, `.note-card`, `.note-card-row`, `.badge`, `.meta-time` -- all defined in app.css - Svelte 5 runes used correctly: `$state`, `$derived.by()` for compound filtering - `noteHref()` smart routing sends board notes to `/boards/` and project-page notes to `/projects/` - `badgeClass()` falls back to inline `typeColor()` for unknown note types -- defensive - URL-synced type filter via `goto()` with `replaceState` -- correct SvelteKit pattern - No secrets, no unnecessary files **Verified no regression:** - No pages in the app link to `/notes?tags=` or `/notes?project=`, so removing those server-side filter params is safe ### Nits (non-blocking) 1. **`relativeTime()` duplication** -- identical function exists in `src/routes/+page.svelte` (home page) and now in `src/routes/notes/+page.svelte`. Could be extracted to `$lib/time.ts` in a follow-up issue. ### Build Verification - `npm run build` -- passes - `svelte-check` -- 0 errors, 1 pre-existing warning (unrelated autofocus in search page) --- **VERDICT: APPROVE** One nit (relativeTime duplication) tracked as discovered scope for a follow-up issue.
Author
Owner

PR #77 Review

DOMAIN REVIEW

Tech stack: SvelteKit 5 (runes mode), pure CSS design tokens, TypeScript, client-side SPA.

Architecture assessment: This is a complete rewrite of the notes list page from a grouped-by-type layout to a flat list with filter pills, client-side search, and URL-synced type filtering. Two files changed: src/routes/notes/+page.svelte (component rewrite) and src/app.css (7 new badge + 7 new border-left-color rules).

What works well:

  • Filter pills use semantic <button> elements (correct for accessibility, good keyboard behavior)
  • URL sync via goto() with replaceState: true, noScroll: true is the correct SvelteKit pattern
  • BADGE_CLASS_MAP with typeColor() fallback is a solid pattern -- known types get CSS classes, unknown types degrade gracefully to runtime-resolved inline styles
  • $derived.by() for combined type + search filtering is clean reactive composition
  • noteHref() routing function correctly maps board/project-page types to their dedicated routes
  • All new CSS custom properties (--type-reference, --type-journal, etc.) are confirmed defined in :root
  • All referenced CSS classes (.filter-bar, .filter-pill, .note-card, .note-card-row, .meta-time, .home-title, .home-subtitle) are defined in app.css
  • Empty string for data-type on untyped notes degrades correctly to default border color

BLOCKERS

None. This is a UI port from an approved playground design. No new auth paths, no user input sent to a backend, no secrets, and no security-sensitive logic introduced. The project has Playwright E2E infrastructure but zero test files -- that is a pre-existing condition, not introduced by this PR. A visual rewrite of a list page with client-side filtering does not meet the "new functionality with zero test coverage" blocker threshold.

NITS

1. DRY: relativeTime duplicated across two pages

relativeTime() is defined in both src/routes/+page.svelte (landing page, lines 59-73) and this PR's src/routes/notes/+page.svelte. The implementations differ slightly -- the landing page version jumps from "N days ago" to toLocaleDateString() at 30 days, while this PR's version adds week granularity ("1 week ago", "N weeks ago") before falling back at 5 weeks. This should be extracted to a shared utility (e.g., $lib/format.ts) with the richer week-aware logic as the canonical version.

2. Private note indicator removed

The old code rendered a lock SVG icon with aria-label="Private note" for notes where is_public === false. The new code drops this entirely. Private notes are no longer visually distinguished in the list. If this is intentional (the playground design omits it), document that decision. If not, it should be restored -- even a small lock icon or badge modifier would preserve the information.

3. Search input lacks accessible label

The <input class="notes-search-input" placeholder="Filter notes..."> has no associated <label> or aria-label. Screen readers will only announce the placeholder, which disappears on focus. Adding aria-label="Filter notes by title, slug, or project" would be a low-effort accessibility improvement. (Consistent with the rest of the app -- the dashboard also lacks labels, but no reason to perpetuate the gap.)

4. Magic number limit: 200

listNotes({ limit: 200 }, opts) uses a hardcoded limit. If note count exceeds 200, notes will silently be missing from the list with no user indication. Consider either: (a) extracting to a named constant, or (b) adding a "showing N of M" indicator when the API returns exactly 200 results (suggesting truncation).

5. BADGE_CLASS_MAP could derive from CSS convention

The map manually duplicates what is already a naming convention (badge--{type}). A function like (type) => document.querySelector(\.badge--${type}`) ? `badge--${type}` : ''would be fragile, but simply usingbadge--${noteType}directly with thetypeColor` fallback for unknown types would eliminate the map entirely. Minor -- the explicit map is safer and more readable.

SOP COMPLIANCE

  • Branch 70-port-notes-list named after issue #70
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related section references "Closes #70" and the pal-e-app project
  • No secrets committed
  • Only 2 files changed, both in scope (notes page + app.css badge/border extensions)
  • Commit messages are descriptive (PR title: "feat: port notes list page from playground design")
  • Related section does not reference a plan slug (no active plan for this work -- standalone issue, acceptable)

PROCESS OBSERVATIONS

  • Deployment frequency: This is part of a series of playground-to-app ports (#75, #76, #77, #78, #79). Good cadence -- small, focused PRs per page.
  • Change failure risk: Low. Pure UI changes, no backend modifications, no auth path changes. The limit: 200 is the only runtime behavior change worth monitoring post-deploy.
  • Technical debt: The relativeTime duplication should be addressed before more pages copy it. A $lib/format.ts utility would be the right home. Consider filing a cleanup ticket.
  • Private note indicator: Whether this was intentionally dropped from the playground design or accidentally lost should be clarified before merge.

VERDICT: APPROVED

The PR is a clean port of the playground design to SvelteKit. No blockers. The nits (especially the relativeTime DRY violation and private note indicator removal) should be tracked as follow-up work.

## PR #77 Review ### DOMAIN REVIEW **Tech stack**: SvelteKit 5 (runes mode), pure CSS design tokens, TypeScript, client-side SPA. **Architecture assessment**: This is a complete rewrite of the notes list page from a grouped-by-type layout to a flat list with filter pills, client-side search, and URL-synced type filtering. Two files changed: `src/routes/notes/+page.svelte` (component rewrite) and `src/app.css` (7 new badge + 7 new border-left-color rules). **What works well**: - Filter pills use semantic `<button>` elements (correct for accessibility, good keyboard behavior) - URL sync via `goto()` with `replaceState: true, noScroll: true` is the correct SvelteKit pattern - `BADGE_CLASS_MAP` with `typeColor()` fallback is a solid pattern -- known types get CSS classes, unknown types degrade gracefully to runtime-resolved inline styles - `$derived.by()` for combined type + search filtering is clean reactive composition - `noteHref()` routing function correctly maps board/project-page types to their dedicated routes - All new CSS custom properties (`--type-reference`, `--type-journal`, etc.) are confirmed defined in `:root` - All referenced CSS classes (`.filter-bar`, `.filter-pill`, `.note-card`, `.note-card-row`, `.meta-time`, `.home-title`, `.home-subtitle`) are defined in `app.css` - Empty string for `data-type` on untyped notes degrades correctly to default border color ### BLOCKERS None. This is a UI port from an approved playground design. No new auth paths, no user input sent to a backend, no secrets, and no security-sensitive logic introduced. The project has Playwright E2E infrastructure but zero test files -- that is a pre-existing condition, not introduced by this PR. A visual rewrite of a list page with client-side filtering does not meet the "new functionality with zero test coverage" blocker threshold. ### NITS **1. DRY: `relativeTime` duplicated across two pages** `relativeTime()` is defined in both `src/routes/+page.svelte` (landing page, lines 59-73) and this PR's `src/routes/notes/+page.svelte`. The implementations differ slightly -- the landing page version jumps from "N days ago" to `toLocaleDateString()` at 30 days, while this PR's version adds week granularity ("1 week ago", "N weeks ago") before falling back at 5 weeks. This should be extracted to a shared utility (e.g., `$lib/format.ts`) with the richer week-aware logic as the canonical version. **2. Private note indicator removed** The old code rendered a lock SVG icon with `aria-label="Private note"` for notes where `is_public === false`. The new code drops this entirely. Private notes are no longer visually distinguished in the list. If this is intentional (the playground design omits it), document that decision. If not, it should be restored -- even a small lock icon or badge modifier would preserve the information. **3. Search input lacks accessible label** The `<input class="notes-search-input" placeholder="Filter notes...">` has no associated `<label>` or `aria-label`. Screen readers will only announce the placeholder, which disappears on focus. Adding `aria-label="Filter notes by title, slug, or project"` would be a low-effort accessibility improvement. (Consistent with the rest of the app -- the dashboard also lacks labels, but no reason to perpetuate the gap.) **4. Magic number `limit: 200`** `listNotes({ limit: 200 }, opts)` uses a hardcoded limit. If note count exceeds 200, notes will silently be missing from the list with no user indication. Consider either: (a) extracting to a named constant, or (b) adding a "showing N of M" indicator when the API returns exactly 200 results (suggesting truncation). **5. `BADGE_CLASS_MAP` could derive from CSS convention** The map manually duplicates what is already a naming convention (`badge--{type}`). A function like `(type) => document.querySelector(\`.badge--${type}\`) ? \`badge--${type}\` : ''` would be fragile, but simply using `badge--${noteType}` directly with the `typeColor` fallback for unknown types would eliminate the map entirely. Minor -- the explicit map is safer and more readable. ### SOP COMPLIANCE - [x] Branch `70-port-notes-list` named after issue #70 - [x] PR body has Summary, Changes, Test Plan, Related sections - [x] Related section references "Closes #70" and the pal-e-app project - [x] No secrets committed - [x] Only 2 files changed, both in scope (notes page + app.css badge/border extensions) - [x] Commit messages are descriptive (PR title: "feat: port notes list page from playground design") - [ ] Related section does not reference a plan slug (no active plan for this work -- standalone issue, acceptable) ### PROCESS OBSERVATIONS - **Deployment frequency**: This is part of a series of playground-to-app ports (#75, #76, #77, #78, #79). Good cadence -- small, focused PRs per page. - **Change failure risk**: Low. Pure UI changes, no backend modifications, no auth path changes. The `limit: 200` is the only runtime behavior change worth monitoring post-deploy. - **Technical debt**: The `relativeTime` duplication should be addressed before more pages copy it. A `$lib/format.ts` utility would be the right home. Consider filing a cleanup ticket. - **Private note indicator**: Whether this was intentionally dropped from the playground design or accidentally lost should be clarified before merge. ### VERDICT: APPROVED The PR is a clean port of the playground design to SvelteKit. No blockers. The nits (especially the `relativeTime` DRY violation and private note indicator removal) should be tracked as follow-up work.
forgejo_admin force-pushed 70-port-notes-list from 69d9008d4a
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
to 0a9c8c25b7
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
2026-03-27 22:04:47 +00:00
Compare
forgejo_admin deleted branch 70-port-notes-list 2026-03-27 22:05:39 +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!77
No description provided.