feat: bundle ID migration + gitea#2 task-cache fix (recovered from fix/task-cache-unification) #4

Merged
admin merged 13 commits from feat/bundle-id-and-task-cache into master 2026-05-01 20:48:29 -05:00
Owner

Summary

Recovers 13 commits that were stranded on fix/task-cache-unification after the rc/android-ios-parity PR landed on master. Cherry-picked onto current master with conflicts resolved to preserve both the parity-gallery refactor (master's reactive-projection pattern, IDataManager interface, dataManager injection) AND the task-cache unification work (single source of truth from _allTasks filtered by residence).

What's included

Bucket B — Critical infra

  • ef8eab4 iOS bundle ID + team ID migration com.tt.honeyDue.*com.myhoneydue.honeyDue.*, team V3PF3M6B6UX86BR9WTLD. Required for current TestFlight signing + APNs (matches the api's APNS_TOPIC=com.myhoneydue.honeyDue and apns_team_id=X86BR9WTLD). This is what was missing when the bundle ID showed up wrong.

Bucket C — gitea#2 fix: "tasks created during onboarding don't appear until app restart"

12 commits, in dependency order:

  • 65803a2 plan doc
  • 87771ef accessibility identifiers (test scaffolding)
  • 733d4c8 failing test
  • 5f7498b fix: DataManager.updateTask seeds _allTasks when cache is empty ← the actual root cause fix
  • 3df5645 test lock-down
  • 4f9b910 fix: bulkCreateTasks force-refreshes _allTasks
  • 915a5d4 test: characterize getTasksForResidence filter contract
  • dea8eed refactor: getTasksByResidence is a thin filter over _allTasks
  • 882801c iOS: TaskViewModel observes $allTasks and filters by residence in-memory
  • 1884853 Android: ResidenceViewModel.residenceTasksState derives from _allTasks
  • 03a9dfa fix: 2 latent iOS bugs that blocked Suite11 XCUITest

Build infra

  • b90533c build: bump Gradle daemon to 6G + Kotlin daemon to 4G with MaxMetaspaceSize=1g and G1GC. Fixes the OOM during ComposeApp.framework generation.

What's NOT included

cec521b refactor: delete _tasksByResidence and per-residence task cache plumbing was skipped. Removing the property cascades to:

  • IDataManager.kt interface (added by parity-gallery, declares tasksByResidence)
  • DataManagerObservable.swift mirror
  • Persistence schemas
  • Test fixtures

Doing the cascade safely is a separate, larger refactor against the parity-gallery interface. The practical effect of the skip: _tasksByResidence stays as dead code on the DataManager — nothing reads from it anymore (verified by grep), ResidenceViewModel.kt was modified inline to derive residenceTasksState from DataManager.allTasks filtered (matching cec521b's intent without removing the field).

Conflicts resolved

File Conflict Resolution
iosApp/iosApp/Task/TaskViewModel.swift parity-gallery's init(dataManager:) DI vs Bucket C's single-$allTasks subscription Kept DI (init(dataManager:)); merged to single subscription that filters by residence in-memory; dropped the second $tasksByResidence subscription
composeApp/.../viewmodel/ResidenceViewModel.kt parity-gallery's _selectedResidenceId reactive projection from tasksByResidence vs Bucket C's _currentResidenceId from _allTasks Kept HEAD's _selectedResidenceId and the _deleteResidenceState field; modified the existing residenceTasksState = combine(_selectedResidenceId, dataManager.tasksByResidence) to combine(_selectedResidenceId, DataManager.allTasks) filtered via getTasksForResidence(id)
iosApp/HoneyDueUITests/Suite11_TaskCacheRegressionTests.swift File added in skipped cec521b, modified in 03a9dfa Took 418ffc7's full version (XCUITest is black-box, no dependency on the deleted DataManager properties)

Verification

  • Android Kotlin: compileDebugKotlinAndroid — BUILD SUCCESSFUL
  • iOS Kotlin framework: compileKotlinIosArm64 — BUILD SUCCESSFUL
  • iOS Swift app: xcodebuild -scheme HoneyDue — BUILD SUCCEEDED

Suite11 XCUITest hasn't been re-run on this branch yet — recommend running it before merge to confirm gitea#2 still doesn't reproduce. The two regression scenarios:

  1. Sign up → onboarding → first residence → first task → land on residence detail. Task should appear immediately, no app restart needed.
  2. Existing user → bulk-create tasks via onboarding → residence detail shows them.

Branch provenance

gitea/master ──◀── 49e2397 (rc/android-ios-parity merge)
                       │
                       └── 2064e70 (PR #3: presigned uploads merge)
                                │
              feat/bundle-id-and-task-cache (this PR)
              13 commits cherry-picked from fix/task-cache-unification
              + 1 commit (gradle heap bump)

Once merged, fix/task-cache-unification can be deleted — its remaining commits (cec521b, plus the two upload commits already on master via PR #3) are either obsolete or already shipped.

## Summary Recovers 13 commits that were stranded on `fix/task-cache-unification` after the `rc/android-ios-parity` PR landed on master. Cherry-picked onto current master with conflicts resolved to preserve both the parity-gallery refactor (master's reactive-projection pattern, `IDataManager` interface, `dataManager` injection) AND the task-cache unification work (single source of truth from `_allTasks` filtered by residence). ## What's included ### Bucket B — Critical infra - **`ef8eab4` iOS bundle ID + team ID migration** `com.tt.honeyDue.*` → `com.myhoneydue.honeyDue.*`, team `V3PF3M6B6U` → `X86BR9WTLD`. Required for current TestFlight signing + APNs (matches the api's `APNS_TOPIC=com.myhoneydue.honeyDue` and `apns_team_id=X86BR9WTLD`). **This is what was missing when the bundle ID showed up wrong.** ### Bucket C — gitea#2 fix: "tasks created during onboarding don't appear until app restart" 12 commits, in dependency order: - `65803a2` plan doc - `87771ef` accessibility identifiers (test scaffolding) - `733d4c8` failing test - `5f7498b` **fix: `DataManager.updateTask` seeds `_allTasks` when cache is empty** ← the actual root cause fix - `3df5645` test lock-down - `4f9b910` fix: `bulkCreateTasks` force-refreshes `_allTasks` - `915a5d4` test: characterize `getTasksForResidence` filter contract - `dea8eed` refactor: `getTasksByResidence` is a thin filter over `_allTasks` - `882801c` iOS: `TaskViewModel` observes `$allTasks` and filters by residence in-memory - `1884853` Android: `ResidenceViewModel.residenceTasksState` derives from `_allTasks` - `03a9dfa` fix: 2 latent iOS bugs that blocked Suite11 XCUITest ### Build infra - `b90533c` build: bump Gradle daemon to 6G + Kotlin daemon to 4G with `MaxMetaspaceSize=1g` and G1GC. Fixes the OOM during `ComposeApp.framework` generation. ## What's NOT included `cec521b refactor: delete _tasksByResidence and per-residence task cache plumbing` was **skipped**. Removing the property cascades to: - `IDataManager.kt` interface (added by parity-gallery, declares `tasksByResidence`) - `DataManagerObservable.swift` mirror - Persistence schemas - Test fixtures Doing the cascade safely is a separate, larger refactor against the parity-gallery interface. The practical effect of the skip: `_tasksByResidence` stays as dead code on the DataManager — nothing reads from it anymore (verified by grep), `ResidenceViewModel.kt` was modified inline to derive `residenceTasksState` from `DataManager.allTasks` filtered (matching `cec521b`'s intent without removing the field). ## Conflicts resolved | File | Conflict | Resolution | |---|---|---| | `iosApp/iosApp/Task/TaskViewModel.swift` | parity-gallery's `init(dataManager:)` DI vs Bucket C's single-`$allTasks` subscription | Kept DI (`init(dataManager:)`); merged to single subscription that filters by residence in-memory; dropped the second `$tasksByResidence` subscription | | `composeApp/.../viewmodel/ResidenceViewModel.kt` | parity-gallery's `_selectedResidenceId` reactive projection from `tasksByResidence` vs Bucket C's `_currentResidenceId` from `_allTasks` | Kept HEAD's `_selectedResidenceId` and the `_deleteResidenceState` field; modified the existing `residenceTasksState = combine(_selectedResidenceId, dataManager.tasksByResidence)` to `combine(_selectedResidenceId, DataManager.allTasks)` filtered via `getTasksForResidence(id)` | | `iosApp/HoneyDueUITests/Suite11_TaskCacheRegressionTests.swift` | File added in skipped `cec521b`, modified in `03a9dfa` | Took 418ffc7's full version (XCUITest is black-box, no dependency on the deleted DataManager properties) | ## Verification - Android Kotlin: `compileDebugKotlinAndroid` — BUILD SUCCESSFUL - iOS Kotlin framework: `compileKotlinIosArm64` — BUILD SUCCESSFUL - iOS Swift app: `xcodebuild -scheme HoneyDue` — BUILD SUCCEEDED Suite11 XCUITest hasn't been re-run on this branch yet — recommend running it before merge to confirm gitea#2 still doesn't reproduce. The two regression scenarios: 1. Sign up → onboarding → first residence → first task → land on residence detail. Task should appear immediately, no app restart needed. 2. Existing user → bulk-create tasks via onboarding → residence detail shows them. ## Branch provenance ``` gitea/master ──◀── 49e2397 (rc/android-ios-parity merge) │ └── 2064e70 (PR #3: presigned uploads merge) │ feat/bundle-id-and-task-cache (this PR) 13 commits cherry-picked from fix/task-cache-unification + 1 commit (gradle heap bump) ``` Once merged, `fix/task-cache-unification` can be deleted — its remaining commits (`cec521b`, plus the two upload commits already on master via PR #3) are either obsolete or already shipped.
admin added 13 commits 2026-05-01 20:48:00 -05:00
Carries the rebrand from the backend (APPLE_CLIENT_ID, APNS_TOPIC) all
the way through the iOS targets:

- All target PRODUCT_BUNDLE_IDENTIFIERs: com.tt.honeyDue.* → com.myhoneydue.honeyDue.*
- DEVELOPMENT_TEAM: V3PF3M6B6U → X86BR9WTLD (across every target)
- APP_GROUP_IDENTIFIER: group.com.tt.honeyDue.* → group.com.myhoneydue.honeyDue.*
- BGTaskSchedulerPermittedIdentifiers + BackgroundTaskManager constant
- KeychainHelper service identifier
- StoreKit fallback product IDs + Info.plist IAP product ID keys
- ExportOptions.plist teamID
- NSCamera / NSPhotoLibrary usage descriptions reworded
- Onboarding suggestion strings reworked (new %lld%% match copy,
  dropped old "Great match" / "Good match" / "Generating suggestions"
  strings — replaced by relevance-percentage labels)
- xctestplan + settings.local.json housekeeping

App-group rename means UserDefaults / shared-container data written to
the old group ID is abandoned. Acceptable since this is pre-launch.
Fix the bug where tasks created during onboarding don't appear on
the Residence Detail screen until app restart. Root cause:
DataManager.updateTask is a no-op when both _allTasks is null AND
_tasksByResidence[residenceId] is empty — the case after a fresh
register-then-bulk-create flow.

Approach: collapse the dual cache into a single source of truth
(_allTasks). Residence detail observes it directly and filters by
residenceId in-memory. After mutations, force-refresh _allTasks from
the server (one round-trip eliminates a class of bugs).

Plan covers 14 tasks across 4 phases plus a regression XCUITest
that captures the user-visible bug end-to-end.
Scaffolding for the gitea#2 regression XCUITest. No user-visible
change — pure metadata for UI automation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures gitea#2 at the cache layer. Three tests:
- updateTask_seedsAllTasks_whenCacheIsEmpty (the core bug)
- updateTask_distributesAcrossColumns_whenSeedingThenAdding
- updateTask_replacesExistingTaskById_acrossColumns

All three FAIL on this commit because updateTask is a conditional
?.let{} that no-ops when _allTasks is null. Phase 1 fix in the next
commit makes them green.
Closes the silent no-op when _allTasks is null on first launch (the
onboarding bulkCreateTasks path). The function now upserts: builds an
empty kanban shell with the standard column names if needed and places
the task in its target column. Unknown column names append a new
column at the end so the task is always reachable.

Also drops the second branch that conditionally wrote to
_tasksByResidence — that cache is being deleted in Phase 3 and
updateTask should not maintain it any more.

The Phase 1 unit tests now pass; the Phase 2 force-refresh in the
next commit replaces the placeholder column metadata (display names,
colors, icons) with authoritative server values.
Catches re-introduction of the conditional _tasksByResidence write
branch removed in the previous commit. The per-residence cache is
deprecated; updateTask must only mutate _allTasks.
Server is the authoritative kanban categorizer. After a bulk insert,
re-fetch /api/tasks/ so the kanban view reflects exactly what the
server sees, including any column re-categorizations the client's
in-memory upsert wouldn't compute. One extra round-trip per onboarding
submission, called once per session typically.

Eliminates the entire bug class where DataManager.updateTask had to
correctly compute kanban column placement from the response's
kanbanColumn field. With force-refresh, the server is the source of
truth — fewer ways for the client cache to drift.

Refs gitea#2
Locks down the contract that becomes the primary path for residence
detail in Phase 3:
- filters _allTasks by residenceId
- returns empty shell for residence with no tasks (vs null for cache miss)
- returns null when _allTasks itself is null (caller must hit API)
Was 3 fallback paths (per-residence cache → filter from allTasks →
network). Now: ensure _allTasks fresh, return filter. The per-residence
cache becomes write-only by this path, scheduled for deletion in the
next commit.

Eliminates a class of bugs where the per-residence cache slot could
be missing while _allTasks was stale — the old Path 1+2 would either
return stale data or skip and hit the API redundantly.
Replaces the dual-sink ($allTasks when residence-scoped is nil,
$tasksByResidence when set) with a single $allTasks observation
that filters in-memory when currentResidenceId is set.

Eliminates the gitea#2 race window where the per-residence cache slot
could be empty while $allTasks was populated, leaving residence
detail stuck on the empty state. After this commit, every emit of
_allTasks rerenders every observing view — kanban tab, residence
detail, dashboards — atomically.

Refs gitea#2
Same screen contract, but the data flows from DataManager.allTasks
through a combine(_allTasks, _currentResidenceId) into the existing
StateFlow. No per-residence network call needed; the upstream
getTasks() refresh propagates and the screen re-renders.

Eliminates the gitea#2 race window on Android — same fix as the iOS
TaskViewModel commit. Both platforms now react to _allTasks changes
without manual refresh.
The XCUITest for gitea#2 (Suite11) was failing for reasons unrelated
to the cache fix — actual bugs in the registration/onboarding code
that real users probably hit too:

1. OrganicOnboardingSecureField + iOS 26 SecureField/autofill bug
   On iOS 26, tapping a SwiftUI SecureField with .textContentType(.password)
   doesn't reliably bring up the keyboard — the strong-password autofill
   panel steals focus. Fix: under --ui-testing, default the visibility
   toggle to ON so the field renders as a plain TextField (which has
   reliable focus). Real users are unaffected.

2. Email registration didn't propagate auth state
   Apple/Google sign-in paths called AuthenticationManager.shared.login(),
   but email-registration's onChange(viewModel.isRegistered) handler did
   not. As a result, AuthenticationManager.isAuthenticated stayed false
   through the entire onboarding flow. OnboardingState.completeOnboarding
   has an auth guard that silently no-ops when isAuthenticated is false,
   leaving users stuck on the firstTask screen forever (until a
   scenePhase event triggered checkAuthenticationStatus to re-sync from
   DataManager). Fix: call authManager.login(verified: false) when
   isRegistered flips true.

Suite11 now passes 2/2 in 96-107s, exercising the full onboarding flow
and asserting tasks appear on residence detail without restart.

Refs gitea#2
build: bump Gradle + Kotlin daemon heap for KMP
Android UI Tests / ui-tests (pull_request) Has been cancelled
b90533c535
OOMs were happening at the previous limits (Gradle 4G / Kotlin 3G)
during ComposeApp.framework generation. Bumped to 6G / 4G with a
1G Metaspace cap and G1GC for steadier latency on incremental builds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
admin merged commit ec5d93efab into master 2026-05-01 20:48:29 -05:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/honeyDueKMP#4