Fix blank Focus + calm-standup nag chrome (found dogfooding live)

Poking at the live app (a real repo with no open issues) surfaced two empty-state gaps:

- Focus (Morning service) rendered a bare header — no cards, no empty state —
  because scheduleFocus returns a FocusView with empty now/next/later rather than
  undefined, so the `if (!focus)` guard never fired. Guard on 'no now/next/later'
  too → shows the 'Nothing to pour' EmptyState.
- Standup's calm sentinel nag (id 0) still rendered '#0 · steeping 0d · blocks'
  chrome and was a click target (would try to open issue #0). Render the calm
  case as a plain reassurance pill, no chrome, not clickable.

Both verified against the live christian/commitea repo (all issues closed → no
open work). desktop tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Croissant Le Doux
2026-07-10 15:57:30 -04:00
parent 1000d053b3
commit 50db790f86
2 changed files with 59 additions and 36 deletions

View File

@@ -7,9 +7,10 @@ import { EmptyState } from '../shell/states.js'
import { Badge, Button, Card, IconButton, Tag } from '../ui/index.js'
/**
* Morning service — the Now/Next/Later focus cards + the burn-up cone.
* `focus` (scheduler) and `forecast` (Monte Carlo) override the demo fixtures
* when gitea is configured; both fall back to the handoff demo otherwise.
* Morning service — the Now/Next/Later focus cards + the burn-up cone, from the
* scheduler (`focus`) and Monte Carlo forecast (`forecast`). Both come from the
* reconciled backlog; with no open work the scheduler has nothing to pour, so we
* show the empty state.
*/
export function FocusScreen({
onOpenIssue,
@@ -66,7 +67,9 @@ export function FocusScreen({
</Card>
)
if (!focus) {
// The scheduler returns a FocusView even when the backlog has no open work —
// treat "no now/next/later" as empty, not just an absent `focus`.
if (!focus || (!focus.now && !focus.next && !focus.later)) {
return (
<EmptyState
icon="coffee"

View File

@@ -161,6 +161,25 @@ export function StandupScreen({
</Section>
<Section overline="Stale blockers" order={3}>
{s.nag.id === 0 ? (
// Calm sentinel — nothing is over-steeping; render just the reassurance,
// no issue chrome, no click target (there's no #0 to open).
<div
style={{
display: 'flex',
gap: 10,
alignItems: 'center',
background: 'var(--paper-2)',
borderRadius: 'var(--radius-2)',
padding: '12px 14px',
}}
>
<span style={{ color: 'var(--ok)', display: 'inline-flex' }}>
<Icon name="circle-check" size={15} />
</span>
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0 }}>{s.nag.text}</p>
</div>
) : (
<div
role="button"
tabIndex={0}
@@ -194,6 +213,7 @@ export function StandupScreen({
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '5px 0 0' }}>{s.nag.text}</p>
</div>
</div>
)}
</Section>
<footer