diff --git a/web/src/components/BudgetPanel.tsx b/web/src/components/BudgetPanel.tsx
index ce7b38b..f1edd2f 100644
--- a/web/src/components/BudgetPanel.tsx
+++ b/web/src/components/BudgetPanel.tsx
@@ -40,7 +40,7 @@ export function BudgetPanel({ demo }: Props) {
{data.error ?? 'Budget state unavailable.'}
) : (
-
+
)}
diff --git a/web/src/components/BudgetTodayCard.test.tsx b/web/src/components/BudgetTodayCard.test.tsx
index 7e08481..dd0ce2e 100644
--- a/web/src/components/BudgetTodayCard.test.tsx
+++ b/web/src/components/BudgetTodayCard.test.tsx
@@ -5,7 +5,7 @@ import { BudgetTodayCard } from './BudgetTodayCard'
describe('BudgetTodayCard', () => {
it('shows remaining time, usage and the next warning', () => {
- render()
+ render()
expect(screen.getByText('7h 0m')).toBeInTheDocument()
expect(screen.getByText(/1h 0m of 8h 0m used/)).toBeInTheDocument()
expect(screen.getByText(/Next warning at 1h 0m remaining/)).toBeInTheDocument()
@@ -16,6 +16,7 @@ describe('BudgetTodayCard', () => {
render(
,
)
expect(document.querySelector('.budget-meter--warn')).not.toBeNull()
@@ -28,26 +29,33 @@ describe('BudgetTodayCard', () => {
next_warning_seconds: null,
warned_seconds: [3600, 1800, 600, 300],
})}
+ maskedCount={0}
/>,
)
expect(screen.getByText(/No further warnings today/)).toBeInTheDocument()
expect(document.querySelector('.budget-meter--warn')).not.toBeNull()
})
+ const blockedToday = () =>
+ makeBudgetToday({
+ blocked: true,
+ blocked_at: 1,
+ seconds_used: 28800,
+ seconds_remaining: 0,
+ fraction_used: 1,
+ next_warning_seconds: null,
+ })
+
it('shows the cutoff badge when blocked', () => {
- render(
- ,
- )
- expect(screen.getByText(/Cutoff engaged/)).toBeInTheDocument()
+ render()
+ expect(screen.getByText(/Cutoff engaged — launchers are masked/)).toBeInTheDocument()
expect(document.querySelector('.budget-meter--blocked')).not.toBeNull()
})
+
+ it('does not claim a mask that is not in place', () => {
+ render()
+ expect(
+ screen.getByText(/Cutoff engaged — launchers are not currently masked/),
+ ).toBeInTheDocument()
+ })
})
diff --git a/web/src/components/BudgetTodayCard.tsx b/web/src/components/BudgetTodayCard.tsx
index 6e42f9f..65b5557 100644
--- a/web/src/components/BudgetTodayCard.tsx
+++ b/web/src/components/BudgetTodayCard.tsx
@@ -3,6 +3,7 @@ import type { BudgetToday } from '../types'
interface Props {
today: BudgetToday
+ maskedCount: number
}
/** Which colour the meter takes: normal, into the warning band, or spent.
@@ -17,7 +18,7 @@ function meterState(today: BudgetToday): string {
return today.warned_seconds.length > 0 ? 'warn' : 'ok'
}
-export function BudgetTodayCard({ today }: Props) {
+export function BudgetTodayCard({ today, maskedCount }: Props) {
return (
Today
@@ -39,7 +40,14 @@ export function BudgetTodayCard({ today }: Props) {
{today.blocked ? (
- Cutoff engaged — launchers are masked
+ {/* Read the live mount count rather than inferring it from
+ `blocked`: the total-block branch releases every mount while
+ leaving `blocked_at` set, so the two can genuinely disagree —
+ and a badge claiming a mask that is not there is the one thing
+ this panel must never do. */}
+ {maskedCount > 0
+ ? 'Cutoff engaged — launchers are masked'
+ : 'Cutoff engaged — launchers are not currently masked'}
) : today.next_warning_seconds === null ? (
'No further warnings today.'