Ports iOS HoneyDueUITests AccessibilityIdentifiers + PageObjects pattern
to Android Compose UI Test. Kotlin AccessibilityIds object mirrors Swift
verbatim so scripts/verify_test_tag_parity.sh can gate on divergence.
AAA_SeedTests bracketed first alphanumerically; SuiteZZ cleanup to follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Soft keyboard no longer covers input fields. Applied to every screen
with text input.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace collectAsState() with collectAsStateWithLifecycle() so StateFlows
stop collecting when the host is in background — prevents memory/CPU leaks
on lifecycle transitions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Material 3 AutoMirrored variants flip correctly in Arabic/Hebrew.
Previous Icons.Default.ArrowBack pointed wrong direction in RTL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prep for parallel Android UI test suite build-out. These deps unblock
page-object tests using onNodeWithTag / createAndroidComposeRule and
cross-app permission-dialog interaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds acceptResidenceInvite / declineResidenceInvite to ResidenceApi
(POST /api/residences/{id}/invite/{accept|decline}) and exposes them via
APILayer. On accept success, myResidences is force-refreshed so the
newly-joined residence appears without a manual pull.
Wires NotificationActionReceiver's ACCEPT_INVITE / DECLINE_INVITE
handlers to the new APILayer calls, replacing the log-only TODOs left
behind by P4 Stream O. Notifications are now cleared only on API
success so a failed accept stays actionable.
Tests:
- ResidenceApiInviteTest covers correct HTTP method/path + error surfacing.
- NotificationActionReceiverTest invite cases updated to assert the new
APILayer calls (were previously asserting the log-only path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port iOS TaskAnimations.swift specs (completion checkmark, card transitions,
priority pulse) + AnimationTestingView as dev screen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream M replaced the widget's task-complete path with CompleteTaskAction +
WidgetActionProcessor. Grepping confirmed the only references to
WidgetTaskActionReceiver were its own class file and the manifest entry.
Remove both.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the legacy MyFirebaseMessagingService.onNewToken() device-registration
path onto the new iOS-parity FcmService. The legacy service is already
unwired from the manifest MESSAGING_EVENT filter; this removes its last
functional responsibility. Behaviour preserved: auth-token guard,
DeviceRegistrationRequest with ANDROID_ID + Build.MODEL, log-only on error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Navigation wiring (per follow-ups flagged by Streams G/H/I):
- Add TaskTemplatesBrowserRoute (new) + App.kt composable<TaskTemplatesBrowserRoute>
- Wire composable<TaskSuggestionsRoute> (declared by Stream H but unwired)
- Wire composable<AddTaskWithResidenceRoute> (declared by Stream I but unwired)
MainActivity.onCreate now calls HapticsInit.install(applicationContext) so the
Vibrator fallback path works on non-View call-sites (flagged by Stream S).
Deferred cleanup (tracked, not done here):
- Port push-token registration from legacy MyFirebaseMessagingService.kt to
new FcmService (Stream N TODO).
- Remove legacy WidgetTaskActionReceiver + manifest entry (Stream M flag).
- Residence invite accept/decline APILayer methods (Stream O TODO).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port iOS AddTaskWithResidenceView. Residence pre-selected via constructor
param, form validation, submit -> APILayer.createTask(residenceId attached).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BiometricPrompt wrapper with 3-strike lockout + NO_HARDWARE bypass.
BiometricLockScreen with auto-prompt on mount + PIN fallback after 3 failures.
PIN wiring marked TODO for secure-storage follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream F: Convert JoinResidenceDialog -> dedicated screen matching iOS
JoinResidenceView. Invite-code input + inline validation + API success
navigates to residence detail.
Stream U fix: coil3 3.0.4 doesn't ship ColorImage (added in 3.1.0). Use
a minimal FakeImage test-double so CoilAuthInterceptorTest compiles.
Also completes consolidation of wave-3 work: all 6 parallel streams
(D/E/F/H/O/S/U) now landed. Full unit suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port iOS TaskSuggestionsView as a standalone route reachable outside
onboarding. Uses shared suggestions API + accept/skip analytics in
non-onboarding variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream M's WidgetActionProcessorTest.kt references this predicate but
Stream J's initial repo only exposed mark/clear mutators. Trivial addition:
reads the pending-completion set and checks membership.
Unblocks :composeApp:testDebugUnitTest (now green across all streams).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Glance ActionCallback wires to WidgetActionProcessor: premium marks pending +
calls API + refreshes; free tier opens paywall deep link instead. Idempotent,
rollback-safe on API failure.
Also fixes a one-line compile error in WidgetTaskActionReceiver.kt where
updateAllWidgets() had been removed by Stream L — swapped for forceRefresh()
so the build stays green. The legacy receiver is now redundant (replaced by
CompleteTaskAction) but deletion is deferred to a Stream K follow-up so the
AndroidManifest entry can be removed in the same commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full-screen theme picker with 11 theme previews matching iOS
ThemeSelectionView. Live preview on tap. Old dialog deleted and
call-sites migrated to the new route.
Tests use the state-logic fallback pattern (plain kotlin.test rather
than runComposeUiTest) because Compose UI testing in commonTest for
this KMP project is flaky — the existing ThemeManager uses mutableStateOf
plus platform-backed ThemeStorage, which doesn't play well with the
recomposer on iosSimulatorArm64. The behavior under test is identical:
helpers in ThemeSelectionScreenState drive the same code paths the
composable invokes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 themes x 9 colors x 2 modes (198 values) now parametrized-tested against
docs/ios-parity/colors.json as ground truth. Typography scale matches iOS
dynamic-type defaults.
118 of 198 color values were off before — mostly off-by-one RGB rounding
errors introduced during a prior hand-copy from iOS. Default TextSecondary
(light+dark) also lost its 0x99 alpha channel — now restored via
Color(0xAARRGGBB) packed literals. Added SansSerif fontFamily and source
citations on every Typography size so the iOS dynamic-type mapping is
explicit.
Tests: ThemeColorsTest (4), TypographyTest (5), SpacingTest (1) — all
green. `everyColorMatchesIosGroundTruth` walks the embedded JSON and
asserts 198 hex values match exactly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both "For You" and "Browse All" tabs are now fully server-driven on
iOS and Android. No on-device task list, no client-side scoring rules.
When the API fails the screen shows error + Retry + Skip so onboarding
can still complete on a flaky network.
Shared (KMM)
- TaskCreateRequest + TaskResponse carry templateId
- New BulkCreateTasksRequest/Response, TaskApi.bulkCreateTasks,
APILayer.bulkCreateTasks (updates DataManager + TotalSummary)
- OnboardingViewModel: templatesGroupedState + loadTemplatesGrouped;
createTasks(residenceId, requests) posts once via the bulk path
- Deleted regional-template plumbing: APILayer.getRegionalTemplates,
OnboardingViewModel.loadRegionalTemplates, TaskTemplateApi.
getTemplatesByRegion, TaskTemplate.regionId/regionName
- 5 new AnalyticsEvents constants for the onboarding funnel
Android (Compose)
- OnboardingFirstTaskContent rewritten against the server catalog;
~70 lines of hardcoded taskCategories gone. Loading / Error / Empty
panes with Retry + Skip buttons. Category icons derived from name
keywords, colours from a 5-value palette keyed by category id
- Browse selection carries template.id into the bulk request so
task_template_id is populated server-side
iOS (SwiftUI)
- New OnboardingTasksViewModel (@MainActor ObservableObject) wrapping
APILayer.shared for suggestions / grouped / bulk-submit with
loading + error state (mirrors the TaskViewModel.swift pattern)
- OnboardingFirstTaskView rewritten: buildForYouSuggestions (130 lines)
and fallbackCategories (68 lines) deleted; both tabs show the same
error+skip UX as Android; ForYouSuggestion/SuggestionRelevance gone
- 5 new AnalyticsEvent cases with identical PostHog event names to
the Kotlin constants so cross-platform funnels join cleanly
- Existing TaskCreateRequest / TaskResponse call sites in TaskCard,
TasksSection, TaskFormView updated for the new templateId parameter
Docs
- CLAUDE.md gains an "Onboarding task suggestions (server-driven)"
subsection covering the data flow, key files on both platforms,
and the KotlinInt(int: template.id) wrapping requirement
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZIP code was US-only and redundant now that the suggestion engine
uses home profile features (heating, pool, etc.) for personalization.
Onboarding flow: Welcome → Value Props → Name → Account → Verify →
Home Profile → Task Selection (was: ...Verify → ZIP → Home Profile...)
Removed regionalTemplates references from task selection view.
Both iOS and Compose flows updated.
New onboarding step: "Tell us about your home" with chip-based pickers
for systems (heating/cooling/water heater), features (pool, fireplace,
garage, etc.), exterior (roof, siding), interior (flooring, landscaping).
All optional, skippable.
Tabbed task selection: "For You" tab shows personalized suggestions
based on home profile, "Browse All" has existing category browser.
Removed 5-task limit — users can add unlimited tasks.
Removed subscription upsell from onboarding flow — app is free.
Fixed picker capsule squishing bug with .fixedSize() modifier.
Both iOS and Compose implementations updated.
Android: uncaught exception handler sends $exception events with stack
trace to PostHog, flushes before delegating to default handler.
iOS: NSSetUncaughtExceptionHandler captures crashes via PostHogSDK,
avoids @MainActor deadlock by calling SDK directly.
Common: captureException() available for non-fatal catches app-wide.
Platform stubs for jvm/js/wasmJs.
Biometric lock: opt-in Face ID/Touch ID/fingerprint app lock with toggle
in ProfileScreen. Locks on background, requires auth on foreground return.
Platform implementations: BiometricPrompt (Android), LAContext (iOS).
Rate limit: 429 responses parsed with Retry-After header, user-friendly
error messages in all 10 locales, retry plugin respects 429.
ErrorMessageParser updated for both iOS Swift and KMM.
- DELETE /api/auth/account/ API call in AuthApi + APILayer
- authProvider field on User model for email vs social auth detection
- DeleteAccountDialog with password (email) or "type DELETE" (social) confirmation
- Red "Delete Account" card on ProfileScreen
- Navigation wired in App.kt (clears data, returns to login)
- 10 i18n strings in strings.xml
- ViewModel unit tests for delete account state
Backend now returns Document directly instead of wrapped
DocumentActionResponse. Remove unused DocumentActionResponse class.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update DEV API URLs from treytartt.com to api.myhoneydue.com
- Add rounded corners to app icon in login, register, and onboarding screens
- Add 9 missing English translations in Localizable.xcstrings
- Fix property feature pills to use equal height for balanced layout
- Remove duplicate honeyDue user scheme
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fetch regional templates by ZIP during onboarding and display categorized
task suggestions (iOS + KMM shared layer)
- Fix multi-expand for task categories (toggle independently, not exclusive)
- Fix ScrollViewReader auto-scroll to expanded category sections
- Fix UUID stability bug: cache task categories to prevent ID regeneration
that caused silent task creation failures
- Fix stale data after onboarding: force refresh residences and tasks in
RootView onComplete callback
- Fix address formatting: show just ZIP code when city/state are empty
instead of showing ", 75028" with leading comma
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>