/** * 倒计时 / Countdown — ONE timer reading, TWO subjects, and a picker of its own. * * ── The three views ───────────────────────────────────────────────────────── * `pick` (the default subject) is a pomodoro, and it has TWO views: * - the PICKER: a 2×2 grid of duration capsules (1 / 3 / 5 / 10 minutes — the * owner's reference, 2026-10-14). A press dispatches `timer:pick:`, * which writes the length AND starts the run in one op; the settings field * 自定义分钟数 replaces the longest slot when it is set; * - the RUN view: the stopwatch's dial (`layout: 'dial-head'`) — the remaining * time over the same two round controls — with a back chevron in the TITLE ROW * (`headAction`), which is the owner's ask. * Which of the two is drawn is DERIVED, not stored: `startedAt === null && * tmBase === 0` is "nothing has been started", and every op that stops a run * (`timer:reset`) leaves exactly that state. A fifth persisted field would have to * be kept in sync with those two, and could disagree with them. * * `next` is read-only: it counts down to the instant the DSH scheduled-task * subsystem will next fire, and has no controls at all — nothing about a cron rule * is the user's to press here. It is selected in the component config. * * ── Why `next` reuses the very same `timer` payload ───────────────────────── * A countdown to an ABSOLUTE instant is expressed in the reading's own two * numbers instead of a third `targetAt` field: `startedAt = ` and `durationMs = target − anchor`. The renderer computes * `durationMs − (now − startedAt)`, which for ANY later `now` is exactly * `target − now` — so the figure stays honest however stale this render output * is (the rail's idle tick is 30 s, the renderer's own tick is 1 s). * * The anchor therefore MUST be the same instant the two numbers were derived * from: `pinnedInstant(sim.at)` under a preview, `Date.now()` live. Mixing an * older anchor with a freshly computed duration would drift by however long * the render output sat in the rail. * * ── The target line's zone ────────────────────────────────────────────────── * `目标 HH:MM` is the browser's OWN local zone (`readClock(target, 'local')`). * The card deliberately ships no timezone field: the instant it counts to is * this user's own pomodoro end, not a zone's wall clock, and a rule's own * `timeZone` describes the SCHEDULED task (next mode formats no time at all). * * ── Why the title stays ───────────────────────────────────────────────────── * CARD-TITLE-STANDARD **T2 度量型**: a bare `18:00` names nothing — the title is * the only place that says WHICH clock this is (a pomodoro, not a task's elapsed * time). On the run view the title row is also the back affordance's home, which is * what the owner asked for; on the picker it is the card's only label. * * ── The two degradation rules (`schedule`) ────────────────────────────────── * `stats.schedule` is `null` when no card on this rail waits on the schedule * source (the manifest declares `reads: ['schedule']`, so the collector subscribes * whenever THIS card is installed), and `{ status: 'unavailable' }` when the * deployment carries no schedule surface at all. NEITHER means "you have no tasks" * — the card says 定时任务不可用 for both, and only a READY snapshot with no future * active entry earns 暂无日程. The preview may PIN that verdict with * `sim.scheduleStatus`, which is what keeps BOTH branches reachable offline. */ import type { WidgetRenderMeta, WidgetRenderOut, WidgetStats } from '../../client/lib/contract/types'; declare function renderCountdown(stats: WidgetStats, meta?: WidgetRenderMeta): WidgetRenderOut; declare const _default: { id: string; name: () => string; desc: () => string; builtin: true; group: string; sizes: "2x2"[]; simToggle: () => string; render: typeof renderCountdown; configSchema: ({ key: string; label: () => string; type: "mode"; default: string; options: [string, () => string][]; hint?: undefined; } | { key: string; label: () => string; type: "text"; default: string; hint: () => string; options?: undefined; })[]; example: { stats: { cdMode: string; cdCustom: string; cdMin: string; tmBase: number; tmStart: null; schedule: { status: "ready"; entries: never[]; }; }; sim: { at: number; mode: string; }; simSteps: ({ at: number; mode: string; cdCustom?: undefined; cdMin?: undefined; baseMs?: undefined; startedAt?: undefined; started?: undefined; done?: undefined; nextAt?: undefined; nextTitle?: undefined; nextKind?: undefined; } | { at: number; mode: string; cdCustom: string; cdMin?: undefined; baseMs?: undefined; startedAt?: undefined; started?: undefined; done?: undefined; nextAt?: undefined; nextTitle?: undefined; nextKind?: undefined; } | { at: number; mode: string; cdMin: number; baseMs: number; startedAt: null; started: boolean; cdCustom?: undefined; done?: undefined; nextAt?: undefined; nextTitle?: undefined; nextKind?: undefined; } | { at: number; mode: string; cdMin: number; baseMs: number; startedAt: number; started: boolean; cdCustom?: undefined; done?: undefined; nextAt?: undefined; nextTitle?: undefined; nextKind?: undefined; } | { at: number; mode: string; cdMin: number; baseMs: number; startedAt: null; started: boolean; done: boolean; cdCustom?: undefined; nextAt?: undefined; nextTitle?: undefined; nextKind?: undefined; } | { at: number; mode: string; nextAt: number; nextTitle: string; nextKind: string; cdCustom?: undefined; cdMin?: undefined; baseMs?: undefined; startedAt?: undefined; started?: undefined; done?: undefined; })[]; note: string; }; }; export default _default;