Skip to content

refactor(oobe): expose TOS acceptance as UI state and ban event flows in ViewModels - #263

Merged
Lemkinator merged 5 commits into
mainfrom
refactor/oobe-state-only
Oct 5, 2026
Merged

Lemkinator merged 5 commits into
mainfrom
refactor/oobe-state-only

Conversation

@Lemkinator

@Lemkinator Lemkinator commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

OOBE state

  • OOBEViewModel exposes ToS acceptance as a TosAcceptance StateFlow instead of a Channel event.
  • OOBEActivity renders the state with collectState and opens the main screen from a RESUMED collector.
  • collectEvents loses its last caller and goes away.

Architecture rules

  • ViewModel files can no longer import, declare or construct Channel or SharedFlow streams.
  • ui files can no longer import collectEvents.
  • Every class named ViewModel must extend ViewModel or AndroidViewModel directly, so the event stream rule cannot be bypassed.
  • Probe tests pin the detection of qualified, typed, inferred, aliased and lookalike uses against false positives.

Tests

  • OOBE tests pin the RESUMED gate and single navigation across recreation.

Behavior changes

  • OOBE accept moves through TosAcceptance states: Idle, Accepting, Accepted, Navigated.
  • The activity renders the state at STARTED and navigates at RESUMED.
  • A double-tap on accept is guarded synchronously.
  • Navigation to MainActivity happens exactly once across recreation.
  • The unused local collectEvents helper is deleted.

Coverage

Kover floors 100% instruction / 100% branch hold.

Verification

spotlessCheck, detekt, lintDebug, testDebugUnitTest, koverVerifyDebug, verifyRoborazziDebug, pixel9Api35DebugAndroidTest and assembleRelease PASS. The last three review-fix commits touch tests only.

Parity: the only drift is the renovate.json EXACT drift. It clears when the renovate files align.

Review: an Opus review found 1 should-fix and 3 nits. All are fixed.

Notes

  • OOBEViewModelTest keeps a MockK for CompleteOnboardingUseCase, because the use case needs a Context.
  • tosAcceptance is not saved across process death, same as the Channel before.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VvNnYRB6Qeb51U6jGybaRF

  • Replaced OOBE ToS acceptance events with TosAcceptance state, and gated navigation on the activity reaching RESUMED.
  • Removed collectEvents and added architecture checks against event streams in ViewModels and collectEvents imports in UI files.
  • Updated ViewModel, activity, and architecture tests for state transitions, lifecycle gating, and duplicate navigation.

…vent

OOBEViewModel now holds a TosAcceptance state (Idle, Accepting, Accepted,
Navigated). The activity renders it with collectState and opens the main
screen from a RESUMED collector on Accepted, then reports it handled. A
StateFlow keeps the result across configuration changes without a
buffered Channel, and the state cannot hold a navigation request without
the progress view. collectEvents loses its last caller and goes away.
ViewModel files may not import, declare or construct Channel or
SharedFlow streams, and ui files may not import collectEvents, so one-off
results stay in UI state that survives configuration changes. A probe
test pins the detection of qualified, typed and inferred uses against
false positives from comments, strings and unrelated channels APIs.
An Accepted state reached while the activity is only STARTED must not open MainActivity until it resumes. A recreated activity must not open it a second time once the state is Navigated. Without this test, dropping minActiveState or the onTosAcceptedHandled call would pass the suite.
The event stream rule selects files through a direct ViewModel or AndroidViewModel parent. A ViewModel built on an intermediate base class would escape that rule, so the name rule forces every class named ViewModel onto a direct base the event stream rule recognizes.
The ui rule had no probe, so a broken predicate would pass on the current clean codebase without notice. The probe feeds plain, aliased and lookalike imports through the same predicate the rule uses.
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
CLAUDE.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d9ad7c52-e8c7-43f4-b121-147c104fb7e0
📥 Commits

Reviewing files that changed from the base of the PR and between 27c2437 and 555f6b9.

📒 Files selected for processing (8)
  • app/src/main/java/de/lemke/oneuisample/ui/OOBEActivity.kt
  • app/src/main/java/de/lemke/oneuisample/ui/OOBEViewModel.kt
  • app/src/main/java/de/lemke/oneuisample/ui/util/LifecycleUtils.kt
  • app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt
  • app/src/test/java/de/lemke/oneuisample/ui/OOBEActivityTest.kt
  • app/src/test/java/de/lemke/oneuisample/ui/OOBEViewModelTest.kt
  • app/src/test/java/de/lemke/oneuisample/ui/util/LifecycleUtilsKtActivityTest.kt
  • app/src/test/java/de/lemke/oneuisample/ui/util/LifecycleUtilsKtFragmentTest.kt
💤 Files with no reviewable changes (3)
  • app/src/main/java/de/lemke/oneuisample/ui/util/LifecycleUtils.kt
  • app/src/test/java/de/lemke/oneuisample/ui/util/LifecycleUtilsKtFragmentTest.kt
  • app/src/test/java/de/lemke/oneuisample/ui/util/LifecycleUtilsKtActivityTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

OOBE acceptance now uses a TosAcceptance state flow instead of a Boolean state and navigation event. The activity updates its footer from that state and navigates when acceptance is collected while resumed. Architecture tests check ViewModel inheritance and event-stream usage.

Changes

OOBE acceptance flow

Layer / File(s) Summary
Acceptance state and transitions
app/src/main/java/de/lemke/oneuisample/ui/OOBEViewModel.kt, app/src/test/java/de/lemke/oneuisample/ui/OOBEViewModelTest.kt
The view model replaces isAccepting and the navigation event channel with TosAcceptance states. Tests cover the 500 ms transition, duplicate calls, handled acceptance, and version values.
Lifecycle-aware activity navigation
app/src/main/java/de/lemke/oneuisample/ui/OOBEActivity.kt, app/src/main/java/de/lemke/oneuisample/ui/util/LifecycleUtils.kt, app/src/test/java/de/lemke/oneuisample/ui/OOBEActivityTest.kt, app/src/test/java/de/lemke/oneuisample/ui/util/LifecycleUtilsKt*Test.kt
The activity renders the acceptance state and navigates when Accepted is collected while resumed. The collectEvents extensions and related tests are removed. Activity tests cover the delay, lifecycle pause, and recreation.
Architecture checks for event streams
app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt
Architecture tests check ViewModel inheritance, event-stream usage in ViewModel files, and collectEvents imports in UI files. Probe tests cover imports, types, calls, comments, and strings.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant OOBEActivity
  participant OOBEViewModel
  participant MainActivity
  OOBEActivity->>OOBEViewModel: onAcceptTos()
  OOBEViewModel->>OOBEViewModel: Set Accepting and complete onboarding
  OOBEViewModel->>OOBEViewModel: Set Accepted after 500 ms
  OOBEViewModel-->>OOBEActivity: Emit Accepted through tosAcceptance
  Note over OOBEActivity: Handle Accepted only while RESUMED
  OOBEActivity->>MainActivity: Navigate
  OOBEActivity->>OOBEViewModel: onTosAcceptedHandled()
  OOBEViewModel->>OOBEViewModel: Set Navigated
Loading

Merge Risk: ⚪ Minimal · up to 555f6

The acceptance and navigation changes are ready to merge after normal checks. A narrow architecture-test gap does not block this change.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 555f6

The change is contained within onboarding and preserves the existing consent gate. Acceptance is guarded before asynchronous work starts, and navigation waits until the activity is resumed. No newly exposed acceptance path was found, but interruption recovery and the deployment of special builds remain incompletely established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed path affects onboarding preferences and activity navigation within an app installation. Its inspected entrypoint does not grant an external caller new acceptance or navigation authority relative to the reviewed base.

Trust Boundaries and Controls

  • observed — The ordinary build configuration disables caller-requested onboarding bypass. Existing nonMinifiedRelease and benchmarkRelease configuration enables skipOnboarding intent handling. This exception predates the PR; whether those variants are distributed is not established.

Resilience and Maintainability Implications

  • inferred — A completion exception before Accepted does not authorize navigation, but leaves acceptance unavailable without an explicit retry/reset transition. The base implementation also retained its accepting flag on failure, so this is inherited recovery behavior rather than an introduced security concern. Navigation exceptions and preference-write interruption remain incompletely verified.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 18 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: exposing ToS acceptance as UI state and prohibiting event flows in ViewModels.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

I’m a rabbit watching states take flight,
From Idle into Accepting bright.
Five hundred milliseconds pass,
Accepted hops through lifecycle grass.
MainActivity waits, then joins the run,
Navigated marks the journey done.

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Refactors onboarding state management from events to state flow.

The PR appears safe to merge; the architecture-check gaps are non-blocking but worth closing.

Fix All in Claude CodeFindings

  1. P2 Channel parameters go unchecked ▶
  2. P2 Typealiases bypass event checks ▶
Fix with agent prompt
### Issue 1
app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt:207-212
`declaredTypes()` checks properties, return types, and local variables, but not function parameters. A ViewModel can therefore declare `fun forward(events: Channel<Int>)` and pass the new event-stream rule if it has no matching import or constructor call. The negative probe at line 90 treats this case as allowed, leaving a gap in future enforcement. Include parameter types in the check.

### Issue 2
app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt:187-205
The new check does not inspect typealias targets. A ViewModel file can define `typealias Events = kotlinx.coroutines.channels.Channel<Int>` and use `Events` as a property type without matching the import, declared-type, or call checks. That leaves an alias-based gap in future enforcement; inspect typealias targets as well.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

The PR replaces OOBE’s navigation event with a ToS acceptance StateFlow, navigates from a RESUMED collector, removes the unused event collector, and adds architecture and lifecycle tests.

  • The new architecture check has two declaration-form gaps: function parameters and typealiases.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Idle -->|Accept tap| Accepting
  Accepting -->|Onboarding completes; 500 ms| Accepted
  Accepted -->|Activity RESUMED; open MainActivity| Navigated
Loading

Reviews (1) · Last reviewed commit: "test(architecture): probe the collectEve..."

Comment thread app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt
Comment thread app/src/test/java/de/lemke/oneuisample/ArchitectureTest.kt
@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@Lemkinator
Lemkinator merged commit fd9d81e into main Oct 5, 2026
10 checks passed
@Lemkinator
Lemkinator deleted the refactor/oobe-state-only branch October 5, 2026 17:11
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