feat: private notes frontend (Quick-Jot toggle + lock icon + API key) #29

Merged
forgejo_admin merged 3 commits from 26-feat-private-notes-frontend-quick-jot-to into main 2026-03-15 02:42:16 +00:00

Summary

Adds private note support across the pal-e-app frontend: API key authentication for server-side requests, a private/public toggle in Quick-Jot, and lock icons on private notes in list and search views.

Changes

  • src/lib/api.ts: Pass X-PaleDocs-Token API key header in apiFetch() for authenticated server-side requests; add is_public field to Note, SearchResult, and CreateNotePayload interfaces; add UpdateNotePayload interface and updateNote() function
  • src/lib/components/QuickJot.svelte: Add private note checkbox toggle with lock icon SVG; wire is_public: false into create payload when checked; reset toggle on form clear
  • src/routes/notes/+page.svelte: Show subtle gray lock icon next to title for notes where is_public === false in both search-filtered and grouped-by-type views
  • src/routes/search/+page.svelte: Show lock icon on private notes in search results

Test Plan

  • npm run build passes with zero errors
  • npm run check passes with zero errors (only pre-existing autofocus warning)
  • Open Quick-Jot modal, verify "Private note" checkbox appears after Project/Type row
  • Create a note with private toggle checked, verify is_public: false is sent in POST payload
  • View notes list, confirm lock icon appears only on notes where is_public === false
  • Search for a private note, confirm lock icon appears in search results
  • Verify API key header is sent when PAL_E_DOCS_API_KEY env var is set

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #26
  • plan-pal-e-docs -- Phase F6 private notes frontend support
## Summary Adds private note support across the pal-e-app frontend: API key authentication for server-side requests, a private/public toggle in Quick-Jot, and lock icons on private notes in list and search views. ## Changes - `src/lib/api.ts`: Pass `X-PaleDocs-Token` API key header in `apiFetch()` for authenticated server-side requests; add `is_public` field to `Note`, `SearchResult`, and `CreateNotePayload` interfaces; add `UpdateNotePayload` interface and `updateNote()` function - `src/lib/components/QuickJot.svelte`: Add private note checkbox toggle with lock icon SVG; wire `is_public: false` into create payload when checked; reset toggle on form clear - `src/routes/notes/+page.svelte`: Show subtle gray lock icon next to title for notes where `is_public === false` in both search-filtered and grouped-by-type views - `src/routes/search/+page.svelte`: Show lock icon on private notes in search results ## Test Plan - [x] `npm run build` passes with zero errors - [x] `npm run check` passes with zero errors (only pre-existing autofocus warning) - [ ] Open Quick-Jot modal, verify "Private note" checkbox appears after Project/Type row - [ ] Create a note with private toggle checked, verify `is_public: false` is sent in POST payload - [ ] View notes list, confirm lock icon appears only on notes where `is_public === false` - [ ] Search for a private note, confirm lock icon appears in search results - [ ] Verify API key header is sent when `PAL_E_DOCS_API_KEY` env var is set ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #26 - `plan-pal-e-docs` -- Phase F6 private notes frontend support
feat: private notes frontend (Quick-Jot toggle + lock icon + API key)
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
da8f48f9cd
Add private note support across the frontend:
- Pass X-PaleDocs-Token API key header in apiFetch() for authenticated
  server-side requests
- Add is_public field to Note, SearchResult, and CreateNotePayload interfaces
- Add UpdateNotePayload interface and updateNote() function
- Add private/public toggle checkbox in QuickJot modal with lock icon
- Show lock icon on private notes in both note list views (search filter
  and grouped-by-type)
- Show lock icon on private notes in search results

Closes #26

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

Review: APPROVED

Reviewed the diff (4 files, +72/-4 lines). No issues found.

Verified:

  • apiFetch() correctly builds headers object and conditionally adds X-PaleDocs-Token from env.PAL_E_DOCS_API_KEY (already imported at line 1) -- no secrets hardcoded
  • is_public?: boolean is correctly optional on Note, SearchResult, CreateNotePayload interfaces
  • UpdateNotePayload + updateNote() added cleanly (bonus from linter, consistent with existing patterns)
  • QuickJot toggle: isPrivate state initialized to false, reset in resetForm(), correctly sets payload.is_public = false only when checked
  • Lock icon uses strict === false check across all 3 locations, correctly ignoring undefined (public notes that omit the field)
  • SVG lock icon is consistent across search-filtered list, grouped-by-type grid, and search results
  • npm run build -- zero errors
  • npm run check -- zero errors (1 pre-existing autofocus warning)
## Review: APPROVED Reviewed the diff (4 files, +72/-4 lines). No issues found. **Verified:** - `apiFetch()` correctly builds headers object and conditionally adds `X-PaleDocs-Token` from `env.PAL_E_DOCS_API_KEY` (already imported at line 1) -- no secrets hardcoded - `is_public?: boolean` is correctly optional on `Note`, `SearchResult`, `CreateNotePayload` interfaces - `UpdateNotePayload` + `updateNote()` added cleanly (bonus from linter, consistent with existing patterns) - QuickJot toggle: `isPrivate` state initialized to `false`, reset in `resetForm()`, correctly sets `payload.is_public = false` only when checked - Lock icon uses strict `=== false` check across all 3 locations, correctly ignoring `undefined` (public notes that omit the field) - SVG lock icon is consistent across search-filtered list, grouped-by-type grid, and search results - `npm run build` -- zero errors - `npm run check` -- zero errors (1 pre-existing autofocus warning)
Author
Owner

PR #29 Review

BLOCKERS

1. /api/notes/+server.ts proxy does not forward is_public -- private toggle is silently broken

The QuickJot modal correctly sends is_public: false in its POST payload to /api/notes. However, the server-side proxy at src/routes/api/notes/+server.ts (lines 26-45) explicitly whitelists only these fields: title, slug, html_content, project_slug, note_type, status, tags. It never extracts or forwards is_public from the request body.

This means the private toggle checkbox will appear to work in the UI, but every note created will be public regardless. The is_public field is silently dropped at the proxy layer.

Fix: Add is_public forwarding in +server.ts:

if (typeof body.is_public === 'boolean') {
    payload.is_public = body.is_public;
}

2. PAL_E_DOCS_API_KEY not provisioned in deployment -- API key header will never be sent

The diff adds env.PAL_E_DOCS_API_KEY to apiFetch(), but:

  • k8s/deployment.yaml does not define this env var
  • k8s/pal-e-auth-secrets.enc.yaml does not contain it
  • pal-e-deployments overlays do not contain it
  • .env.example does not document it

The if (apiKey) guard means this fails silently (no crash), but the entire API key feature is inert until the secret is provisioned. If the pal-e-docs API actually requires this header for private note operations, creating private notes will fail with a 401/403 from the upstream API.

This needs either:
(a) The API key added to the SOPS secret and deployment, or
(b) An explicit note in the PR/test plan that API key provisioning is a separate follow-up issue, with a reference to that issue.

NITS

1. Lock icon SVG duplicated 4 times

The same lock SVG path (M12 2C9.24 2 7 4.24 7 7v3H6...) is copy-pasted in:

  • QuickJot.svelte (1x)
  • notes/+page.svelte (2x -- search results + grouped view)
  • search/+page.svelte (1x)

Consider extracting a <LockIcon /> component (e.g., src/lib/components/LockIcon.svelte) to avoid drift if the icon styling changes later.

2. apiFetch header spread order could lose API key

In apiFetch, the pattern { headers, ...init } means if any caller ever passes init.headers, it will overwrite the entire headers object, silently dropping the API key and Content-Type. Currently no callers do this, so it is not an active bug. But a safer pattern would be to merge headers:

const res = await fetch(url, { ...init, headers: { ...headers, ...(init?.headers as Record<string, string>) } });

3. UpdateNotePayload and updateNote() are added but unused

The diff adds an UpdateNotePayload interface and updateNote() function to api.ts, but nothing in this PR calls them. No proxy route exists for PUT /api/notes/[slug]. This is likely prep for a future PR (PR #30 "note editing"), which is fine, but it means they are dead code in this PR's scope. If intentional, a brief comment in the PR body would clarify.

4. .env.example should document PAL_E_DOCS_API_KEY

Even if the actual value is in SOPS, .env.example should list the variable so developers know it exists for local dev.

5. Missing ml-1 spacing on first lock icon in search-filtered notes view

In notes/+page.svelte, the lock icon in the search-filtered list (line ~100 in the diff) lacks the ml-1 class that the grouped-by-type view's lock icon has. Minor visual inconsistency.

SOP COMPLIANCE

  • Branch named after issue (26-feat-private-notes-frontend-quick-jot-to references issue #26)
  • PR body has: Summary, Changes, Test Plan, Related
  • Related references plan slug (plan-pal-e-docs -- Phase F6)
  • Closes #26 in PR body
  • No secrets committed (API key read from env var, not hardcoded)
  • No .env files or credentials in diff
  • 4 changed files -- all directly related to the feature scope
  • Commit messages are descriptive
  • updateNote() is out of scope for this issue (prep for #27) -- minor scope creep but non-blocking

Security assessment: The API key is properly confined to server-side code. $env/dynamic/private is imported only in src/lib/api.ts. All .svelte files import only type from $lib/api (verified: BlockRenderer.svelte, QuickJot.svelte, NoteLayout.svelte, boards/[slug]/+page.svelte all use import type). The key cannot leak to the client bundle. This is correct.

VERDICT: NOT APPROVED

The proxy not forwarding is_public (blocker #1) means the core feature -- creating private notes -- does not work end-to-end. This must be fixed before merge. The API key provisioning (blocker #2) should at minimum be acknowledged with a follow-up issue reference.

## PR #29 Review ### BLOCKERS **1. `/api/notes/+server.ts` proxy does not forward `is_public` -- private toggle is silently broken** The QuickJot modal correctly sends `is_public: false` in its POST payload to `/api/notes`. However, the server-side proxy at `src/routes/api/notes/+server.ts` (lines 26-45) explicitly whitelists only these fields: `title`, `slug`, `html_content`, `project_slug`, `note_type`, `status`, `tags`. It never extracts or forwards `is_public` from the request body. This means the private toggle checkbox will appear to work in the UI, but every note created will be public regardless. The `is_public` field is silently dropped at the proxy layer. Fix: Add `is_public` forwarding in `+server.ts`: ```typescript if (typeof body.is_public === 'boolean') { payload.is_public = body.is_public; } ``` **2. `PAL_E_DOCS_API_KEY` not provisioned in deployment -- API key header will never be sent** The diff adds `env.PAL_E_DOCS_API_KEY` to `apiFetch()`, but: - `k8s/deployment.yaml` does not define this env var - `k8s/pal-e-auth-secrets.enc.yaml` does not contain it - `pal-e-deployments` overlays do not contain it - `.env.example` does not document it The `if (apiKey)` guard means this fails silently (no crash), but the entire API key feature is inert until the secret is provisioned. If the pal-e-docs API actually requires this header for private note operations, creating private notes will fail with a 401/403 from the upstream API. This needs either: (a) The API key added to the SOPS secret and deployment, or (b) An explicit note in the PR/test plan that API key provisioning is a separate follow-up issue, with a reference to that issue. ### NITS **1. Lock icon SVG duplicated 4 times** The same lock SVG path (`M12 2C9.24 2 7 4.24 7 7v3H6...`) is copy-pasted in: - `QuickJot.svelte` (1x) - `notes/+page.svelte` (2x -- search results + grouped view) - `search/+page.svelte` (1x) Consider extracting a `<LockIcon />` component (e.g., `src/lib/components/LockIcon.svelte`) to avoid drift if the icon styling changes later. **2. `apiFetch` header spread order could lose API key** In `apiFetch`, the pattern `{ headers, ...init }` means if any caller ever passes `init.headers`, it will overwrite the entire headers object, silently dropping the API key and Content-Type. Currently no callers do this, so it is not an active bug. But a safer pattern would be to merge headers: ```typescript const res = await fetch(url, { ...init, headers: { ...headers, ...(init?.headers as Record<string, string>) } }); ``` **3. `UpdateNotePayload` and `updateNote()` are added but unused** The diff adds an `UpdateNotePayload` interface and `updateNote()` function to `api.ts`, but nothing in this PR calls them. No proxy route exists for PUT `/api/notes/[slug]`. This is likely prep for a future PR (PR #30 "note editing"), which is fine, but it means they are dead code in this PR's scope. If intentional, a brief comment in the PR body would clarify. **4. `.env.example` should document `PAL_E_DOCS_API_KEY`** Even if the actual value is in SOPS, `.env.example` should list the variable so developers know it exists for local dev. **5. Missing `ml-1` spacing on first lock icon in search-filtered notes view** In `notes/+page.svelte`, the lock icon in the search-filtered list (line ~100 in the diff) lacks the `ml-1` class that the grouped-by-type view's lock icon has. Minor visual inconsistency. ### SOP COMPLIANCE - [x] Branch named after issue (`26-feat-private-notes-frontend-quick-jot-to` references issue #26) - [x] PR body has: Summary, Changes, Test Plan, Related - [x] Related references plan slug (`plan-pal-e-docs -- Phase F6`) - [x] `Closes #26` in PR body - [x] No secrets committed (API key read from env var, not hardcoded) - [x] No `.env` files or credentials in diff - [x] 4 changed files -- all directly related to the feature scope - [x] Commit messages are descriptive - [ ] `updateNote()` is out of scope for this issue (prep for #27) -- minor scope creep but non-blocking **Security assessment:** The API key is properly confined to server-side code. `$env/dynamic/private` is imported only in `src/lib/api.ts`. All `.svelte` files import only `type` from `$lib/api` (verified: `BlockRenderer.svelte`, `QuickJot.svelte`, `NoteLayout.svelte`, `boards/[slug]/+page.svelte` all use `import type`). The key cannot leak to the client bundle. This is correct. ### VERDICT: NOT APPROVED The proxy not forwarding `is_public` (blocker #1) means the core feature -- creating private notes -- does not work end-to-end. This must be fixed before merge. The API key provisioning (blocker #2) should at minimum be acknowledged with a follow-up issue reference.
B1: The /api/notes proxy silently dropped is_public from the request
body, causing all Quick-Jot notes to be created as public regardless
of the toggle state. Add the boolean field to the whitelist.

B2: PAL_E_DOCS_API_KEY was referenced in api.ts but never injected
into the pod. Add secretKeyRef in deployment.yaml pointing to
pal-e-auth-secrets, and document the var in .env.example.

Closes #26

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: add PAL_E_DOCS_API_KEY to k8s deployment manifest
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
275afcb331
The env var was referenced in api.ts but not provisioned in the
deployment. Add secretKeyRef pointing to pal-e-auth-secrets so
the pod receives the API key at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
forgejo_admin deleted branch 26-feat-private-notes-frontend-quick-jot-to 2026-03-15 02:42:16 +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!29
No description provided.