Skip to content

feat(e14): first-time coachmark on per-app release-channel chip#586

Merged
rainxchzed merged 7 commits into
mainfrom
feat/e14-beta-channel-ux
May 12, 2026
Merged

feat(e14): first-time coachmark on per-app release-channel chip#586
rainxchzed merged 7 commits into
mainfrom
feat/e14-beta-channel-ux

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 12, 2026

Per Sprint 2 brief E14. Survey #17 — users don't realise the per-app channel chip on Details toggles beta releases. Add discoverability nudge.

Scope (this PR):

  • One-shot channel_chip_coachmark_shown flag in TweaksRepository / DataStore.
  • DetailsViewModel.observeChannelChipCoachmark gates a pending flag on installedApp != null so the chip is actually rendered when the pulse fires.
  • Pulse animation + Material 3 Popup tooltip above the chip, dismissable via "Got it" button.
  • Acknowledged implicitly on toggle tap (DetailsAction.ToggleIncludeBetas) or explicitly via OnAcknowledgeChannelChipCoachmark. Either path flips the persistent flag — coachmark never re-fires.
  • 13-locale strings + 1.8.2 what's-new bullet.

Out of scope:

  • Default-channel preference already exists (TweaksRepository.getIncludePreReleases(), applied to new tracks in InstallationManagerImpl) — only the discoverability half of E14 was missing.
  • "Ask each time" mode deferred — would need install-flow prompt UI, separate scope.

Compile-verified: :composeApp:compileDebugKotlinAndroid BUILD SUCCESSFUL.

Summary by CodeRabbit

  • New Features

    • One‑time coachmark on the Details screen that pulses the release‑channel chip and supports acknowledging/dismissing when switching between stable and beta.
  • Localization

    • Updated “default beta channel” copy and added coachmark strings across multiple locales (13 languages).
  • Documentation

    • What's New updated to include coachmark guidance.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e22faba-68a5-4c4f-bcf5-0723dcbda75c

📥 Commits

Reviewing files that changed from the base of the PR and between 1089d83 and 465cc55.

📒 Files selected for processing (2)
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt

Walkthrough

Adds a one-shot channel-chip coachmark: DataStore-backed persistence, Details presentation action/state and ViewModel orchestration, pulsing chip + popup UI, and localized strings plus updated release notes.

Changes

Channel Chip Coachmark

Layer / File(s) Summary
Coachmark persistence contract
core/domain/src/commonMain/kotlin/.../TweaksRepository.kt, core/data/src/commonMain/kotlin/.../TweaksRepositoryImpl.kt
TweaksRepository adds getChannelChipCoachmarkShown(): Flow<Boolean> and setChannelChipCoachmarkShown(shown: Boolean). TweaksRepositoryImpl overrides both and adds CHANNEL_CHIP_COACHMARK_SHOWN_KEY to persist the boolean via DataStore.
UI state and action modeling
feature/details/presentation/src/commonMain/kotlin/.../DetailsAction.kt, feature/details/presentation/src/commonMain/kotlin/.../DetailsState.kt
Adds DetailsAction.OnAcknowledgeChannelChipCoachmark and DetailsState.isChannelChipCoachmarkPending: Boolean to represent pending coachmark visibility.
ViewModel coachmark orchestration
feature/details/presentation/src/commonMain/kotlin/.../DetailsViewModel.kt
observeChannelChipCoachmark() observes eligibility after initial load; acknowledgeChannelChipCoachmark() clears pending and calls tweaksRepository.setChannelChipCoachmarkShown(true); toggling the channel chip or explicit dismiss triggers acknowledgment.
ReleaseChannel pulse and popup
feature/details/presentation/src/commonMain/kotlin/.../components/sections/ReleaseChannel.kt
Wraps the channel chip in a pulsing Box via rememberChipPulse(active) and conditionally shows ChannelChipCoachmark as a Popup with icon, localized title/body, and dismiss TextButton. Animation and popup imports added.
Multilingual strings and release notes
core/presentation/src/commonMain/composeResources/values*/strings*.xml, core/presentation/src/commonMain/composeResources/files/whatsnew/17.json
Adds channel_chip_coachmark_title/body/dismiss and updates include_pre_releases_* wording across base and localized resources; release notes updated with the first-time coachmark bullet.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A gentle pulse to guide the way,
Toggle beta, stable—learn to play,
A coachmark pops with science cheer,
Many tongues will hear it clear,
Seen once, dismissed, it hops away. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. 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 PR title clearly and concisely summarizes the main change: adding a first-time coachmark UI component for the per-app release-channel chip, which aligns perfectly with the changeset's primary objective.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/e14-beta-channel-ux

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

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

  • Introduces a one-shot release-channel coachmark: a channel_chip_coachmark_shown DataStore flag gates a pulse animation and Popup tooltip on the per-app channel chip in DetailsScreen, dismissed via "Got it" or implicitly on chip tap, with 13-locale string coverage and a what's-new bullet.
  • Three issues flagged in previous review rounds are confirmed still present in the current diff: a raw-pixel y = -220 offset in the Popup that breaks positioning at non-standard screen densities (P1), an unconditional rememberInfiniteTransition that wastes GPU frames after dismissal (P2), and focusable = false that makes the "Got it" button unreachable via keyboard/switch-access (P2).
  • The observeChannelChipCoachmark one-shot coroutine correctly gates the coachmark on installedApp != null and the acknowledgeChannelChipCoachmark path is idempotent and race-safe; the data + domain layers follow existing coachmark patterns exactly.

Confidence Score: 4/5

Safe to merge after resolving the raw-pixel Popup offset, which breaks coachmark positioning on mdpi/xxxhdpi devices.

One confirmed P1 (hard-coded IntOffset in raw pixels rather than dp-converted) caps the score at 4. The DataStore + ViewModel logic is sound, the one-shot semantic is correct, and localization coverage is complete. The two P2 items (infinite transition when inactive, focusable=false) are non-blocking but should be addressed.

ReleaseChannel.kt — Popup offset (P1 density bug), rememberChipPulse (P2 wasted animation), focusable=false (P2 accessibility).

Important Files Changed

Filename Overview
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt Adds pulse animation and Popup coachmark overlay for the channel chip — three pre-existing review comments remain open: hard-coded raw-pixel y-offset in Popup, infinite transition running when inactive, and focusable=false making the "Got it" button keyboard-inaccessible.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt Adds observeChannelChipCoachmark (one-shot DataStore read + state snapshot) and acknowledgeChannelChipCoachmark (idempotent persist with runCatching); wires into ToggleIncludeBetas and OnAcknowledgeChannelChipCoachmark actions correctly.
core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt Adds getChannelChipCoachmarkShown / setChannelChipCoachmarkShown following the same DataStore pattern used by the existing APK-inspect coachmark — straightforward and consistent.
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt Interface extended with getChannelChipCoachmarkShown / setChannelChipCoachmarkShown, well-documented with a clear one-shot semantic comment.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt Adds isChannelChipCoachmarkPending: Boolean = false to DetailsState, following the same convention as isApkInspectCoachmarkPending.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt Adds OnAcknowledgeChannelChipCoachmark data object, mirroring the APK-inspect coachmark action cleanly.
core/presentation/src/commonMain/composeResources/values/strings.xml Adds three coachmark strings (title, body, dismiss) and refreshes include_pre_releases copy to clarify per-app vs global scope.

Sequence Diagram

sequenceDiagram
    participant VM as DetailsViewModel
    participant DS as TweaksRepository (DataStore)
    participant UI as ReleaseChannel (Compose)
    participant User

    VM->>DS: getChannelChipCoachmarkShown().first()
    DS-->>VM: false (never shown)
    VM->>VM: "_state.first { !isLoading }"
    Note over VM: guard: installedApp != null?
    VM->>VM: "_state.update(isChannelChipCoachmarkPending = true)"
    VM-->>UI: "state.isChannelChipCoachmarkPending = true"
    UI->>UI: "rememberChipPulse(active=true) → 1.0f↔1.06f"
    UI->>UI: show ChannelChipCoachmark Popup

    alt User taps Got it
        User->>UI: click TextButton
        UI->>VM: OnAcknowledgeChannelChipCoachmark
        VM->>VM: "_state.update(isChannelChipCoachmarkPending = false)"
        VM->>DS: setChannelChipCoachmarkShown(true)
    else User taps channel chip
        User->>UI: click ChannelChip
        UI->>VM: ToggleIncludeBetas
        VM->>VM: acknowledgeChannelChipCoachmark()
        VM->>DS: setChannelChipCoachmarkShown(true)
        VM->>VM: toggleIncludeBetas()
    else User presses Back
        User->>UI: "back press (dismissOnBackPress=true)"
        UI->>VM: OnAcknowledgeChannelChipCoachmark (via onDismissRequest)
        VM->>DS: setChannelChipCoachmarkShown(true)
    end

    Note over DS: flag persisted — coachmark never re-fires
Loading

Reviews (2): Last reviewed commit: "Update core/presentation/src/commonMain/..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt (2)

286-298: 💤 Low value

Consider stopping the animation when inactive.

The infinite transition runs continuously even when active = false, animating from 1f to 1f. This creates unnecessary animation frames and recompositions. Consider wrapping the animation in a conditional or using AnimatedVisibility to avoid running the animation when the coachmark isn't pending.

`@Composable`
private fun rememberChipPulse(active: Boolean): Float {
    val infiniteTransition = rememberInfiniteTransition(label = "channel-chip-pulse")
    return if (active) {
        infiniteTransition.animateFloat(
            initialValue = 1f,
            targetValue = 1.06f,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 1100),
                repeatMode = RepeatMode.Reverse,
            ),
            label = "channel-chip-pulse-scale",
        ).value
    } else {
        1f
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt`
around lines 286 - 298, The rememberChipPulse function currently creates a
rememberInfiniteTransition and animateFloat even when active is false; change
rememberChipPulse to only start the infinite transition/animateFloat when active
is true and return a constant 1f otherwise (e.g., use rememberInfiniteTransition
+ animateFloat and return its .value when active, else return 1f), referencing
rememberChipPulse, rememberInfiniteTransition and animateFloat to locate the
logic and avoid needless animations and recompositions.

300-360: ⚡ Quick win

Consider using PopupPositionProvider for more robust positioning instead of hardcoded offsets.

The popup uses a fixed y-offset of -220dp with Alignment.TopStart, which mirrors the pattern in InspectApkButton.kt. However, the codebase already uses PopupPositionProvider for dynamic positioning in HomeRoot.kt's PlatformsPopup. For better robustness across screen sizes and layouts (especially on small devices or when the Details screen is scrolled), consider adopting the dynamic positioning approach.

The focusable=false, dismissOnClickOutside=false, and dismissOnBackPress=true settings are consistent with the one-shot coachmark pattern and intentional—they work as designed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt`
around lines 300 - 360, ChannelChipCoachmark currently uses a hardcoded
IntOffset (y = -220) which breaks on different screens; replace the static
offset/alignment usage in ChannelChipCoachmark with a PopupPositionProvider
implementation (following the dynamic approach used by PlatformsPopup in
HomeRoot.kt) that computes the popup position relative to an anchor bounds or
parent layout, then pass that provider via the Popup's positionProvider
parameter (keep existing PopupProperties: focusable=false,
dismissOnBackPress=true, dismissOnClickOutside=false and the same
onDismissRequest). Locate ChannelChipCoachmark and implement a small
PopupPositionProvider class/function that calculates safe x/y to avoid clipping
(respecting screen/window insets) and use that provider in the Popup call
instead of offset/alignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt`:
- Around line 558-567: The early return in acknowledgeChannelChipCoachmark
prevents persisting the coachmark acknowledgement when
_state.value.isChannelChipCoachmarkPending is false; change the function so that
persistence via viewModelScope.launch { runCatching {
tweaksRepository.setChannelChipCoachmarkShown(true) }... } always runs
(regardless of the pending flag) while keeping the existing _state.update {
it.copy(isChannelChipCoachmarkPending = false) } behavior idempotent; e.g.,
remove or move the early return and ensure
tweaksRepository.setChannelChipCoachmarkShown(true) is invoked (with the
existing onFailure logger) and the state update still happens via _state.update
in acknowledgeChannelChipCoachmark.

---

Nitpick comments:
In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt`:
- Around line 286-298: The rememberChipPulse function currently creates a
rememberInfiniteTransition and animateFloat even when active is false; change
rememberChipPulse to only start the infinite transition/animateFloat when active
is true and return a constant 1f otherwise (e.g., use rememberInfiniteTransition
+ animateFloat and return its .value when active, else return 1f), referencing
rememberChipPulse, rememberInfiniteTransition and animateFloat to locate the
logic and avoid needless animations and recompositions.
- Around line 300-360: ChannelChipCoachmark currently uses a hardcoded IntOffset
(y = -220) which breaks on different screens; replace the static
offset/alignment usage in ChannelChipCoachmark with a PopupPositionProvider
implementation (following the dynamic approach used by PlatformsPopup in
HomeRoot.kt) that computes the popup position relative to an anchor bounds or
parent layout, then pass that provider via the Popup's positionProvider
parameter (keep existing PopupProperties: focusable=false,
dismissOnBackPress=true, dismissOnClickOutside=false and the same
onDismissRequest). Locate ChannelChipCoachmark and implement a small
PopupPositionProvider class/function that calculates safe x/y to avoid clipping
(respecting screen/window insets) and use that provider in the Popup call
instead of offset/alignment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e9116a5e-2d0f-46ae-a229-baffa7014621

📥 Commits

Reviewing files that changed from the base of the PR and between 872b4e3 and a591208.

📒 Files selected for processing (20)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/17.json
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/ReleaseChannel.kt

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml`:
- Around line 773-775: Update the coachmark dismiss text to be consistent with
other instructional coachmarks by changing the string resource
channel_chip_coachmark_dismiss from the current wording to the same
acknowledgment used elsewhere (e.g., "समझ गया"); locate and edit the string with
name="channel_chip_coachmark_dismiss" so it matches the APK inspect coachmark
dismiss string used elsewhere for consistent UX.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c95b94e3-d0db-4eb1-bacf-bbdf662d789b

📥 Commits

Reviewing files that changed from the base of the PR and between a591208 and 1089d83.

📒 Files selected for processing (13)
  • core/presentation/src/commonMain/composeResources/values-ar/strings-ar.xml
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
✅ Files skipped from review due to trivial changes (4)
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • core/presentation/src/commonMain/composeResources/values-ko/strings-ko.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml

Comment thread core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml Outdated
…rings-hi.xml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@rainxchzed rainxchzed merged commit 3dadbf1 into main May 12, 2026
1 check was pending
@rainxchzed rainxchzed deleted the feat/e14-beta-channel-ux branch May 12, 2026 15:57
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