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 flagged isFinal (freezes actualEnd / 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/total counter (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

  1. 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.

  2. Open the form

    From the project list, click “+ New project”. The form is served by route /projects/new.

  3. 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.

  4. Submit

    The call POST/projects creates the project and auto-assigns the instance isInitial status. You are redirected to the kanban view.

Work on the board

Day-to-day usage

  1. 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.

  2. 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.

  3. 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/transition call as drag-drop), without going through the “Save” button. Handy on keyboard or mobile, where dragging a card between columns is awkward.

  4. Track progress

    Moving to an isFinal column automatically fills completedAt. At Project level, the first transition out of isInitial sets actualStart; moving to the final status sets actualEnd. 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 assigneeActor matches the current user (read from the X-Actor header sent by the frontend).
  • This week — only show items whose dueDate falls within the next 7 days and that are not completed (completedAt null).
  • Blocked — only show items whose status has the blocked code. 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/total counter for completed sub-tasks (completedAt not 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
) stays possible without breaking existing data.

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 📎 N pill 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.