---
id: STATE-WP-0057
type: workplan
title: "Overview Workstream Stage Counts"
domain: infotech
repo: state-hub
status: finished
owner: codex
topic_slug: custodian
created: "2026-06-07"
updated: "2026-06-07"
state_hub_workstream_id: "43fb4c68-8ff8-4dc0-96f2-3c7976c16e9c"
---

# Overview Workstream Stage Counts

## Summary

Address the open dashboard-improvement suggestion asking for counts in the
Overview page's "Workstreams by Repository" selector. Operators should be able
to see how many workstreams match each lifecycle stage, derived health filter,
and recently-changed window before changing the chart mode.

This should be a small dashboard usability improvement, not a data-model
change. The Overview page already receives a bounded `/state/overview` read
model with `workplan_rows`, and it already uses shared workplan status helpers
to normalize lifecycle and derived health filters.

## Current Findings

Suggestion reviewed on 2026-06-07:

- `70e9bfd4-235d-4677-b053-39b78af8e5aa` — "UI: Workstreams by Repository"
  asks: "In the selector for which workstreams to show I want to see the count
  of workstreams in the respective stage."
- Location: `Overview | Custodian State Hub > Workstreams by Repository`
- Current status: `review`

Relevant repo findings:

- Older dashboard-improvement suggestions for TPSC repo labels and task /
  workstream status editing are already finished under `STATE-WP-0043`.
- `dashboard/src/index.md` renders the Workstreams by Repository mode selector
  with static `<option>` labels.
- The same page already loads `overview.workplan_rows` into `wsAll`, normalizes
  workstream statuses, filters chart rows by the selected mode, and renders
  empty-state copy for lifecycle, health, and time-window modes.
- `dashboard/src/components/workplan-status.js` provides the canonical
  lifecycle statuses plus `normalizeWorkstreamStatus`, `needsReviewWorkstream`,
  and `isStalledWorkstream`.
- `/state/overview` currently returns lifecycle totals and per-workplan rows,
  so this appears frontend-only unless verification finds missing or stale row
  data.

## Out of Scope

- Redesigning the Overview chart or dashboard information architecture.
- Changing workplan lifecycle semantics or adding new stored statuses.
- Reworking task or workstream status editing controls.
- Adding authentication, authorization, or multi-user mutation behavior.
- Replacing Observable Framework.

## T01 — Define Selector Count Semantics

```task
id: STATE-WP-0057-T01
status: done
priority: high
state_hub_task_id: "e2dd7231-f378-49c1-9869-0eb5970cc54d"
```

Define exactly what each selector count means so the option labels and chart
stay aligned.

Implementation notes:

- Count workstreams, not tasks.
- Lifecycle options count rows whose normalized `status` equals the option
  value: `proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, and
  `archived`.
- Health options count rows using the existing derived predicates:
  `needsReviewWorkstream` and `isStalledWorkstream`.
- Recently-changed options count rows with `updated_at` or `created_at` on or
  after the same cutoff used by the chart filter.
- Keep zero-count options visible so operators can distinguish "none" from a
  missing mode.

Done when the count behavior is captured in the implementation structure or
dashboard docs and is reused consistently by the selector and chart.

## T02 — Render Count-Aware Selector Options

```task
id: STATE-WP-0057-T02
status: done
priority: high
state_hub_task_id: "b73557f4-c524-407f-b6b1-f48d89ba277f"
```

Replace the static selector labels on the Overview page with labels that include
the current matching count.

Implementation notes:

- Build a small option/group data structure in `dashboard/src/index.md` for the
  Lifecycle, Health, and Recently Changed groups.
- Render labels such as `active (10)` while keeping option values unchanged.
- Preserve the existing default selection of `active`.
- Make labels update when `wsAll` refreshes, including stale-while-refresh data
  from the existing overview loader.
- Avoid resetting the selected mode during normal polling refreshes.
- Keep the existing option order and group names unless there is a clear reason
  to improve them.

Done when every visible selector mode shows a matching workstream count and
changing modes still filters the chart exactly as before.

## T03 — Share Filtering Logic Between Counts and Chart

```task
id: STATE-WP-0057-T03
status: done
priority: medium
state_hub_task_id: "eebfa200-3953-4de3-955b-f808c8c54a0a"
```

Reduce the chance of the selector counts drifting away from the chart rows by
using one mode-filtering path.

Implementation notes:

- Extract the current mode filter into a small local helper such as
  `_workstreamsForMode(mode, rows)`.
- Reuse that helper for both the option counts and `_chartWsFiltered`.
- Keep `_STATUS_MODES`, `_HEALTH_MODES`, `_timeCutoff`, and existing status
  helper imports.
- Preserve current empty messages, chart sorting, tooltip data, and href
  behavior.
- Treat missing or invalid timestamps conservatively so a bad row does not make
  time-window counts throw.

Done when selector counts and chart row counts are mechanically derived from the
same filter function.

## T04 — Polish Selector Fit and Accessibility

```task
id: STATE-WP-0057-T04
status: done
priority: medium
state_hub_task_id: "2aa95f2d-be3e-489c-b8ee-5b6018109061"
```

Make the count-aware selector fit cleanly in the Overview section across normal
desktop and mobile widths.

Implementation notes:

- Keep the selector compact and operational; do not add explanatory page text.
- Add a stable width or responsive `max-width` if longer option labels become
  clipped.
- Add or preserve an accessible label/title that makes the count meaning clear.
- Ensure option text uses normal spacing and does not rely on color alone.
- Confirm the selector does not cause nearby chart content to overlap.

Done when the selector remains readable and professionally sized with both
small and large count values.

## T05 — Close the Suggestion Feedback Loop

```task
id: STATE-WP-0057-T05
status: done
priority: medium
state_hub_task_id: "5e673e25-407d-43f0-95d6-5a596afc5b3b"
```

Update the dashboard-improvement record so the UI Feedback page tells the same
story as the workplan and shipped code.

Implementation notes:

- Add a note to suggestion `70e9bfd4-235d-4677-b053-39b78af8e5aa` that
  references `STATE-WP-0057`.
- Move the suggestion through `plan`, `implement`, `test`, and `review` as the
  implementation progresses.
- Move it to `finished` only after build and browser verification, or after
  operator confirmation if review is intentionally deferred.
- Confirm the Todo page's open suggestion count and UI Feedback active/closed
  sections reflect the final status.

Done when the technical-debt suggestion record has implementation evidence and
no longer appears as an open suggestion after verified completion.

## T06 — Verification

```task
id: STATE-WP-0057-T06
status: done
priority: high
state_hub_task_id: "8513290e-02a4-428d-8f1a-5de4fe447aa8"
```

Verify the selector counts against code, build output, and running dashboard
behavior.

Implementation notes:

- Run `make dashboard-check`.
- If any API behavior changes, run the relevant backend tests for
  `/state/overview`.
- Open the local dashboard and verify:
  - Lifecycle counts match the chart rows for each lifecycle option.
  - Health counts match `needs review` and `stalled` chart results.
  - Recently-changed counts match the chart for at least one non-empty and one
    empty time window.
  - Polling refreshes update counts without losing the selected mode.
  - Offline/stale overview behavior keeps last-known counts rather than
    flashing misleading zeros.
- Check desktop and mobile widths for clipped option text or overlapping chart
  content.

Done when the dashboard build passes and the Overview selector visibly reports
accurate workstream counts for all mode groups.

## Verification Notes

- 2026-06-07: Implemented the Overview selector change in
  `dashboard/src/index.md`. The selector now renders lifecycle, health, and
  recently-changed options with matching workstream counts.
- The selector and chart now share one `_workstreamsForMode(mode, rows)` helper
  so option counts and chart rows are derived from the same filtering logic.
- Added selector fit/accessibility polish with a stable width, `aria-label`, and
  title text.
- Live count check against `/state/overview` returned: `ready=2`, `active=12`,
  `blocked=0`, `proposed=3`, `backlog=6`, `finished=350`, `archived=15`,
  `needs_review=0`, `stalled=4`, `1h=5`, `1d=10`, `7d=54`, `30d=264`,
  `today=7`, `week=54`, and `month=54`.
- `npm run test` in `dashboard/` passed 11 tests.
- `npm run build` in `dashboard/` passed: Observable built 61 pages and
  validated 49 links.
- 2026-06-07 follow-up: Fixed a selector reactivity regression where
  `_chartWsFiltered` was computed in the same cell as `view(_modeSelect)`.
  Moving `_chartWsFiltered = _workstreamsForMode(_chartMode, wsAll)` into the
  downstream chart cell restored filtering when operators choose a mode other
  than `active`. A second pass kept `_chartMode` as the raw reactive view value
  and normalizes it inside the downstream chart cell before filtering and empty
  state selection. Added a `change` listener alongside `input` for persisted
  mode selection. `npm run test` and `npm run build` both passed after the fix.
- 2026-06-07 second follow-up: Replaced the remaining custom-select `view(...)`
  wiring with an explicit `Mutable` chart mode state after the operator reported
  the selector was still stuck on `active`. Selector `input` and `change` events
  now call `_setChartMode`, and the Plot cell depends directly on
  `_chartModeState`. The running preview and built `dist/index.html` both show
  that dependency shape. `npm run test` and a clean `npm run build` both passed.
- The earlier browser click-through gap was closed in the final pass below with
  a temporary Playwright/Chromium fallback.
- 2026-06-07 final closeout: Installed a temporary Playwright/Chromium fallback
  under `/tmp/statehub-playwright` after the in-app browser bridge still failed.
  Browser verification against the local preview on `http://127.0.0.1:3001/`
  confirmed lifecycle, health, and recently-changed selector counts match chart
  rows; desktop and mobile selector fit passed; polling refresh updated counts
  without resetting the selected mode; stale/offline refresh kept last-known
  counts; and a synthetic empty recent window rendered the expected empty state.
- Final verification found and fixed a stalled-health count bug:
  `_workstreamsForMode("stalled", rows)` was passing `isStalledWorkstream`
  directly to `Array.filter`, so row indexes were being treated as the helper's
  optional `staleDays` argument. The Overview page now wraps the helper call,
  and `isStalledWorkstream` no longer accepts that positional override. Added a
  regression test covering `rows.filter(isStalledWorkstream)`.
- Final checks passed: `npm --prefix dashboard run test` passed 12 tests,
  `make dashboard-check` built 61 pages and validated 49 links, and final
  Playwright smoke verified `stalled (4)` with four rendered chart rows.
- Suggestion `70e9bfd4-235d-4677-b053-39b78af8e5aa` now has the final review
  evidence note and status `finished`. API verification shows UI Feedback
  active suggestions = 0, closed suggestions = 8, and Todo open suggestions = 0.
