27 new tests covering SubscriptionCacheWrapper: currentTier derivation, shouldShowUpgradePrompt with per-resource limits and boundary conditions, canShareResidence/canShareContractor gating, and deprecated prompt property. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
56 KiB
56 KiB
| 1 | Test_ID | Domain | Feature | Scenario | Test_Method | Priority | Platforms | Preconditions | Steps | Expected_Result | Edge_Cases | Assumptions | Automation_Recommendation | automated |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 | AUTH-001 | Authentication | App start routing | First launch routes to onboarding when hasCompletedOnboarding=false | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Fresh install, no token | Launch app | Onboarding welcome is shown, not login/main tabs | Corrupted local onboarding flag | Onboarding state is persisted locally | Automate (UI smoke) | 🟢 testF001_ColdLaunchShowsOnboardingWelcome |
| 3 | AUTH-002 | Authentication | App start routing | Returning user with onboarding complete and no token routes to login | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | hasCompletedOnboarding=true, token missing | Launch app | Login screen shown | Stale cached user object present | Token is source of truth | Automate | 🟢 testAppLaunchesAndShowsLoginScreen |
| 4 | AUTH-003 | Authentication | App start routing | Authenticated + verified user routes to main tabs | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Valid token, verified user | Launch app | Main tab shell displayed | Cached token exists but API fails | Current-user fetch determines validity | Automate | 🟢 testR203_validLoginTransitionsToMainAppRoot |
| 5 | AUTH-004 | Authentication | App start routing | Authenticated but unverified user routes to verify email | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Valid token, verified=false | Launch app | Verify email screen shown | User gets verified between launches | Verification status fetched from backend | Automate | 🟢 testR104_verificationGateBlocksMainAppBeforeCodeEntry | testR110_relaunchUnverifiedUserNeverLandsInMainApp |
| 6 | AUTH-005 | Authentication | Token invalidation | Invalid token at startup clears session and returns to login | Manual + Integration | P0 | iOS, Android, Web, Desktop | Expired/invalid token stored | Launch app | Data cleared and login shown | Backend 500 vs 401 behavior | 401/failed current-user means logout | Automate | 🟢 test08_invalidatedTokenRedirectsToLogin |
| 7 | AUTH-006 | Authentication | Login | Valid username/password login success | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Existing verified account | Submit login form | Token persisted, lookups initialized, main shown | Slow network during login | Login API returns token+user | Automate | 🟢 test02_loginWithValidCredentials | testR202_validCredentialsSubmitFromLogin |
| 8 | AUTH-007 | Authentication | Login | Invalid credentials shows actionable error | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | No session | Submit wrong password | Error shown, user remains on login | Rate-limit or lockout responses | Error parser maps backend errors | Automate | 🟢 test01_loginWithInvalidCredentials |
| 9 | AUTH-008 | Authentication | Login validation | Empty username/password blocked client-side | Manual + UI | P1 | iOS, Android, Web, Desktop | No session | Tap login with empty fields | Validation errors shown, no API call | Whitespace-only input | Client validation active | Automate | 🟢 testF205_LoginButtonDisabledWhenCredentialsAreEmpty | testF207_LoginScreenShowsAllExpectedElements | testF208_RegisterFormShowsAllRequiredFields |
| 10 | AUTH-009 | Authentication | Registration | Create account success then verify-email step | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Unique email/username | Submit valid registration | Session established and verify-email screen appears | Existing email conflict | Register API auto-authenticates | Automate | 🟢 testR103_successfulRegistrationTransitionsToVerificationGate |
| 11 | AUTH-010 | Authentication | Registration validation | Invalid email/password formats rejected | Manual + UI | P1 | iOS, Android, Web, Desktop | No session | Try invalid email, short password, weak password | User-friendly validation errors | Unicode emails, long usernames | Validation rules are enforced consistently | Automate | 🟢 test03_registrationWithEmptyFields | test04_registrationWithInvalidEmail | test06_registrationWithWeakPassword |
| 12 | AUTH-011 | Authentication | Email verification | Valid 6-digit code verifies account | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged-in unverified user | Enter 6-digit numeric code | Account marked verified and app routes forward | Code already used | Code must be exactly 6 digits | Automate | 🟢 testR105_validVerificationCodeTransitionsToMainApp | test07_successfulRegistrationAndVerification |
| 13 | AUTH-012 | Authentication | Email verification | Non-numeric/short/long code blocked | Manual + UI | P1 | iOS, Android, Web, Desktop | On verify-email screen | Enter invalid codes | Verify action disabled or error shown | Pasted with spaces | Code input sanitizes to digits | Automate | 🟢 testR107_invalidVerificationCodeShowsErrorAndStaysBlocked | testR109_verifyButtonDisabledForIncompleteCode | test10_verificationCodeFieldValidation | emptyCodeReturnsRequired | wrongLengthReturnsInvalidCode | nonNumericCodeReturnsInvalidCode | validSixDigitCodeReturnsNil | customLengthFourDigitCode | customLengthWrongDigitCount (ValidationRulesTests) |
| 14 | AUTH-013 | Authentication | Logout | Logout clears token, user data, lookups, and returns to login | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in | Tap logout | Login shown; protected API calls fail without token | Logout API fails network-side | Client clears state even if API call fails | Automate | 🟢 test06_logout | testR205_logoutFromMainAppReturnsToLoginRoot |
| 15 | AUTH-014 | Authentication | Forgot password | Request reset by email success path | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Known account email | Submit forgot-password email | Success message and next step available | Unknown email behavior privacy-safe | Backend may still return generic success | Automate | 🟢 test05_forgotPasswordNavigation | testF206_ForgotPasswordButtonIsAccessible | testF209_ForgotPasswordNavigatesToResetFlow |
| 16 | AUTH-015 | Authentication | Reset code verification | Verify reset code success path | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Reset email submitted | Enter correct code | Reset-token available for password reset | Expired code | Code verification endpoint returns token | Automate | 🟢 test03_verifyResetCodeSuccess |
| 17 | AUTH-016 | Authentication | Password reset | Reset password success with matching confirmation | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Valid reset token | Enter new password + confirm | Password changed; user can log in (or auto-login flow) | Token expires mid-flow | Reset endpoint ignores confirm field | Automate | 🟢 test04_resetPasswordSuccessAndLogin |
| 18 | AUTH-017 | Authentication | Password reset | Mismatched password confirmation blocked | Manual + UI | P1 | iOS, Android, Web, Desktop | Reset password screen | Enter mismatch | Inline error; no reset request | Trailing spaces | Client validates before submit | Automate | 🟢 mismatchedPasswordsFails | caseSensitiveMismatchFails (ValidationHelpersTests) | matchingPasswordsReturnsNil | mismatchedPasswordsReturnsMismatch | caseSensitiveMismatch (ValidationRulesTests) | requiredFieldErrorDescription | invalidEmailErrorDescription | passwordTooShortErrorDescription | passwordMismatchErrorDescription | passwordMissingLetterErrorDescription | passwordMissingNumberErrorDescription | invalidCodeErrorDescription | invalidUsernameErrorDescription | customMessageErrorDescription (ValidationRulesTests) |
| 19 | AUTH-018 | Authentication | Deep link reset | Password-reset deep link opens flow with token prefilled | Manual + E2E UI | P0 | iOS, Android | Installed app with deep-link support | Open reset deep link | Forgot/reset flow opens and token is consumed | Malformed token in URL | Deep link token can be cleared on back | Automate | 🟢 defaultInitStartsAtRequestCode | initWithTokenStartsAtResetPassword | moveToNextStepFromRequestCode | moveToNextStepFromVerifyCode | moveToNextStepFromResetPassword | moveToNextStepFromLoggingIn | moveToNextStepFromSuccessIsNoOp | moveToPreviousStepFromVerifyCode | moveToPreviousStepFromResetPassword | moveToPreviousStepFromRequestCodeIsNoOp | moveToPreviousStepFromLoggingInIsNoOp | moveToPreviousStepFromSuccessIsNoOp | resetClearsAllState | clearErrorNilsOutErrorMessage | clearSuccessNilsOutSuccessMessage | verifyResetCodeWithEmptyCodeSetsError | verifyResetCodeWithNonNumericCodeSetsError | resetPasswordWithEmptyPasswordSetsError | resetPasswordWithWeakPasswordSetsError | resetPasswordWithMismatchedPasswordsSetsError | resetPasswordWithNilTokenSetsError | allCasesHasFiveValues | allCasesContainsExpectedSteps (PasswordResetViewModelTests) |
| 20 | AUTH-019 | Authentication | SSO Apple | Apple Sign-In success creates or signs in account | Manual + Integration | P1 | iOS | Apple-capable test device/simulator | Complete Apple sign-in | Session established; verification state handled | User hides email, first-login only email scope | Apple credential mapped to backend request | Automate partially (mock) | |
| 21 | AUTH-020 | Authentication | SSO Google | Google Sign-In success path | Manual + Integration | P1 | Android, Web, KMP UI | Google sign-in configured | Complete Google sign-in | Session established with backend token | Revoked Google token | Backend validates ID token | Automate partially | |
| 22 | ONB-001 | Onboarding | Intent split | Start Fresh path goes through value props and name residence | Manual + E2E UI | P0 | iOS, Android (KMP) | Fresh install | Choose Start Fresh and continue | Step order matches intended flow | Back navigation at each step | Flow differs by intent | Automate | 🟢 testF101_StartFreshFlowReachesCreateAccount | testR002_startFreshFlowReachesCreateAccount |
| 23 | ONB-002 | Onboarding | Intent split | Join Existing path skips value/name steps and goes to account creation | Manual + E2E UI | P0 | iOS, Android (KMP) | Fresh install | Choose Join Existing | Expected condensed step order | User switches intent mid-flow | Intent persisted during onboarding | Automate | 🟢 testF102_JoinExistingFlowGoesToCreateAccount | testF105_JoinExistingFlowSkipsValuePropsAndNameResidence |
| 24 | ONB-003 | Onboarding | Navigation | Back button transitions and step history correctness | Manual + UI | P1 | iOS, Android (KMP) | In onboarding multi-step flow | Navigate forward then back | Returns to correct previous step | Back from verify-email triggers logout on iOS | Back behavior is product-defined | Automate | 🟢 testF103_BackNavigationFromNameResidenceReturnsToValueProps | testF108_BackFromCreateAccountNavigatesToPreviousStep |
| 25 | ONB-004 | Onboarding | Skip behavior | Skippable screens skip to next valid state | Manual + UI | P1 | iOS, Android (KMP) | On skippable step | Tap Skip | Progress advances without corrupting state | Skip on terminal upsell step completes onboarding | Skip availability is step-dependent | Automate | 🟢 testF104_SkipOnValuePropsMovesToNameResidence |
| 26 | ONB-005 | Onboarding | Residence bootstrap | Start Fresh creates residence automatically after verification | Manual + Integration | P0 | iOS, Android (KMP) | Onboarding start-fresh with residence name | Verify email and continue | Residence created or graceful fallback if creation fails | Blank residence name bypasses creation | Creation failure should not hard-block onboarding | Automate | 🟢 testF110_startFreshCreatesResidenceAfterVerification |
| 27 | ONB-006 | Onboarding | Join residence | Join-existing flow joins via share code | Manual + E2E UI | P0 | iOS, Android (KMP) | Valid join code | Submit code in onboarding | User added to shared residence | Expired/invalid/reused code | Join code endpoint enforces ownership rules | Automate | |
| 28 | ONB-007 | Onboarding | First task | First-task onboarding step can create selected tasks | Manual + Integration | P1 | iOS, Android (KMP) | On first-task step | Select templates and continue | Tasks created; flow proceeds | Partial task creation failures | At least one success considered success in KMP VM | Automate | |
| 29 | ONB-008 | Onboarding | Completion persistence | Completing onboarding persists flag and bypasses onboarding next launch | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Completed onboarding once | Restart app | Onboarding is skipped | App data clear resets flag | Onboarding flag stored locally | Automate | 🟢 testF111_completedOnboardingBypassedOnRelaunch |
| 30 | RES-001 | Residences | List | Residences list loads with empty and non-empty states | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in | Open residences tab | Correct list/empty UI shown | Slow API and stale cache | List can come from cache or network | Automate | 🟢 test01_viewResidencesList | testR303_residencesListLoadsAfterTabSelection |
| 31 | RES-002 | Residences | Create | Create residence with required fields only | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in | Fill minimum fields and save | Residence appears in list/detail | Duplicate name allowed/not allowed behavior | Name required; other fields optional | Automate | 🟢 test05_createResidenceWithMinimalData | testR306_createResidenceMinimalDataSubmitsSuccessfully |
| 32 | RES-003 | Residences | Create validation | Prevent submit when required name missing | Manual + UI | P1 | iOS, Android, Web, Desktop | Open add residence form | Submit blank name | Validation error, no API call | Whitespace-only name | Required validation trims input | Automate | 🟢 test01_cannotCreateResidenceWithEmptyName |
| 33 | RES-004 | Residences | Create optional fields | All optional numeric/text/address fields persist correctly | Manual + Integration | P1 | iOS, Android, Web, Desktop | Open add residence form | Fill all fields and save | Values round-trip correctly in detail/edit | Decimals for bathrooms/lot size | Type conversion preserves precision | Automate | 🟢 test12_updateAllResidenceFields |
| 34 | RES-005 | Residences | Edit | Edit residence updates list and detail views | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Existing residence | Modify fields and save | Updated values shown immediately | Concurrent edit from another user | DataManager updates local caches | Automate | 🟢 test11_editResidenceName | test12_updateAllResidenceFields |
| 35 | RES-006 | Residences | Delete | Delete residence removes related task/document cached subsets | Manual + Integration | P0 | iOS, Android, Web, Desktop | Residence with tasks/docs exists | Delete residence | Residence gone; related cached sections removed | Delete primary/only residence | Server cascade semantics documented | Automate | 🟢 test02_residenceCRUDFlow |
| 36 | RES-007 | Residences | Primary residence | Set/retain primary residence behavior | Manual | P2 | iOS, Android, Web, Desktop | Multiple residences | Mark one as primary and reload | Only intended residence is primary | Two rapid primary updates | Backend enforces uniqueness | Automate partially | 🟢 test18_setPrimaryResidence |
| 37 | RES-008 | Residences | Summary | Residence summary counts derive from kanban columns correctly | Manual + Integration | P1 | iOS, Android, Web, Desktop | Residence with mixed task statuses | Open summary cards | Counts match task column truth | No tasks state | Summary can be client-computed | Automate | 🟢 test09_fullFlowSummary | test06_verifyKanbanStructure |
| 38 | RES-009 | Residences | Join by code | Join residence from manual share code entry | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Valid share code from another account | Enter code in join flow | Membership granted and residence appears | Code expired or already member | Join endpoint returns reasoned errors | Automate | |
| 39 | RES-010 | Residences | Generate share code | Generate/refresh residence share code | Manual + Integration | P1 | iOS, Android, Web, Desktop | Owner access to residence | Generate code | New valid code returned/visible | Repeated generation invalidates old codes | Access limited by role | Automate | 🟢 test07_residenceSharingUIElements |
| 40 | RES-011 | Residences | Share package export | Create residence .casera package and open share sheet | Manual | P1 | Android | Residence exists, logged in | Tap share residence | Share sheet opens with file attachment | Filename sanitization with special chars | Backend generates package with shareCode | Automate partially | |
| 41 | RES-012 | Residences | Share package import | Import .casera residence file joins residence | Manual + Integration | P0 | Android | Valid .casera file received | Open file via app/import handler and confirm | Join succeeds and success dialog shown | Invalid JSON/wrong extension/no auth | Import requires auth and parse success | Automate partially | |
| 42 | RES-013 | Residences | Task report | Generate residence tasks report with/without email | Manual + Integration | P2 | iOS, Android, Web, Desktop | Residence exists | Trigger generate report | Report creation success feedback | Invalid email format optional field | Backend handles async report generation | Automate | |
| 43 | RES-014 | Residences | Manage users view | Residence users list loads with owner/member roles | Manual + E2E UI | P1 | iOS, Android (KMP) | Residence with multiple users | Open manage users | Accurate users and role indicators displayed | Owner missing from response | Role data trusted from API | Automate | |
| 44 | RES-015 | Residences | Remove user | Owner removes a member successfully | Manual + Integration | P0 | iOS, Android (KMP) | Owner logged in, target member exists | Remove member | Member removed and state refreshes | Attempt remove self/owner | API enforces permission rules | Automate | |
| 45 | TASK-001 | Tasks | All tasks load | Kanban columns load for all residences | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in with at least one residence | Open tasks tab | Columns and counts render correctly | Zero tasks across all columns | Columns include overdue/in-progress/due-soon/upcoming/completed/cancelled | Automate | 🟢 test03_viewTasksList |
| 46 | TASK-002 | Tasks | Residence filtering | Tasks by residence uses filtered allTasks cache correctly | Manual + Integration | P1 | iOS, Android, Web, Desktop | Multiple residences with tasks | Open residence detail tasks | Only selected residence tasks shown | Residence cache stale while global cache fresh | Client-side filtering path exists | Automate | 🟢 test07_residenceDetailsShowTasks |
| 47 | TASK-003 | Tasks | Create task | Create minimal valid task | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Residence exists, lookups loaded | Submit task with required fields | Task appears in expected column | Missing lookups due to failed seed load | Category/frequency/priority are required | Automate | 🟢 test03_createTaskWithMinimalData | test06_createBasicTask |
| 48 | TASK-004 | Tasks | Create from template | Create task from template browser | Manual + E2E UI | P1 | iOS, Android, Web, Desktop | Templates available | Open templates, select one, save | Task prefilled and created | Template with long description/tags | Template data from seeded lookups | Automate | 🟢 test16_createTaskFromTemplate |
| 49 | TASK-005 | Tasks | Template search | Search templates requires >=2 chars and limits results | Manual + Unit | P2 | iOS, Android, Web, Desktop | Templates loaded | Search with 1 char then 2+ chars | 1 char returns empty, 2+ filtered max 10 | Case-insensitive tag match | Search is local in DataManager | Automate | 🟢 searchWithSingleCharReturnsEmpty | searchWithTwoCharsMatchesTitles | searchIsCaseInsensitive | searchMatchesDescription | searchMatchesTags | searchReturnsMaxTenResults | emptyQueryReturnsEmpty (TemplateSearchTests) |
| 50 | TASK-006 | Tasks | Edit task | Edit task fields and persist updates | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Existing task | Change title/category/frequency/priority/due/cost | Task updates and remains consistent in caches | Invalid cost string conversion | Edit route passes serialized fields | Automate | 🟢 test09_editTaskTitle | test10_updateAllTaskFields |
| 51 | TASK-007 | Tasks | Mark in progress | Transition task to in-progress column | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Existing non-in-progress task | Tap mark in progress | Task moves to in-progress column | Already in progress idempotency | UpdateTask uses kanbanColumn target | Automate | 🟢 test03_taskStateTransitions |
| 52 | TASK-008 | Tasks | Clear in progress | Clear in-progress state returns task to scheduled column | Manual + Integration | P1 | iOS, Android, Web, Desktop | Task currently in progress | Clear in-progress | Task leaves in-progress with correct new column | Due date past while clearing | Backend decides target column | Automate | 🟢 test03_taskStateTransitions |
| 53 | TASK-009 | Tasks | Cancel task | Cancel action moves task to cancelled state | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Active task exists | Cancel task | Task appears in cancelled column/state | Cancel already-cancelled task | Endpoint idempotency defined | Automate | 🟢 test04_taskCancelOperation |
| 54 | TASK-010 | Tasks | Uncancel task | Restore cancelled task to active lifecycle | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Cancelled task exists | Uncancel task | Task restored to appropriate column | Uncancel archived task | State transitions validated by API | Automate | 🟢 test15_uncancelRestorescancelledTask |
| 55 | TASK-011 | Tasks | Archive/unarchive | Archive hides task from active flow; unarchive restores | Manual + Integration | P1 | iOS, Android, Web, Desktop | Task exists | Archive then unarchive | Visibility toggles correctly | Archived + cancelled combined states | Archive APIs available and cached updates propagate | Automate | 🟢 test05_taskArchiveOperation |
| 56 | TASK-012 | Tasks | Delete semantics | Task removal updates all cached columns and summaries | Manual + Integration | P1 | iOS, Android, Web, Desktop | Task exists in cached data | Delete or server-remove task then refresh | Task absent in all views and counts updated | Task present in multiple cached views | DataManager removeTask updates all maps | Automate | 🟢 allTasksIsNilAfterClear | removeTaskOnNilAllTasksIsNoOp | tasksByResidenceIsEmptyAfterClear (RemoveTaskTests) |
| 57 | TASK-013 | Tasks | Due date handling | Past/now/future due dates map to correct columns | Manual + Integration | P1 | iOS, Android, Web, Desktop | Tasks with boundary due dates | Load tasks around timezone boundaries | Kanban placement matches backend logic | DST transitions | Backend provides canonical kanban_column | Automate | 🟢 test04_kanbanColumnDistribution | mixedTaskCategories (TaskMetricsTests) |
| 58 | TASK-014 | Tasks | Cost fields | Estimated cost accepts numeric and rejects invalid formats | Manual + UI | P2 | iOS, Android, Web, Desktop | Task form open | Enter valid/invalid cost strings | Valid persisted; invalid blocked or sanitized | Locale decimal separators | Cost parsed to Double | Automate | 🟢 test04_createTaskWithAllFields |
| 59 | TASK-015 | Tasks | Task detail navigation | Push/deep-link task navigation opens tasks tab and relevant task context | Manual + E2E UI | P0 | iOS, Android | App receives task navigation ID | Tap notification/open intent | Tasks tab selected and task reachable | Task deleted before open | Main shell handles navigate_to_task contract | Automate | |
| 60 | TASK-016 | Tasks | Bulk state refresh | After any task CRUD/action, summary cards refresh immediately | Manual + Integration | P1 | iOS, Android, Web, Desktop | Dashboard visible with summaries | Perform task action | Summary counts update without hard reload | Rapid consecutive actions | Summary derived from cached kanban | Automate | 🟢 test09_fullFlowSummary |
| 61 | TASK-017 | Tasks | Concurrency | Two users edit same task; conflict resolution UX | Manual | P2 | iOS, Android, Web, Desktop | Same task open on two accounts | Save conflicting edits | Consistent final state and clear error/last-write behavior | Out-of-order responses | Server conflict strategy documented | Manual | |
| 62 | TCOMP-001 | Task completion | Complete task basic | Complete task with notes/cost/rating | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Active task exists | Open complete flow and submit | Completion created and task column updates | Null rating vs default value | Completion endpoint accepts optional fields | Automate | 🟢 test03_taskLifecycleFlow |
| 63 | TCOMP-002 | Task completion | Complete with images | Attach one or multiple images during completion | Manual + Integration | P0 | iOS, Android | Camera/gallery permission granted | Submit completion with images | Images uploaded and linked to completion | Large image compression/failure mid-upload | Image compression and multipart upload enabled | Automate partially | |
| 64 | TCOMP-003 | Task completion | Validation | Required completed-by field enforced (iOS form state) | Manual + UI | P1 | iOS | Open complete task form | Submit blank completed-by | Validation error shown | Whitespace-only value | CompletedBy required by client form | Automate | 🟢 completedByFieldRequired | completedByFieldWithValuePasses | completedByFieldWhitespaceOnlyFails (TaskCompletionValidationTests) |
| 65 | TCOMP-004 | Task completion | Completion history | History list loads for task and sorted correctly | Manual + Integration | P1 | iOS, Android, Web, Desktop | Task with multiple completions | Open completion history | Entries ordered and accurate | Missing images in older completion records | History endpoint returns full list | Automate | 🟢 test10_completionHistoryLoadsAndIsSorted |
| 66 | DOC-001 | Documents | List | Load all documents and residence-filtered documents | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in with docs data | Open documents tab and residence detail docs | Correct sets shown | Residence with zero docs | List supports optional residence filter | Automate | 🟢 test01_NavigateToDocumentsScreen | test20_HandleEmptyDocumentsList | test21_HandleEmptyWarrantiesList |
| 67 | DOC-002 | Documents | Create document | Create generic document with required fields | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Residence exists | Create document with title/type/residence | Document appears in lists | Missing residence selection on create | Create requires title/type/residence | Automate | 🟢 test04_CreateDocumentWithMinimalFields |
| 68 | DOC-003 | Documents | Create warranty fields | Create warranty/appliance with provider/dates metadata | Manual + Integration | P1 | iOS, Android, Web, Desktop | Document form open | Enter warranty-specific fields and save | Fields persist and render in detail | End date before start date | Date validation expectations defined | Automate | 🟢 test06_CreateWarrantyWithAllFields | test07_CreateWarrantyWithFutureDates | test08_CreateExpiredWarranty |
| 69 | DOC-004 | Documents | Edit document | Edit existing document including type/category/tags | Manual + E2E UI | P1 | iOS, Android, Web, Desktop | Document exists | Modify and save | Detail/list reflect updates | Switching types with stale fields | Backend accepts partial update | Automate | 🟢 test15_EditDocumentTitle | test16_EditWarrantyDates |
| 70 | DOC-005 | Documents | Delete document | Delete removes from global and residence caches | Manual + Integration | P0 | iOS, Android, Web, Desktop | Document exists in caches | Delete document | Document removed everywhere | Delete while detail screen open | DataManager removeDocument updates both caches | Automate | 🟢 test17_DeleteDocument | test18_DeleteWarranty |
| 71 | DOC-006 | Documents | Upload image | Upload document image from camera/gallery | Manual + Integration | P0 | iOS, Android | Permissions granted | Add image to document | Thumbnail and full image accessible | Upload timeout | Upload endpoint returns image metadata | Automate partially | |
| 72 | DOC-007 | Documents | Delete image | Delete specific document image updates detail immediately | Manual + Integration | P1 | iOS, Android, Web, Desktop | Document with multiple images | Delete one image | Remaining images preserved | Deleting last image | Image delete endpoint idempotency | Automate | 🟢 test22_documentImageSectionExists |
| 73 | DOC-008 | Documents | Download | Download/open document file via URL | Manual | P1 | iOS, Android, Web, Desktop | Document has downloadable URL | Tap download/open | File retrieved and usable | Expired signed URL | Download API wraps binary result | Automate partially | |
| 74 | DOC-009 | Documents | Validation | Claim email optional but must be valid if provided | Manual + UI | P2 | iOS | Document form open | Enter invalid claim email | Validation error shown | Internationalized domains | Email regex defines valid format | Automate | 🟢 validEmailPasses | invalidEmailMissingAtFails (ValidationHelpersTests + StringExtensionsTests) |
| 75 | DOC-010 | Documents | Media viewer | Image viewer navigation, zoom, swipe, close behavior | Manual | P2 | iOS, Android | Document with multiple images | Open viewer and interact | No crashes; correct index and gestures | Very large images | Viewer supports list index initialization | Manual | |
| 76 | CON-001 | Contractors | List | Load contractors with empty and populated states | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in | Open contractors tab | List/empty state correct | Large list pagination if any | API supports optional filters | Automate | 🟢 test16_refreshContractorsList |
| 77 | CON-002 | Contractors | Create | Create contractor minimal required fields | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Logged in | Create contractor with name | Contractor appears in list/detail | Special characters in names | Name is required | Automate | 🟢 test03_createContractorWithMinimalData |
| 78 | CON-003 | Contractors | Create optional data | Persist optional company/contact/address/specialties | Manual + Integration | P1 | iOS, Android, Web, Desktop | Contractor form open | Fill optional fields and save | All values persist accurately | Invalid website URL format | Optional fields may be null | Automate | 🟢 test04_createContractorWithAllFields | test14_updateAllContractorFields |
| 79 | CON-004 | Contractors | Email validation | Optional email must be valid if supplied | Manual + UI | P1 | iOS, Android, Web, Desktop | Contractor form open | Enter invalid email | Validation error blocks save | Uppercase email + spaces | Validation trims and regex-checks | Automate | 🟢 validEmailPasses | invalidEmailMissingAtFails | invalidEmailMissingDomainFails (ValidationHelpersTests + StringExtensionsTests) |
| 80 | CON-005 | Contractors | Edit | Edit contractor and verify list/detail sync | Manual + E2E UI | P1 | iOS, Android, Web, Desktop | Existing contractor | Update fields and save | Updated across views | Concurrent remote update | DataManager updateContractor handles summary mapping | Automate | 🟢 test13_editContractorName | test14_updateAllContractorFields |
| 81 | CON-006 | Contractors | Delete | Delete contractor removes from caches and residence associations | Manual + Integration | P0 | iOS, Android, Web, Desktop | Existing contractor | Delete contractor | Removed from all views | Deleting favorite contractor | Association cleanup server-side | Automate | 🟢 test08_contractorCRUD |
| 82 | CON-007 | Contractors | Favorite toggle | Toggle favorite updates UI and persistence | Manual + E2E UI | P1 | iOS, Android, Web, Desktop | Existing contractor | Toggle favorite twice | State toggles correctly and persists refresh | Rapid repeated taps | Endpoint supports idempotent toggles | Automate | 🟢 test20_toggleContractorFavorite |
| 83 | CON-008 | Contractors | By residence filter | Load contractors scoped to residence | Manual + Integration | P1 | iOS, Android, Web, Desktop | Multiple residences and linked contractors | Open residence contractor section | Only related contractors shown | Contractor with null residence | Filtering done server-side | Automate | 🟢 test21_contractorByResidenceFilter |
| 84 | CON-009 | Contractors | Share export | Share contractor as .casera file | Manual | P1 | Android | Contractor exists | Tap share contractor | Share sheet opens with valid file payload | Name containing '/' and long length | Filename sanitization applied | Manual | |
| 85 | CON-010 | Contractors | Import .casera | Import contractor from valid file | Manual + Integration | P0 | Android | Logged in, valid contractor file | Open file/import confirm | Contractor created and success dialog shown | Unknown specialty names in file | Specialties mapped by name to known IDs | Automate partially | |
| 86 | CON-011 | Contractors | Import invalid file | Invalid extension/JSON/auth state handled safely | Manual | P0 | Android | Invalid file or logged out | Attempt import | Clear error shown; no partial creation | Huge malformed JSON | Import parser errors are surfaced | Manual | |
| 87 | NOTIF-001 | Notifications | Permission prompt | Notification permission request outcomes handled | Manual | P0 | iOS, Android | Fresh install | Respond Allow and Deny in separate runs | App continues gracefully; state reflects permission | User changes setting later | Permission checked on foreground | Manual | |
| 88 | NOTIF-002 | Notifications | Device registration | Register device token after login only | Manual + Integration | P0 | iOS, Android | Have APNs/FCM token | Login with token available | registerDevice called once and succeeds | Token arrives before auth | Auth gating prevents unauthenticated registration | Automate | |
| 89 | NOTIF-003 | Notifications | Token refresh | New push token triggers backend re-registration | Manual + Integration | P1 | iOS, Android | Previously registered token | Simulate token rotation | Backend receives updated registration id | No token change should skip call | Last registered token cache used | Automate | |
| 90 | NOTIF-004 | Notifications | Foreground notification | Foreground notifications display banner/sound and update read state | Manual | P1 | iOS, Android | Permission granted, send test push | Receive push while app active | Banner shown; notification handled | Malformed payload missing type | Foreground presentation explicitly enabled | Manual | |
| 91 | NOTIF-005 | Notifications | Notification tap navigation | Tap task push opens app and routes to tasks context | Manual + E2E | P0 | iOS, Android | Task-linked push sent | Tap push from background/terminated | App opens on tasks flow for target task | Task no longer exists | Task id may be string or int | Automate partially | |
| 92 | NOTIF-006 | Notifications | Action buttons premium | Premium users see and can execute task action buttons | Manual + Integration | P0 | iOS, Android | Premium subscription active | Receive actionable task notification, tap action | Action API executes and UI refreshes | Action timeout/network loss | Actions gated by premium/limitationsEnabled | Manual | 🟢 encodeDecodeRoundTrip | decodedValuesMatch | sameValuesAreEqual | differentTaskIdNotEqual | differentTaskTitleNotEqual | taskIdReturnsCorrectValue | taskTitleReturnsCorrectValue (WidgetActionTests) |
| 93 | NOTIF-007 | Notifications | Action buttons free-tier gating | Free users with limitations enabled are routed home/not allowed actions | Manual | P0 | iOS, Android | Free user limitationsEnabled=true | Tap actionable notification | No privileged task action executed | Subscription cache stale nil | Nil subscription defaults allow on iOS currently | Manual | 🟢 encodeDecodeRoundTrip | sameValuesAreEqual | differentTaskIdNotEqual | differentTaskTitleNotEqual (WidgetActionTests) |
| 94 | NOTIF-008 | Notifications | Preferences | Load and update notification preference toggles | Manual + Integration | P1 | iOS, Android, Web, Desktop | Logged in | Open notification preferences, toggle settings, save | Preferences persist and affect server payloads | Partial update failures | Preferences API supports patch/update | Automate | |
| 95 | NOTIF-009 | Notifications | History/read state | Notification history list and mark-read operations | Manual + Integration | P2 | iOS, Android, Web, Desktop | Notifications exist | Open history, mark one and mark all | Unread counts decrement correctly | Race with incoming push | Unread count endpoint consistent | Automate | |
| 96 | SUB-001 | Subscription | Status load | Subscription status loads at app launch/foreground | Manual + Integration | P0 | iOS, Android, Web, Desktop | Logged in | Launch then background/foreground app | Status cache updates and UI gating accurate | Backend temporarily unavailable | Status refresh should be non-fatal | Automate | |
| 97 | SUB-002 | Subscription | Upgrade screen products | Load purchasable plans and pricing | Manual | P0 | iOS, Android | Store config present | Open upgrade screen | Monthly/annual products displayed | Store connectivity issues | Product IDs configured as expected | Manual + mocked automation | |
| 98 | SUB-003 | Subscription | Purchase monthly | Complete monthly purchase and backend verification | Manual + Integration | P0 | iOS, Android | Sandbox account, product available | Buy monthly plan | Entitlement granted, subscription status becomes pro | Pending transactions | Verification endpoint must return success | Manual | |
| 99 | SUB-004 | Subscription | Purchase annual | Complete annual purchase and backend verification | Manual + Integration | P0 | iOS, Android | Sandbox account | Buy annual plan | Entitlement granted and reflected in app | Upgrade/downgrade from existing plan | Latest transaction determines tier | Manual | |
| 100 | SUB-005 | Subscription | Restore purchases | Restore on clean install/device migration | Manual + Integration | P0 | iOS, Android | Existing prior subscription | Tap restore | Entitlements restored and backend synced | No previous purchases | Restore should not duplicate grants | Manual | |
| 101 | SUB-006 | Subscription | Purchase cancellation | User-cancelled purchase does not show fatal error | Manual | P1 | iOS, Android | Open paywall | Start then cancel purchase | No entitlement changes; UX remains stable | Repeated cancel attempts | User cancel considered non-error | Manual | |
| 102 | SUB-007 | Subscription | Backend verification failure | Store purchase succeeds but backend verify fails | Manual | P0 | iOS, Android | Force backend verify failure | Complete purchase | User sees recoverable state and can retry/restore | Receipt parsing mismatch | App should avoid false pro unlock | Manual | |
| 103 | SUB-008 | Subscription | Feature gating | Pro-only features hidden/disabled for limited users | Manual + E2E | P0 | iOS, Android, Web, Desktop | Test free and pro accounts | Traverse gated features (actions, limits, upgrades) | Gating consistent across surfaces | Cache stale after plan change | Gating uses subscription status + limitationsEnabled | Automate | 🟢 noSubscriptionTierIsFree | subscriptionWithExpiresAtTierIsPro | subscriptionWithEmptyExpiresAtTierIsFree | subscriptionWithNilExpiresAtTierIsFree | nilSubscriptionNeverBlocks | limitationsDisabledNeverBlocks | proTierNeverBlocks | freeTierUnderLimitAllowed | freeTierAtLimitBlocked | freeTierOverLimitBlocked | tasksLimitEnforced | contractorsLimitEnforced | documentsLimitEnforced | nilLimitMeansUnlimited | unknownLimitKeyReturnsFalse | noLimitsForTierReturnsFalse | deprecatedPromptFreeWithLimitations | deprecatedPromptFreeWithoutLimitations | deprecatedPromptNilSubscription | canShareResidenceWhenNoSubscription | canShareResidenceWhenLimitationsDisabled | cannotShareResidenceWhenFreeWithLimitations | canShareResidenceWhenProWithLimitations | canShareContractorWhenNoSubscription | canShareContractorWhenLimitationsDisabled | cannotShareContractorWhenFreeWithLimitations | canShareContractorWhenProWithLimitations (SubscriptionGatingTests) |
| 104 | WID-001 | Widgets | Small widget rendering | Small widget shows counts and opens app | Manual | P1 | Android | Widget added, logged in | Place small widget and tap | Correct counts; tap opens app | No data cached yet | Widget reads from shared preferences state | Manual | |
| 105 | WID-002 | Widgets | Medium widget list | Medium widget shows top tasks and overdue badge | Manual | P1 | Android | Tasks exist | Place medium widget | Task rows and overdue badge correct | Malformed tasks_json | JSON parse fallback to empty list | Manual | |
| 106 | WID-003 | Widgets | Large widget interactions | Large widget actions execute for pro users | Manual + Integration | P0 | Android | Pro account, widget configured | Tap row and action controls | Opens task or executes action as expected | Free user should not see/execute pro actions | Widget passes task_id in intent | Manual | 🟢 encodeDecodeRoundTrip | decodedValuesMatch | taskIdReturnsCorrectValue | taskTitleReturnsCorrectValue (WidgetActionTests) |
| 107 | WID-004 | Widgets | Widget refresh | Widgets refresh after task state changes | Manual | P1 | Android | Widget present | Complete/cancel task in app | Widget counts/list update within expected interval | Background restrictions | Widget update manager triggers refresh | Manual | |
| 108 | SHR-001 | Sharing/Import | File association | .casera files open app import flow | Manual | P1 | Android | Have .casera file | Open file from files app/share sheet | Import confirmation dialog shown | Multiple apps can open same MIME | Intent filter handles application/json with extension | Manual | |
| 109 | SHR-002 | Sharing/Import | Security | Import rejects when unauthenticated | Manual | P0 | Android | Logged out, .casera file ready | Attempt import | Error shown, no data mutation | Stale token in storage | Auth check occurs before API call | Manual | |
| 110 | SHR-003 | Sharing/Import | Corrupt payload | Corrupt .casera payload fails safely without crash | Manual | P0 | Android | Malformed JSON file | Attempt import | Graceful error dialog | Very large payload | Parser exceptions are caught | Manual | |
| 111 | DATA-001 | Data layer | Lookups init | Seeded lookup data loads once and marks initialized | Manual + Integration | P0 | iOS, Android, Web, Desktop | Fresh login | Observe first data bootstrap | Lookups available for forms/templates | Slow network during bootstrap | initializeLookups has concurrency guard | Automate | 🟢 test06_lookupDataAvailable | setTaskCategoriesPopulatesList | setTaskCategoriesBuildsMappedLookup | setTaskPrioritiesPopulatesListAndMap | setTaskFrequenciesPopulatesListAndMap | setResidenceTypesPopulatesListAndMap | setContractorSpecialtiesPopulatesListAndMap | markLookupsInitializedSetsFlag (DataLayerTests) |
| 112 | DATA-002 | Data layer | ETag refresh | Lookup refresh uses ETag and handles 304 not modified | Manual + Integration | P1 | iOS, Android, Web, Desktop | Lookups already loaded + ETag | Foreground app or force refresh | No unnecessary data churn on 304 | ETag lost between sessions | ETag persisted in storage | Automate | 🟢 staticDataEndpointReturnsETag | conditionalRequestReturns304WhenDataUnchanged | staleETagReturns200 (DataLayerETagTests) |
| 113 | DATA-003 | Data layer | Legacy fallback | If seeded-data endpoint fails, fallback static data path works | Manual + Integration | P1 | iOS, Android, Web, Desktop | Mock seeded endpoint failure | Initialize lookups | Core lookups still available | Templates missing in fallback | Fallback endpoint still reachable | Automate | 🟢 staticDataContainsAllRequiredLookupTypes | allLookupItemsHaveIdAndName | taskCategoriesHaveColorAndIcon | taskPrioritiesHaveLevelAndColor (DataLayerAPISchemaTests) |
| 114 | DATA-004 | Data layer | Cache timeout | One-hour cache timeout triggers fresh fetch after expiry | Manual + Integration | P1 | iOS, Android, Web, Desktop | Cached data older/newer than timeout | Request data with forceRefresh=false | Valid cache reused; stale cache refetched | Clock skew/device time changes | Timeout constant is 3600000 ms | Automate | 🟢 cacheTimeZeroIsInvalid | recentCacheTimeIsValid | expiredCacheTimeIsInvalid | cacheTimeoutConstantIsOneHour | allCacheTimestampsStartAtZeroAfterClear (DataLayerTests.CacheValidationTests) |
| 115 | DATA-005 | Data layer | Cache invalidation on logout | Logout clears user data/cache/ETag but retains theme | Manual + Integration | P0 | iOS, Android, Web, Desktop | Logged in with populated data | Logout then inspect next launch | No user data remains; theme preference retained | Persistence clear implementation drift | Theme stored separately from persistenceManager.clear | Automate | 🟢 testR206_postLogoutMainAppIsNoLongerAccessible | clearResetsAllCacheTimestamps | clearEmptiesLookupLists | clearResetsLookupsInitializedFlag | clearUserDataKeepsLookups | clearUserDataResetsCacheTimestamps (DataLayerTests.ClearTests) |
| 116 | DATA-006 | Data layer | Disk persistence | Current user and lookups reload correctly after app restart | Manual + Integration | P1 | iOS, Android, Web, Desktop | Logged in and data loaded | Kill and relaunch app | State restored without full refetch where valid | Partial/corrupt persisted JSON | Deserializer ignoreUnknownKeys is enabled | Automate | 🟢 test08_diskPersistencePreservesLookupsAfterRestart |
| 117 | DATA-007 | Data layer | Map/list consistency | Lookup map IDs and list values remain consistent after updates | Unit + Integration | P2 | iOS, Android, Web, Desktop | Lookup update operation | Compare list and map representations | No missing or mismatched IDs | Duplicate IDs from backend | Maps are built via associateBy(id) | Automate | 🟢 setTaskCategoriesBuildsMappedLookup | getterReturnsNilForMissingId | getterReturnsNilForNilId | allIdsAreUniquePerLookupType (DataLayerTests.LookupSetterTests + DataLayerAPISchemaTests) |
| 118 | OFF-001 | Resilience | Offline launch | Offline launch with cached token/data behaves gracefully | Manual | P0 | iOS, Android, Web, Desktop | Previously logged in with cached data | Launch with network off | No crash; clear messaging; cached UI where possible | Token validation cannot reach server | Current behavior may clear session on fetch failure | Manual | |
| 119 | OFF-002 | Resilience | Offline action handling | Create/update actions fail with retriable errors when offline | Manual | P0 | iOS, Android, Web, Desktop | Network disabled | Attempt create/edit/delete flows | Errors shown; no phantom local success | Intermittent network flaps | No offline queue currently assumed | Manual | |
| 120 | OFF-003 | Resilience | Retry behavior | Retry from error dialogs succeeds without stale UI state | Manual + E2E | P1 | iOS, Android, Web, Desktop | Force transient API failure | Tap retry then restore network | Action succeeds and loading/error states reset | Double-tap retry race | ApiResult state machine handles Idle/Loading/Error | Automate | 🟢 testP010_retryButtonExistsOnErrorState |
| 121 | OFF-004 | Resilience | Idempotency UX | Double submit protection for create flows | Manual + UI | P1 | iOS, Android, Web, Desktop | Open any create form | Rapid tap save | Single record created | Very slow API response | Buttons disabled during loading | Automate | 🟢 test19_doubleSubmitProtection |
| 122 | SEC-001 | Security | Auth boundaries | Protected endpoints reject without token and UI handles 401 | Manual + Integration | P0 | iOS, Android, Web, Desktop | No token | Attempt protected operations | Redirect/login prompt or clear error | Token injected but expired | APILayer checks token before API in many calls | Automate | 🟢 test05_crossUserAccessControl |
| 123 | SEC-002 | Security | Session cleanup | Sensitive data not accessible after logout and app restart | Manual | P0 | iOS, Android, Web, Desktop | Logged in then logout | Force close and relaunch | No protected screens/data accessible | Widget still showing stale data | Widget caches should be cleared on logout (iOS path) | Manual | 🟢 testR206_postLogoutMainAppIsNoLongerAccessible |
| 124 | SEC-003 | Security | Import validation | Imported files cannot execute code or break parser boundaries | Manual | P1 | Android | Craft malicious JSON payload | Import payload | App rejects safely, no crash | Oversized strings, deep nesting | Deserializer should throw and be caught | Manual | |
| 125 | SEC-004 | Security | PII exposure | Logs and analytics avoid leaking sensitive credential values | Manual + Code audit | P1 | iOS, Android, Web, Desktop | Enable debug logging | Run auth/payment flows | No passwords/tokens in logs/events | Third-party SDK auto-capture risk | PostHog config reviewed | Manual | |
| 126 | PERF-001 | Performance | Cold start | Startup time within target for logged-out and logged-in states | Manual + Perf | P1 | iOS, Android | Profile build with instrumentation | Measure cold launch | Meets agreed startup SLA | Slow network path for verified user | Auth check performs network call | Automate perf | |
| 127 | PERF-002 | Performance | Large data rendering | Task/document/contractor lists remain responsive with large datasets | Manual + Perf | P1 | iOS, Android, Web, Desktop | Seed large dataset | Scroll and interact lists | No jank/crash; acceptable memory | Thousands of items and images | Virtualized list behavior depends on platform | Manual + benchmarks | 🟢 test17_residenceListPerformance | test14_taskListPerformance | test19_contractorListPerformance |
| 128 | PERF-003 | Performance | Image handling | Large image capture/upload/compression memory stability | Manual + Perf | P0 | iOS, Android | Use high-resolution photos | Attach multiple images | No OOM/crash; upload completes or fails gracefully | Low-memory device | ImageCompressor platform implementations are active | Manual | |
| 129 | PERF-004 | Performance | Background operations | Foreground/resume refresh does not block UI thread | Manual + Perf | P2 | iOS, Android | App with valid session | Background then foreground repeatedly | UI remains interactive during refresh | Concurrent refresh and navigation | Lookups refresh runs async | Manual | 🟢 test16_residencePersistsAfterBackgroundingApp | test13_taskPersistsAfterBackgroundingApp | test18_contractorPersistsAfterBackgroundingApp |
| 130 | A11Y-001 | Accessibility | Basic semantics | All primary controls have accessible labels/identifiers | Manual | P0 | iOS, Android, Web, Desktop | Screen reader on | Traverse major flows | Controls are announced clearly | Custom components lacking labels | Accessibility identifiers partly defined | Manual + lint | 🟢 testA001_OnboardingPrimaryControlsAreReachable | testA003_CoreControlsExposeIdentifiers | testA004_ValuePropsScreenControlsAreReachable | testA005_NameResidenceScreenControlsAreReachable | testA006_CreateAccountScreenControlsAreReachable |
| 131 | A11Y-002 | Accessibility | Dynamic type | Text scales correctly without clipping in key screens | Manual | P1 | iOS, Android | Large accessibility font settings | Open forms/lists/dialogs | Layout remains usable | Long localized strings | Design system supports flexible layout | Manual | |
| 132 | A11Y-003 | Accessibility | Keyboard navigation | Tab/focus order valid on web/desktop forms | Manual | P1 | Web, Desktop | Hardware keyboard | Navigate forms using keyboard only | Logical focus traversal and visible focus states | Modal dialogs focus trap | Compose/web focus handling may differ | Manual | |
| 133 | A11Y-004 | Accessibility | Color contrast | Theme variants meet minimum contrast requirements | Manual + Tooling | P1 | iOS, Android, Web, Desktop | Cycle themes | Inspect text/icon contrast | WCAG contrast thresholds met | Error/success states on tinted backgrounds | Multiple custom themes supported | Manual + automated audit | |
| 134 | I18N-001 | Localization | String coverage | No missing keys/placeholders across supported locales | Manual + Static check | P1 | iOS, Android, Web, Desktop | Run app in each locale | Traverse major screens | No raw keys, no placeholder mismatches | Pluralization and gender strings | Locales include es/fr/de/it/ja/ko/nl/pt/zh etc. | Automate (lint + screenshot) | |
| 135 | I18N-002 | Localization | Layout expansion | Long translations do not break onboarding/forms/buttons | Manual | P1 | iOS, Android, Web, Desktop | Switch to longest-string locale | Review high-density screens | No clipping/overlap | RTL future locale support | Current locales may be LTR only | Manual | |
| 136 | THEME-001 | Theming | Theme persistence | Theme choice persists across app restarts | Manual + Integration | P1 | iOS, Android, Web, Desktop | Logged in or logged out | Change theme then relaunch | Selected theme reapplied | Theme ID missing/corrupt in storage | Theme stored separately from auth data | Automate | 🟢 defaultThemeIdIsDefault | setThemeIdUpdatesValue | clearDoesNotResetTheme | themeIdIsPreservedAsOceanAfterClear (ThemePersistenceTests) | test09_themePersistsAcrossRestart |
| 137 | THEME-002 | Theming | Theme switch live update | Changing theme updates active screen without broken states | Manual + UI | P2 | iOS, Android, Web, Desktop | Open app on any tab | Change theme in profile/settings | Immediate UI recolor with legible components | Transition while modal open | Theme manager publishes updates | Manual | 🟢 allCasesCountIsEleven | allDisplayNamesAreNonEmpty | allDescriptionsAreNonEmpty | rawValueRoundTripsForAllCases | allRawValuesAreUnique | brightDisplayNameIsDefault | oceanRawValueIsOcean (ThemeIDTests) |
| 138 | NAV-001 | Navigation | Bottom tabs | Residences/Tasks/Contractors/Documents tabs navigate and preserve expected state | Manual + E2E UI | P0 | iOS, Android | Logged in | Switch tabs repeatedly | Correct screens shown; no stuck navigation | Push navigation then tab switch back | Nested nav stacks differ per platform | Automate | 🟢 test03_navigationBetweenTabs | test10_navigateBetweenTabs | testR309_navigationAcrossPrimaryTabsAndBackToResidences |
| 139 | NAV-002 | Navigation | Back stack safety | Back from details/forms returns to correct parent | Manual + E2E UI | P0 | iOS, Android, Web, Desktop | Navigate into detail/edit screens | Use back gestures/buttons | Parent state intact and refreshed as expected | Direct deep-link entry without parent | Saved-state refresh flags used in KMP nav | Automate | 🟢 test02_cancelRegistration | test04_cancelResidenceCreation | test02_cancelTaskCreation | test19_CancelDocumentCreation |
| 140 | NAV-003 | Navigation | Duplicate routes | Avoid duplicate screen instances from repeated tap/navigation actions | Manual | P2 | iOS, Android, Web, Desktop | Rapidly tap nav actions | Observe back stack and UI | No duplicate stacking or loops | Double tap race conditions | NavHost popUpTo rules applied | Manual | 🟢 testP003_RapidDoubleTapOnValuePropsContinueLandsOnNameResidence | testP004_StartFreshThenBackToWelcomeThenJoinExistingDoesNotCorruptState | testP005_RepeatedLoginNavigationRemainsStable |
| 141 | ANL-001 | Analytics | Auth events | Login/register/logout/verify events fire once with correct properties | Manual + Integration | P2 | iOS, Android | Analytics enabled in test env | Perform auth flows | Expected events emitted once | Retries causing duplicate events | Event taxonomy defined in analytics layer | Automate with mocked sink | |
| 142 | ANL-002 | Analytics | Core feature events | Residence/task/document/contractor create-edit-delete events tracked | Manual + Integration | P2 | iOS, Android | Analytics test workspace | Perform CRUD actions | Events mapped to correct feature | Failed actions should not emit success | PostHog wrappers used consistently | Automate | |
| 143 | ANL-003 | Analytics | Subscription events | Upgrade prompt open, purchase, restore, verification outcomes tracked | Manual + Integration | P2 | iOS, Android | Store sandbox + analytics | Run subscription flows | Lifecycle events present and correctly attributed | Purchase canceled path | No sensitive receipt data in events | Manual | |
| 144 | QA-001 | Cross-platform parity | Feature parity | Core flows behave equivalently between KMP UI and iOS native UI | Manual regression | P0 | iOS, Android, Web, Desktop | Same seeded account | Run same scenarios on each platform | Equivalent outcomes and data integrity | Known UX differences documented | iOS native app is source for iOS UX | Manual | |
| 145 | QA-002 | Cross-platform parity | API contract consistency | All clients handle new/unknown JSON fields gracefully | Integration | P1 | iOS, Android, Web, Desktop | Backend adds extra fields | Run major endpoints | No crash; fields ignored where unknown | Type changes breaking parsing | Kotlin serializers set ignoreUnknownKeys | Automate | 🟢 widgetTaskDecodesWithExtraFields | widgetTaskIgnoresUnknownNestedObjects (JSONUnknownFieldsResilienceTests) |
| 146 | QA-003 | Release quality | Smoke suite | Minimal release gate across auth, residence, task, document, contractor, notification, subscription | Manual + Automated | P0 | iOS, Android | Release candidate build | Run smoke checklist | No blockers before release | Environment instability | Smoke should run against stable test backend | Automate + manual signoff | 🟢 test01_authenticationFlow | test02_residenceCRUDFlow | test03_taskLifecycleFlow | test08_contractorCRUD | test01_NavigateToDocumentsScreen |
| 147 | QA-004 | Release quality | Data migration | Upgrade app version preserves critical local state safely | Manual | P1 | iOS, Android, Web, Desktop | Install old build with data then upgrade | Launch and use app | No corruption; expected resets only | Schema/key changes in persistence | Persistence keys remain compatible | Manual |