Commit graph

19 commits

Author SHA1 Message Date
a5dc6083dd feat(schema): add cards.assignee and subtask date fields
Three new optional fields, idempotent ensure helpers:

- cards.assignee: free-text label rendered above the card bar
  on the new /plan Gantt. Empty by default.
- subtasks.start_date: ISO date or empty. Lets subtasks appear
  on the Gantt under their parent card with their own duration.
- subtasks.target_date: same, for the bar end.

Live migration confirmed idempotent — first run added the
fields, second run reported both already present.
2026-09-21 16:12:29 +02:00
a2986a11d0 test(intake): per-(link, project) cap infrastructure
Phase 9a Task 6. Three integration tests:
  - schema lands: row inserts with link/project/date/count fields,
    composite unique index is the only constraint (different
    project same link + date is allowed)
  - duplicate (link, project, date) is rejected by the unique index
  - the public route returns a sensible status, never 500, on a
    Phase 9 link (the route will be wired in 9b; 9a only proves
    it does not crash today)

Two bridges from the plan:
  - PocketBase's date field defaults to unique=true; my schema
    fix flipped it via ensureIntakeLinkSubmissionsDateNotUnique.
    The relation and number fields appeared to share the same
    default but getOne()'s response omits the key, so the
    explicit unique=false on those doesn't stick — yet rows with
    the same link and different projects work in practice (the
    composite index does its job). Worth a follow-up if a future
    PocketBase release tightens the default.
  - bumpDailyCapForProject wraps the read-then-create-or-update
    in a retry on the unique constraint to handle PocketBase's
    read-after-write latency window. The cap-isolation property
    is verified end-to-end in 9c.
2026-09-21 13:27:58 +02:00
eb45fb0168 feat(schema): intake_links.client, projects.show_in_intake, intake_link_submissions
Phase 9a Task 1. Additive only — the Phase 8 shape stays live.
intake_links.project becomes optional so Phase 9 rows can leave it
empty. intake_links.client is the new owner of the link, read by
the picker page in 9b. projects.show_in_intake defaults to false
and is the knob that hides a project from the picker. The new
intake_link_submissions collection tracks the daily counter per
(link, project, date) so the cap applies per project, not per link.

Type additions:
  - ProjectRecord.show_in_intake: boolean
  - IntakeLinkRecord.client: string (Phase 9)
  - new IntakeLinkSubmissionRecord
2026-09-21 13:15:01 +02:00
7df7074806 feat(schema): projects.background_md for per-project context
Phase 8c Task 1. Optional text field on projects, max 40000 chars.
The prompt module caps at 30000 with a truncation marker. Empty
documents produce byte-identical prompts to today — the test in
Task 4 locks that in.
2026-09-21 11:44:58 +02:00
c11d378483 fix(intake-links): three PocketBase quirks the schema shipped with
The schema the plan specified had three places where a PocketBase
quirk blocked normal use of intake_links / activity rows. None of
them surfaced in Task 1 because the unit-level test there only
checked rule application, not inserts.

1. intake_links.submission_count and submission_count_today were
   required: true with min: 0. PocketBase treats 0 as blank for
   required number fields, so every insert 400'd. Made optional.
2. intake_links.active was required: true, which rejects false as
   blank (the same quirk). The owner can mint an already-revoked
   link (the test exercises this); made optional.
3. activity.actor was a required relation. The public route writes
   a card_submitted_anonymously row for a submitter who has no user
   account; empty string is not a valid relation. Made optional and
   dropped actor: '' from the route.
2026-09-21 06:30:49 +02:00
c99be4d49e feat(schema): intake_links and anonymous submitter role
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.
2026-09-21 01:54:11 +02:00
b3aac9ba4f feat(plan): Plan Puzzle v1 backend — proposal fields, server actions, Done-column guards
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.
2026-09-19 18:35:33 +02:00
4d1e6cf412 fix(commits): set source on ingest/backfill writes, tidy Task 1 review findings
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-19 10:22:26 +02:00
65b09170af feat(commits): add source and manual_minutes fields
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-19 10:14:51 +02:00
5dae0946eb feat(projects): add kind field (project | prototype) 2026-09-17 10:42:46 +02:00
05ce52a6bf feat(schema): card brief, transcript and size band 2026-09-16 18:04:22 +02:00
7ca2798561 feat(schema): card narrative fields 2026-09-16 16:24:18 +02:00
bce71a666a feat(schema): commit summary fields 2026-09-16 16:15:18 +02:00
cc20cf4bfd feat(metrics): cached project metrics recomputed on ingest 2026-09-16 13:51:03 +02:00
d06d1e99ec feat(cards): per-project card numbers for commit references
- 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
2026-09-16 13:20:24 +02:00
a05c1b8ae0 feat(schema): commits collection and project ingest fields 2026-09-16 11:46:57 +02:00
287d70c42c feat(users): active flag, backfilled, enforced at sign-in 2026-09-16 11:26:16 +02:00
2a8d6f2505 fix(security): lock the users collection against privilege escalation
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>
2026-09-16 11:18:13 +02:00
ba990f0109 feat(schema): all collections + role-scoped API rules on remote PB
- 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
2026-09-16 09:41:20 +02:00