web/README.md's Layout section hadn't been touched since early in the
session: it was missing ManageMenu.vue, CardManageMenu.vue,
StatusManager.vue, useDialog.ts, and four of the ten views
(ProjectExploreView, ProjectKanbanView, CardView, CardConfigureView)
entirely, still described CardRow as inline-editable-with-a-delete-
button, and still claimed the top-level <RouterView> was keyed by
plain route.path (true before this session's Explore/Kanban route
split, wrong after -- see App.vue's routeKey). The Inbox section also
still gated the post-drag cards refresh on route.name === 'project'
alone, from before Explore itself could send a card there.
Also fixed two lingering "all tasks" references (the tab's name before
it became "Explore") in a stores/cards.ts comment and a style.css one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches KanbanCard, which never had one -- deleting a card lives on
its own view now (CardManageMenu's "Delete card"), reachable by
clicking through from either list.
Cleaned up what that leaves unused: the cards store's remove() (its
only caller), and .card-row__delete's styling.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each column gets its own form at the bottom (styled like the sidebar
inbox's), creating the card directly in that status, appended after
its existing cards.
- API: POST /projects/{id}/cards takes an optional status_id, which
must belong to the project (422 otherwise); omitted, it still
defaults to the project's first status as before. Position is
already "end of that status" for free -- createInProject() already
ranks by (owner, project, status).
- cards store: add() takes an optional statusId, forwarded as
status_id when given.
- ProjectView: one draft string per status (keyed by status id) so
typing in one column doesn't touch another's, mirroring the
per-status independence the columns already have for reordering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Clicking a card -- in the "all tasks" list, a kanban column, or the
inbox -- now opens /cards/:id instead of editing the text in place:
- CardRow's text is now plain (its RouterLink wraps the text + status
badge; the delete button stays a sibling so it isn't nested inside
the link). KanbanCard is now itself a RouterLink.
- New CardView.vue: header follows the project view's layout, but the
back link sits inline inside the title (before the card's text)
rather than off in the actions corner, since it isn't paired with a
manage menu here. Inbox cards have no project to link back to, so
they go to the dashboard instead. Below the header, an "Edit text"
section (styled like the project rename form) replaces the inline
editing that used to live in the list row, and a delete button in
the header actions replaces CardRow's per-row delete for cards
reached via kanban/inbox (which never had one).
- No backend changes: GET/PATCH/DELETE /cards/{id} already existed.
- Renamed .card-row__status to the more general .status-badge, now
shared by the list row and the card view.
- cards store: dropped setText, now unused now that editing goes
through a direct PATCH + store refresh in CardView instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A card either sits in its owner's inbox (project_id AND status_id both NULL)
or belongs to exactly one project with a status in it (both set) -- enforced
by a CHECK constraint, never one without the other. The inbox is global to a
user now, not per-project: cards can move from a project into the inbox and
back into any status column of any project.
Backend
- migrations/009: rebuilds `cards` (SQLite can't relax NOT NULL / add a CHECK
in place) with a nullable project_id, a new owner_id (cards need direct
ownership once they can have no project), and the CHECK constraint. Cards
that had no status (the old per-project inbox) move to the new global inbox.
status_id's FK is now ON DELETE RESTRICT, not SET NULL -- nulling it alone
would violate the invariant, and there's no status-delete endpoint anyway.
- CardRepository: "column" is now (owner_id, project_id, status_id); every
method that dealt with a project's columns is generalised to also cover the
inbox and cross-project moves (orderColumn, idsInColumn, repack, ...).
- CardController/routes: single-card and ordering routes move to global,
since a card may have no project to nest them under --
GET/PATCH/DELETE /api/cards/{id}, PUT /api/cards/order (body now takes
project_id + status_id, both null for the inbox). New GET/POST
/api/inbox/cards. PATCH no longer accepts status_id -- moving a card, in or
out of a project, is exclusively PUT /api/cards/order now. A card created
directly in a project (POST /api/projects/{id}/cards) lands in its first
status, since a project card can't have no status.
- Tests: ProjectTest/CardStatusTest updated for the new routes; CardOrderTest
rewritten with full inbox/cross-project coverage. 57 tests pass.
Frontend
- New stores/inbox.ts (the global inbox) and lib/cardOrder.ts (the shared
PUT /api/cards/order call, used by both the sidebar and a project's board).
- AppSidebar: an Inbox section under the project list -- a vuedraggable list
in the same "kanban" drag group as every project's kanban columns, so a
card drags straight from the sidebar into whichever project is open, or
back out. (The empty-inbox state needed a real bugfix: it wasn't rendering
a <draggable> at all, so there was nowhere to drop a card back into an
empty inbox.) A drop reloads the inbox and, if a project is open, its cards.
- ProjectView's kanban board drops its synthetic Inbox column -- just the
real statuses now.
- DashboardView simplified to a plain grid of project tiles (name + card
count); its per-project "New" section is gone, since a project card can no
longer have no status.
- stores/cards.ts: patch/remove move to the global /api/cards/{id} routes.
Verified end-to-end against the rebuilt container (existing per-project-inbox
cards correctly migrated to the global inbox, 0 invariant violations) and the
dev server via headless Chrome: sidebar inbox -> project A "To do" -> back to
inbox -> project B "Done", full journey confirmed via the API at each step.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Statuses
- Migration 006: card_statuses table (project-scoped) and cards.status_id, a
nullable FK with ON DELETE SET NULL. Every new project is seeded with
"To do" / "Doing" / "Done"; GET /api/projects/{id}/statuses lists them.
- New cards have no status -- they sit in an "inbox" until moved.
Project view
- Full-width and tabbed: "All tasks" (a flat list, sorted by name
case-insensitively) and "Kanban" (Inbox plus one column per status).
- Drag a card within or between columns to reorder / restatus; the Inbox
column has its own name + Add form.
Ordering
- Migration 007: `position` is now a dense 0..n-1 rank within a
(project_id, status_id) column, not a project-wide order. New composite
index idx_cards_project_status_position; existing rows re-ranked.
- PUT /api/projects/{id}/cards/order takes { status_id, card_ids } and sets one
column's contents and order, re-parenting moved-in cards and re-packing their
source column in a single transaction. PATCH status_id appends the card to the
end of the destination column.
58 phpunit tests pass; the frontend type-checks and builds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>