# Milestones

> Organize releases, sprints, and delivery checkpoints — track progress, link runs, and manage the milestone lifecycle.

A **milestone** is a named delivery checkpoint in a project — a sprint, a release, or any bounded set of test activity you want to track as a unit. Milestones collect test runs, carry optional dates, and automatically compute progress from their associated runs.

## Reaching the screen

Open a project, then click **Milestones** in the project sidebar. The URL is `/projects/:projectId/milestones`.

## The milestones list

The list groups milestones by their parent. Each **parent milestone** renders as one bordered **group card**: a tinted parent row at the top, followed by a sub-table of its live child milestones. Parents and children are never shown as undifferentiated siblings — a child always sits inside its parent's card. A shared **column-header row** sits once above the stack of cards, and the parent row and every child row line up under that same grid:

| Column        | Description                                                                                                                                                                                                                                              |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Milestone** | Status-colored flag icon, the milestone name (children are smaller and indented), and a one-line description beneath the name when one is set                                                                                                            |
| **Status**    | Color-coded lifecycle chip (`Upcoming`, `Active`, `Completed`) — status carries the visual weight; the list shows no progress bar                                                                                                                        |
| **Dates**     | Plain-text dates with no urgency coloring: `Finished {date}` for completed milestones, `Due {date}` when a due date is set, `No due date` when only a start/forecast date exists, or `No dates` when none are set — with a secondary start/forecast line |
| **Cases**     | The subtree test-case count, shown as a mono number                                                                                                                                                                                                      |
| **Author**    | The milestone author's avatar and name, or an em-dash for API-token-created and legacy milestones                                                                                                                                                        |
|               | A per-row actions menu (⋯) with **Edit** and **Delete** (members and above)                                                                                                                                                                              |

A right-aligned mono summary above the list reads `{n} milestones · {m} sub-milestones`, counting the parents and children currently shown.

On a narrow (phone-width) screen the list drops the column-header row and the
group-card grid: each parent and each of its children presents as its own
card, in the same order, showing the same status, dates, cases, and author
values underneath the name. A parent card notes how many sub-milestones it
has, and its children follow it indented, each marked with a corner arrow. Every filter, sort, and action you have on a
wider screen is still there — only the layout changes. At tablet width and
above the list is the grouped grid described above.

Every row is clickable and opens that milestone's detail page — clicking a child row opens the child's own detail.

Use the search input at the top to filter by name. The status filter lets you narrow the list by one or more lifecycle states. Changing the search or status resets to the first page.

### Pagination is by parent milestone

The pager below the list follows the app-wide rule: the rows-per-page selector and the range
summary always show once there are enough milestones to paginate, and the previous/next and
page-number controls appear only once the list spans more than one page.

The unit of pagination is the **parent milestone**, not the individual milestone. A page contains up to `pageSize` parent milestones; each parent's children travel with it inside the same card and are never counted toward the page limit. A parent and its children are therefore never split across pages. The pager summary (`X–Y of Z`) counts parent milestones only — children are not included in the total.

## Creating a milestone

1. Click **New milestone** (visible to members and above; viewers see a read-only list).
2. Enter a **Name** (required, unique within the project).
3. Optionally choose a **Status** (defaults to `Upcoming`).
4. Optionally set a **Start date**, **Due date**, and **Forecast date**. The due date must be on or after the start date — the UI enforces this before submitting.
5. Optionally write a **Description**. It can hold formatting and images: paste or drop an image into it, or type **/** and choose **Image**.
6. Optionally select a **Parent milestone** to form a hierarchy (a milestone can only be a root or a direct child — grandchildren are not allowed).
7. Click **Create milestone**.

If the name is already in use, an inline error appears on the name field and the dialog stays open.

## Milestone status lifecycle

Milestones follow a lifecycle with four statuses:

| Status        | Meaning                                                           |
| ------------- | ----------------------------------------------------------------- |
| **Upcoming**  | Planned but not yet started                                       |
| **Active**    | Currently in progress                                             |
| **Completed** | All work is done; the milestone is locked for new run assignments |
| **Archived**  | Retired; the milestone is locked and excluded from selectors      |

Valid transitions are:

- `Upcoming` → `Active`
- `Active` → `Completed`
- `Completed` → `Active` (re-open)
- Any non-archived status → `Archived`

Transitions to `Completed` from `Upcoming` (skipping `Active`) are not allowed. An invalid transition shows an inline error in the edit dialog.

When all associated runs are closed, the milestone **auto-completes**: the status transitions to `Completed` automatically without user intervention.

## Hierarchy

A milestone can optionally have a parent milestone, forming a two-level hierarchy. This is useful for grouping related sprints under a release, or epics under a quarter milestone. Hierarchy rules:

- A root milestone has no parent.
- A child milestone has exactly one parent.
- Grandchildren (depth > 2) are not allowed.
- You cannot set a milestone as its own parent.

The parent selector in the create and edit dialogs shows only root milestones that are not the milestone being edited.

## Attaching runs

Test runs can be assigned to a milestone in two ways:

1. **Run creation** — when creating a new test run, select an optional milestone from the milestone selector. The selector shows only live non-locked (`Upcoming` or `Active`) milestones.
2. **Milestone detail view** — open a milestone's detail page and use the **Assign run** control in the Linked Runs tab.

Removing a run assignment is always available regardless of the milestone's status — even on locked (`Completed` or `Archived`) milestones.

## Progress

Progress values are computed server-side and surfaced in the **detail view**: a full progress bar plus stats — total runs, closed runs, and pass rate. The grouped **list view** intentionally shows no progress bar or percentage — status carries the weight there.

Progress updates when associated runs are closed or reassigned.

## Milestone detail view

Click a milestone's name to open its detail page. Its URL carries the milestone's readable ID, for example `/projects/ACME/milestones/M-3` — a per-project number that never changes, so the link is safe to share. The page is a **two-pane layout**: a scrollable main column on the left and a fixed **summary rail** on the right.

### Summary rail

The right rail always shows:

- **Progress donut** — percent of runs executed (closed / total).
- **Results legend** — individual counts for `Passed`, `Failed`, `Blocked`, `Skipped`, and `Untested`, sourced from the server's outcome counts.
- **Inline status control** — click the pill to transition the milestone status. When the milestone is `Completed`, the pill shows a check icon and is non-editable.
- **Date cells** — Start, Due, Forecast (hidden when `Completed`), and Completed dates (each shown when present, em-dash when null).
- **Author** — the user who created the milestone (or "—" for API-token-created or legacy milestones).
- **Created / Updated** timestamps.

On a phone or tablet (below 1024px) the rail is not a column. A **Summary** band
sits directly under the page header carrying the headline readout — percent
executed, the executed/total count, the failure count when there is one, and the
current state — and activating it opens the whole rail as a bottom sheet. No
value is dropped; only the number of taps to reach it changes.

### Resizing and collapsing the summary panel

On a desktop screen (1024px and wider) the rail's width is yours to set. Drag its **leading edge** — dragging left widens the rail and dragging right narrows it. With the handle focused, `ArrowLeft` widens by one step and `ArrowRight` narrows by one step, and a **double-click** restores the rail's default width.

The **round chevron button on the panel's left edge** — shown when you point at the panel or tab to it — or the `]` key collapses it into a 56px readout column: the progress ring, the completion percentage and the executed/total count stay, a hairline separates them from a rack of icon keys, and each key opens that section's values — pass rate, status, the date cells, author and timestamps — in a popover anchored beside the column. The live status control stays operable inside its key, so a status transition costs the same whether the rail is expanded or collapsed. Collapsing removes no value; every section is one click deeper. The same toggle, in the same place, expands the rail again.

**The width and the collapsed state are shared by all four summary rails** — runs, test plans, milestones and defects. Resizing or collapsing this rail changes the run, plan and defect rails too, and both settings survive a reload.

### Main column — header

The header shows a back button, a breadcrumb (`project › milestones › shortId`), the milestone name in the display font, a clamped description with an expand affordance, a parent-milestone link (when the milestone has a parent), an **Edit milestone** button (members and above), and a more-actions menu.

### Main column — sub-milestones band

When a milestone has children, a sub-milestones band appears below the header showing a grid of clickable child cards. Each card shows the child's status chip, test case count, a progress bar, and closed/total runs. Clicking a card navigates to that child's detail page. Progress values in the rail roll up to include all child milestones.

### Main column — tabs

Three tabs organize the milestone's linked items. The active tab is driven by the `?tab=` URL search param (default: `linked-runs`).

#### Linked Runs tab

Shows the runs assigned to this milestone:

- An **Assign run** control (dropdown picker + button) when the milestone is not `Completed`.
- A **locked banner** instead of the assign control when the milestone is `Completed`.
- A runs table (Title / Environment / Author / Total time / Elapsed / Status / Results columns) with row-click navigation to the run detail page.
- A per-row **Remove assignment** button (hidden when the milestone is `Completed`).

#### Related Test Cases tab

Shows the test cases linked to this milestone in collapsible suite-grouped bands (the same structure as the Plan detail's Cases tab):

- Each row shows a priority chip, the display ID, the case title, and a lifecycle status chip (hidden when lifecycle is "Active" — the normal state).
- Collapsing a suite band hides all its nested child bands.
- An empty state message when no test cases are linked.

#### Related Defects tab

Shows the defects linked to this milestone in a read-only table with columns: the defect (its `D-n` reference and title), status chip, severity, and assignee. Clicking a row navigates to the defect detail page. A defect appears here when its **Milestone** field is set to this milestone; until one is, the tab says so.

## Editing a milestone

Open a row's actions menu (⋯) in the list and choose **Edit**, or click **Edit milestone** on the detail page, to open the edit dialog. All fields are editable. The status dropdown shows only transitions that are valid from the current status.

A `409 conflict` (name already in use by another milestone) shows an inline error on the name field. A `422 validation_failed` shows inline errors on the offending fields.

## Deleting a milestone

Open a row's actions menu (⋯) in the list and choose **Delete**. A confirmation dialog names the milestone and warns that associated runs will be unassigned. Click **Delete** to proceed, or **Cancel** to return to the list without changes.

Deletion is permanent and cannot be undone from the UI.
