Skip to content

feat(ui): slim click-to-filter stat strip on Tasks (reference)#157

Merged
unidoc-ahall merged 1 commit into
masterfrom
feat/register-stat-strip
Jul 10, 2026
Merged

feat(ui): slim click-to-filter stat strip on Tasks (reference)#157
unidoc-ahall merged 1 commit into
masterfrom
feat/register-stat-strip

Conversation

@unidoc-ahall

Copy link
Copy Markdown
Contributor

Part of #156 — Tasks first, as the reference.

The tall stat-card grid at the top of the registers pushes the list down. This swaps it for a slim, always-visible StatStrip: chips (count + label), each click-to-filter, active chip highlighted. Counts + filtering stay glanceable (no discoverability loss) but take a fraction of the height — the list gets ~3× more rows above the fold.

  • New shared components/StatStrip.vue (:stats + v-model for the active filter key) — reused by the other registers next.
  • Tasks: the 5-card grid → <StatStrip :stats="statusStats" v-model="filterStatus" />. Same data, same click-to-filter behaviour.

Design approved from a before/after mock. just build-web green. Follow-up PRs roll this out to Risks/Suppliers/Assets/Legal/Systems/Incidents/CAs/Changes/Objectives/Programs (Risk heat-map stays in its collapsible "More").

Replaces the tall stat-card grid at the top of Tasks with a slim, always-visible
StatStrip whose chips are click-to-filter — the list gets the vertical space back
while counts + filtering stay glanceable. New shared components/StatStrip.vue so the
other registers can adopt the same treatment.

Part of #156 (Tasks is the reference; other registers to follow).
@unidoc-ahall unidoc-ahall added this to the 0.7.1 milestone Jul 10, 2026
@unidoc-ahall unidoc-ahall requested a review from unidoc-alip July 10, 2026 13:30

@unidoc-alip unidoc-alip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, low-risk UI refactor. StatStrip.vue cleanly extracts the click-to-filter chip pattern from the old 5-card grid with a minimal, well-typed prop contract (stats + v-model). Tasks.vue's swap is 1:1 — no data/logic changes, same statusStats/filterStatus wiring, no XSS/security concerns (no v-html, all interpolation is auto-escaped). The color convention (text-* classes) already matches other registers (e.g. Changes.vue), so it should generalize well to the planned follow-up PRs for the other registers. Both findings below are non-blocking nits aimed at hardening this component before it becomes the shared reference for ~10 more register rollouts.


1. [nit] web/src/components/StatStrip.vue line 20

No runtime shape validation on stats items (key/label/count/color) — a follow-up register PR could pass an item missing count or using a bg-*/dot color convention (like Dashboard.vue's activity types) instead of text-*, and it'd silently render wrong rather than fail loudly. Confirmed against real precedent: Dashboard.vue:659-663 already uses color: 'bg-purple-900/40 text-purple-300' paired with a separate dot field, a different convention from the plain text-* string this component expects — while Changes.vue:496-501 does match. So a register PR modeled on Dashboard's stats shape instead of Changes.vue's would silently mis-style rather than error.

Suggested fix — add a dev-time prop validator so a mismatch fails loudly instead of silently mis-rendering:

defineProps({
  stats: {
    type: Array,
    required: true,
    validator: (arr) => arr.every(s =>
      typeof s.key === 'string' &&
      typeof s.label === 'string' &&
      typeof s.count === 'number' &&
      (s.color === undefined || /^text-/.test(s.color))
    ),
  },
  modelValue: { type: String, default: '' },
})

Vue logs a console warning in dev builds when a validator fails, catching a future register PR passing a bg-*/dot-style color before it ships. Pair with the existing // color is a text-* class for the count comment so the constraint is documented in both places.


2. [nit] web/src/components/StatStrip.vue line 8

The <button> has no aria-* attribute — active/filtered state is conveyed purely via the border/background/text-color class binding. Screen-reader users get no indication a chip is the active filter beyond the static title tooltip, which doesn't change when active.

Suggested fix — bind aria-pressed to the same active-state expression already used for styling, and make the tooltip reflect the toggle:

<button
  v-for="s in stats"
  :key="s.key"
  type="button"
  :aria-pressed="modelValue === s.key"
  @click="$emit('update:modelValue', modelValue === s.key ? '' : s.key)"
  class="inline-flex items-baseline gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors"
  :class="modelValue === s.key
    ? 'border-blue-500/50 bg-blue-500/10 text-blue-200'
    : 'border-slate-800 bg-slate-900 text-slate-400 hover:border-slate-700 hover:text-slate-300'"
  :title="modelValue === s.key ? `Clear filter: ${s.label}` : `Filter: ${s.label}`">

Since this is slated to become the shared pattern for every other register, landing this in the reference component now avoids repeating the fix in each of the ~10 follow-up PRs.

@unidoc-ahall unidoc-ahall merged commit e7f2b12 into master Jul 10, 2026
2 checks passed
@unidoc-ahall unidoc-ahall deleted the feat/register-stat-strip branch July 10, 2026 14:05
unidoc-ahall added a commit that referenced this pull request Jul 10, 2026
…156) (#158)

Adopts the shared StatStrip (from #157) across Suppliers, Assets, Legal, Systems,
Incidents, Corrective Actions, Objectives, Changes and Risks — replacing the tall
stat-card grids that pushed each list far down. Counts + filtering stay glanceable
in a fraction of the height.

StatStrip gains an optional per-chip `static` flag for derived metrics that aren't
a filter dimension (Critical / Severe / High), rendered as display-only chips.
Risk's summary chips are display-only (they span different filter dimensions) and
its Risk Map stays in its collapsible "More". Corrective Actions' summary cards
become click-to-filter for the first time, matching the other registers.

Closes #156.
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.

2 participants