Skip to the handbook

WHIMAWAY / ENGINEERING REFERENCE

Inside WhimAway

How a task moves from an idea to durable storage, across devices, and back to your screen.

Android · Kotlin / ComposeiOS · Swift / SwiftUIShared services · Firebase / FastAPI

Source audit: · 55f8edc9a70b
Released-version correspondence unverified. Read the evidence scope →

Browse the handbook · 15 sections

What WhimAway does

Two native clients. Local tasks. Optional cloud assistance.

WhimAway turns written entries, camera captures and voice notes into tasks you can review, edit and complete. Android uses Kotlin and Jetpack Compose; iOS uses Swift and SwiftUI. Both keep tasks in a local database and synchronize authenticated accounts through shared Firebase services.

  • Write a task directly, or capture a photo or voice note and review an optional AI suggestion before saving.
  • Add details, a due date, calendar recurrence and subtasks. Edit a saved task, toggle completion or remove it from the list.
  • Browse pending, completed and all tasks; see progress and a locally ranked focus section.
  • Use profile settings, explicit offline-task claiming, permission controls, AI consent controls and account deletion.

Local capture and manual editing do not require an AI response. Sign-in, shared-account synchronization and remote suggestions need their respective services. Camera/microphone permission, AI-processing consent and notification permission are separate decisions.

Audit snapshot & confidence

A fixed source reference, with implementation and runtime evidence kept separate.

SourceAudited referenceWhat it establishes
Android, iOS, backend and shared contracts3 October 2026 · 55f8edc9a70bc1c96acf01bcab1391384693f4ceOne clean source snapshot matching the inspected main branch.
Android build settingsVersion 1.0.6 · build 7Configured in source; correspondence to the installed/store release is unverified.
iOS build settingsVersion 1.0 · build 5Configured in source; correspondence to the installed/store release is unverified.
Evidence methodProduction entry points, callers, persistence, adapters and existing testsSanitized source landmarks appear below. The full ledger stays private; repository visibility has not been established for public deep links.

Implementation means a reachable source path was inspected. Test inspection means assertions were read, not that they passed. Executed results are listed in Testing & development. Mobile runtime behavior, deployed service configuration and store-release linkage remain separate verification questions.

iOS has production, offline and scaffold composition paths. Normal launch configures real Firebase, SwiftData and device services; tests/previews or an explicit scaffold launch argument use demo providers. A demo provider or simulated recording is not evidence of production behavior.

Android / iOS capability map

Shared workflows do not imply identical edge cases.

WorkflowAndroidiOSShared boundary / evidence
Written taskHomeViewModel → Room; title requiredWrittenTaskCreationModel → SwiftData; title requiredManual entry. No text-to-AI request found in inspected creation paths. Code inspected.
Photo and voiceCamera activity / AAC recording; editable AI draftAVFoundation capture / recording; editable AI draftAuthenticated FastAPI contract; device permission and AI consent govern separate steps. Code inspected.
Suggested subtasksExpanding an empty suggestions editor copies suggestions into the draft; remove unwanted entriesSuggestions populate untouched draft fields; edit or remove entriesConfirmation persists the draft. This is not a per-subtask reminder selector. Code inspected.
Local account dataOne Room database, owner-filtered recordsSeparate SwiftData account stores and separate offline storeFirebase UID identifies cloud ownership. Code inspected.
Pending cloud changesUnsynced Room rows; one-shot and periodic WorkManagerPersisted JSON outbox; in-process SyncEngineFirestore task documents and Storage media. Code inspected.
Sign-inEmail/password, Google, Continue OfflineEmail/password, Google, Apple, Continue OfflineFirebase Auth; no live sign-in exercised.
RemindersDelayed WorkManager notificationNon-repeating local notification requestTask dueAt, Mark Done and fixed 10-minute snooze. Code inspected, delivery not device-tested.
RecurrenceNative calendar arithmetic; completion advances same taskNative calendar arithmetic; completion advances same taskDaily, weekly, monthly, yearly; due-date anchor. Code and tests inspected.
Progress and focusDerived locally; streak calculated from task historyDerived locally; production profile currently supplies streakDays = 0Similar UI does not establish identical streak behavior. Code inspected.

System boundaries

The screen reads local state. Sync and suggestions cross different network boundaries.

01 · The two clients and shared services

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

Arrows show inspected call/data paths. Auth supplies identity; Firebase rules and backend token verification enforce their own authorization boundaries. Neither UI reads tasks directly from AI output or Firestore.
Mermaid definition
flowchart TB
    accTitle: WhimAway system boundaries
    accDescr: Each native client owns local tasks. Firebase synchronizes accounts and media. Optional capture processing crosses a separate backend and OpenAI boundary.
    subgraph Android[Android device]
      A[Compose and HomeViewModel] --> AR[TaskRepositoryImpl]
      AR <--> ROOM[(Room)]
      AR --> AS[SyncCoordinator and SyncWorker]
    end
    subgraph iOS[iOS device]
      I[SwiftUI and observable models] --> IR[Repository decorators]
      IR <--> SD[(SwiftData)]
      IR --> IS[JSON outbox and SyncEngine]
    end
    AS <-->|Task documents| F[(Firestore)]
    IS <-->|Task documents| F
    AS <-->|Media| S[(Firebase Storage)]
    IS <-->|Media| S
    A -->|Optional consented capture| B[FastAPI suggestion service]
    I -->|Optional consented capture| B
    B -->|Image or audio and transcript| O[OpenAI]
    AUTH[Firebase Auth] -->|ID token| B
    AUTH -.->|Owner authorization| F
    AUTH -.->|Owner authorization| S

Room and SwiftData hold the task state that feeds each screen. Firestore carries task documents between signed-in devices; Firebase Storage carries saved media. FastAPI interprets captures through OpenAI and returns suggestions. The suggestion handlers do not save a task to Firestore: client confirmation is still required.

The product/support website and this portfolio handbook are presentation surfaces, outside the mobile task-save path. Client configuration, signing material, real user content and deployment credentials are intentionally excluded.

Android: from action to screen

Compose actions → ViewModel → repository → Room → Flow → UI state.

Save a written task

The written sheet emits HomeAction.ConfirmWrittenTask. HomeViewModel rejects a blank title or recurrence without a due date, sets saving state, constructs a domain Task and invokes CreateTaskUseCase. TaskRepositoryImpl checks the active owner, inserts the parent through TaskLocalDataSource and then inserts its subtasks.

02 · Android durable save and observed update

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

Room commits the parent before the separate child insert. TaskLocalDataSource combines owner-filtered task and subtask flows; HomeViewModel maps them into HomeUiState and Compose redraws. Observation runs asynchronously: its position here does not guarantee that a screen redraw precedes reminder refresh or sync enqueue.
Mermaid definition
sequenceDiagram
    accTitle: Android task save and observed screen update
    accDescr: A confirmed draft reaches Room before the repository schedules side effects. Room flows update HomeUiState independently of cloud acknowledgment.
    participant UI as Compose sheet
    participant VM as HomeViewModel
    participant R as TaskRepositoryImpl
    participant DB as Room / TaskDao
    participant BG as Reminders / SyncWorker
    UI->>VM: Confirm written draft
    VM->>VM: Validate title and recurrence anchor
    VM->>R: CreateTaskUseCase / createTask
    R->>DB: Insert parent row
    DB-->>R: Local ID / parent persisted
    R->>DB: Insert children separately
    DB-->>VM: Owner-scoped Flow emissions
    VM-->>UI: Map tasks into HomeUiState
    R->>BG: Refresh reminder and enqueue authenticated sync
    R-->>VM: Return local ID
    VM-->>UI: Close draft and show success
    Note over R,DB: These writes and side effects are not one transaction

Edit, complete and delete

Editing updates task fields and reconciles the child list; removed children become tombstones. Completion and subtask toggles update parent completion state. Completing a recurring occurrence can advance its due date and reset active children. Deletion soft-deletes parent and children in a DAO transaction; normal queries hide deleted records.

The repository refreshes reminders after local mutations and queues sync when Firebase Auth matches the owner. UI observation runs independently of these side effects. An error retains the editor or displays a message, but does not prove that earlier writes were rolled back: the main parent/child create and edit paths do not use one enclosing transaction.

State and lifetime

HomeViewModel owns HomeUiState and uses viewModelScope for mutations and suggestion jobs. Capture and written fields remain in-memory drafts. Closing capture sheets cancels their suggestion jobs; ViewModel cleanup releases recording resources. This does not establish restoration of unsaved drafts after process death or cancellation of a network request already executing.

iOS: from draft to snapshot

SwiftUI observable models → explicit SwiftData save → AsyncStream snapshots.

Save a written task

WrittenTaskCreationModel owns an in-memory draft, validates its title and recurrence anchor, and retains a task UID and creation timestamp across a save retry. Confirmation calls the injected TaskRepository. The SwiftData model actor validates identifiers and the title, inserts the persistent task and calls modelContext.save(). That explicit save is the local durable boundary.

03 · iOS durable save and snapshot publication

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

SwiftDataTaskRepository reloads visible tasks and publishes a loaded AsyncStream snapshot. HomeModel consumes it asynchronously; a screen redraw need not precede the decorators' outbox and reminder work. The outbox file write is separate from the SwiftData transaction.
Mermaid definition
sequenceDiagram
    accTitle: iOS task save and snapshot publication
    accDescr: A model actor explicitly saves SwiftData and the repository publishes a snapshot. Account sync queues and reminder scheduling follow through decorators.
    participant UI as SwiftUI draft
    participant M as WrittenTaskCreationModel
    participant R as Repository decorators
    participant SD as SwiftDataTaskRepository / store
    participant H as HomeModel
    UI->>M: Confirm validated draft
    M->>R: create with stable draft task UID
    R->>SD: Validate and insert task with subtasks
    SD->>SD: modelContext.save
    SD-->>H: AsyncStream loaded snapshot
    H-->>UI: Observable tasks / render state
    SD-->>R: Saved task
    R->>R: Account outbox enqueue and request sync
    R->>R: Refresh reminder / permission relevance
    R-->>M: Return saved task
    M-->>UI: Complete creation
    Note over R,SD: Store save and outbox file write are separate

Edit, complete and delete

TaskManagementModel keeps a TaskEditDraft separate from the saved task. It validates the title, existing subtask titles and recurrence anchor. A successful update dismisses editing; removing a child creates a tombstone. SwiftData explicitly saves completion, parent/child completion effects, recurrence advancement and soft deletion. Failed mutations show a notice; the list remains driven by repository snapshots.

Composition and lifetime

AppEnvironment selects repositories, authentication, media services and suggestion providers. AccountTaskRepositoryFactory opens a store per authenticated owner; SyncOutboxFactory opens that owner's queue. RootView recreates SignedInHomeRoute when homeSessionID changes. HomeModel starts/stops its observation task; repository streams remove terminated observers.

Photo and voice models track suggestion attempts and user-edited fields, rejecting results from older attempts and protecting edits from replacement. Media is finalized into task-owned local files before repository creation. Database persistence, media operations and outbox persistence are separate boundaries. A failed later step does not automatically roll back an earlier one.

Capture, AI & confirmation

Suggestions prepare an editable draft. They do not execute the task or save it for you.

04 · Capture to a confirmed task

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

Capture permission and per-account AI consent are independent. Declined consent, unavailable processing or unusable output leads to an editable fallback. Saving and authenticated cloud synchronization are separate operations.
Mermaid definition
flowchart TB
    accTitle: Capture, suggestion, review, and save
    accDescr: Capture permission and AI consent are separate. Failed or declined suggestions still lead to an editable manual draft. Only confirmation writes a task.
    C[Choose photo or voice] --> P[Camera or microphone permission]
    P --> M[Capture local media]
    M --> G{Authenticated AI consent?}
    G -->|No / unavailable| D[Editable fallback draft]
    G -->|Yes| REQ[JPEG JSON or M4A multipart + ID token]
    REQ --> B[Backend verifies token and checks request]
    B --> AI[Image interpretation or audio transcription + interpretation]
    AI --> N[Backend normalization]
    N --> V{Client decoding and validation}
    V -->|Failure| D
    V -->|Accepted| REVIEW[Review title, date, recurrence, details and subtasks]
    D --> REVIEW
    W[Written entry: manual fields] --> REVIEW
    REVIEW --> CONF[Explicit confirmation]
    CONF --> LOCAL[(Local task repository)]
    LOCAL --> SYNC[Separate authenticated cloud sync]

Photo and voice on each device

Android launches a camera activity with a FileProvider URI and uses the returned file in its draft. AiTaskParser encodes JPEG at quality 85 for AI processing. Its separate Storage upload path resizes a copy to at most 1280 pixels on the longest edge at quality 80. Those upload settings are not the AI-input settings.

iOS uses AVFoundation capture, normalizes orientation and writes a temporary JPEG at quality 0.92. The inspected normalizer preserves the original maximum pixel dimension rather than applying Android's 1280-pixel upload limit. Confirmation commits the temporary photo to task-owned storage before saving the task.

Android records AAC in an MPEG-4 container. iOS uses temporary M4A media with a 60-second recording limit; backgrounding or interruption stops recording and retains a usable finalized note. Each platform requests camera/microphone access for capture. Hardware quality and interruption behavior were not exercised in this audit.

What crosses the network

RequestPayloadBackend behavior
POST /parse/imageJSON image_base64 containing JPEG bytesVerify Firebase token, rate-limit identity, check JPEG envelope, send image and instructions to OpenAI.
POST /parse/audioMultipart file containing M4A audioVerify token, rate-limit identity, check container envelope, transcribe audio, interpret transcript.
Both responsestitle, dueAt, recurrenceType, recurrenceInterval, details, subtasksNormalize model output; no task persistence in these handlers.

Both clients attach an ID token and X-Client-Timezone. Each retries a 401 once with a refreshed token; neither falls back to anonymous AI. Android configures 30-second connect/read/write timeouts; iOS configures a 30-second request timeout. The backend constructs the OpenAI SDK without an application-specific timeout/retry override.

Validation and review

Backend normalization removes a surrounding code fence, parses JSON, supplies a fallback title for unusable output and normalizes optional fields. This is not full semantic validation: a date still needs client parsing, and a plausible suggestion may be wrong. Both clients reject the sentinel title Unable to parse task and unusable dates/recurrence values.

Android requires a positive interval when a recurrence type is returned. iOS permits an omitted interval, interpreted as one by its recurrence calculation. Android also accepts a zone-less local date through a fallback parser; iOS uses internet-date ISO 8601 parsing. Partial responses can therefore be accepted differently.

Android keeps suggested subtasks apart until the empty suggestions editor is expanded, which copies them into the draft; users can remove or add entries. iOS populates untouched fields and subtasks while protecting user-edited fields. Explicit confirmation saves the resulting draft. Neither flow establishes reminders for individual subtasks.

Sync, conflict & deletion

Local changes wait for connectivity; reconciliation operates on whole tasks.

05 · Pending work and incoming records

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

The conflict decision is client-side incoming reconciliation. Firestore writes use merge operations, not a shared conditional conflict transaction. Neither retries nor listeners establish exactly-once delivery or lossless concurrent editing.
Mermaid definition
flowchart TB
    accTitle: Synchronization and local reconciliation
    accDescr: Android scans unsynced Room rows; iOS drains a persisted outbox. Listeners reconcile whole task records. Local deletion flags are transported as tombstones.
    L[Local mutation / tombstone] --> A[Android: Room synced=false]
    L --> I[iOS: task-keyed JSON outbox]
    A --> AW[Network-constrained WorkManager]
    I --> IW[SyncEngine in app process]
    AW --> MEDIA[Upload missing media URL]
    IW --> MEDIA
    MEDIA --> WRITE[Merge task document with complete subtask array]
    WRITE --> F[(Firestore)]
    F --> LIST[Owner collection snapshot]
    LIST --> CHECK{Local reconciliation policy}
    CHECK -->|Deleted vs live| T[Tombstone wins]
    CHECK -->|Same deletion state| TIME[Newer updatedAt wins; tie stays local]
    T --> DB[(Local store and UI observation)]
    TIME --> DB
    AW -->|Failure| AR[Worker retry / later scheduled work]
    IW -->|Failure| IR[Eight-second in-process retry / foreground sync]
    AR --> AW
    IR --> IW

Android pending work

Local mutations mark Room rows unsynced. SyncWorker requires connectivity and checks its owner against Firebase Auth. SyncCoordinator scans pending rows, uploads missing media URLs, writes the task and complete subtask array, then marks the local row synced after acknowledgment. One-shot work uses an owner-specific name and replacement policy; periodic work requests a 15-minute interval. Exceptions return a retry with exponential backoff.

A foreground Firestore listener merges owner snapshots into Room. Sync Now requests pending writes and a full fetch. Incoming records attributed to the current device are ignored. Listener errors are recorded silently. Background SyncWorker pushes pending rows rather than performing the full foreground fetch path.

iOS pending work

SyncingTaskRepository saves locally, persists a task-keyed operation in SyncOutbox and requests SyncEngine processing. The outbox is an atomically replaced per-account JSON file and preserves media object timestamps across retries. SyncEngine reads the current local record, uploads missing media for live tasks, writes Firestore and clears operations through the acknowledged timestamp.

Incoming documents are mapped into SwiftData. The listener may queue a newer local record or local tombstone; it also queues local IDs absent from a received snapshot. Failures set retry status and schedule an eight-second Task delay; foreground activation requests sync. This runs in the app process. No equivalent periodic background scheduler was found in the inspected iOS composition.

Conflicts and tombstones

Both TaskConflictPolicy implementations prefer a tombstone over a live record regardless of device-clock order. With matching deletion state, a strictly newer updatedAt wins; a tie stays local. Subtasks travel as one embedded array, not as independently merged edits. Device clocks therefore affect concurrent changes.

Normal task deletion retains a tombstone and does not immediately delete its cloud media. Legacy records without a deleted field preserve a known local tombstone during mapping. A missing remote document is not a tombstone; on iOS, missing IDs can be queued for upload.

Identity & account lifetimes

Ownership follows an identity, not the task currently visible on screen.

06 · Entering and leaving an account

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

These are the inspected resource transitions. Cancellation and owner checks apply at particular boundaries; they do not establish revocation of every already-started operation. Ordinary sign-out retains local account data.
Mermaid definition
flowchart TB
    accTitle: Account transitions and resource lifetimes
    accDescr: Both apps separate offline ownership from authenticated ownership. Android switches scoped rows and work; iOS changes account stores, outboxes, engine and view identity. Sign-out does not erase all local account data.
    START[Resolve Firebase identity or selected offline mode] --> MODE{Session}
    MODE -->|Offline| OFF[Installation-owned local tasks; no cloud AI or sync]
    MODE -->|Authenticated| AND[Android: activate owner in Room repository]
    MODE -->|Authenticated| IOS[iOS: open account store and outbox]
    AND --> AW[Owner-scoped listeners and WorkManager]
    IOS --> IW[SyncEngine and reminder context; new Home session ID]
    AW --> OUT[Logout / account transition]
    IW --> OUT
    OUT --> AC[Android: cancel AI requests, stop cloud work, clear session]
    OUT --> IC[iOS: cancel engine/status tasks and consent waits; deactivate reminders]
    AC --> KEEP[Local account data retained for later sign-in]
    IC --> KEEP
    OFF -->|Explicit user claim| CLAIM[Move or copy offline tasks into authenticated ownership]
    CLAIM --> MODE

Both clients implement email/password, account creation with verification email, password reset and Google sign-in through Firebase Auth. iOS additionally implements Apple sign-in. Email/password client flows reject unverified identities. Profile-loading failure is distinguished from a missing username; failed loading is not taken to mean a new account.

Android

LaunchSessionResolver prefers a verified Firebase identity; otherwise it clears stale authenticated state and returns the selected offline identity or the sign-in screen. Room queries are owner-scoped. Owner activation stops previous cloud work/listeners, cancels other-owner reminder work and reschedules the current owner's eligible tasks.

Logout cancels AI requests, stops account cloud sync, signs out and opens login with a cleared activity stack. Account rows remain cached locally. Reminder workers/actions check active ownership; this differs from claiming that logout instantly erases delivered notifications or aborts every in-flight continuation.

iOS

AppEnvironment observes Firebase session updates. Account activation changes the SwiftData store, outbox, SyncEngine, media resolvers, UID-bound token provider and reminder context. Session-generation checks guard selected activation results. A new homeSessionID recreates the signed-in view.

Cloud-session teardown cancels engine/status tasks and pending consent requests, drops session services and deactivates the reminder owner. It retains the account store and outbox. Offline claiming is explicit: Android transfers scoped rows; iOS copies records into the account repository and queues them before clearing the offline repository after the loop succeeds.

Server authorization and deletion

The backend derives the UID from a verified Firebase bearer token. Checked-in Firestore/Storage rules allow operations only when the authenticated UID matches the owner in the path. Emulator results do not establish which rules are deployed. Client UI/identity checks do not replace server authorization.

DELETE /account removes the verified owner's task documents and profile, media and finally Auth user. Clients then clean local account data and leave the session. These sequential service operations can fail partially. iOS also coordinates Apple reauthorization/revocation where appropriate. No real account was deleted during this audit.

Reminders, snooze & recurrence

A due date is task intent; notification delivery is a separate platform operation.

Both clients schedule live, incomplete tasks with a future dueAt. Removing that date, completing or deleting a task makes it ineligible. Permission denial leaves the task saved. Mark Done and Snooze 10 min act on the whole task; snoozing sets dueAt to now plus ten minutes and increments timesSnoozed.

07 · Android reminder lifecycle

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

A unique owner/task WorkManager request has an initial delay and no network requirement. The worker checks active ownership and notification permission before posting its stored content. This is not an exact-alarm implementation.
Mermaid definition
flowchart TB
    accTitle: Android reminder lifecycle
    accDescr: Local mutations refresh unique delayed WorkManager jobs. The worker checks active ownership and notification permission. Notification actions perform task-level mutations.
    EDIT[Local create / edit / completion / snooze] --> EL{Live, incomplete, future dueAt?}
    EL -->|Yes| W[Replace owner + task unique WorkManager job]
    EL -->|No| X[Cancel scheduled work]
    W --> D[Initial delay from dueAt]
    D --> V{Active owner and notification permission?}
    V -->|Yes| N[Post task notification]
    V -->|No| STOP[Finish without posting]
    N --> DONE[Mark Done]
    N --> S[Snooze 10 min]
    DONE --> MUT[Repository completion / recurrence advancement]
    S --> SN[Set dueAt to now + 10 min; increment timesSnoozed]
    MUT --> EL
    SN --> EL

Android refreshes reminder work after local mutations and owner activation. Its inspected remote-merge path does not itself invoke ReminderScheduler. Notification behavior after remote edits, completion or deletion needs device verification; immediate cross-device cancellation is not established.

08 · iOS reminder lifecycle

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

The repository decorator reconciles loaded snapshots and individual mutations. UNUserNotificationCenter receives a non-repeating interval request. Account deactivation removes that owner's pending and delivered notifications.
Mermaid definition
flowchart TB
    accTitle: iOS reminder lifecycle
    accDescr: Repository mutations and observed snapshots reconcile local notification requests. Account context scopes scheduling and actions; requests do not repeat automatically.
    R[Repository mutation or loaded task snapshot] --> E{Active owner, eligible future task?}
    E -->|No| C[Remove stale pending request]
    E -->|Yes| P{Notification authorization?}
    P -->|Allowed| N[UNUserNotificationCenter: one non-repeating interval request]
    P -->|Not allowed| PROMPT[Contextual permission UI; task remains saved]
    N --> DONE[Mark Done action]
    N --> S[Snooze 10 min action]
    DONE --> OWNER{Response matches active owner and live task?}
    S --> OWNER
    OWNER -->|Yes| M[Repository mutation]
    M --> R
    OUT[Account deactivation] --> REMOVE[Remove that owner's pending and delivered notifications]

iOS request identifiers include owner and task identity. Actions use the active account repository; initial-session responses may wait for the matching context. Foreground activation refreshes permission state and reconciles requests. Scheduler try? calls suppress registration errors, so a successful task save does not prove notification registration succeeded.

Recurrence is not notification repetition

Completing a recurring task—or its final active subtask—advances from the due-date anchor using native calendar arithmetic until the next occurrence is in the future. The same task ID remains; completion and snooze count reset, and active subtasks reopen. Units are daily, weekly, monthly and yearly; UI intervals are limited to 1–99.

dueAt is an absolute Unix-millisecond instant. The task does not store a separate original time-zone identifier. Recurrence uses the current device calendar/time zone; travel, daylight-saving transitions and month-end dates need validation. The AI time-zone header helps interpret relative phrases at request time, not preserve a permanent task zone.

Source inspection establishes scheduling requests and cancellation paths, not delivery guarantees under all OS power, focus, permission or lifecycle conditions. Real-device timing, reboot/background behavior and cross-device reminder changes remain unverified.

Data & interoperability

Shared identities cross devices; local database keys and media paths do not.

09 · Logical task model

Wide diagram · scroll horizontally to explore

Diagram loads when it comes into view.

These are logical relationships, not literal Firestore collections. Cloud subtasks are embedded in the task document. Android persists separate child rows; iOS saves the task and its subtask representation through SwiftData.
Mermaid definition
erDiagram
    OWNER ||--o{ TASK : owns
    TASK ||--o{ SUBTASK : contains
    TASK ||--o{ MEDIA : references
    OWNER {
      string userId "Firebase UID or local offline identity"
    }
    TASK {
      string taskUid "Cross-platform identity"
      string type "WRITTEN PHOTO VOICE"
      string title
      int64 updatedAt "Device-clock Unix milliseconds"
      int64 dueAt "Optional instant"
      string recurrenceType "Optional calendar unit"
      int recurrenceInterval
      int timesSnoozed
      boolean completed
      boolean deleted
    }
    SUBTASK {
      string subtaskUid
      string title
      boolean completed
      int64 updatedAt
      boolean deleted
    }
    MEDIA {
      string localPath "Device only"
      string cloudURL "Optional synced reference"
    }
ConcernWire semanticsCompatibility consequence
Identityusers/{uid}/tasks/{taskUid}; embedded subtaskUidAndroid's numeric Room key stays local; reconciliation scopes ownership to the account path.
Task kindWRITTEN, PHOTO, VOICEAndroid falls back to WRITTEN for unknown incoming kinds; iOS rejects unknown kinds.
TimeInteger Unix milliseconds for createdAt, updatedAt, completedAt, lastInteractionAt, dueAtNot Firestore Timestamp objects. AI dates arrive as strings and are parsed on-device.
RecurrencerecurrenceType and recurrenceIntervalDue date anchors the recurrence; optional-interval acceptance differs in AI adapters.
DeletionTask/subtask deleted flagsTombstones persist; a missing legacy field is not an instruction to resurrect a known deletion.
Sync bookkeepingsynced / autoSync are legacy local flagsCurrent cloud writers omit them. Android has local dirty rows; iOS a separate outbox.
MediaOptional imageUrl / audioUrlDevice paths do not go into cloud task fields; uploaded media uses owner/task paths.
Legacy child identityMissing subtaskUidAndroid generates a UUID; iOS derives an ID from task UID and child index. Not a universal deduplication guarantee.

Writers supply the complete task field set and embedded subtask array in merge writes. iOS writes explicit nulls for absent optionals and preserves local media references during remote mapping. A media download can fail while task metadata remains available.

Backend & operational boundaries

Shared suggestion processing, identity verification and account cleanup.

ComponentImplementationResponsibility
AndroidCompose, coroutines/Flow, Room, WorkManager, Firebase SDKs, OkHttpNative local UI and scheduled device work.
iOSSwiftUI/Observation, Swift concurrency, SwiftData, AVFoundation, UserNotifications, Firebase SDKsNative local UI, account stores and queues.
BackendFastAPI, Pydantic, Firebase Admin, OpenAI SDKAuthenticated interpretation, account deletion and public health endpoint.
WebNext.js product site and portfolio handbookSupport/documentation, outside mobile persistence.

Source defaults are gpt-4o-mini for interpretation and gpt-4o-mini-transcribe for transcription, with configurable model names. Production model configuration is unverified. Prompts ask for structured fields; there is no model tool-call path that completes tasks, schedules reminders or changes saved records.

AI endpoints use an in-memory per-UID limit of 20 requests in 60 seconds, not a shared distributed quota. Missing service configuration returns 503; rate limiting 429; invalid media envelopes 422. Parsing exceptions return errors, while malformed model JSON may return a fallback payload rejected by the clients.

Audio processing uses a temporary file with attempted cleanup in finally. Image data, audio and derived transcript leave the device for processing. Application request logs record request IDs, stages, outcomes and duration rather than task/media bodies or bearer tokens. This does not establish hosting logs, provider retention policy or end-to-end encryption.

Where to read the source

Follow a behavior through callers, storage and observations.

QuestionAndroid landmarkiOS landmark
How is home created?MainActivity; HomeViewModelWhimAwayApp; AppEnvironment; RootView; HomeModel
When is a task saved?TaskRepositoryImpl; TaskLocalDataSource; TaskDaoWrittenTaskCreationModel; SwiftDataTaskRepository; SyncingTaskRepository
How does capture become a draft?HomeScreen; AiTaskCoordinator; AiTaskParserPhotoTaskFlowModel; PhotoTaskCreationModel; VoiceTaskCreationModel; RailwayTaskSuggestionClient
How does cloud data return?SyncCoordinator; TaskRemoteSyncDataSource; RemoteTaskMapperSyncEngine; SyncOutbox; FirestoreTaskMapper
What happens on logout?LaunchSessionResolver; SignOutCoordinator; HomeViewModelAppEnvironment; FirebaseAuthenticationService; TaskNotificationSystem
What does a reminder do?ReminderScheduler; ReminderWorker; ReminderActionReceiverTaskReminderScheduler; ReminderSchedulingTaskRepository; TaskNotificationActionHandler

The source snapshot contains WhimAway-Android, WhimAway-iOS, WhimAway-Backend and WhimAway-Web. Backend behavior is in main.py; checked-in rules and emulator tests are under WhimAway-Backend/firebase. Shared contracts and fixtures are under Docs. This reference follows reachable code where broader prose would imply unverified guarantees.

Testing & development

Attributable checks for the source snapshot, with the limits of each result.

CheckCommand / targetResult on 3 October 2026
Android./gradlew test assembleDebug --no-daemonPreviously executed in this audit session at the same SHA: 74 Debug + 74 Release test executions passed; debug APK assembled. No device/instrumentation run.
Backendpython -m pytest -qRerun: 26 passed. Firebase/OpenAI/account effects use test doubles; no paid AI or production data.
Firebase rulesnpm test in WhimAway-Backend/firebaseRerun: 8 tests / 2 suites; 8 passed, 0 failed/skipped. Firestore emulator 1.22.0; Storage rules runtime 1.1.3; CLI 15.28.2; Java 21.
iOSWhimAwayTests; WhimAwayUITests; simulator buildsSource and selected persistence, contract, sync and reminder tests inspected. Not executed on Linux; macOS/Xcode required.
Live integrationsSign-in, AI, media sync, account deletion, notificationsNot exercised. No claim about deployed rules, service availability, delivery or store linkage.

Android uses JDK 17, the Gradle wrapper and SDK platform 36. Backend tests use an isolated Python environment and requirements-dev.txt. Rules tests use demo-whimaway-rules and are distinct from deploying rules. Existing iOS CI specifies macOS 15 / Xcode 16.4, deterministic DerivedData, unsigned simulator builds, WhimAwayTests and separately enabled serial UI tests.

Inspected tests cover conflict selection, contract fixtures, token retry, AI consent, account lifecycle, persistence and reminder eligibility. Passing unit/emulator tests establish those assertions in that environment; they do not establish complete cross-device correctness or notification delivery.

Reliability & open verification

Separate the implemented mechanism from the evidence still needed.

  • Unsaved drafts are not durable tasks. Captured media on disk is not itself a saved task record.
  • Local storage is not a backup guarantee. Android permits destructive fallback for unsupported Room migrations; account stores and media have separate cleanup lifetimes.
  • Clock-based whole-task reconciliation can discard concurrent field/subtask edits. A lossless merge protocol is not established.
  • Cancellation is cooperative and scoped. Owner checks and generation checks occur at specific points; account-switch race coverage is not universal.
  • Task recurrence, snooze and notification delivery have different semantics. Per-subtask schedules and configurable repeated-alert counts were not found.
  • Production releases, deployed rules, hardware behavior and provider configuration need independent verification.

This audit changes documentation only. Implementation issues stay owner findings, not silent fixes or roadmap features presented as complete. Describing the source does not establish personal authorship, an exhaustive security review or production reliability.