feat(dashboard): Exclude subtasks from board MetricsRibbon and All Projects KPI counters#104
Merged
mhersson merged 8 commits intoMay 18, 2026
Conversation
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.
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
*Parentsfields onDashboardDataandMetricSeries, populated inside the existing single-passGetDashboardcard loop under onecard.Parent == ""gate. Existing fields are unchanged — current consumers (state distribution bars etc.) see no semantic shift.New fields:
StateCountsParents map[string]intCardsCompletedTodayParents,CardsCompletedLast7dParents,CardsCompletedPrior7dParents intMetricSeries.InFlightParents,StalledParents,ShippedParents []intActiveAgentshas 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
*_parentsdata. 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.tsxderivesinFlightTotal/stalledTotal/openCount/inReviewCountfrom server-sidestate_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.openCountpreserves pre-PR semantics — stalled cards still count as open.A
SubCounthelper centralises the+N subpredicate so a future tile cannot accidentally render+0 subor a negative count.All Projects KpiRow (
web/src/components/AllProjectsDashboard/KpiRow.tsx)Open tasks / In progress / Done today read from
state_counts_parentsandcards_completed_today_parentssummed byaggregateDashboards. Each tile carries both a nativetitle=tooltip and anaria-labelexplaining the filter, reachable by keyboard / screen-reader / touch users. Total cost is unchanged — subtask cost is real money spent.Docs
New
web/CLAUDE.mdsection "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.go—TestGetDashboard_ParentOnlyCounters: parent + 3 subtasks acrossin_progress/stalled/done(today and prior 7d windows), asserts that existing fields include all cards while*Parentsfields exclude subtasks.AllProjectsDashboard/utils.test.ts— 4 new cases foraggregateDashboardscovering 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*Subtasksprops andSubCountdirectly.Board.test.tsx— initial-mount fallback forinFlight;openCount+inReviewCountfrom both server-state-counts and cards-fallback branches.Test plan
go test ./internal/...— greenmake lint— clean (0 issues)cd web && npm test— 636 tests passcd web && npm run build— clean (tsc -b strict project references)go build ./...— clean