It never got a UI home on the frontend -- removed from ProjectView a
few sessions back and never restored anywhere -- so it was a fully
live field (validated, stored, returned by the API, covered by tests)
with no consumer.
- Migration 011: ALTER TABLE projects DROP COLUMN description.
- ProjectRepository: description dropped from ProjectRow, SELECT,
create() and update() -- update() is now just a rename (string, not
a $fields array; there was never more than one editable field once
this left).
- ProjectController: store()/update() no longer accept or validate
it; update() drops the "provide at least one of title, description"
branch, since title is unconditionally the only field now.
- Frontend Project type, root README's API docs and curl example.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
requireOwnedProjectId() was copy-pasted identically in CardController
and CardStatusController; ProjectController's own
requireOwnedProject() was the same lookup, just returning the full
row instead of the id. New abstract ProjectScopedController (extends
Controller) holds one copy of both, and all three controllers now
extend it instead of Controller directly, forwarding their
ProjectRepository to its constructor.
Separately, CardController::reorder() (card_ids) and
CardStatusController::reorder() (status_ids) each had the same inline
'must be an array of ids, no duplicates' check. Both now call a new
Validator::intIdArray(), which does the same shape check once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>