From f86a87af3854dea0d536c325f486f29d81860a54 Mon Sep 17 00:00:00 2001 From: Yashas Date: Thu, 17 Sep 2026 13:30:54 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20custom=20run=20is=20one=20of=20two=20mod?= =?UTF-8?q?es=20=E2=80=94=20replay=20a=20morning,=20or=20analyse=20a=20per?= =?UTF-8?q?iod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three-date form let 'data as of' and 'window to' overlap and resolved the clash by a hidden rule. Now a two-card choice: Replay (one date; 6 weeks before it vs the 8 before those) or Period (from–to vs the 8 weeks before it). Each mode sends only its own fields; the preview names the exact periods. Co-Authored-By: Claude Fable 5.1 --- src/screens/RunReviewScreen.tsx | 98 +++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/src/screens/RunReviewScreen.tsx b/src/screens/RunReviewScreen.tsx index 6fba93a..35d3bd7 100644 --- a/src/screens/RunReviewScreen.tsx +++ b/src/screens/RunReviewScreen.tsx @@ -21,14 +21,36 @@ import { shortDate } from '../format'; * the same thing; anchored before the August spike, a run can genuinely come * back "nothing to report" — which is what most mornings look like. */ +function ModeCard({ active, onClick, title, body }: { active: boolean; onClick: () => void; title: string; body: string }) { + return ( + + ); +} + export function RunReviewScreen() { const navigate = useNavigate(); const { client } = useZino(); const [busy, setBusy] = useState<'now' | 'custom' | null>(null); const [err, setErr] = useState(null); - const [asOf, setAsOf] = useState(''); - const [from, setFrom] = useState(''); - const [to, setTo] = useState(''); + // Two custom runs, never mixed. "Replay" = one date, the morning to stand on; + // "Period" = from–to, the span to analyse. Each sends only its own fields. + const [mode, setMode] = useState<'replay' | 'period'>('replay'); + const [asOfInput, setAsOf] = useState(''); + const [fromInput, setFrom] = useState(''); + const [toInput, setTo] = useState(''); + const asOf = mode === 'replay' ? asOfInput : ''; + const from = mode === 'period' ? fromInput : ''; + const to = mode === 'period' ? toInput : ''; // The data's own span, so a window cannot be set where there is nothing to // compare: a start before the first claim leaves no baseline, and a run on @@ -47,9 +69,9 @@ export function RunReviewScreen() { const baseTo = recentFrom ? addDays(recentFrom, -1) : ''; const dateErr = from && effectiveTo && from > effectiveTo - ? 'The window must start before it ends.' - : to && asOf && to > asOf - ? '"Window to" cannot be after "data as of".' + ? 'The period must start before it ends.' + : mode === 'period' && (!from || !to) + ? null : dataFrom && baseFrom && baseFrom < dataFrom ? `The 8-week baseline before ${shortDate(recentFrom)} needs data from ${shortDate(baseFrom)}, but the data begins ${shortDate(dataFrom)}. Start the window on or after ${shortDate(addDays(dataFrom, 56))}.` : null; @@ -61,11 +83,10 @@ export function RunReviewScreen() { // Every field on the activity is optional; send only the ones the // reviewer set, so a plain run is a genuinely empty submission. const data: Record = {}; - if (custom) { - // "Window to" is the ceiling; "data as of" only matters when no window end is given. - if (asOf && !to) data.review_as_of__review = asOf; - if (from) data.review_from__review = from; - if (to) data.review_to__review = to; + if (custom && mode === 'replay') data.review_as_of__review = asOf; + if (custom && mode === 'period') { + data.review_from__review = from; + data.review_to__review = to; } const res = await client.startInstance(WORKFLOW, ACT.REVIEW, data); if (res.instance_id) navigate(`/c/${res.instance_id}`); @@ -137,31 +158,48 @@ export function RunReviewScreen() {

Custom run

- Replay a past morning, or point the review at a specific period. Leave a date blank to - use the default. + Choose one of the two. {dataFrom && dataTo ? ` Data available ${shortDate(dataFrom)} – ${shortDate(dataTo)}, ${dataTo.slice(0, 4)}.` : ''}

-
-
- - -

Ignore anything after this date. Try {shortDate(QUIET_MORNING_AS_OF)} for a quiet morning.

-
-
- - -

Start of the period Ava analyses.

-
-
- - -

End of the period. Defaults to "data as of".

-
+
+ setMode('replay')} + title="Replay a past morning" + body="One date. The review runs as if it were that morning: the 6 weeks before it, compared with the 8 weeks before those. Anything after the date is ignored." + /> + setMode('period')} + title="Analyse a specific period" + body="A start and an end. That span is compared with the 8 weeks before it starts. Anything after the end is ignored." + />
+ {mode === 'replay' ? ( +
+ + +

Try {shortDate(QUIET_MORNING_AS_OF)} for a morning with no findings.

+
+ ) : ( +
+
+ + +

First day of the period.

+
+
+ + +

Last day. Up to 12 weeks after "From".

+
+
+ )} +

{dateErr ? ( {dateErr} @@ -184,7 +222,7 @@ export function RunReviewScreen() { kind="secondary" onClick={() => run(true)} busy={busy === 'custom'} - disabled={busy !== null || !!dateErr || (!asOf && !from && !to)} + disabled={busy !== null || !!dateErr || (mode === 'replay' ? !asOf : !from || !to)} > Run with these dates