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.
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)
-
app/notes/page.tsx— Server Componentexport 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} />
-
app/notes/NotesList.tsx— Client Component- Holds
notesstate seeded frominitialNotes. - Top bar: filter chips (
All/Open/Done),+ Add notebutton on the right. + Add notereveals an inline form (title input + content textarea + Save / Cancel).- Filtered list of rows. Each row:
- Checkbox (toggles
is_doneand triggersPATCH). - Title (click to inline-edit).
- Content (small text below title; click "Edit" to enter edit mode for both).
- Right-side buttons:
Edit/Delete.Deletecallsconfirm(). - Done rows:
text-muted, strikethrough title, dim checkbox, sorted to bottom regardless of filter view.
- Checkbox (toggles
- Filter chips only affect which rows show; within each filter view, rows
are sorted open-first (by
createddesc), then done (bydone_atdesc). - 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".
- Holds
-
app/api/notes/route.ts—POSThandler- Reads
{ title, content, is_done }from JSON body. - Hardcoded
collection = "ysterbaard_notes". - Field allowlist:
title,content,is_done,done_at. - If
is_done === true, setdone_atto now. - Calls
createRecord("ysterbaard_notes", sanitised). - Returns the created record JSON, or
{ error }with status 400/502 on failure.
- Reads
-
app/api/notes/[id]/route.ts—PATCH+DELETEhandlersPATCH:- Reads
{ title?, content?, is_done? }from JSON body. - Sanitises against the same field allowlist.
- If
is_doneflips from false → true, setdone_atto now. - If
is_doneflips from true → false, cleardone_at. - Calls
updateRecord("ysterbaard_notes", id, sanitised). - Returns the updated record JSON.
- Reads
DELETE:- Uses the path
[id]from Next.js route params. - Calls
deleteRecord("ysterbaard_notes", id). - Returns
{ ok: true }.
- Uses the path
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
createddesc. - 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
- PB collection exists —
GET /api/collections/ysterbaard_notesreturns 200 (not 404). The/notespage will throw a helpful error if 404. - Page loads — visit
/notes. Empty state if no notes, otherwise the list of existing notes. - Create — click
+ Add note, fill title, Save. New row appears at top. - Toggle done — click checkbox. Row moves to bottom, title strikes through.
- Edit — click row title or Edit button. Inline editor opens, save updates text in place.
- Delete — click Delete, confirm. Row disappears.
- Filter —
Allshows everything;Openhides done;Donehides open. - 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,
/noteswill fail. Mitigation: page-level error message tells the user to create the collection (we'll show the PB admin UI URL). done_atis set server-side, not by PB rule. If the user editsis_donedirectly in the PB admin UI,done_atwill be stale. Out of scope to fix.- Concurrent edits between tabs. Last-write-wins. No locking in v1.