Project configuration view: manage a project's statuses

New /projects/:id/configure view, linked from a new 'Configure' item on
the project view's Manage menu.

Backend:
- CardStatusRepository/CardStatusController gain full CRUD: create
  (appended at the end), reorder (dense positions, like card
  ordering), and delete.
- Deleting a status with cards attached is rejected with 409 and
  error.details.card_count, rather than hitting the existing FK
  RESTRICT constraint -- retrying with { reassign_to: <status id> }
  moves those cards to that status first (CardRepository::
  reassignStatus, appended after the destination's existing cards)
  and deletes in one transaction (CardStatusRepository::transaction,
  shared PDO connection across repositories).
- The last status in a project can't be deleted, since a project card
  is required to have one.
- Routes: POST/DELETE .../statuses(/:id), PUT .../statuses/order.
- 14 new CardStatusTest cases covering all of the above.

Frontend:
- ProjectConfigureView.vue: header (title, back-to-project link, the
  shared Manage menu) + a vuedraggable status list (reorder persists
  the whole new order) with a delete button per row and an add-status
  form. A row's plain delete either succeeds immediately or, on 409,
  opens a modal to choose a different status before retrying the
  delete with reassign_to.
- Extracted ProjectManageMenu.vue (the Manage dropdown + delete-project
  modal) out of ProjectView so both views share it; it now also has a
  Configure link (hidden on the configure page itself).
- ApiError gains a cardCount getter (details.card_count), mirroring
  the existing retryAfter getter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 20:31:10 +01:00
co-authored by Claude Sonnet 5
parent 21e148aa4d
commit dd7d217e8e
13 changed files with 995 additions and 126 deletions
+29 -5
View File
@@ -42,8 +42,9 @@ src/components/AppSidebar.vue left nav: Dashboard link, project dropdown, Inbox
src/components/CardRow.vue editable text + status chip + delete, one card
src/components/KanbanCard.vue small draggable card for the board columns and the inbox
src/components/PasskeyNotice.vue dismissible "add a passkey" banner across the top of the page
src/views/ DashboardView, ProjectView, LoginView, ProfileView,
VerifyEmailView
src/components/ProjectManageMenu.vue "Manage" dropdown + delete-project modal, shared by ProjectView and ProjectConfigureView
src/views/ DashboardView, ProjectView, ProjectConfigureView, LoginView,
ProfileView, VerifyEmailView
```
Signed-in "app" routes (`meta.requiresAuth`) render inside a persistent shell:
@@ -82,9 +83,10 @@ under the list adds a card straight to the inbox.
`/projects/:id` shows one project. It renders on a **full-width** layout (the
route sets `meta.wide`, which widens `.app__main` in `App.vue`), so the header
spans the full width and the **Manage** menu sits top right. The title is
inline-editable (saved on blur via `PATCH /api/projects/:id`). Manage has a
**Delete project** action that opens a confirmation modal; confirming calls
`DELETE /api/projects/:id` and returns to the dashboard.
inline-editable (saved on blur via `PATCH /api/projects/:id`). Manage
(`ProjectManageMenu.vue`) has a **Configure** link (to the status-management
view below) and a **Delete project** action that opens a confirmation modal;
confirming calls `DELETE /api/projects/:id` and returns to the dashboard.
Below the header are two tabs (local `activeTab` state, `v-show` so both stay
mounted). The tab order is fixed — **All tasks** first, **Kanban** second — but
@@ -112,6 +114,28 @@ card and re-packs whatever column it left; afterwards the view always reloads
both `inbox` and this project's `cards`, since either could have been the other
side of the move.
## Project configuration
`/projects/:id/configure` (`ProjectConfigureView.vue`) manages a project's
statuses. The header mirrors the project view's — title, `ProjectManageMenu`
top right — plus a "← Back to project" link (Manage's own Configure link is
hidden here, since it would just point at the current page).
The status list is a `vuedraggable` list (its own list, no shared drag group
with the kanban board) bound directly to a local `statuses` ref; dragging
mutates it in place, and `@change` persists the whole new order via
`PUT /api/projects/:id/statuses/order`, reverting to the server's copy on
failure. A small form below it adds a status
(`POST /api/projects/:id/statuses`) at the end of the list.
Each row has a delete button. A status with no cards deletes immediately; one
still holding cards gets `409` back from `DELETE .../statuses/:statusId` with
`error.details.card_count` (surfaced as `ApiError#cardCount`) -- that opens a
modal asking which other status to move its cards to, then resubmits the same
delete with `{ reassign_to }`, which reassigns and deletes in one request. The
last remaining status can't be deleted (a project card always needs one); its
row's delete button is disabled once `statuses.length <= 1`.
## Auth flow
There is no password and no separate sign-up — `LoginView` is an email field