Clock state and scheduling (pure core)

State::new(ValidatedPreferences) starts at shell generation zero, without a wall snapshot or taskbars. reduce(state, event) -> (state, Vec<Effect>) is pure; it reads no platform time and owns no native resources. overlay_state(key) returns Unavailable for absent or stale keys. Effects have stable transient-ID ordering within each reconciliation phase.

Input contract and transitions

InputTransition / effects
TopologyChangedRequest discovery once while pending; preserve unchanged overlays until the authoritative result.
DiscoveryResultAccept only the current generation and matching keys; reject duplicate keys atomically. Batch is authoritative: destroy removed keys, preserve existing identities/fullscreen, add new keys.
VisibilityChangedUpdate one current-generation taskbar; unknown destroys, hidden hides, visible reconciles eligibility.
FullscreenChangedUpdate one current-generation taskbar; fullscreen hides, leaving fullscreen reconciles eligibility.
SessionLockedHide existing visible overlays, create none.
SetPaused(bool)User paused/resumed replacement from the clock/tray menu. Paused hides every overlay (Hidden(Paused)), creates none and keeps windows; resume repaints once per visible clock. Session-only, unaffected by Explorer loss.
SessionUnlockedInvalidate old visibility and time; request discovery. Runtime supplies a fresh Tick/TimeChanged as well as discovery before showing.
Tick / TimeChangedReplace wall snapshot for all clocks, render once, paint changed text only; replace the one-shot timer even for repeated snapshots.
ThemeChanged { revision }A distinct resolved appearance revision dirties overlays; repeated revisions do nothing. Hidden overlays defer painting.
PreferencesApplied(validated)Commit the validated value; compare preferences, reconcile monitor selection, repaint changed text/font/theme, rearm changed cadence.
ExplorerLostDestroy overlays, invalidate keys, increment generation, request discovery. Repeated loss while awaiting recovery does nothing. Generation exhaustion terminates safely.
QuitDestroy overlays, terminal state; runtime cancels timer and discovery work. Late inputs do nothing.

Visibility precedence is Unavailable (missing/invalid bounds, zero DPI, unknown visibility, missing/unrenderable time), then Hidden(SessionLocked), Hidden(Paused) for unselected monitors or a user pause, Hidden(TaskbarHidden), Hidden(Fullscreen), then Visible. Proven clock-only bounds are supplied by discovery; the reducer also checks taskbar/monitor containment defensively. New hidden entries have no window. Existing hidden windows are retained until removal, invalid geometry/unknown visibility, shell loss or quit.

Runtime effect contract

next_delay(snapshot, cadence) returns a strictly positive duration to the next local second/minute boundary. Date-only formats recheck every minute. Leap-second subseconds are normalized to a one-second fraction. Identical time events have idempotent overlay effects but rearm timers because the prior one-shot may have fired. Theme revisions are equality tokens; platform high-contrast/palette changes must produce a distinct token even when the preference remains System.

Test fixtures

clock-core/tests/state.rs uses negative-coordinate dual-taskbar fixtures, unknown/invalid geometry, removal, fullscreen, lock/unlock, stale and malformed batches, Explorer replacement, theme/config changes, midnight, forward/backward jumps and explicit spring/fall fixed-offset DST inputs. Property tests check boundary waits and effect/window lifetime invariants over generated event sequences. No test sleeps or reads live time. No Windows UI, hooks, COM, actual timer, or application event loop is part of the core.