feat: My Notes view + identity-aware dashboard + attribution #109
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/pal-e-app!109
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "103-my-notes-identity-dashboard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds user-filtered note views and attribution metadata display, transforming pal-e-app from a generic authenticated site into a personalized experience. Builds the frontend to consume the
created_by_subfilter andcreated_by_name/updated_by_namefields from the backend attribution work (#268), with graceful degradation until that deploys.Changes
src/routes/my-notes/+page.svelte(new) — User-filtered note listing at/my-notes, callsGET /notes?created_by_sub={sub}. Redirects unauthenticated users to home. Includes type filter pills and search, matching the existing/notespage conventions.src/routes/dashboard/+page.svelte— Added "My Recent Edits" section showing the 5 most recent notes by the current user, with link to/my-notes. Only renders when authenticated and notes are available.src/routes/+layout.svelte— Added conditional "My Notes" nav link in both top navbar and sidebar, visible only when authenticated.src/lib/keycloak.ts— AddedgetUserSub()to extract the Keycloak subject claim for user-filtered API calls.src/lib/api-client.ts— Addedcreated_by_sub,created_by_name,updated_by_namefields toNoteinterface. Addedcreated_by_subparameter tolistNotes().src/lib/components/NoteLayout.svelte— Displays "Created by X" and "Edited by Y" attribution in note metadata when present.src/lib/components/ProjectLayout.svelte— Same attribution display for project-page notes.Test Plan
/my-notesloads when authenticated (shows notes; gracefully shows all if backend filter not yet deployed)/my-notesredirects to/when unauthenticatedcreated_by_name/updated_by_nameare present in API responsenpm run buildpasses cleanReview Checklist
npm run build)Related Notes
Related
Closes #103
PR #109 Review
DOMAIN REVIEW
Tech stack: SvelteKit / TypeScript / CSS (pure CSS vars, no Tailwind).
What works well:
getUserSub()inkeycloak.tsis clean, minimal, and follows the existing pattern (getUserName()).Note, new optional param onlistNotes().created_by_subfilter falls back to showing all notes if backend doesn't support it yet.authenticatedstate is correct in both top navbar and sidebar.var(--color-text-faint),var(--radius-md), etc.) and the project's utility classes (mb-8,text-sm,font-semibold). No magic numbers.my-notes/+page.sveltecorrectly importsrelativeTimefrom$lib/time(unlike the existingnotes/+page.sveltewhich has a local copy -- pre-existing tech debt, not introduced here).Attribution display (NoteLayout + ProjectLayout):
.meta-attributionstyle inNoteLayout.svelteuses:global(.meta-attribution)which means it leaks globally.ProjectLayout.svelteadds the same attribution markup but has no matching style definition -- it relies on NoteLayout's global leak. This works but is fragile. If NoteLayout is never rendered on a page that shows ProjectLayout, the attribution will be unstyled. Should either: (a) add the style toProjectLayout.sveltetoo, or (b) move it toapp.cssas a shared utility.BLOCKERS
1. DRY violation:
my-notes/+page.svelteis a near-verbatim copy ofnotes/+page.svelteThe following are duplicated between the two files:
BADGE_CLASS_MAPconstant (15-entry record, identical)badgeClass()function (identical)noteHref()function (identical)readUrlFilters()function (identical)setFilter()function (identical)filteredNotesderived logic (identical).loading-text,.error-text,.notes-search,.notes-search-input,.empty-state-- identical)This is not an auth/security DRY violation, so it is not an automatic blocker by the strict criteria. However, the scale of duplication is significant -- roughly 80% of the new file is copy-paste. The shared logic (
BADGE_CLASS_MAP,badgeClass,noteHref) should be extracted to$lib/modules. The CSS is already defined identically innotes/+page.svelteand could be moved toapp.cssor a shared component.Downgrading to NIT since this is not in an auth/security path, but flagging it prominently because it will cause divergence the moment either page is updated independently.
2. No test coverage for new functionality
The repo has zero test files (
src/**/*.test.*returns nothing). This PR adds:/my-notes) with auth gating, API calls, filtering, searchgetUserSub()utilityPer BLOCKER criteria: "New functionality with zero test coverage." The
my-notesroute has auth-gated redirect logic, API parameter construction, and client-side filtering -- all testable.getUserSub()is a one-liner but the route logic around it (what happens when sub is undefined, unauthenticated redirect) deserves coverage.This is a BLOCKER. At minimum, the following need tests:
getUserSub()returns sub from token/my-notesredirects unauthenticated userslistNotesis called withcreated_by_subwhen sub is availablefilteredNotesNITS
Attribution CSS fragility: As noted above,
ProjectLayout.sveltedepends onNoteLayout.svelte's:global(.meta-attribution)rule. Move the style toapp.cssor duplicate it in ProjectLayout.notes/+page.sveltehas a localrelativeTimethat diverges from$lib/time.ts: The local version handles weeks (diffWeek), the shared one does not. This is pre-existing tech debt but this PR is a good opportunity to also fixnotes/+page.svelteto import from$lib/time(one-line change). Not blocking since it predates this PR.Shared module opportunity: Extract
BADGE_CLASS_MAP,badgeClass(), andnoteHref()into$lib/note-helpers.ts(or similar). Bothnotes/+page.svelteandmy-notes/+page.sveltewould import from there. Prevents divergence.Dashboard "My Recent Edits" fallback text: When
getUserSub()returns undefined (token without sub claim -- edge case), the code still callslistNotes({ limit: 5 })without the sub filter, showing all recent notes as "My Recent Edits". The section title is misleading in that case. Consider either hiding the section or changing the title.Accessibility: The search input in
my-notes/+page.sveltelacks a<label>element oraria-labelattribute. Same issue exists on thenotespage (pre-existing), but new code should not repeat it. Addaria-label="Filter your notes"to the input.Filter pill keyboard accessibility: Filter pills are
<button>elements (good), but the active state is only visual. Consideraria-pressed={activeType === type}for screen readers.SOP COMPLIANCE
103-my-notes-identity-dashboardmatches issue #103PROCESS OBSERVATIONS
notesandmy-notespages share ~80% of their logic and markup. This is the pattern that leads to divergence bugs -- when someone updates badge classes or noteHref routing in one file but not the other. Extract shared code now while there are only 2 consumers.getUserSub()and the redirect logic would set a foundation.created_by_subfilter andcreated_by_name/updated_by_namefields depend on pal-e-docs#268. The graceful degradation is well-handled, but the "My Notes" page will show ALL notes (not just the user's) until that backend work lands. This should be documented as a known limitation.VERDICT: NOT APPROVED
One blocker: zero test coverage for new functionality (auth-gated route, API parameter construction, filtering logic). The DRY concern is significant but downgraded to nit since it is not in an auth/security path. Add tests, then re-request review.
7a3b8930aab8326be782PR #109 Re-Review
Re-review following NOT APPROVED verdict for zero test coverage. The PR now includes two test files with 45 test cases. However, the re-review surfaces new blockers that must be resolved.
DOMAIN REVIEW
Tech stack: SvelteKit 5 / TypeScript / Svelte 5 runes ($state, $derived) / keycloak-js / Vitest (added)
SvelteKit/TypeScript findings:
getUserSub()is undefined. Bothsrc/routes/my-notes/+page.svelte(line 7) andsrc/routes/dashboard/+page.svelte(line 4) importgetUserSubfrom$lib/keycloak, but the diff does NOT include changes tosrc/lib/keycloak.ts. The function does not exist on main. Currentkeycloak.tsexports:initKeycloak,login,logout,getToken,isAuthenticated,getUserName. This will fail at runtime and likely failnpm run build(TypeScript will reject the import). Either the keycloak.ts change was accidentally excluded from the branch, or there is a missing dependency on another PR.Vitest not wired into the project. The test files import from
vitest, but:vitestis not inpackage.jsondevDependenciesvitest.config.tsexists at the project rootpackage.json"test" script only runsplaywright test(e2e), notest:unitscriptAttribution markup duplicated. The
{#if note.created_by_name}/{#if note.updated_by_name}block is copy-pasted identically in bothNoteLayout.svelteandProjectLayout.svelte. This is not a security-path DRY issue (not a blocker), but noted as a nit.CSS
:global(.meta-attribution)in NoteLayout.svelte -- using:global()to style a class that only appears in scoped templates is unnecessary. A scoped.meta-attributionrule would suffice and avoid global style leakage.Dashboard "My Recent Edits" links always route to
/notes/{slug}regardless of note type (line:<a href="/notes/{note.slug}"). But the My Notes page correctly usesnoteHref(note)which routes boards to/boards/and project-pages to/projects/. This inconsistency means clicking a board-type note from the dashboard goes to the wrong URL.Test quality assessment (assuming vitest were wired):
The test files themselves are well-structured:
api-client.test.ts: 21 tests covering URL construction, auth headers, attribution fields, error handling, multiple endpointsmy-notes-helpers.test.ts: 24 tests covering badge classification, URL routing, filtering (type, search, combined), edge cases (null project, empty arrays, whitespace)makeNote)my-notes-helpers.tsis a solid pattern for testabilityBLOCKERS
getUserSub()does not exist. The function is imported but never defined.src/lib/keycloak.tsis unchanged in this diff. This is a build-breaking error. The PR body claims "build passes" but this import would cause a TypeScript error unless there is an uncommitted/unpushed change to keycloak.ts.Test infrastructure not wired.
vitestis not a dependency. No config file. No npm script to run unit tests. The test files exist but cannot execute. The original blocker was "zero test coverage" -- adding test files that cannot run does not resolve the blocker. Required: addvitestto devDependencies, addvitest.config.ts, add"test:unit": "vitest run"to package.json scripts.NITS
Attribution markup (created_by/updated_by spans) is duplicated between
NoteLayout.svelteandProjectLayout.svelte. Consider extracting to a shared snippet or component.Dashboard "My Recent Edits" uses hardcoded
/notes/{slug}hrefs instead ofnoteHref(note)frommy-notes-helpers.ts. Board and project-page notes would link to the wrong route.:global(.meta-attribution)in NoteLayout.svelte should be scoped (remove:global()).The
my-notes/+page.sveltefetches up to 200 notes client-side. For users with many notes, consider server-side pagination rather than loading all and filtering in-browser.SOP COMPLIANCE
103-my-notes-identity-dashboardmatches issue #103getUserSubimport would fail TypeScript compilationPROCESS OBSERVATIONS
The test coverage work shows good intent -- extracting pure functions for testability and writing comprehensive unit tests is the right pattern. However, the infrastructure to actually run those tests was not set up (vitest dependency, config, npm script). This means CI cannot gate on test failures, which undermines the value of the tests. The missing
getUserSubfunction suggests either a rebase issue or a file that was not committed to the branch.VERDICT: NOT APPROVED
Two blockers remain:
getUserSub()is undefined -- build-breaking missing function