Skip to content

feat(dashboard): Exclude subtasks from board MetricsRibbon and All Projects KPI counters#104

Merged
mhersson merged 8 commits into
mainfrom
ctxmax-513/exclude-subtasks-from-board-metricsribbon-and-all
May 18, 2026
Merged

feat(dashboard): Exclude subtasks from board MetricsRibbon and All Projects KPI counters#104
mhersson merged 8 commits into
mainfrom
ctxmax-513/exclude-subtasks-from-board-metricsribbon-and-all

Conversation

@contextmatrix-runner
Copy link
Copy Markdown
Contributor

Summary

Throughput and in-flight counters on the Board MetricsRibbon and All Projects KpiRow used to count every card including subtasks, so a parent with N subtasks read as N+1 in flight or N+1 shipped. This PR treats the delivery unit as the headline metric — cards where parent == "" (standalone tasks + parent tasks).

Backend (internal/service/service_dashboard.go)

Additive *Parents fields on DashboardData and MetricSeries, populated inside the existing single-pass GetDashboard card loop under one card.Parent == "" gate. Existing fields are unchanged — current consumers (state distribution bars etc.) see no semantic shift.

New fields:

  • StateCountsParents map[string]int
  • CardsCompletedTodayParents, CardsCompletedLast7dParents, CardsCompletedPrior7dParents int
  • MetricSeries.InFlightParents, StalledParents, ShippedParents []int

ActiveAgents has no parents variant by design — an agent working a subtask is still real activity.

Board MetricsRibbon (web/src/components/Board/MetricsRibbon.tsx)

The four affected tiles — In flight, Stalled, Shipped today, Shipped · 7d — compute their headline, delta %, and sparkline from the *_parents data. When subtasks add to the all-cards count, a muted <span class="metric-tile__sub">+N sub</span> is rendered next to the headline. Active Agents is unaffected.

Board.tsx derives inFlightTotal / stalledTotal / openCount / inReviewCount from server-side state_counts (not filtered local counts), with a cards-derived fallback during initial mount so headlines never flash "0" while the dashboard fetch is in flight. openCount preserves pre-PR semantics — stalled cards still count as open.

A SubCount helper centralises the +N sub predicate so a future tile cannot accidentally render +0 sub or a negative count.

All Projects KpiRow (web/src/components/AllProjectsDashboard/KpiRow.tsx)

Open tasks / In progress / Done today read from state_counts_parents and cards_completed_today_parents summed by aggregateDashboards. Each tile carries both a native title= tooltip and an aria-label explaining the filter, reachable by keyboard / screen-reader / touch users. Total cost is unchanged — subtask cost is real money spent.

Docs

New web/CLAUDE.md section "MetricsRibbon and KpiRow — delivery-unit counters" documents the convention, the per-view rendering rule, the CSS class for the muted suffix, and the deliberate exclusions (Active Agents, Total cost).

Tests

  • service_dashboard_test.goTestGetDashboard_ParentOnlyCounters: parent + 3 subtasks across in_progress / stalled / done (today and prior 7d windows), asserts that existing fields include all cards while *Parents fields exclude subtasks.
  • AllProjectsDashboard/utils.test.ts — 4 new cases for aggregateDashboards covering parent-only sums, regression of all-cards aggregation, missing-field defensive path, and empty-input baseline.
  • MetricsRibbon.test.tsx — render paths covering positive / zero / undefined *Subtasks props and SubCount directly.
  • Board.test.tsx — initial-mount fallback for inFlight; openCount + inReviewCount from both server-state-counts and cards-fallback branches.

Test plan

  • go test ./internal/... — green
  • make lint — clean (0 issues)
  • cd web && npm test — 636 tests pass
  • cd web && npm run build — clean (tsc -b strict project references)
  • go build ./... — clean

ContextMatrix Runner added 8 commits May 18, 2026 12:07
…MetricSeries

- Extend MetricSeries with InFlightParents, StalledParents, ShippedParents slices
- Extend DashboardData with StateCountsParents map and three CardsCompleted*Parents int fields
- Populate *Parents fields in GetDashboard by gating increments on card.Parent == ""
- Add TestGetDashboard_ParentOnlyCounters covering all 10 assertions from the spec
- Mirror new JSON fields into TypeScript MetricSeries and DashboardData interfaces
… subtask suffix

- MetricsRibbon now accepts optional `*Subtasks` props for in-flight, stalled,
  shipped-today, and shipped-7d tiles; renders `+N sub` span when > 0
- Sparklines for in-flight, stalled, and shipped-7d now use parent-only series
  from metric_series (in_flight_parents, stalled_parents, shipped_parents)
- Shipped · 7d delta computed from parent-only prior values
- Board.tsx derives parent-only counts from stateCountsParents prop, computes
  subtask deltas, and passes them through to MetricsRibbon
- ProjectShell passes all new parent-only dashboard fields to Board
- Add .metric-tile__sub CSS style adjacent to .metric-tile__delta
- Add render tests for +N sub suffix presence and omission
…ry tooltip

- Extend aggregateDashboards to sum state_counts_parents,
  cards_completed_today/last7d/prior7d_parents across all projects
- KpiRow Open tasks / In progress / Done today now read from *_parents
  fields so subtasks are excluded from headline counts
- Add HTML title tooltip on the three affected tiles:
  "Counts delivery units (standalone tasks + parents). Subtasks are excluded."
- Wire stateCountsParents and doneTodayParents props through
  AllProjectsDashboard -> KpiRow
- Add unit tests for parent-only aggregation and regression guard for
  all-cards totals
- Remove now-unused openTasks/inProgress/doneToday props from KpiRow
  and its call site in AllProjectsDashboard.
- Drop the orphaned openTaskCount helper from utils.ts.
- Backfill in_flight_parents/stalled_parents/shipped_parents on
  MetricSeries literals so DashboardData type-checks under tsc -b.
- Add MetricsRibbon and KpiRow delivery-unit counters section explaining
  that parent-only (*_parents) fields drive headlines and sparklines
- Document the metric-tile__sub CSS class for the muted +N sub suffix
- Note which tiles are intentionally unfiltered (Active Agents, Total cost)
- Fix filter/server-data mismatch: derive inFlightTotal/stalledTotal from
  server-side stateCounts prop (unfiltered) instead of cardsByState (filtered),
  preventing negative subtask counts under any active kanban filter
- Fix *Subtasks fallback: compute *Subtasks only when both total and parents
  values are concretely available; pass undefined otherwise to suppress the
  muted "+N sub" suffix until dashboard data arrives (eliminates pop-in)
- Fix test fragility: extend in_progress transitions to include stalled in
  setupDashboardServiceAt; drive sub2 to stalled via PatchCard instead of
  direct UpdateCard state bypass
- Add doc comment rationale: ActiveAgents has no parents variant by design
- Add aria-label to KpiTile tooltip for screen-reader / keyboard accessibility
- Extract SubCount helper in MetricsRibbon.tsx; replace 4 repeated inline
  predicates with the private component
- Extend MetricsRibbon.test.tsx to cover SubCount (undefined/0/positive) and
  the suppression of +N sub spans when stateCountsParents is undefined
…data

- inFlightTotal/stalledTotal now fall back to cards-derived counts when
  stateCounts is undefined, so MetricsRibbon shows populated values during
  initial mount instead of "0 in flight"
- openCount now uses stateCounts when available (avoid mixing unfiltered
  cards.length with filtered cardsByState lengths); falls back to
  cards.filter() (not cardsByState)
- Simplified inFlightSubtasks/stalledSubtasks guards: no longer need to
  check inFlightTotal !== undefined since it is always number now
- Added 4-tile fallback contract comment block above the derivation block
- Extended Board.test.tsx with a regression test asserting the cards-derived
  inFlight count reaches MetricsRibbon when stateCounts is undefined
…rver data

- openCount no longer drops stalled cards (matches pre-PR semantics).
- inReviewCount now derives from stateCounts with a cards-based fallback,
  matching the openCount pattern instead of mixing filtered cardsByState.
- Add two regression tests covering both branches.
@mhersson mhersson merged commit 28f74f2 into main May 18, 2026
7 checks passed
@mhersson mhersson deleted the ctxmax-513/exclude-subtasks-from-board-metricsribbon-and-all branch May 18, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant