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:
@@ -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"
|
||||
|
||||
@@ -161,39 +161,59 @@ export function StandupScreen({
|
||||
</Section>
|
||||
|
||||
<Section overline="Stale blockers" order={3}>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onOpenIssue(NAG_REF)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') onOpenIssue(NAG_REF)
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 10,
|
||||
alignItems: 'flex-start',
|
||||
cursor: 'pointer',
|
||||
background: 'var(--warn-tint)',
|
||||
borderRadius: 'var(--radius-2)',
|
||||
padding: '12px 14px',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--warn)', display: 'inline-flex', marginTop: 2 }}>
|
||||
<Icon name="clock" size={15} />
|
||||
</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||
<span style={{ font: '500 12.5px var(--font-mono)', color: 'var(--ink-1)' }}>#{s.nag.id}</span>
|
||||
<Badge tone="warn" dot>
|
||||
steeping {s.nag.days}
|
||||
</Badge>
|
||||
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)' }}>
|
||||
blocks {s.nag.blocks.join(', ')}
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '5px 0 0' }}>{s.nag.text}</p>
|
||||
{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>
|
||||
) : (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onOpenIssue(NAG_REF)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') onOpenIssue(NAG_REF)
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 10,
|
||||
alignItems: 'flex-start',
|
||||
cursor: 'pointer',
|
||||
background: 'var(--warn-tint)',
|
||||
borderRadius: 'var(--radius-2)',
|
||||
padding: '12px 14px',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'var(--warn)', display: 'inline-flex', marginTop: 2 }}>
|
||||
<Icon name="clock" size={15} />
|
||||
</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||||
<span style={{ font: '500 12.5px var(--font-mono)', color: 'var(--ink-1)' }}>#{s.nag.id}</span>
|
||||
<Badge tone="warn" dot>
|
||||
steeping {s.nag.days}
|
||||
</Badge>
|
||||
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)' }}>
|
||||
blocks {s.nag.blocks.join(', ')}
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '5px 0 0' }}>{s.nag.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<footer
|
||||
|
||||
Reference in New Issue
Block a user