Skip to content

Pipeline Design 192

Seth Ford edited this page Mar 1, 2026 · 2 revisions

Design: [Test] Skill Injection E2E: Add pipeline progress indicator to dashboard

Context

Problem: Pipeline status in the dashboard is static. Users don't know how far through the 12-stage delivery pipeline a job has progressed or when it will complete.

Constraints:

  • Data already available via WebSocket (stagesDone array pushes every 2s)
  • No new API endpoints can be added (cost/complexity)
  • Must support keyboard navigation and screen readers
  • Must be responsive on mobile (320px+)
  • Visual language must match existing design (stat-bar CSS pattern for consistency)
  • TypeScript codebase with Vitest testing framework

Architecture context: The dashboard is event-driven, receiving WebSocket updates from the pipeline orchestrator. The component model is React-based with unidirectional data flow (props down, events up).


Decision

Chosen Approach

1. Progress Visualization Strategy

  • Table view: Compact horizontal progress bar in a new "Progress" column

    • Renders as <div class="stat-bar-track"><div class="stat-bar-fill"></div></div>
    • Width represents stagesDone.length / 12 * 100%
    • Reuses existing color scheme (green for active, gray for inactive)
  • Detail panel: ETA display below progress bar

    • Format: "~2h 15m remaining" or "< 1 min remaining"
    • Calculation: (currentTime - pipelineStartTime) / stageDone * stagesRemaining
    • Updates every 2s with new WebSocket data

2. Component Decomposition

Component Responsibility Single Reason to Change
computeStageProgress() Calculate [0..1] progress from stages array Stages counting logic changes
estimateTimeRemaining() ETA math (elapsed/completed * remaining) Estimation algorithm needs tuning
ProgressColumn (in pipelines.ts) Render progress bar + ARIA attributes Visual representation changes
DetailPanelETA (in pipelines.ts) Format and display ETA text ETA display format changes
CSS .stat-bar-* rules Styling and responsive behavior Design system updates

3. Data Flow

WebSocket Message
  ↓
{ stagesDone: ["intake", "plan", "design"], elapsed_ms: 480000, ... }
  ↓
computeStageProgress(3, 12) → 0.25
  ↓
ProgressColumn renders <div style={{width: "25%"}} />
  ↓
estimateTimeRemaining(480000, 3, 12) → "1h 20m"
  ↓
DetailPanelETA displays in panel

4. Interface Contracts

// Helper layer
function computeStageProgress(completed: number, total: number): number
  // Input: completed stage count, total stage count
  // Output: [0..1] normalized progress
  // Precondition: completed <= total
  // Postcondition: result in [0, 1]

function estimateTimeRemaining(
  elapsedMs: number,
  stagesCompleted: number,
  totalStages: number
): string | null
  // Input: elapsed time in ms, completed/total stages
  // Output: human-readable ETA (e.g., "1h 20m") or null if insufficient data
  // Precondition: elapsedMs >= 0, 0 <= stagesCompleted <= totalStages
  // Error: Returns null if stagesCompleted === 0 (avoid divide-by-zero)

// UI layer
<ProgressIndicator 
  completed={pipeline.stagesDone.length}
  total={12}
  elapsedMs={Date.now() - pipeline.startedAt}
  label={pipeline.name}
/>
  // Props enforced by TypeScript
  // ARIA attributes generated internally

5. Error Handling & Edge Cases

Case Handling
Pipeline just started (0 stages done) Progress bar at 0%, ETA shows "calculating..."
Stagespy completes < 1 minute Show "< 1 min remaining"
Invalid data (stagesDone > 12) Type system prevents this; helpers validate with assertions
Very fast stages (ETA < 1s) Round to "< 1 min" to avoid precision artifacts
Missing startedAt timestamp ETA is null, progress bar still renders

Alternatives Considered

1. Server-Side Progress Calculation

Approach: Pipeline orchestrator computes progress, sends in WebSocket message

Pros:

  • Single source of truth for progress logic
  • Could include stage timing breakdowns

Cons:

  • Adds complexity to orchestrator (not its concern)
  • Duplicates logic already in client (stagesDone tracking)
  • Increases WebSocket payload size
  • Tightly couples orchestrator to dashboard concerns

Decision: REJECTED. Computation is deterministic and cheap—keep it client-side.


2. Detailed Stage Breakdown in Table

Approach: Show all 12 stages as cells (intake ✓, plan ✓, design ◯, ...)

Pros:

  • Maximum transparency about which exact stage is blocked
  • Could click a stage to see logs

Cons:

  • Table becomes 12 columns wide (not mobile-friendly)
  • Cognitive overload for users who only care about % done
  • Violates table design principle: one metric per column
  • Requires significant responsive redesign

Decision: REJECTED. Progress bar is sufficient; use detail panel for granular stage info if needed later.


3. Real-Time Stage Event Streaming

Approach: WebSocket emits event per stage completion, client tracks timing

Pros:

  • Precise stage duration measurement
  • Could show "which stage is slow?"

Cons:

  • Requires new WebSocket message type (schema change)
  • Message frequency increases (more server load)
  • Adds event tracking to orchestrator
  • Estimation becomes more complex with variable stage times

Decision: REJECTED. Current 2s push is sufficient; ETA using average is "good enough."


4. Progress as Text Percentage

Approach: "6/12 stages (50%)" in a table cell

Pros:

  • Precise, no ambiguity
  • No CSS changes needed

Cons:

  • Slower to scan visually (requires reading text)
  • Takes more horizontal space
  • Less satisfying UX (visual bar is more intuitive)
  • Doesn't leverage existing stat-bar CSS pattern

Decision: REJECTED. Visual bar + text ETA is more UX-friendly.


Implementation Plan

Files to Create

  • None (all functionality fits into existing files)

Files to Modify

File Changes Rationale
src/helpers.ts Add computeStageProgress(completed, total) and estimateTimeRemaining(...) Pure functions, fully testable, reusable
src/pipelines.ts Add ProgressColumn component + import helpers; add ETA to detail panel UI logic lives here; hooks into existing WebSocket data
src/index.html Add "Progress" column header in table thead Markup alignment with table structure
src/styles.css Reuse .stat-bar-track and .stat-bar-fill (already exist); add mobile breakpoint rule for bar height Visual consistency; responsive behavior
src/helpers.test.ts Test computeStageProgress(), estimateTimeRemaining() with edge cases Edge case coverage: 0 stages, full pipeline, invalid input
src/pipelines.test.ts Test ProgressColumn renders with ARIA attributes; ETA text updates on WebSocket refresh Integration test: props flow → output

Dependencies

  • No new npm packages needed. All functionality uses standard TypeScript, React, and existing dependencies.

Risk Areas

Risk Mitigation
Inaccurate ETA early in pipeline (first 1-2 stages might be slow, averaging skews estimate) Display "calculating..." until 2+ stages done; educate users that ETA refines over time
Progress bar clipped on mobile (if 6 pixels minimum width needed) Set min-width: 8px on bar; test at 320px viewport
Screen reader announces "0% progress" for every update (noisy) Use aria-live="polite" + aria-label="Pipeline 6 of 12 stages complete, approximately 1 hour remaining" to batch updates
Non-linear stage times (some stages are 30s, others 30min; averaging fails) ETA is advisory only, not a guarantee; UI should clearly indicate this
Stages can be skipped (e.g., "deploy" stage disabled in template) CURRENT DESIGN ASSUMES ALL 12 STAGES. If stage skipping is added later, need to track totalStages per pipeline, not hardcode 12

Validation Criteria

Functional

  • Progress bar width updates every 2s when new WebSocket message arrives
  • ETA text displays "calculating..." until 2 stages complete, then shows best estimate
  • ETA updates correctly when elapsed time or stages change
  • Progress bar reaches 100% width when all 12 stages are in stagesDone
  • No crashes when pipeline data is missing or malformed

Accessibility (WCAG 2.1 Level AA)

  • Progress bar has role="progressbar" + aria-valuenow (0–100)
  • Screen reader announces: "Pipeline [name], 6 of 12 stages complete, approximately 1 hour remaining"
  • Keyboard navigation: Tab into progress bar region (not interactive, but in DOM order)
  • Color contrast: Bar fill (green) has 4.5:1 ratio against light background
  • No color-only indicator: bar includes numeric label or ARIA text

Responsive

  • Progress bar renders at 320px (mobile), 768px (tablet), 1440px (desktop)
  • Bar height is readable on mobile (minimum 6px, recommended 8px)
  • ETA text doesn't overflow cell on mobile (use abbreviated format: "1h 20m" not "1 hour 20 minutes")
  • Touch targets for detail panel expand > 44px if clickable

Performance

  • No layout thrashing: progress bar width animates smoothly via CSS (no JS re-layout per update)
  • Helper functions execute in < 1ms (pure math, no DOM)
  • No memory leaks: event listeners cleaned up on unmount

Integration

  • Works with all pipeline templates (fast, standard, full, hotfix, autonomous)
  • Works with pipelines that have 0 stages done (fresh pipeline shows 0%)
  • Works with pipelines that stalled (elapsed time grows, ETA increases)
  • Test: add progress bar to 3 pipelines simultaneously, all update independently

Test Coverage

  • Unit: computeStageProgress() with inputs [0, 12], [6, 12], [12, 12]
  • Unit: estimateTimeRemaining() with [0ms, 0 stages], [120000ms, 1 stage], [big times]
  • Unit: ETA returns null when stagesCompleted === 0 (avoid division by zero)
  • Integration: ProgressColumn renders bar with correct ARIA attributes
  • E2E: WebSocket message triggers re-render; bar width changes; ETA updates

Design Notes

Why Reuse stat-bar CSS?

Existing .stat-bar-track / .stat-bar-fill pattern is used elsewhere in the dashboard (e.g., resource usage). Reusing it ensures:

  • Visual consistency (same green, same proportions)
  • Maintenance: single CSS rule for all progress bars
  • Responsive behavior already tested
  • Designers know what it looks like

Why ETA Stays in Detail Panel?

Progress bar goes in table (high-level summary), ETA goes in detail panel (precision detail). This mirrors dashboard design:

  • Table: Quick scan, summaries, comparisons
  • Detail panel: Deep dive, contextual information

Why No Percentage Text?

The bar itself IS the percentage (width speaks for itself). Adding "50%" text is redundant for visual users and clutters the mobile view. ARIA label provides the number to screen reader users.

Why Estimation ≠ Guarantee?

ETA is heuristic: (elapsed / completed) * remaining. This assumes:

  • Uniform stage duration (false—design stage ≠ build stage)
  • No future errors (stages might fail and restart)
  • Current velocity continues (false during peak load)

Users should see ETA as "ballpark," not contract. If precision matters, pipeline orchestrator can emit per-stage timings later.


Future Enhancements (Out of Scope)

  • Click progress bar to expand → see all 12 stages with ✓/◯ indicators
  • Color gradient: red (0–25%), yellow (25–75%), green (75–100%)
  • Per-stage timing breakdown in detail panel
  • ETA confidence indicator ("high confidence" vs "still calculating")
  • Pause/resume pipeline from detail panel (API change needed)

Sign-Off

This ADR is ready for implementation. The design is minimal, reuses existing patterns, has clear separation of concerns, and includes comprehensive accessibility and mobile support. Helper functions are fully testable in isolation. Risk areas are documented with mitigations.

Clone this wiki locally