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.
| Source | Audited reference | What it establishes |
|---|---|---|
| Android, iOS, backend and shared contracts | 3 October 2026 · 55f8edc9a70bc1c96acf01bcab1391384693f4ce | One clean source snapshot matching the inspected main branch. |
| Android build settings | Version 1.0.6 · build 7 | Configured in source; correspondence to the installed/store release is unverified. |
| iOS build settings | Version 1.0 · build 5 | Configured in source; correspondence to the installed/store release is unverified. |
| Evidence method | Production entry points, callers, persistence, adapters and existing tests | Sanitized 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.
| Workflow | Android | iOS | Shared boundary / evidence |
|---|---|---|---|
| Written task | HomeViewModel → Room; title required | WrittenTaskCreationModel → SwiftData; title required | Manual entry. No text-to-AI request found in inspected creation paths. Code inspected. |
| Photo and voice | Camera activity / AAC recording; editable AI draft | AVFoundation capture / recording; editable AI draft | Authenticated FastAPI contract; device permission and AI consent govern separate steps. Code inspected. |
| Suggested subtasks | Expanding an empty suggestions editor copies suggestions into the draft; remove unwanted entries | Suggestions populate untouched draft fields; edit or remove entries | Confirmation persists the draft. This is not a per-subtask reminder selector. Code inspected. |
| Local account data | One Room database, owner-filtered records | Separate SwiftData account stores and separate offline store | Firebase UID identifies cloud ownership. Code inspected. |
| Pending cloud changes | Unsynced Room rows; one-shot and periodic WorkManager | Persisted JSON outbox; in-process SyncEngine | Firestore task documents and Storage media. Code inspected. |
| Sign-in | Email/password, Google, Continue Offline | Email/password, Google, Apple, Continue Offline | Firebase Auth; no live sign-in exercised. |
| Reminders | Delayed WorkManager notification | Non-repeating local notification request | Task dueAt, Mark Done and fixed 10-minute snooze. Code inspected, delivery not device-tested. |
| Recurrence | Native calendar arithmetic; completion advances same task | Native calendar arithmetic; completion advances same task | Daily, weekly, monthly, yearly; due-date anchor. Code and tests inspected. |
| Progress and focus | Derived locally; streak calculated from task history | Derived locally; production profile currently supplies streakDays = 0 | Similar UI does not establish identical streak behavior. Code inspected. |
System boundaries
The screen reads local state. Sync and suggestions cross different network boundaries.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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| SRoom 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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 transactionEdit, 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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 separateEdit, 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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
| Request | Payload | Backend behavior |
|---|---|---|
| POST /parse/image | JSON image_base64 containing JPEG bytes | Verify Firebase token, rate-limit identity, check JPEG envelope, send image and instructions to OpenAI. |
| POST /parse/audio | Multipart file containing M4A audio | Verify token, rate-limit identity, check container envelope, transcribe audio, interpret transcript. |
| Both responses | title, dueAt, recurrenceType, recurrenceInterval, details, subtasks | Normalize 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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 --> IWAndroid 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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 --> MODEBoth 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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 --> ELAndroid 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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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.
Wide diagram · scroll horizontally to explore
Diagram loads when it comes into view.
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"
}| Concern | Wire semantics | Compatibility consequence |
|---|---|---|
| Identity | users/{uid}/tasks/{taskUid}; embedded subtaskUid | Android's numeric Room key stays local; reconciliation scopes ownership to the account path. |
| Task kind | WRITTEN, PHOTO, VOICE | Android falls back to WRITTEN for unknown incoming kinds; iOS rejects unknown kinds. |
| Time | Integer Unix milliseconds for createdAt, updatedAt, completedAt, lastInteractionAt, dueAt | Not Firestore Timestamp objects. AI dates arrive as strings and are parsed on-device. |
| Recurrence | recurrenceType and recurrenceInterval | Due date anchors the recurrence; optional-interval acceptance differs in AI adapters. |
| Deletion | Task/subtask deleted flags | Tombstones persist; a missing legacy field is not an instruction to resurrect a known deletion. |
| Sync bookkeeping | synced / autoSync are legacy local flags | Current cloud writers omit them. Android has local dirty rows; iOS a separate outbox. |
| Media | Optional imageUrl / audioUrl | Device paths do not go into cloud task fields; uploaded media uses owner/task paths. |
| Legacy child identity | Missing subtaskUid | Android 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.
| Component | Implementation | Responsibility |
|---|---|---|
| Android | Compose, coroutines/Flow, Room, WorkManager, Firebase SDKs, OkHttp | Native local UI and scheduled device work. |
| iOS | SwiftUI/Observation, Swift concurrency, SwiftData, AVFoundation, UserNotifications, Firebase SDKs | Native local UI, account stores and queues. |
| Backend | FastAPI, Pydantic, Firebase Admin, OpenAI SDK | Authenticated interpretation, account deletion and public health endpoint. |
| Web | Next.js product site and portfolio handbook | Support/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.
| Question | Android landmark | iOS landmark |
|---|---|---|
| How is home created? | MainActivity; HomeViewModel | WhimAwayApp; AppEnvironment; RootView; HomeModel |
| When is a task saved? | TaskRepositoryImpl; TaskLocalDataSource; TaskDao | WrittenTaskCreationModel; SwiftDataTaskRepository; SyncingTaskRepository |
| How does capture become a draft? | HomeScreen; AiTaskCoordinator; AiTaskParser | PhotoTaskFlowModel; PhotoTaskCreationModel; VoiceTaskCreationModel; RailwayTaskSuggestionClient |
| How does cloud data return? | SyncCoordinator; TaskRemoteSyncDataSource; RemoteTaskMapper | SyncEngine; SyncOutbox; FirestoreTaskMapper |
| What happens on logout? | LaunchSessionResolver; SignOutCoordinator; HomeViewModel | AppEnvironment; FirebaseAuthenticationService; TaskNotificationSystem |
| What does a reminder do? | ReminderScheduler; ReminderWorker; ReminderActionReceiver | TaskReminderScheduler; 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.
| Check | Command / target | Result on 3 October 2026 |
|---|---|---|
| Android | ./gradlew test assembleDebug --no-daemon | Previously executed in this audit session at the same SHA: 74 Debug + 74 Release test executions passed; debug APK assembled. No device/instrumentation run. |
| Backend | python -m pytest -q | Rerun: 26 passed. Firebase/OpenAI/account effects use test doubles; no paid AI or production data. |
| Firebase rules | npm test in WhimAway-Backend/firebase | Rerun: 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. |
| iOS | WhimAwayTests; WhimAwayUITests; simulator builds | Source and selected persistence, contract, sync and reminder tests inspected. Not executed on Linux; macOS/Xcode required. |
| Live integrations | Sign-in, AI, media sync, account deletion, notifications | Not 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.