/** Home “Move now” checklist → Daily + optional Log prefill. */ import { useState } from "preact/hooks"; import { ApiError, getDaily, putDaily, type DailyUpsert, } from "../api"; import { isoDate } from "../dates"; import { navigate } from "../router"; import { Button } from "./Button"; import { Pressable } from "./Pressable"; import { Sheet } from "./Sheet"; import { useToast } from "./Toast"; const EMPTY: DailyUpsert = { primary_brick: "none", brick_done: false, corn_sessions: 0, delay_ok: true, daydream: "none", rerun: "clean", court: "closed", stayed_indoors_all_day: false, left_room: false, left_home: false, movement_minutes: 0, interrupt_walk_minutes: 0, fantasy_walk_minutes: 0, headphones_on_walk: false, music_cinematic_on_walk: false, proof_brick_done: false, proof_brick_kind: null, fantasy_minutes_scheduled: 0, fantasy_minutes_unplanned: 0, note: "", }; type Props = { open: boolean; onClose: () => void; indoorsStreak?: number; }; export function MoveNowSheet({ open, onClose, indoorsStreak = 0 }: Props) { const toast = useToast(); const [leftBed, setLeftBed] = useState(false); const [walletHome, setWalletHome] = useState(true); const [walkKind, setWalkKind] = useState<"interrupt" | "fantasy">("interrupt"); const [minutes, setMinutes] = useState(20); const [busy, setBusy] = useState(false); const save = async (alsoLog: boolean) => { setBusy(true); const today = isoDate(new Date()); try { let base: DailyUpsert = { ...EMPTY }; try { const row = await getDaily(today); base = { primary_brick: row.primary_brick, brick_done: row.brick_done, corn_sessions: row.corn_sessions, delay_ok: row.delay_ok, daydream: row.daydream, rerun: row.rerun, court: row.court, stayed_indoors_all_day: false, left_room: leftBed || Boolean(row.left_room), left_home: Boolean(row.left_home) || minutes >= 15, movement_minutes: row.movement_minutes ?? 0, interrupt_walk_minutes: row.interrupt_walk_minutes ?? 0, fantasy_walk_minutes: row.fantasy_walk_minutes ?? 0, headphones_on_walk: walkKind === "interrupt" ? false : Boolean(row.headphones_on_walk), music_cinematic_on_walk: walkKind === "interrupt" ? false : Boolean(row.music_cinematic_on_walk), proof_brick_done: true, proof_brick_kind: "interrupt_walk", fantasy_minutes_scheduled: row.fantasy_minutes_scheduled ?? 0, fantasy_minutes_unplanned: row.fantasy_minutes_unplanned ?? 0, note: row.note || "", }; } catch { base = { ...EMPTY, left_room: leftBed, left_home: minutes >= 15, proof_brick_done: true, proof_brick_kind: "interrupt_walk", headphones_on_walk: walkKind !== "interrupt", }; } if (walkKind === "interrupt") { base.interrupt_walk_minutes = Math.max( base.interrupt_walk_minutes || 0, minutes, ); base.headphones_on_walk = false; base.music_cinematic_on_walk = false; } else { base.fantasy_walk_minutes = Math.max( base.fantasy_walk_minutes || 0, minutes, ); } base.movement_minutes = (base.interrupt_walk_minutes || 0) + (base.fantasy_walk_minutes || 0); base.stayed_indoors_all_day = false; await putDaily(today, base); toast.show("Movement saved on Daily"); onClose(); if (alsoLog) { const happened = encodeURIComponent( walkKind === "interrupt" ? `Interrupt walk ${minutes} min — wallet home, first 15 no headphones` : `Music / fantasy walk ${minutes} min — leisure, not medicine`, ); navigate(`/log?happened=${happened}&remedy=${encodeURIComponent("Interrupt walk")}`); } } catch (err) { toast.show( err instanceof ApiError ? err.message : "Could not save movement", "error", ); } finally { setBusy(false); } }; return (

Preview, not proof. Interrupt walk: no headphones, eyes outside. {indoorsStreak >= 2 ? ` ${indoorsStreak} days indoors — 20 min out, wallet home, first 15 min no headphones.` : ""}

Walk type
setWalkKind("interrupt")} > Interrupt (medicine) setWalkKind("fantasy")} > Music walk (leisure)
{walkKind === "interrupt" ? (

Interrupt walk: no headphones, eyes outside.

) : (

Music walk: leisure, not medicine.

)} setMinutes(Number((e.target as HTMLInputElement).value))} />
); }