Phase 8a Task 1. Adds the new intake_links collection (per-token
public entry point, owner-only rules), extends cards.created_by_role
with 'anonymous', and extends activity.action with
'card_submitted_anonymously'.
Note: getOrCreateCollection is a no-op on existing collections, so
the two existing-value extensions are applied by new/extended
ensure functions (ensureCardsCreatedByRoleValues and
ensureActivityActionValues respectively). Re-runs are idempotent.
Adds the three proposal fields (proposed_target_date, proposed_by,
proposed_at) to the cards collection with a partial index that backs
the 'pending proposals' query, plus two new ActivityAction values
(date_proposed, date_proposal_resolved) and column_changed for
return-to-scheduled.
New server actions under src/app/actions/:
- client-proposal.ts: client-manager drag creates a proposal (not
a target_date write); 404 (not 403) on cross-tenant failures and
Done-column cards, matching the rest of /api/client/*.
- owner-plan.ts: accept and dismiss clear the three proposal
fields together and emit the audit rows; returnToScheduledAction
moves a Done card back to the first working column.
Server-side Done-column guards added to:
- src/app/actions/reschedule.ts (was UI-only)
- src/app/api/client/cards/[id]/route.ts (was UI-only)
Both pass through the new writeCardWithAudit helper at
src/lib/cards/write-with-audit.ts — single source of truth for
'patch card + emit activity rows', replacing the inline copies in
the PATCH route and rescheduleCardAction.
Adds src/lib/roadmap/slot-date.ts (slot <-> target_date mapping)
consumed by Pixel's puzzle UI; named SlotId API rather than the
plan's numeric indices for self-documenting callers.
CLIENT_EDITABLE_CARD_FIELDS stays at priority + target_date per
the brief; proposals go through the dedicated action so they get
their own audit row type.
The methodology panel still explains 'what hours are' and 'what they cannot
see'. The session-clustering detail — the constants — was the part a client
might want to interrogate rather than just be told. With this removed, the
constants are still stored inside the payload (so a future question can be
answered) but the visible panel is shorter.
Also dropped the now-unused destructured local in Methodology().
- number field on cards, unique per project
- backfilled existing cards in created order
- both create paths (owner action, client API) assign on creation
- seed script is idempotent across re-runs
The users collection kept PocketBase's defaults because the Plan 1a rules
migration covered eight collections and missed this one:
createRule "" -> anyone could sign up
updateRule "id = @request.auth.id" -> anyone could edit their own record
`role` is an ordinary field here, so both were escalations. Verified
exploitable against the live instance: an anonymous request created an
account with role "owner", and a client manager promoted themselves to
owner by patching their own record. Either route yields read and write
access to every client's board.
Create, update and delete are now superuser-only; list and view are
owner-or-self. Adds four integration tests so a regression fails loudly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- migrations/ documented in pocketbase/pb_migrations/ for local PB
- scripts/setup-remote.ts applies them via admin API to remote PB
- collections: clients, projects, columns, cards, subtasks, comments, activity, settings
- user extension: role (owner|client_manager), client (relation)
- writes: owner only for app collections, superuser-only for activity and settings