---
phase: 09
slug: etapes
status: approved
shadcn_initialized: false
preset: none
created: 2026-05-15
---

# Phase 09 - UI Design Contract

> Visual and interaction contract for Phase 9. This phase adds etape controls to the existing Go/HTMX Tasks tab without redesigning the kanban.

---

## Design System

| Property | Value |
|----------|-------|
| Tool | none |
| Preset | not applicable |
| Component library | local `backend/internal/web/ui` helpers plus Tailwind utility classes |
| Icon library | none currently used in this backend UI; do not introduce an icon package for this phase |
| Font | inherited app/system sans-serif |

---

## Product Intent

Phase 9 should feel like an extension of the current working kanban, not a new task product. The user asked for a first functional UI now and a beautiful UI later. Keep the surface quiet, compact, and explicit.

Locked decisions from `09-CONTEXT.md`:

- Etapes live in a compact top strip above the existing kanban.
- Etapes render as chips with task counts.
- Include an `Unassigned` chip.
- Selecting an etape filters the existing kanban.
- Status columns and task drag/drop behavior remain the primary task interaction.
- Task assignment happens in task create/edit forms.

---

## Layout Contract

### Tasks Tab Structure

`TasksTabFragment` must render in this order:

1. Etape strip zone.
2. Existing kanban board.

Use a whole-fragment swap for etape filtering so active chip state, chip counts, add-task forms, and kanban contents stay in sync.

Required outer ids/classes:

- `#tab-content` remains the tab switch target.
- `#tasks-tab` should wrap the full Tasks tab fragment after Phase 9.
- `#etape-strip` should wrap the chip strip and etape management controls.
- `#kanban-board` remains the kanban board id.
- Existing `.sortable-column`, `.task-card-zone`, and `.task-card` semantics must remain stable.

### Etape Strip

The etape strip sits directly above `#kanban-board`.

Visual contract:

- Horizontal flex row with wrapping: `flex flex-wrap items-center gap-2`.
- Bottom spacing before kanban: `mb-4`.
- No surrounding card, no nested card, no panel border around the whole strip.
- Chips are compact inline buttons or links, not large cards.
- Chips should not resize the kanban columns.
- Long etape titles must truncate inside the chip with `max-w-*`, `truncate`, and visible count.

Minimum chip set:

- `All` chip: shows all tasks.
- `Unassigned` chip: filters tasks with no etape.
- One chip per etape.

The `All` chip is allowed even though the user only explicitly requested `Unassigned`; it is the necessary return path from a filtered view.

### Kanban Board

Keep the existing four horizontal columns and fixed column width.

Do not:

- split columns into etape groups,
- turn etapes into lanes,
- move status labels under etapes,
- change `TaskColumns`,
- place the kanban inside a decorative card.

When a filter has no tasks in a status, keep the existing empty copy style: `No tasks yet`.

---

## Interaction Contract

### Filtering

Chip behavior:

- `All` chip requests `/tablos/{id}/tasks` with no etape filter.
- `Unassigned` chip requests `/tablos/{id}/tasks?etape=unassigned`.
- Etape chips request `/tablos/{id}/tasks?etape={etape_id}`.
- HTMX target should be `#tab-content` or `#tasks-tab`, not only `#kanban-board`, because active chip state and counts must update with the board.
- `hx-push-url` should keep the current filter URL shareable and refresh-safe when practical.

Active chip state:

- Active chip uses a stronger border/text/background than inactive chips.
- Active chip must be visible without relying on color alone: use both border weight/color and text weight.

### Etape Create/Edit/Delete

Management controls should remain close to the strip.

Recommended first shape:

- A compact `New etape` button at the end of the strip.
- Inline create form swaps into a small strip-adjacent zone, not into the kanban columns.
- Edit/delete controls may be exposed from the active etape chip or an inline edit state; the plan may choose the simplest HTMX pattern that stays compact.
- Delete confirmation copy must make clear that tasks are kept and unassigned.

Etape reorder:

- Reordering can be implemented with explicit up/down controls or Sortable.js over the chip strip.
- If using Sortable.js, do not interfere with `.sortable-column` task drag/drop.
- Reorder controls must preserve chip dimensions and avoid layout shift.

### Task Assignment

Task create/edit forms must include an etape selector.

Selector contract:

- Label: `Etape`.
- Options include `No etape`.
- Existing etapes are listed by title.
- If the current board filter is a specific etape, new task forms may preselect that etape.
- If the current filter is `Unassigned`, forms should default to `No etape`.
- If the current filter is `All`, forms should default to `No etape` unless editing an assigned task.

Selector styling should match existing inputs:

- text size `text-sm`,
- border `border-slate-300`,
- rounded border,
- `px-2 py-1` inside compact task cards or `px-3 py-2` in larger forms.

### Deletion

Etape delete confirmation copy:

- Heading: `Delete etape?`
- Body: `Tasks in this etape will stay in the tablo and move to Unassigned.`
- Primary destructive action: `Delete etape`
- Secondary action: `Keep etape`

Deleting an etape must not visually remove task cards permanently from the tablo. After deletion, affected tasks should appear under `Unassigned` or `All` depending on the active filter.

---

## Spacing Scale

Declared values are multiples of 4 and align with existing Tailwind usage.

| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon-free inline gaps, chip inner count spacing |
| sm | 8px | Chip gaps, compact form field gaps |
| md | 16px | Strip-to-board spacing, card padding, column gaps |
| lg | 24px | Existing tab content rhythm |
| xl | 32px | Not required for this compact feature |
| 2xl | 48px | Not required |
| 3xl | 64px | Not required |

Exceptions: none.

---

## Typography

Use current app typography; do not introduce viewport-scaled text.

| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| Body | 14px (`text-sm`) | 400 | default Tailwind normal |
| Label | 14px (`text-sm`) | 500 | default Tailwind normal |
| Compact meta/count | 12px (`text-xs`) | 500 | default Tailwind normal |
| Section heading | 16px (`text-base`) | 600 | default Tailwind normal |
| Page heading | unchanged existing page heading | unchanged | unchanged |

Chip titles should use `text-sm`. Counts should use `text-xs` or local badge styling.

---

## Color

Use the existing slate-forward palette. Do not add a new brand accent for etapes.

| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | `#ffffff` / `bg-white` | Task cards, form surfaces |
| Secondary (30%) | `#f1f5f9` / `bg-slate-100`; `#e2e8f0` / `border-slate-200` | Column headers, chip backgrounds, borders |
| Accent (10%) | `#1e293b` / `text-slate-800`, `border-slate-800` | Active chip, active tab-like state |
| Destructive | existing danger button styles | Etape delete confirmation only |

Accent reserved for:

- active etape chip,
- selected tab/filter state,
- primary create/save actions through existing `ui.Button` default styling.

Do not use gradients, decorative colored strips, or a one-off etape color palette.

---

## Copywriting Contract

| Element | Copy |
|---------|------|
| Primary CTA | `New etape` |
| Etape title label | `Title` |
| Etape description label | `Description (optional)` |
| Empty etape strip body | `No etapes yet` |
| Empty etape strip action | `New etape` |
| Filter chip for all tasks | `All` |
| Filter chip for tasks without etape | `Unassigned` |
| Task assignment label | `Etape` |
| Task assignment empty option | `No etape` |
| Etape title validation | `Title is required` |
| Etape delete heading | `Delete etape?` |
| Etape delete body | `Tasks in this etape will stay in the tablo and move to Unassigned.` |
| Etape delete action | `Delete etape` |
| Etape delete cancel | `Keep etape` |
| Generic server error | `Something went wrong. Please try again.` |

Avoid explanatory UI text about keyboard shortcuts, implementation, or how filtering works. The chip labels and active state should carry the interaction.

---

## Accessibility Contract

- Chips must be real links or buttons.
- Active chip must include `aria-current="true"` when rendered as a link.
- Delete/edit controls must have task- or etape-specific `aria-label` values when the visible label is short.
- Form labels must be associated with inputs via `for`/`id` or an equivalent explicit label pattern.
- Count badges must remain readable as text, not only visual badges.
- Focus styles must remain visible through existing browser/Tailwind focus treatment.

---

## HTMX Swap Contract

Preferred fragment boundaries:

- Filtering: swap `#tasks-tab` or `#tab-content`.
- Etape create/edit/delete: swap a dedicated etape strip/form zone and include out-of-band updates if counts or board contents change.
- Task create/update: preserve current `.task-card-zone` and add-task slot behavior.
- Reorder: preserve `#kanban-board` as the task reorder response target.

Any response that changes etape assignment or etape deletion should update chip counts. Whole Tasks-tab swap is acceptable for the first version.

---

## Registry Safety

| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none | not required |
| third-party registry | none | do not use |

---

## Checker Sign-Off

- [x] Dimension 1 Copywriting: PASS
- [x] Dimension 2 Visuals: PASS
- [x] Dimension 3 Color: PASS
- [x] Dimension 4 Typography: PASS
- [x] Dimension 5 Spacing: PASS
- [x] Dimension 6 Registry Safety: PASS

**Approval:** approved 2026-05-15
