Projects & Kanban
The Projects module is a generic tool for steering work as a kanban board. It is meant to organise any structured list of tasks — modernisation programme, certification campaign, dev backlog, support tickets — without being tied to the maintenance model. Each instance configures its own project kinds, its own columns, and runs its items via drag-and-drop.
Four core objects
- Project
The board itself: a top-level work unit identified by a human-readable code unique to the instance (e.g.
PROJ-MOD-MOTUR-2026). Carries a title, description, priority, owner, target schedule and an optional hierarchy (programme → project → sub-project).
- ProjectKind
The project “type”, configurable per instance. Examples: “Dev”, “Certification”, “Fleet modernisation”, “Sales”. Used to classify and filter projects. Each kind has a stable code, a label, optional colour and icon.
- ProjectStatus
A kanban column — also configurable per instance. Examples: “Backlog”, “In progress”, “Blocked”, “Done”. Two constraints per instance: at least one status flagged
isInitial(auto-assigned on creation) and at least one flaggedisFinal(freezesactualEnd/completedAt).
- ProjectItem
A kanban card, attached to a project. Carries title, description, priority, assignee, estimated and actual effort, due date, and optional links to a business entity ( aircraft, WP, Part Number).
Multi-tenant isolation
Like every Envergure module, Projects is strictly partitioned by instance. All API requests require
the X-Instance-Id header. The Projects, ProjectKinds and ProjectStatuses of one
instance are never visible from another — even when several instances share the same cluster. Cross
FKs (kindId, statusId, parentProjectId) are checked at the
service layer so that an item cannot reference a reference object from a different instance.
Project list (tabular view)
The Projects page is the module’s main entry point: a table listing every project in the instance with filters, sorting and pagination. This is where you find a project to open, delete it, or just track overall progress.
Filter and sort
- Search: free-text input on the top left, applies a case- and accent-insensitive filter on the code and the title.
- Kind and Status: two selects next to the search, listing the instance ProjectKinds and ProjectStatuses.
- Priority: four multi-select buttons (
Low,Med.,High,Crit.). Cumulative — clicking several shows projects matching any of the selected priorities. - Reset: button to the right of the filters, only visible when at least one filter is active.
- Sort: clicking a column header (Code, Title, Kind, Status, Priority, Progress, Activity,
Created) sorts on that column; a second click flips the order. An
↑/↓arrow marks the active column and order.
All these settings are persisted in the URL (search params q, kind, status, priority,
sort, order, page). Sharing the link shares the exact filtered view; using the browser back
button restores the previous filters.
Columns
- Progress: compact progress bar +
n/totalcounter (done sub-tasks over total). Italic—when the project has no items. - Activity: most recent item update date, in relative format (
yesterday,3 d ago,2 w ago…) with the absolute date in a tooltip.—when no items. - Created: project creation date (
YYYY-MM-DD). - Actions: trash button for deletion. Disabled while
itemCount > 0— you must delete or reassign items first to avoid an accidental soft cascade. A confirmation is required before final deletion.
Pagination
50 projects per page. If the filtered list exceeds 50 items, a « Previous / Page X / Y / Next »
bar appears below the table. The page number is part of the URL and is reset whenever a filter
changes (we jump back to page 1 to avoid landing on an empty page).
Mobile layout
Below the md breakpoint (~768 px), each table row collapses to a stacked card automatically, with
an inline label in front of each value (Kind, Status, Priority, etc.). The filter bar and
pagination switch to flex-wrap. No action is hidden — the trash button stays accessible at the
bottom of each card.
Create a project
Project creation
- Prepare the references
Before first use, the instance must hold at least one ProjectKind and a set of ProjectStatus covering initial + final (configurable in the admin). The demo seed already provides a set:
backlog,todo,in_progress,blocked,done,cancelled. - Open the form
From the project list, click “+ New project”. The form is served by route
/projects/new. - Set code, title and kind
The code is a short slug unique to the instance (letters, digits, dot, dash, underscore — immutable after creation). Pick the kind (ProjectKind) that drives classification. Default priority is Medium.
- Submit
The call POST
/projectscreates the project and auto-assigns the instanceisInitialstatus. You are redirected to the kanban view.
Work on the board
Day-to-day usage
- Add an item to a column
On the project sheet, the “+” button at the top of each column opens a dialog. Status is pre-filled with the column’s status; rank is appended. The API call is POST
/projects/:projectId/items. - Move a card
Native drag-and-drop (dnd-kit). Dropping onto another card = insert before that card; dropping onto the empty area of a column = append at the bottom. The UI applies an optimistic update and fires POST
/projects/:projectId/items/:id/transition. On conflict (two users move the same item simultaneously) the API returns 409 and the UI rolls back. - Change status from the card
No drag-and-drop needed: open a task (or subtask) — click the card or press Enter — then use the Status selector at the top of the drawer. The change is applied immediately (the same POST
/projects/:projectId/items/:id/transitioncall as drag-drop), without going through the “Save” button. Handy on keyboard or mobile, where dragging a card between columns is awkward. - Track progress
Moving to an
isFinalcolumn automatically fillscompletedAt. At Project level, the first transition out ofisInitialsetsactualStart; moving to the final status setsactualEnd. Quick filters (search, assignee, priority) at the top of the board narrow the view without touching storage.
Board ergonomics
The kanban board ships several surface refinements that smooth day-to-day usage.
Quick filters
To the right of the assignee/priority filters, three mutually exclusive buttons:
- My cards — only show items whose
assigneeActormatches the current user (read from theX-Actorheader sent by the frontend). - This week — only show items whose
dueDatefalls within the next 7 days and that are not completed (completedAtnull). - Blocked — only show items whose status has the
blockedcode. Disabled if the instance has no « blocked » status configured.
Clicking the active filter turns it off. Stacks with search / assignee / priority filters.
Display density
« Compact / Comfort » toggle in the toolbar (persisted in localStorage per user). Compact mode: reduced card padding, hides the footer (assignee, due date, tags) to save vertical space — useful on large boards to scan 30+ items at a glance. Comfort mode (default): full card with all metadata.
Loading skeletons
During the initial load (before data arrives), the board shows three skeleton columns (pulsing gray rectangles) in place of the « Loading… » text. Gives a progressive fill-in feel instead of a white flash followed by everything appearing at once.
Drop animation
When you release a card after a drag-drop, it lights up green (emerald pulsing ring) for 600 ms before returning to normal. Visually confirms the move was taken in by the API (on top of the optimistic update).
Keyboard shortcuts
The board is fully keyboard-navigable, Linear/Jira-style.
| Key | Action |
|---|---|
| j / k | Next / previous card in the current column |
| h / l | Previous / next column (focus first card) |
| Enter / e | Open the focused card’s edit drawer |
| n | New card (status = current column if focused, otherwise first status) |
| / | Focus the search field |
| Esc | Close the drawer; otherwise clear the card focus |
| ? | Open the shortcuts help modal |
Shortcuts are disabled while an input/textarea has focus (except Esc which blurs) and
during an active drag. The keyboard icon button at the top right opens the help modal with the
mouse for users who haven’t yet found the ? shortcut.
Sub-tasks
A ProjectItem can attach to another via parentItemId to express a « goal → activities »
breakdown. Key rule: maximum depth = 1 level — a parent cannot itself be a child. UI surfaces:
- In the + Add item dialog or the edit drawer, a Parent (optional) picker lists project items that do not already have a parent. Items that are already sub-tasks do not appear.
- A sub-task card on the board shows a
↪ <parent-code>badge that opens the parent drawer when clicked. - A parent card shows a
n/totalcounter for completed sub-tasks (completedAtnot null) over the total. - The parent drawer contains a Sub-tasks section with a + Add button that pre-fills the parent in the create dialog.
- A Hide sub-tasks toggle in the board toolbar filters the view to keep only standalone tasks and parents (locally persisted preference).
Status transitions do not cascade: closing a parent does not close its sub-tasks, nor the
other way around. That is intentional — each activity has its own life cycle. Drag-dropping a
sub-task to another column does not touch its parentItemId.
Labels
To distinguish the nature of a task (not its status), each item can carry one or more colored
labels. The set is fixed for now — no admin screen:
Dev · Archi · Tests · Doc · Infra · Bug · UX · Refacto.
- In the create dialog and the edit drawer, a picker shows the labels as clickable toggle chips; selected ones light up in their color.
- On the board card, labels appear as colored pills (3 max, then
+N). - The board filter bar offers a row of label chips: click to activate one or more; an item is shown if it carries at least one of the checked labels. Combines with the other filters and is cleared by Reset.
Technically, labels are stored as codes in the items’ existing tags field — no schema
migration. Legacy freeform tags (arbitrary text entered before labels existed) remain editable
in a dedicated drawer field and render as neutral gray pills on the card. A later switch to a
managed referential (like
- ProjectKind
- project kinds
Images
Each task and sub-task can carry one or more images (bug screenshots, mockups, field photos, diagrams…). The gallery lives in the task’s edit drawer, below the description.
- Add: drag and drop files onto the dashed zone, or click to open the file picker (multiple selection supported). Accepted formats: PNG, JPEG, WebP, GIF, SVG, 5 MB max per image.
- View: images show as thumbnails; click one to open the enlarged view (lightbox).
- Remove: the cross shown on thumbnail hover detaches the image from the task. The image is not deleted from storage (files are deduplicated and may be shared across resources) — only the link to this task is removed.
- On the board card, a
📎 Npill shows the number of attached images.
Technically, images reuse the Documents module: they are stored as documents and linked to the
task through a polymorphic attachment (resourceType = project_item). No dedicated table
was added.
Dogfooding: Envergure Dev Kanban
We use the Projects module ourselves to steer Envergure development (under the name
Envergure Dev Kanban). On every commit that delivers a tracked task, the dev agent fires an
automatic POST/projects/:projectId/items/:id/transition to slide the
card to the relevant column (in_progress, then done). This continuously
exercises the API and proves that concurrent transitions resolve correctly.
API reference
| Method | Path | Effect |
|---|---|---|
| GET | GET/projects |
List instance projects (filters kindId, statusId, priority, assigneeActor, parentProjectId, search). |
| POST | POST/projects |
Create a project. |
| PATCH | PATCH/projects/:id |
Update a project (title, description, kind, priority, schedule, parent, tags). |
| POST | POST/projects/:id/transition |
Change a project’s status. |
| DELETE | DELETE/projects/:id |
Soft-delete (DB-restorable). |
| GET | GET/projects/:projectId/items |
List items of a project. |
| POST | POST/projects/:projectId/items |
Create an item. |
| POST | POST/projects/:projectId/items/:id/transition |
Kanban drag-drop (statusId and/or beforeItemId/afterItemId). |
| GET, POST, PATCH, DELETE | /project-kinds, /project-statuses |
CRUD on both per-instance references. |
Mutations accept expectedVersion for optimistic locking — useful for concurrent
drag-drop.
See also
- Projects — main entry point.
- Work packages — the WP, business equivalent of a technical project carrying APRS sign-off.