Files
roboco/panel/docs/frontend/company-scorecard-card.md
T
roboco-app[bot]GitHubroboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com>Frontend Developer 2Frontend DocumenterFrontend PM
b3dda00e41 [d700055f] Replace StubObjectivesSection with live charter objective cards (#702) (#704)
* [d700055f] feat(scorecard): replace StubObjectivesSection with live charter objective cards

Delete the StubObjectivesSection placeholder (fake 'Revenue growth'/'Customer
retention' labels) and render a real ObjectivesSection with three positional
charter objective cards, each showing its live metric against its target:
first_pass_yield (90%), median_lead_time_hours (<24h), escaped_defects (0).

- CockpitSummary: add optional first_pass_yield and escaped_defects fields
  (backend companion item not yet shipped; UI renders 'No data yet' until then)
- ObjectivesSection: positional mapping objectives[i].metric -> metric i,
  documented as a positional-by-convention assumption; canonical fallback
  labels when the charter objectives array is empty/shorter than 3; 'No data
  yet' italic muted fallback for null/undefined metrics (SpeedSection pattern);
  DeliveryMetric card styling; first_pass_yield formatted as pctOrDash does
- SpeedSection kept as-is; ObjectivesSection references the same
  median_lead_time_hours value as a peer card alongside the other two
- Tests: buildSummary carries the new fields; cover present/missing metrics
  and absence of the fake stub labels; existing lead-time/null tests adjusted
  for the now-shared value and multi-card 'No data yet'

* [d700055f] docs(scorecard): document live ObjectivesSection and new CockpitSummary fields

Add panel/docs/frontend/company-scorecard-card.md covering the new
ObjectivesSection: the three positional charter objective cards
(first_pass_yield 90%, median_lead_time_hours <24h, escaped_defects 0),
the positional-by-convention mapping, canonical fallback labels, the
'No data yet' fallback pattern, and the two new optional CockpitSummary
fields with the backend companion-item caveat. Add a Key Symbols row for
CompanyScorecardCard/ObjectivesSection in docs/map/panel.md.

---------

Co-authored-by: roboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com>
Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
Co-authored-by: Frontend PM <fe-pm@roboco.tech>
2026-07-26 17:13:22 +00:00

6.8 KiB
Raw Blame History

Company Scorecard Card

Overview

CompanyScorecardCard (panel/src/components/business/company-scorecard-card.tsx) is the live charter-performance card on the Business page's Scorecard tab (app/(dashboard)/business/page.tsx, ?tab=scorecard). Its subtitle reads "Live performance against the charter". It renders four sections off a single cockpitApi.summary() call (GET /api/cockpit/summary, typed by CockpitSummary in panel/src/lib/api/cockpit.ts):

  1. Delivery — live task counts across the pipeline (DeliverySection).
  2. Spend — 30-day spend, projected monthly, monthly cap, plus the SpendTrendChart (SpendSection).
  3. Speed — median lead time, intake → merged (SpeedSection).
  4. Objectives — three charter objective cards, each showing a live metric against its target (ObjectivesSection).

Loading, error, and empty states are handled at the card level: ScorecardSkeleton while the query loads, OfflineState with a retry button on error or missing data.

Objectives section

ObjectivesSection replaced the former StubObjectivesSection, a placeholder that rendered fake "Revenue growth" / "Customer retention" labels with a "Not tracked yet" badge. The stub actively misrepresented charter performance; the live section renders the three real charter objectives against their live metrics.

The three objective cards

Each card shows the objective label, the live metric value (or "No data yet"), and the target:

Position Charter objective (objectives[i].metric) Target Metric field Format
0 Tasks shipped to merge with no human code edits 90% first_pass_yield percentage, (v * 100).toFixed(0)%
1 Median lead time, intake → merged < 24h median_lead_time_hours {value.toFixed(1)}h
2 Critical escaped defects per release 0 escaped_defects count, ${value}

The lead-time metric is the same value SpeedSection renders in the Speed section above — ObjectivesSection reads data.median_lead_time_hours a second time and presents it as an objective card alongside the other two. SpeedSection is intentionally kept as-is.

Positional-by-convention mapping

The charter objectives field on CockpitSummary is Record<string, unknown>[] — free-form text from the Goals tab, each entry carrying {metric, target, status}. The three metrics above are hardcoded by contrast. The mapping is positional by convention, not derived: objectives[i].metric supplies the label for card i, and card i reads the i-th hardcoded metric. This holds only while the charter has exactly three objectives in the canonical order; editing the Goals tab can desync labels from metrics. The assumption is documented in a ponytail: code comment at the top of the ObjectivesSection block in company-scorecard-card.tsx — a stated assumption, not something discovered later.

objectiveLabel(objectives, index) returns objectives[index].metric when it is a non-empty string, falling back to OBJECTIVE_FALLBACK_LABELS[index] (the three canonical labels above) when the array is empty or shorter than three. A fabricated label is never rendered.

"No data yet" fallback

Each card guards its metric with a hasData check (value != null, covering both null and undefined). When the metric is absent the card renders "No data yet" in italic muted text instead of a value — mirroring the SpeedSection pattern. This is the contract for the two new fields (first_pass_yield, escaped_defects): until the backend companion item ships them on the /cockpit/summary response, both are absent and both cards show "No data yet". The card never fabricates a value or a label.

CockpitSummary type changes

panel/src/lib/api/cockpit.ts gained two optional fields on CockpitSummary:

// Fraction of tasks shipped to merge with no human code edits (01).
// Backend companion item adds this; until it ships the field is absent
// and the UI renders 'No data yet'. Formatted as a percentage, matching
// the phone's pctOrDash convention in tg-metrics-tab.tsx.
first_pass_yield?: number | null;

// Count of critical escaped defects per release (backend companion item).
escaped_defects?: number | null;

Both are optional and nullable so the UI degrades cleanly while the backend companion item is outstanding. first_pass_yield is a 01 fraction formatted as a percentage, matching the phone's pctOrDash(scorecard.first_pass_yield) convention in tg-metrics-tab.tsx. median_lead_time_hours is unchanged — it already existed at the top level of the type and SpeedSection reads it from there.

The backend companion item (out of scope for this change) is what adds first_pass_yield and escaped_defects to the /api/cockpit/summary response. Until it lands, the two objective cards for those metrics show "No data yet".

Card structure

CompanyScorecardCard
└── ScorecardBody (data, spendTrend, spendTrendLoading)
    ├── DeliverySection   (data.delivery)
    ├── SpendSection      (data.spend, spendTrend, spendTrendLoading)
    ├── SpeedSection      (data.median_lead_time_hours)
    └── ObjectivesSection (data.objectives, first_pass_yield,
                           median_lead_time_hours, escaped_defects)
        └── ObjectiveCard × 3  (label, hasData, formattedValue, targetText)

ObjectiveCard is a presentational leaf: a rounded-lg border bg-card p-3 card (matching DeliveryMetric's styling) with the label on top and a flex justify-between row holding the value (or "No data yet") and the target: {targetText} annotation.

Tests

panel/src/components/business/__tests__/company-scorecard-card.test.tsx covers the Objectives section:

  • Three objective cards render with their target values when all metrics are present (labels from objectives[i].metric, values formatted as 92% / 18.7h / 0, targets 90% / < 24h / 0).
  • A missing first_pass_yield (null) and escaped_defects (undefined) each render "No data yet" — two fallbacks — while a present median_lead_time_hours still shows its value.
  • The fake "Revenue growth" / "Customer retention" / "Not tracked yet" stub labels do not appear in any render path.

The buildSummary test helper carries first_pass_yield: null and escaped_defects: null by default, matching the not-yet-shipped backend state.

  • docs/map/panel.md — the agent-facing codebase map entry for CompanyScorecardCard / ObjectivesSection.
  • panel/src/lib/api/cockpit.tsCockpitSummary type and cockpitApi.summary() client.
  • panel/src/components/business/spend-trend-chart.tsx — the 30-day spend chart embedded in SpendSection.
  • tg-metrics-tab.tsx — the phone cockpit's metrics tab, whose pctOrDash(scorecard.first_pass_yield) convention the first_pass_yield percentage format matches.