Skip to content

Pipeline View

Overview

The Pipeline View is a phased development visualization that tracks issues through a four-stage readiness pipeline: research → architecture → grooming → ready. It complements the Kanban Board by showing progress through design phases rather than execution status. Teams use it to understand work queued for design, underway in specification, ready for implementation, or unlabeled and awaiting phase assignment.

How It Works

View Toggle

The project page (app/pages/p/[slug]/index.vue) has a "Board" / "Pipeline" toggle button. Selecting "Pipeline" switches the display from the Kanban Board to the Pipeline View component.

Two-Layer Data Flow

Layer 1: API Endpoint

GET /api/projects/{projectId}/pipeline
→ Route: server/api/projects/[projectId]/pipeline.get.ts
→ Service: server/services/pipelineService.ts
→ Returns: PipelineResponse { phases: PipelinePhase[] }

Layer 2: Component Visualization

The component fetches the pipeline data on mount and polls every 5 seconds (via usePolling). When data arrives, it computes two derived values:

  • maxCount: The largest phase bucket size (used for proportional bar sizing)
  • allIssues: Flattened array of all phased issues for the table

Data Model

Phase Labels

Each issue can have exactly one phase label: phase:research, phase:architecture, phase:grooming, phase:ready, or none (shown as "unlabeled").

  • Phase labels stored in issues.labels (JSON array)
  • Extracted via extractPhase(label) from phaseLabels.ts utilities
  • Issues without a phase label appear in the "unlabeled" bucket

Artifacts

Each issue has an artifacts array tracking submissions per phase:

typescript
interface IssueArtifact {
  phase: string  // 'research', 'architecture', etc.
  path: string   // File path to artifact (e.g., docs/tickets/SYM-001/research.md)
  status: 'draft' | 'submitted' | 'approved' | 'rejected' | 'superseded'
  revision: number
  hash?: string
}

Artifacts are updated by agents calling complete_phase MCP tool. The artifact dot on each row shows the approval status visually (green=approved, red=rejected, yellow=draft/submitted, gray=none).

Phase Metrics

When an issue enters a phase, orchestrator creates a phaseMetrics object:

typescript
interface PhaseMetrics {
  entered_phase_at: string      // ISO timestamp
  phase_rejection_count: number // Cumulative rejections
  last_completion_hash: string | null
  last_verdict: string | null
}

The "Rejections" column displays phase_rejection_count.

Grouping Algorithm

The service groups all issues by phase in one pass:

  1. Fetch all issues for the project (one query)
  2. For each issue, extract its phase label
  3. Add issue to corresponding bucket (research/architecture/grooming/ready/unlabeled)
  4. Return buckets in canonical order, omitting empty phases except unlabeled (if populated)

Visualization

Phase Distribution Funnel — Proportional bar chart showing count per phase

  • Bar width: (count / maxCount) * 100% (minimum 4% if count > 0)
  • Colors: research=blue, architecture=purple, grooming=orange, ready=green, unlabeled=gray
  • Responsive to dark mode

Issues Table — Five columns per issue:

ColumnShowsCell Logic
TicketID + titleLinks to issue detail page
PhaseCurrent phase labelBadge with phase color
ArtifactsMini dots per phaseColored dots (green/red/yellow/gray) for artifact status
RejectionsCount of phase rejectionsRed text if > 0, gray "0" if none
StatusExecution statusBadge with status color (todo/in_progress/review/done/etc)

Clicking any row navigates to the issue detail page (/p/{slug}/issues/{identifier}).

Key Components

FileResponsibility
app/pages/p/[slug]/index.vueView toggle button ("Board" / "Pipeline")
app/components/PipelineView.vueFunnel visualization + issues table, polling, computed bars
server/api/projects/[projectId]/pipeline.get.tsHTTP endpoint, delegates to service
server/services/pipelineService.tsPhase grouping logic, data transformation
server/utils/phaseLabels.tsPhase label parsing (isPhaseLabel, extractPhase)
app/utils/phaseLabels.tsClient mirror of server utilities (Nuxt auto-import scoped)
server/database/schema.tsissues.labels, issues.artifacts, issues.phaseMetrics JSON columns

Design Decisions

1. Separate View Toggle (Board vs Pipeline)

Decision: Show both Kanban Board and Pipeline View as tabs, not merged.

Rationale: The execution pipeline (backlog → todo → in_progress → review → done) and readiness pipeline (research → architecture → grooming → ready) are distinct mental models. Some teams care more about design readiness; others track execution throughput. Toggle lets both coexist.

Trade-off: User must switch contexts manually; no single unified timeline. Offset by clearer semantics per view.

2. Single-Query Fetch for All Issues

Decision: Fetch all issues for the project in one query, then group in memory.

Rationale: Simpler than streaming or paginating. Phase distribution is quick to compute (linear scan + map insertion). Works well with SQLite WAL mode eventual consistency.

Trade-off: Large projects (10k+ issues) fetch full dataset. Offset by no N+1 queries, simple caching via polling interval.

3. Phase Label as Canonical Phase Source

Decision: Extract phase from issue.labels array, not a separate issue.phase column.

Rationale: Phase is one of many label types (can have priority:high, team:frontend, phase:architecture together). Enforcing exactly-one-phase-per-issue via label namespace is cleaner than a nullable column; label semantics are explicit.

Trade-off: Must parse labels on every fetch. Offset by fast string matching (extractPhase is O(1) prefix check).

4. Artifacts Array for Revision Tracking

Decision: Store all artifact submissions per phase in an array, not a single "current artifact" record.

Rationale: Audit trail — can show revision 1 rejected, revision 2 approved. Simpler than a separate artifacts table with foreign keys. JSON append pattern matches agent workflow (agents call complete_phase, MCP tool appends artifact to array).

Trade-off: Querying "artifact status for phase X" requires scanning array in memory. Offset by small arrays (typically 1-3 artifacts per phase per issue).

5. Proportional Funnel Bars

Decision: Bar width proportional to phase count, with 4% floor if count > 0.

Rationale: Visualizes flow — widest bar = most work in that phase. Floor prevents zero-count bars from being invisible. CSS transitions smooth the bars on update.

Trade-off: Absolute counts less obvious; requires text label. Offset by label on right side.

6. Client-Side Mirror of Phase Utils

Decision: Duplicate phaseLabels.ts in both server/utils/ and app/utils/.

Rationale: Nuxt auto-import scopes prevent importing server/utils/ from app/ code. Duplication ensures both layers can parse phase labels identically without cross-boundary imports.

Trade-off: Maintenance burden if label format changes. Offset by comment linking files, small function set (3 functions).

Known Gaps

  • Large dataset performance: No pagination or virtualization. Projects with 1000+ phased issues may render slowly.
  • Artifact filtering: UI shows all artifacts per issue but doesn't filter by latest status or sort by revision.
  • Phase metrics: entered_phase_at stored but not displayed; could show "in phase for 2 days".
  • Unlabeled issues: Bucket created even if empty; could optimize to skip rendering if zero count.