Skip to content

Kanban Board

Overview

The Kanban Board is Symphony's primary workflow interface, visualizing project issues across two parallel pipeline systems: an execution pipeline (backlog → todo → in progress → review → done) for implementing work, and a readiness pipeline (research → architecture → grooming → ready) for decomposing and validating features before execution. It provides real-time status, progress tracking, and single-click navigation to issue detail.

How It Works

The Kanban Board renders issues in vertical columns, each representing a workflow stage. Issues are filtered into columns based on two independent dimensions: status (execution state) and phase (readiness state).

Backlog │ Phase:Research │ Phase:Arch │ Phase:Grooming │ Todo │ In Progress │ Blocked │ Review │ Done

Column Logic

Backlog (always first) is the intake funnel where new issues enter.

Phase columns appear only when the project has phase-labeled tickets. Three phase columns display research, architecture, and grooming work. Issues with non-ready phase labels (phase:research, phase:architecture, phase:grooming) are excluded from execution columns and shown only in their phase column.

Execution columns (todo, in_progress, blocked, review, done) show work ready for implementation. An issue with phase:ready or no phase label flows through execution columns; any non-ready phase label takes precedence.

Cancelled column is collapsible and shows zero-priority cancelled issues.

Filtering Rules

Within each column, issues are sorted by priority (ascending) — urgent issues float to the top.

ts
// Execution column filter (app/components/KanbanBoard.vue)
function issuesForStatus(status: string): Issue[] {
  return props.issues
    .filter(i => {
      if (i.status !== status) return false
      const phaseLabel = (i.labels ?? []).find(l => l.startsWith('phase:'))
      // Exclude non-ready phase issues from execution columns
      if (phaseLabel && phaseLabel !== 'phase:ready') return false
      return true
    })
    .sort((a, b) => a.priority - b.priority)
}

Frontend Data Flow

  1. Project page (app/pages/p/[slug]/index.vue) fetches project and issue list from API
  2. Polling refreshes the issue list every 5 seconds (usePolling composable)
  3. KanbanBoard component receives issues and splits them into columns based on status and phase
  4. KanbanColumn renders a single column, displaying issues as cards
  5. IssueCard is a link to the issue detail page; clicking navigates to /p/:slug/issues/:identifier

Status Transitions

Status changes are validated server-side. A human or agent can only transition an issue to valid next statuses:

The orchestrator also owns two automatic transitions:

  • todo → in_progress (when dispatching an agent)
  • in_progress → todo (on agent failure or retry)

Status changes are posted to PUT /api/projects/:projectId/issues/:issueId/status and validated by isValidTransition() (server/utils/statusTransitions.ts). A system comment is logged for each change.

Visual Indicators

Issues display several inline signals:

IndicatorMeaning
Pulsing green dot (top-right)Active agent working on this issue
Priority badgeUrgent (red), High (orange), Medium (blue), Low (gray)
Size badgeS/M/L estimate
Phase badgeRing-styled: research (blue), architecture (purple), grooming (orange), ready (green)
Attempt countYellow lightning for retries; red alert + pulse if >15 attempts (possibly stuck)
Last activityRelative timestamp (e.g., "2h ago")
PR status (review column only)Open (blue), Approved (green), Rejected (red)
Risk score (review column only)Confidence bar with affected file count

Key Components

FileResponsibility
app/pages/p/[slug]/index.vueProject page; fetches project + issues, polls for updates, toggles board/pipeline view
app/components/KanbanBoard.vueOrchestrates column layout; filters issues by status and phase; renders phase columns conditionally
app/components/KanbanColumn.vueRenders a single column with header count and scrollable issue list
app/components/IssueCard.vueIssue preview card; displays priority, phase, labels, attempt count, last activity, PR status
app/components/RiskBadge.vueRisk score visualization (confidence bar + file count) shown in review column
server/api/projects/[projectId]/issues/[issueId]/status.put.tsValidates and persists status transitions; logs system comment
app/utils/colors.tsCanonical color map for statuses, priorities, phases, and agents (auto-imported)
server/utils/statusTransitions.tsStatus transition validation rules and helper functions

Design Decisions

1. Dual-Pipeline Visualization

Symphony has two independent workflows: execution (status-driven) and readiness (phase-driven). Rather than hide one, the board makes both visible. Phase columns appear only when needed (when any ticket has a phase label), preventing visual clutter for projects that don't use the readiness pipeline yet.

Trade-off: More complex filtering logic, but clearer mental model. Users see both workflows in one place.

2. Phase-Labeled Issues Hidden from Execution

An issue in phase:research won't appear in the todo or in_progress columns, even if its status is todo. The phase label takes precedence.

Rationale: Phase-labeled work is not yet ready for execution. Researchers and architects need these tickets visible in their phase columns; developers should not see them in the execution pipeline until phase:ready.

3. Backlog First (Intake Funnel)

Backlog is always the first column and never collapsible. New issues land here and flow through the readiness pipeline (if project uses phases) or directly into execution.

Rationale: Clear intake point. Backlog is not a "done" column — it's the entry gate.

4. Persistent Phase Columns

Once a project has phase-labeled issues, phase columns appear on every page refresh. They don't flicker on/off.

Rationale: Users might clear a phase column temporarily (all tickets move to next phase), but the column stays visible so they recognize it's an active workflow, not a stale UI element.

5. Collapsible Cancelled Column

Cancelled issues don't disappear; they collapse into a count badge. Click to expand.

Rationale: Archive without deletion. Users can audit cancellations without cluttering the main workflow.

6. Polling Over WebSocket

The frontend polls /api/projects/:projectId/issues every 5 seconds rather than using real-time WebSocket.

Rationale: Simpler deployment, no persistent connections, works in stateless environments. 5s latency is acceptable for a developer tool.

Known Gaps

  • Drag-and-drop reordering not yet implemented; status changes only via issue detail page
  • Multi-select / bulk actions not supported
  • Column widths not persistent across sessions
  • No search or filter within the board (search is workspace-level)