dailyWorkAreaStore.ts

Persists and queries daily work area circles and prompt-handling state via AsyncStorage

Purpose

This module manages the persistent record of user-defined work area circles and the daily prompt gate that determines whether the user has been asked to set their work area today. All data is stored in AsyncStorage under a single versioned key.

Its responsibilities include:

  • Saving a new work area circle for a given local calendar day
  • Loading the work area circle for a given day
  • Recording that the daily work area prompt has been handled for a given day
  • Checking whether the prompt has been handled today
  • Finding the most recent prior work area within a configurable lookback window
  • Providing a single composite gate-state query used by DailyWorkAreaContext
  • Validating all coordinate and radius inputs before any write
  • Normalizing and deduplicating persisted state on every load

Invariants

Local Day Keys

All date-based operations use toLocalDayKey(date) to derive a string key of the form YYYY-MM-DD in the device’s local timezone.

Passing a Date object or ISO string is accepted; both are converted to the local day before storage or lookup.


One Circle Per Day

saveDailyWorkAreaCircle replaces any existing circle for the same day rather than appending.

After write, circles are re-sorted in descending dayKey order so the most recent entry is always first.


Deduplication on Load

normalizeState deduplicates circles by dayKey on every load, keeping the entry with the latest createdAt value when duplicates exist.

handledDayKeys is also deduplicated and sorted on load.


Coordinate and Radius Validation

saveDailyWorkAreaCircle validates all three fields before reading state:

FieldConstraint
centerLatFinite, in [−90, 90]
centerLonFinite, in [−180, 180]
radiusKmFinite, positive

Violations throw a RangeError.


Graceful Corrupt State

All AsyncStorage reads and JSON parses are wrapped in try/catch.

Any read or parse failure returns EMPTY_STATE rather than propagating, ensuring the module never crashes due to corrupted persisted data.


maxAgeDays Validation

findMostRecentPriorWorkArea requires maxAgeDays to be a positive integer.

Invalid values throw a RangeError.


Constants

ConstantValueDescription
STORE_KEY"heatsafe_daily_work_area_v1"AsyncStorage key
DEFAULT_WORK_AREA_LOOKBACK_DAYS7Default prior work area search window (days)

Exports

DailyWorkAreaCircle

type DailyWorkAreaCircle = {
	dayKey: string;
	centerLat: number;
	centerLon: number;
	radiusKm: number;
	createdAt: string;
};

saveDailyWorkAreaCircle(...)

async function saveDailyWorkAreaCircle(
	circle: Omit<DailyWorkAreaCircle, "dayKey" | "createdAt">,
	date?: Date | string,
): Promise<DailyWorkAreaCircle>

Saves a work area circle for the local day derived from date (default: now). Returns the saved circle including the generated dayKey and createdAt. Throws RangeError on invalid inputs.


loadDailyWorkAreaCircle(...)

async function loadDailyWorkAreaCircle(date?: Date | string): Promise<DailyWorkAreaCircle | null>

Returns the circle saved for the local day of date, or null if none exists.


markDailyWorkAreaHandled(...)

async function markDailyWorkAreaHandled(date?: Date | string): Promise<void>

Records that the daily work area prompt has been handled for date’s local day, without saving a circle.


hasHandledDailyWorkAreaPrompt(...)

async function hasHandledDailyWorkAreaPrompt(date?: Date | string): Promise<boolean>

Returns true if the prompt has been handled for date’s local day.


findMostRecentPriorWorkArea(...)

async function findMostRecentPriorWorkArea(
	date?: Date | string,
	maxAgeDays?: number,
): Promise<DailyWorkAreaCircle | null>

Returns the most recent circle from a prior day (not today) within maxAgeDays, or null if none is found. Throws RangeError if maxAgeDays is not a positive integer.


getDailyWorkAreaGateState(...)

async function getDailyWorkAreaGateState(
	date?: Date | string,
	maxAgeDays?: number,
): Promise<{
	dayKey: string;
	handledToday: boolean;
	todayCircle: DailyWorkAreaCircle | null;
	fallbackCircle: DailyWorkAreaCircle | null;
}>

Composite query returning all gate-state fields in a single call. Runs hasHandledDailyWorkAreaPrompt, loadDailyWorkAreaCircle, and findMostRecentPriorWorkArea in parallel.