Skip to content

DAY-109: Land manual QA remediation and native flow hardening#316

Merged
FleetAdmiralJakob merged 82 commits into
mainfrom
codex/day-109-manual-qa-pass
Jul 25, 2026
Merged

DAY-109: Land manual QA remediation and native flow hardening#316
FleetAdmiralJakob merged 82 commits into
mainfrom
codex/day-109-manual-qa-pass

Conversation

@FleetAdmiralJakob

@FleetAdmiralJakob FleetAdmiralJakob commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Outcome

Lands the current DAY-109 remediation as one atomic change before the remaining open product PRs are rebuilt on the resulting main. DAY-109 is not complete until its outstanding manual-device and final-evidence acceptance criteria are recorded.

Important

Merge-order freeze: this PR must merge before PR #271 and the other currently open product PRs. Do not merge those PRs into main first and do not fold them into this branch. After this PR lands, rebuild or rebase each remaining PR on the new main.

Checkpoints

  • DAY-109 source branch: codex/day-109-manual-qa-pass
  • Final reviewed HEAD: d13aa2c11cc504be4b1f7dc34fb44330f9fa5d48
  • Base at PR creation: 1d0204ddbaec02606df8d5f4a2079a350cda37a2
  • Current merge base: 5250c6c24e44eff1e0389bc2ec08ddb53c03216a (merged into the branch at 4dc7bcfcfbab01c51262350b47c0a1bf6064e79a)
  • Remote recovery tag: checkpoint/day-109-pre-pr-2026-07-21
  • PR DAY-190: Make portrait layouts content-size resilient #271 recovery tag: checkpoint/pr-271-pre-day-109-2026-07-21

The recovery tags preserve both pre-integration histories. The intended final merge method is squash merge, after all merge gates below pass.

Why

The real-device QA pass exposed connected failures across authentication, routing, password security, keyboard handling, accessibility, native controls, notification settings, onboarding, and Expo 57 runtime integration. Fixing those failures required shared UI and platform foundations, so this PR intentionally proposes the current DAY-109 remediation state as the next application baseline, subject to the unresolved merge gates and manual acceptance evidence below.

What changed

Authentication, session persistence, and routing

  • Fits sign-in and recovery content above software keyboards on compact devices.
  • Implements password recovery, forced-reset handling, password change, and reverification.
  • Aligns visibility controls and secure-input behavior.
  • Removes the misleading “Angemeldet bleiben” choice and uses secure native persistence by default.
  • Hardens auth bootstrap, logout, and competing router transitions.
  • Adds accessible state and error announcements.

Shared UI, accessibility, and native controls

  • Unifies Dayova bottom-sheet structure and interaction behavior.
  • Replaces inconsistent onboarding native dropdowns with Dayova select sheets.
  • Adds screen-reader semantics, focus containment/restoration, dismiss actions, and accessible validation errors.
  • Keeps Jetpack Compose switches while preserving them through Android back transitions.
  • Pins Android Material colors to the Dayova design system.
  • Consolidates native/runtime theme tokens.

Notification settings

  • Separates system authorization from in-app notification preferences.
  • Hardens permission gating and logout-state behavior.
  • Prevents Compose switches disappearing during Android back transitions.
  • Preserves accessible labels, values, hints, and error behavior.

Onboarding and manual-QA polish

  • Updates onboarding artwork, pager behavior, progress visuals, copy, icons, fact screens, and compact-layout behavior.
  • Standardizes the affected icons on Hugeicons.
  • Fixes input alignment, picker presentation, button placement, and other DAY-109 visual findings.

Expo 57 and engineering standards

  • Bounds Metro binary-cache reads to avoid unbounded concurrent I/O.
  • Adds the local iOS system-appearance compatibility module and lifecycle recovery.
  • Aligns package patches and Expo 57 dependency behavior.
  • Adds regression tests, ESLint safeguards, agent guidance, ADRs, and context documentation.

User impact

Learners receive recoverable and understandable authentication, consistent secure session behavior, usable forms while the keyboard is open, more reliable notification settings, cohesive native controls, and materially improved screen-reader support.

Tracked work

PR #271 remains a dependent follow-up. Its current branch will be rebuilt on the new main after this PR merges.

Accepted bundled engineering work

This branch also contains the standards/tooling work requested during the DAY-109 remediation (Matt/Expo skill composition, validation scripts, lint architecture rules, and supporting dependency changes). That work is broader than DAY-109's learner-flow wording but is intentionally included in this all-at-once PR; reviewers should evaluate it as an explicit bundled scope rather than infer it from the learner QA ticket.

Explicitly excluded

  • DAY-248: the new native runtime/version boundary.
  • Unsafe OTA runtime continuity: native changes in this PR must not be sent to existing 1.0.3 binaries.
  • PR DAY-190: Make portrait layouts content-size resilient #271 large-text/content-size work.
  • Unrelated learning-plan, dashboard, subscription, tablet, and release-readiness features.

Merging this PR is not authorization to publish an OTA or native release. The production workflow must remain fail-closed until the separate release gates are satisfied.

Review map

Because this is a large atomic PR, review it by seam:

  • Auth, session persistence, password flows, and routing
  • Shared UI, bottom sheets, accessibility, and validation errors
  • Notification settings and Android native controls
  • Onboarding, icons, copy, and visual QA
  • Expo 57, Metro, native appearance module, patches, and configuration
  • Automated tests, lint rules, ADRs, and context documentation

CodeRabbit's single-review limit is 150 files while this PR contains more reviewable files, so local CodeRabbit review is being run through WSL in scoped partitions. Findings will be consolidated below.

Validation completed at d13aa2c11cc

  • pnpm check
  • Vitest: 68 files / 330 tests
  • UI Jest: 20 suites / 48 tests
  • Metro concurrency tests
  • Skill validation tests
  • pnpm check:unused
  • pnpm format:check
  • Skill validation: 21 installed Matt Pocock skills / 22 installed Expo skills
  • Expo dependency compatibility check
  • Expo Doctor: 20/20
  • Merged current origin/main and re-ran all repository gates on the exact final head
  • Independent spec and engineering-standards review completed; all hard findings resolved
  • High-confidence secret scan: no matches
  • Fresh native Android Debug and optimized Release (arm64-v8a) builds, plus a final all-ABI optimized Release build at application/native SHA 79a22ec6dc6f, completed successfully
  • Final Release APK (SHA256 6584778BA0B617F6A9B4A4B384D7E11C960270BB896A30020184D3B40C678ADC) installed on the physical Pixel 9; clean cold start, stopped-state reinstall, and Android's automatic foreground package-replacement relaunch all remained active without Clerk/Fatal logs
  • Investigated the one synthetic Clerk crash: it occurred only when a manual force-stop/Monkey launch raced Android's already scheduled SystemUI relaunch after install -r; the normal package-update lifecycle did not reproduce it, and the source contains one Clerk provider callsite

Final merge and broader closure gates

  • Re-run the full repository gates against the final PR HEAD
  • Complete and triage all scoped CodeRabbit reviews through WSL
  • Complete fresh native Android Debug/Release builds and a physical Pixel 9 signed-out cold-start pass
  • Compile the Swift appearance module on macOS at exact d13aa2c11cc in Debug and optimized Release arm64 simulator builds; Release embeds Hermes bytecode and launches without Metro
  • Complete VoiceOver/TalkBack and auth persistence/revocation evidence (DAY-109 closure/release evidence; not an implementation-PR merge gate)
  • Confirm Convex changes remain compatible with installed clients
  • Confirm the main workflow will skip unsafe OTA publication
  • Obtain fresh human approval against d13aa2c11cc (@Gamius00 requested)

Review findings and merge checklist

CodeRabbit via WSL

  • Secret preflight completed with zero high-confidence matches.
  • src/features/auth: initial focused review at d1e3997bb983; zero findings.
  • Full src partition: 114 changed files reviewed through WSL at d1e3997bb983; four findings, all manually verified (one overlaps the standards review below).
  • scripts: six changed files reviewed through WSL; two verified minor tooling findings.
  • modules: Swift bridge and README reviewed through WSL; one verified major native-lifecycle finding.
  • Final full-src WSL reviews at aa4b8d0d60fe found two minor test-harness issues (fresh app-config module evaluation and an explicit async resolver assertion); both were verified, fixed, and targeted tests pass.
  • Remaining non-src findings were individually verified at 79a22ec6dc6f: shell-operator boundary coverage, whitespace-only OTA identifiers, complete main-queue iOS teardown, the snapshot-shield bridge contract, Windows APP_VARIANT, and the Linear review-status lifecycle are fixed.
  • Reviewed and skipped as stale: Wayfinder already keeps research tickets open while charting and resolves them only in the work-through phase.
  • Reviewed and skipped as unsafe: logout intentionally preserves local state when Clerk sign-out fails because the authenticated session may still be active; clearing local state would create an inconsistent signed-in session. Regression coverage asserts this contract.
  • Final non-src review of exactly 0ec7724..79a22ec found two valid package-script detector gaps; empty assignments and quoted operator-like text are covered at 6728338.
  • WSL review of 79a22ec..6728338 found escaped shell-tokenization gaps; escaped separators and quotes are covered at f99bf4c.
  • Final WSL CodeRabbit review of exactly 6728338..f99bf4c completed with zero findings.

No paid usage was enabled, and the GitHub bot was not used to bypass the WSL-only review policy.

Standards axis — actionable

All prior concrete findings were re-verified against the current code and remain fixed at d13aa2c11cc:

  • Entry deletion now uses a synchronous transaction gate, including the sheet close handler.
  • Learning-time save/remove now share a synchronous mutation gate.
  • Learning-plan deletion's close handler now consults the existing synchronous gate.
  • Registration now has synchronous gating and busy/disabled semantics across affected controls.
  • Notification preference errors are announced with accessibilityRole="alert".
  • Learning-time validation/mutation errors now use alert semantics; the unreachable pre-navigation success banner was removed.
  • Theme-token ownership docs now identify theme-variables.ts as the runtime mirror and theme.ts as orchestration.
  • Auth-routing coverage now tests the reset-success route while Clerk is still loading.
  • iOS app activation now posts the global React Native appearance notification independently of Dayova listeners.
  • Skill-frontmatter parsing rejects malformed closing delimiters such as ---anything, with regression coverage.
  • Skill-body and OpenAI-metadata validation now report independently.

Final standards/build audit additions retained at d13aa2c11cc:

  • Restored a pure onboarding validation/transition seam with regression tests.
  • Removed internal Dev Client rebuild instructions from learner-facing notification copy while preserving diagnostics.
  • Replaced newly introduced static style objects with documented NativeWind design-system classes.
  • Raised the measured Android Gradle heap/metaspace ceiling through an idempotent Expo config plugin with tests and platform documentation.

Standards axis — judgement calls

  • Reviewed and intentionally skipped: keep the explicit PasswordResetStage branches. They represent materially different Clerk states, and a descriptor map would obscure control flow without removing a current defect.
  • Reviewed and intentionally skipped: keep the three password-flow UIs distinct. Validation and service logic are already shared, while current-password reverification, forced session tasks, and recovery/second-factor semantics differ; consolidation here would add risk without fixing a demonstrated issue.

Spec/acceptance axis

The implementation may become merge-ready independently, but the following remain blockers for closing DAY-109 or treating this as a release-ready build:

  • Manually execute and record all ten DAY-109 scenarios, including learning-time edge cases, material permissions/background recovery, plan acceptance, all answer modes, Praxis → Wissensanalyse, and the missed-slot Recovery Loop.
  • Record the Pixel 9 plus tablet/session-coexistence, restart, revocation, keyboard/autofill, TalkBack, and VoiceOver matrix required by DAY-90/DAY-178.
  • Use the corrected replacement build—not stale iOS build 49 or the development-variant OTA—and record EAS build ID, source SHA, runtime, fingerprint, and audience.
  • Add the final DAY-109 Linear comment covering tested build, devices, accounts, findings, fixes, and remaining risks.

Rollback

Squash-merging creates one atomic mainline commit that can be reverted if post-merge verification finds a severe regression. The source branch and checkpoint tags retain the detailed history and both pre-integration states.

Decision record

Jakob Rössner and others added 30 commits July 13, 2026 12:04
Replace the broken vector export that embedded a clipped JPEG logo and repeated placeholder copy. Render the first intro illustration natively with the transparent Dayova mark and the descriptive Figma task examples.
Keep RN styles limited to responsive and artboard geometry, document the SVG-versus-native rendering decision, and remove the superseded broken SVG export.
Replace the temporary HugeIcons flame with the exact 14px SVG supplied for the onboarding streak artwork.
# Conflicts:
#	src/components/onboarding/onboarding-flow.ts
# Conflicts:
#	src/app/notifications.tsx
#	src/components/ui/icon.tsx
#	src/features/auth/dayova-auth-flow.tsx
Document the app icon provenance and exception policy.\n\nRefs DAY-171
@FleetAdmiralJakob

Copy link
Copy Markdown
Collaborator Author

Final local/Android/CI checkpoint for DAY-109

  • Final branch HEAD: f99bf4c7b3eed4403d1c6e2f4495ea5e4b961610; local and origin are identical, and current origin/main is an ancestor.
  • Application/native content SHA: 79a22ec6dc6f; 6728338..f99bf4c changes only tests/package-scripts.test.ts, so the ongoing Mac native verification remains binary-equivalent.
  • Full local gates pass: lint, TypeScript, 52 Vitest files / 242 tests, 19 Jest suites / 46 tests, Metro/autolinking/skill tests, format, unused-code, and 21 Matt / 22 Expo skill validations.
  • Required CodeRabbit runs were performed through WSL. Valid package-script findings were fixed, and the final exact review 6728338..f99bf4c completed with zero findings.
  • Remote PR CI is green; Convex/main/OTA/send jobs are intentionally skipped for this draft PR.
  • Final all-ABI optimized Android Release build succeeded at 79a22ec6dc6f. APK SHA256: 6584778BA0B617F6A9B4A4B384D7E11C960270BB896A30020184D3B40C678ADC.
  • Physical Pixel 9 checks pass for clean cold start, stopped-state reinstall, and Android's own automatic foreground package-replacement relaunch. A single synthetic Clerk crash was isolated to racing a manual force-stop/Monkey launch against that already scheduled SystemUI relaunch; the normal update lifecycle did not reproduce it, and no product-code change was warranted.

Still required before this draft is marked ready: the Mac iOS Debug/Release result, authenticated persistence/revocation evidence, TalkBack/VoiceOver acceptance, and fresh human approval. Unsafe OTA/native runtime continuity remains the separate DAY-248 release blocker; merging this PR is not release authorization.

@FleetAdmiralJakob

Copy link
Copy Markdown
Collaborator Author

PARTIAL — Dayova PR #316 final verification

All code, dependency, compilation, runtime, Android-isolation, regression, complete-timeline, and CI gates pass at 0b1ac8ec0925b94cf6b26374d813aa937cbfba42. The sole remaining required gate is physical-device iOS VoiceOver interaction testing.

Source state

Build and runtime result

Gate Result Evidence summary
Repository checks PASS Frozen install, focused 7-test suite, check, full tests, unused check, Expo dependency check, Expo Doctor 20/20
Apple autolinking PASS Package dayova-system-appearance, pod DayovaSystemAppearance, class DayovaSystemAppearanceModule
iOS Debug compile PASS Exact final working-tree source; arm64; normal simulator CodeSign; Swift module and patched Expo Updates source compiled
iOS Release compile PASS Exact final working-tree source; optimized arm64; embedded 7.9 MB JS bundle; ran with Metro stopped
iOS Debug runtime PASS Authenticated final-source cold Light/Dark and lifecycle smoke, plus complete authenticated settings/surfaces matrix from unchanged module base
iOS Release runtime PASS Authenticated final-source Light/Dark cold launches and lifecycle matrix; no white Dark splash or stale snapshot
Android isolation PASS iOS-only module absent from Android native registration; no Swift compilation/loading
Android behavior PASS Authenticated Light/Dark/System, foreground, resume, 5 ordinary and 5 rapid cycles, sheet and native picker
iOS VoiceOver NOT TESTED Apple does not provide VoiceOver in Simulator; paired physical iPhone is offline

Complete-timeline findings

  • Debug final-source Dark cold launch: 31.038333 s, 298/298 frames reviewed, 0 full-screen near-white frames, 100% timeline coverage.
  • Debug final-source Light cold launch: 33.235 s, 290/290 frames reviewed, 100% timeline coverage.
  • Debug final-source lifecycle smoke: 43.875 s, 378/378 frames reviewed, foreground Light→Dark→Light and background Light→Dark resume remained coherent.
  • Release final-source Dark cold launch: 33.485 s, 132/132 frames reviewed, 0 near-white frames after trigger.
  • Release final-source Light cold launch: 23.426667 s, 181/181 frames reviewed. The initial old-app frame precedes termination/home and the launch trigger; it is not part of the cold-launch transition.
  • Release final-source lifecycle matrix: 95.755 s, 1801/1801 frames reviewed. Both foreground directions, both background directions, five ordinary resumes, and five rapid generation-guard cycles passed. Six abrupt-delta candidates were manually inspected and were intentional solid-cyan snapshot shield frames.
  • Every transition recording starts before the action and continues until settled. Every decoded source frame was measured, and the complete timelines were additionally reviewed through 8-fps samples/contact sheets. Coverage: 100%.
  • Entrance/intro animations were distinguished from wrong-theme flashes. No wrong-theme launch frame or stale previous-theme app snapshot was observed in the fixed build.

Authenticated iOS matrix

Matrix item Debug Release
System Light cold launch PASS PASS
System Dark cold launch; no Light/white native splash PASS PASS
Explicit Dayova Light while iOS Dark PASS PASS
Explicit Dayova Dark while iOS Light PASS PASS
System follows Light and Dark PASS PASS
Explicit → System resolves immediately PASS PASS
Foreground Light→Dark→Light PASS PASS
Background appearance change and resume, both directions PASS PASS
Five ordinary resume cycles PASS PASS
Five rapid generation-guard cycles PASS PASS
Root/status/navigation/text/icons PASS PASS
General/notification/password settings PASS PASS
Sheets/modals/native controls/selectors PASS PASS
Open sheet updates Dark→Light and Light→Dark PASS PASS
Crash, loop, stale screen, remount, module/Fabric errors PASS PASS

VoiceOver matrix

Check Result Reason
Snapshot shield is never announced NOT TESTED Requires physical-device VoiceOver
Covered controls are unreachable NOT TESTED Requires physical-device VoiceOver navigation
Focus remains/restores after resume NOT TESTED Requires physical-device VoiceOver focus observation
Theme controls expose correct spoken labels/roles/values NOT TESTED Static AX data passes, but speech was not inferred
Sheet focus enters modal, background is excluded, dismiss restores trigger NOT TESTED Requires physical-device VoiceOver focus observation
Native switches announce state NOT TESTED Static AX state passes, but speech was not inferred

Apple's testing guidance explicitly says to install on a physical device because VoiceOver is unavailable on Simulator. This Mac sees Jakob’s iPhone (iOS 26.5.2), but it was offline/unavailable during verification.

Logs

No Swift/Objective-C exceptions, missing modules/symbols, JavaScript exceptions, React Native appearance errors, appearance-event loops, unexpected root remounts, Fabric/native-view failures, or crashes attributable to the module were found. The first Debug launch attempted host-only 127.0.0.1:8081 and correctly failed transport; the same binary then loaded over LAN Metro. An Expo Updates network timeout in an earlier run was unrelated to native appearance behavior.

Conclusion

The Swift implementation was verified on iOS, and Android was verified to remain isolated from it while preserving equivalent appearance behavior.

The code and CI are green. Keep the PR draft until the physical-device VoiceOver matrix is completed.

@FleetAdmiralJakob
FleetAdmiralJakob marked this pull request as ready for review July 24, 2026 12:49

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49c027e8d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const verifyPasswordResetCode = async (code: string) =>
withSubmitting(async () => {
try {
await verifyPasswordResetAttempt(getPasswordResetSignIn(), code);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Short-circuit reset-code checks without an attempt

When beginPasswordReset() returns delivery_not_confirmed for an unknown email, startPasswordReset() records passwordResetHasRemoteAttemptRef.current = false but the UI still advances to the code-entry step; entering any 6-digit code then reaches this verifier and calls Clerk even though no reset attempt exists. In that scenario Clerk can return an internal/no-attempt error instead of the generic German reset-code response, breaking the privacy-preserving “Falls ein Konto existiert …” flow. Gate this path the same way resend already does when no remote attempt was created.

Useful? React with 👍 / 👎.

@FleetAdmiralJakob
FleetAdmiralJakob merged commit e7d7cc9 into main Jul 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant