Workflow-App/Workflow/docs/superpowers/specs/2026-09-16-notes-tab-design.md
sjcoetzeesj-coder 11d0178ab1 feat: commit Workflow app source; move CIPC credentials to env
Adds the full Next.js app (app/, components/, lib/, scripts/, assets/,
docs/, Dockerfile, compose, configs) to version control.

Security:
- CIPC BizPortal customer-code logins (Login 1 + Login 3) now read
  CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE /
  CIPC_LOGIN3_PASSWORD from .env.local instead of source.
- BizPortal personal login in apply-bizportal route reads
  BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD.
- Added .env.example as the committed template.

.gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright
storage_state.json (live CIPC cookies), scratch dirs, client PDFs and
debug screenshots.
2026-10-18 15:21:45 +02:00

7.4 KiB

Notes Tab — Design Spec

Date: 2026-09-16 Author: Mavis (brainstorming session with SJ) Status: Draft — awaiting SJ review

Goal

Add a Notes tab to the Workflow app — second tab in the nav, right after Overview — that lets SJ jot down things to do, mark them done, edit them, and delete them. No team features, no cross-device realtime, no analytics — just a personal checklist that survives page reloads and tabs sessions.

Decisions

Axis Choice Rationale
Persistence PocketBase (collection ysterbaard_notes) User picked PB over localStorage. PB is the existing app's store of truth and syncs across browsers.
Collection location New collection, not a sub-table Notes have their own shape (no relations to companies/directors in v1).
Front-end approach Server-Component page + Client-Component list Matches existing pattern (app/tasks/page.tsx, app/companies/page.tsx). Keeps PB token off the client.
API surface Dedicated /api/notes/* routes Notes have different defaults (no is_client, no first_seen) than the generic /api/pb/records/* allowlist assumes. Dedicated route keeps the generic proxy untouched.
Styling Match existing app surfaces (cards, hairline borders, text-fg/text-muted palette) Don't introduce a new design language.
Tabs in nav All tabs (desktop top + mobile bottom) get the new link automatically NavBar reads a single LINKS array — adding one entry covers both surfaces.

PB Collection Spec

User must create this in the PB admin UI (https://pb.agent.apphub.co.za/_/) before the code will work. New collection → write these fields manually:

field type required default notes
title text yes — max length 200
content text (plain) no — multi-line body
is_done bool no false
done_at date no — auto-set when is_done flips to true
created autodate — now PB built-in
updated autodate — now PB built-in

Rules: set listRule, viewRule, createRule, updateRule, deleteRule all to empty string so the server-side calls work. (User confirmed this fix pattern from ysterbaard_annual_returns — empty string means "no restriction", but the PATCH-round-trip may need to be performed once via the admin UI to "wake up" the collection if PB shows "Superusers only" despite empty rules.)

If after creating the rules the in-app server still gets 403, run a PATCH /api/collections/ysterbaard_notes with each rule body set to "" to force the rule to be re-written, exactly like the 2026-09-16 ysterbaard_annual_returns fix.

Files Added (4)

  1. app/notes/page.tsx — Server Component

    • export const dynamic = "force-dynamic" (notes change often)
    • Loads all notes via fetchCollection<YsterbaardNote>("ysterbaard_notes", { perPage: 500, sort: "-created" })
    • Renders <PageHeader /> (eyebrow "Quick notes", title "Notes", subtitle "Things to do. Tick when done, edit, or delete.")
    • Passes notes array to <NotesList initialNotes={notes} />
  2. app/notes/NotesList.tsx — Client Component

    • Holds notes state seeded from initialNotes.
    • Top bar: filter chips (All / Open / Done), + Add note button on the right.
    • + Add note reveals an inline form (title input + content textarea + Save / Cancel).
    • Filtered list of rows. Each row:
      • Checkbox (toggles is_done and triggers PATCH).
      • Title (click to inline-edit).
      • Content (small text below title; click "Edit" to enter edit mode for both).
      • Right-side buttons: Edit / Delete. Delete calls confirm().
      • Done rows: text-muted, strikethrough title, dim checkbox, sorted to bottom regardless of filter view.
    • Filter chips only affect which rows show; within each filter view, rows are sorted open-first (by created desc), then done (by done_at desc).
    • Optimistic updates: state mutation first, then fetch. On error, rollback and surface a toast/error message.
    • Empty states per filter: "No open notes", "No done notes yet", "No notes yet".
  3. app/api/notes/route.ts — POST handler

    • Reads { title, content, is_done } from JSON body.
    • Hardcoded collection = "ysterbaard_notes".
    • Field allowlist: title, content, is_done, done_at.
    • If is_done === true, set done_at to now.
    • Calls createRecord("ysterbaard_notes", sanitised).
    • Returns the created record JSON, or { error } with status 400/502 on failure.
  4. app/api/notes/[id]/route.ts — PATCH + DELETE handlers

    • PATCH:
      • Reads { title?, content?, is_done? } from JSON body.
      • Sanitises against the same field allowlist.
      • If is_done flips from false → true, set done_at to now.
      • If is_done flips from true → false, clear done_at.
      • Calls updateRecord("ysterbaard_notes", id, sanitised).
      • Returns the updated record JSON.
    • DELETE:
      • Uses the path [id] from Next.js route params.
      • Calls deleteRecord("ysterbaard_notes", id).
      • Returns { ok: true }.

Files Modified (1)

components/NavBar.tsx — Insert a new entry into the LINKS array at index 1 (right after the Overview entry):

{ href: "/notes", label: "Notes", icon: IconNotepad },

Plus add an IconNotepad component below the existing icon set (inline SVG, stroke-currentColor, 1.6px stroke, matching the design-system rule). Shape: rounded rectangle with a horizontal ruled line and a small folded corner — distinguishes it from IconClipboard (task list) and IconDocument (page).

No other edits to NavBar — the existing LINKS.map covers both desktop top nav and mobile bottom tab bar.

Out of Scope (YAGNI)

  • Categories, tags, priority, due dates.
  • Search, sort options beyond created desc.
  • Pinning, archiving, undo.
  • Multi-user / created_by field.
  • Sharing or export.
  • Realtime / SSE updates.
  • PB realtime subscriptions.
  • A standalone "Notes" section in the existing sidebar (none exists — this lives only in the top/bottom nav).

Add any of these later by extending the collection + the page + the API.

Verification

  1. PB collection exists — GET /api/collections/ysterbaard_notes returns 200 (not 404). The /notes page will throw a helpful error if 404.
  2. Page loads — visit /notes. Empty state if no notes, otherwise the list of existing notes.
  3. Create — click + Add note, fill title, Save. New row appears at top.
  4. Toggle done — click checkbox. Row moves to bottom, title strikes through.
  5. Edit — click row title or Edit button. Inline editor opens, save updates text in place.
  6. Delete — click Delete, confirm. Row disappears.
  7. Filter — All shows everything; Open hides done; Done hides open.
  8. Nav placement — Notes tab is the 2nd tab in both desktop top nav and mobile bottom bar, immediately after Overview.

Risks

  • PB collection not yet created. Until the user creates it in the admin UI, /notes will fail. Mitigation: page-level error message tells the user to create the collection (we'll show the PB admin UI URL).
  • done_at is set server-side, not by PB rule. If the user edits is_done directly in the PB admin UI, done_at will be stale. Out of scope to fix.
  • Concurrent edits between tabs. Last-write-wins. No locking in v1.