-
Notifications
You must be signed in to change notification settings - Fork 1
Pipeline Design 192
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).
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)
- Renders as
-
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 internally5. 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 |
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.
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.
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."
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.
- None (all functionality fits into existing files)
| 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 |
- No new npm packages needed. All functionality uses standard TypeScript, React, and existing dependencies.
| 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 |
- 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
- 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
- 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
- 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
- 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
- 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
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
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
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.
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.
- 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)
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.