/** * 秒表 (stopwatch) — a counted-up timer the user drives from the card itself. * * WHAT IT IS FOR. The rail is the surface the user is already watching while * working, so the one measurement that has no other home is "how long has this * been going on": a run, a build, a focus stretch. The 时钟 card next door answers * "what time is it", which is a different question — an instant cannot tell you how * much time has PASSED since you started caring. * * THE BODY IS THE OWNER'S REFERENCE (2026-10-14): a large `00:00:56.29` reading — * hundredths in the product's accent — over two round controls, the secondary one * an outlined ring (复位) and the main press a filled disc that flips between ▶ and * ‖ (开始 / 继续 / 暂停). That is `TimerRich.layout: 'dial'`, and it is why this card * carries NO TITLE: per docs/design/CARD-TITLE-STANDARD.md a title may be dropped * only when the body names itself AND fills the tile, and this one does both — a * hundredths stopwatch over play/pause discs is not mistakable for a countdown, and * the 120px body owns the titleless tile (`12 + 120 + 12 = 144 ≤ 150`). The 倒计时 * card, whose reading could be either thing, keeps its title and its pill buttons. * * WHY THE READING CANNOT LIVE ON THE WIDGET. A widget `render` is pure data and the * rail only calls it when the data CHANGES (idle: once every 30s — see * `data/collector.tsx`), so a stopwatch wired to widget renders would jump in * 30-second steps, and `render` cannot own an interval anyway. The figure is * therefore drawn and ticked by the renderer on its own clock — 100ms here, because * the card prints hundredths — exactly like the dial's second hand: * `render/rich/timer-face.tsx`. This widget ships the two NUMBERS the reading is * made of and nothing that could go stale — `at` is written only when a preview * PINNED an instant, which is what makes the offline render snapshot and the gallery * screenshots reproducible. * * WHERE THE STATE LIVES. Not in React state, and not in this module: in the card * instance's own config (`prefs.cardConfigs['stopwatch@2x2']`, persisted through * `/api/widgets-state` and merged into the widget's stats by `runtime/live-stats.ts`). * That is the one store a card already owns, and it is what makes a running * stopwatch the same reading after a page refresh and in a different browser pointed * at this host. The field names are the shared machine's (`tmBase` / `tmStart`), read * through `readTimerState` — never re-derived here, or the writer (the rail) and the * reader would drift apart. * * THE CONTROLS. `TimerControl` is pure data (a glyph NAME, never a node); the * renderer draws the discs and hands back only an action ID. The IDs are the closed * timer vocabulary (`timer:start` / `timer:pause` / `timer:reset`), which the rail * maps to a state write through `lib/timer-state.ts` — so the press samples * `Date.now()` at the moment of the press instead of the moment the card was drawn. */ import type { WidgetRenderMeta, WidgetRenderOut, WidgetStats } from '../../client/lib/contract/types'; declare function stopwatchRender(stats: WidgetStats, meta?: WidgetRenderMeta): WidgetRenderOut; declare const _default: { id: string; name: () => string; desc: () => string; builtin: true; group: string; sizes: "2x2"[]; render: typeof stopwatchRender; simToggle: () => string; configSchema: never[]; example: { stats: { tmBase: number; tmStart: null; }; sim: { at: number; baseMs: number; startedAt: null; }; simSteps: ({ at: number; baseMs: number; startedAt: null; } | { at: number; baseMs: number; startedAt: number; })[]; note: string; }; }; export default _default;