* [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>
6.8 KiB
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):
- Delivery — live task counts across the pipeline (
DeliverySection). - Spend — 30-day spend, projected monthly, monthly cap, plus the
SpendTrendChart(SpendSection). - Speed — median lead time, intake → merged (
SpeedSection). - 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 (0–1).
// 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 0–1 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 as92%/18.7h/0, targets90%/< 24h/0). - A missing
first_pass_yield(null) andescaped_defects(undefined) each render "No data yet" — two fallbacks — while a presentmedian_lead_time_hoursstill 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.
Related
docs/map/panel.md— the agent-facing codebase map entry forCompanyScorecardCard/ObjectivesSection.panel/src/lib/api/cockpit.ts—CockpitSummarytype andcockpitApi.summary()client.panel/src/components/business/spend-trend-chart.tsx— the 30-day spend chart embedded inSpendSection.tg-metrics-tab.tsx— the phone cockpit's metrics tab, whosepctOrDash(scorecard.first_pass_yield)convention thefirst_pass_yieldpercentage format matches.