Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-178Done

3.3.5 Swimlane UI — group-by control, collapsible lanes + catch-all, cross-lane drag-reassign

Estimate: 28m · Depends on: 3.3.1, 3.3.4, 3.2.4, 3.2.5

Render swimlanes on the 3.2 board and make cross-lane drag reassign the grouped field — drawn per design/boards/swimlanes-wip.mock.html, reusing the 3.2 board container, columns, cards, dnd-kit context, and virtualization (NOT a parallel board).

Group-by control. A board-header control (in the 3.2.1-reserved 3.3-controls slot) offering None / Assignee / Epic / Priority, reusing a shipped select/segmented primitive. Changing it PATCHes board.swimlaneGroupBy (3.3.3) and re-lays the board from the 3.3.4 projection (a loading transition, not a flash of the old layout). The active value is read from the projection so all viewers share it.

Swimlane rendering. When group-by ≠ none, render one lane per swimlanes[] entry: a sticky lane header (label/kind from the DTO — assignee avatar+name / epic key+title / priority pill / the catch-all "No <dim>") + the aggregate count + a collapse/expand chevron (collapsed state persisted client-side, e.g. localStorage keyed by board+lane). Within a lane, the existing BoardColumn/BoardCard (3.2.3) render the cards bucketed by swimlaneKey; column boundaries align across lanes; the catch-all lane sorts last. Group-by none renders the flat 3.2 board unchanged.

Cross-lane drag = reassign (reuses existing endpoints, no new backend). Extend the 3.2.4 dnd-kit drop handler: resolve the drop's target column AND target lane. A column change is the 3.2 transition (POST …/board/move, unchanged). A lane change reassigns the grouped field via the EXISTING Story-2.5 issue-field update endpoint — assignee lane → reassign assignee, priority lane → change priority, epic lane → reparent to that epic (drop into the catch-all clears the field where legal). A diagonal drop applies BOTH writes. Each write is optimistic with independent snap-back (the 3.2.4 pattern): on rejection of one (e.g. an illegal transition 409), revert only that axis; never leave the card in a lying position. aria-live announces reassign vs transition vs diagonal per the 3.3.1 copy. Keyboard DnD must move a card across lanes too.

Virtualization coexistence. Lanes must keep the 3.2.5 per-column windowing + load-more working within each lane's column slice (do NOT add a second virtualization lib; reuse the 2.5.15 primitive). A collapsed lane unmounts its card bodies (cheap) but keeps its header + count.

Out of scope here: the WIP config + over-limit warning (3.3.6); per-lane WIP (per-column matches the mirror; no stated use case for per-lane).

Acceptance criteria

  • A board-header group-by control (None/Assignee/Epic/Priority) PATCHes board.swimlaneGroupBy (3.3.3) and re-lays the board from the projection with a loading transition; the active value is shared (read from the projection).
  • Group-by ≠ none renders one collapsible lane per swimlanes[] entry (sticky header + label/kind + aggregate count + chevron), cards bucketed by swimlaneKey, column boundaries aligned across lanes, the catch-all lane last; collapse state persists client-side; group-by none is the flat 3.2 board.
  • A cross-lane drag reassigns the grouped field via the existing 2.5 issue-field endpoint (assignee/priority/epic-reparent; catch-all clears where legal) — NOT the board/move endpoint, NO new backend; a diagonal drop applies both the transition and the reassign, each optimistic with independent snap-back on rejection.
  • Keyboard DnD moves a card across lanes with the correct aria-live announcement; per-column virtualization + load-more still work within lanes (no second virtualization dep).
  • Colours via --el-*, shape via element tokens, AA-safe; drag/lane affordances not colour-alone (finding #35); matches design/boards/swimlanes-wip.mock.html.
  • Component tests assert lane rendering + bucketing + catch-all, collapse persistence, and the cross-lane reassign reducer (incl. diagonal + independent revert).

Context refs

  • app/(authed)/boards/_components/* (Stories 3.2.2–3.2.5) — the board container, BoardColumn/BoardCard, the dnd-kit DndContext + drop handler, and the load-more/virtualization to extend (NOT fork)
  • app/(authed)/issues/_components/IssueInlineEdit.tsx + the Story-2.5 assignee/priority/parent update endpoints — the field-reassign path the cross-lane drop reuses (the same optimistic precedent 3.2.4 mirrors)
  • Story 3.3.3 — the PATCH …/board group-by write; Story 3.3.4 — the swimlaneGroupBy + swimlanes[] + per-card swimlaneKey projection this binds to
  • Story 2.5.15 — the virtualization primitive to reuse; design/boards/swimlanes-wip.mock.html + design-notes.md (3.3.1) — the lane/drag/announcement spec
  • finding #35 (not colour-alone); motir-core/CLAUDE.md--el-* + element-shape rules (client UI)