Skip to content

feat(e20): self-owned ✓ badge on Home + Search cards#584

Merged
rainxchzed merged 10 commits into
mainfrom
feat/e20-self-owned-badge
May 12, 2026
Merged

feat(e20): self-owned ✓ badge on Home + Search cards#584
rainxchzed merged 10 commits into
mainfrom
feat/e20-self-owned-badge

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 12, 2026

Render a Verified-icon badge next to the owner login on RepositoryCard when the current signed-in user owns the repo. Triggers on Home + Search discovery surfaces. Closes E20 first-slice (Sprint 2 brief, no-backend variant).

Plumbing: HomeViewModel + SearchViewModel inject ProfileRepository, read getUser().first()?.username during repo→UI mapping, set new DiscoveryRepositoryUi.isCurrentUserOwner flag via case-insensitive login compare. RepositoryCard renders OfficialBadge (Icons.Filled.Verified, primary tint, 16dp) conditionally next to the owner Text.

Tooltip contentDescription = self_owned_badge string, localized across 13 locales.

Out of scope (follow-up): Details AppHeader, Starred / Favourites / DevProfile surfaces — separate UI models. Will mirror once this lands.

Compile-verified: :core:presentation + :feature:home:presentation + :feature:search:presentation (Android + JVM) BUILD SUCCESSFUL.

Summary by CodeRabbit

  • New Features

    • Added a "self-owned ✓" badge on repository cards in Home, Search and Details for signed-in users' own repos.
    • Improved search in Starred/Favourites with quick filtering by repository attributes.
  • Localization

    • Added localized text for the new badge across 13 locales.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eef61c98-60b0-4631-98a9-a0be88142f69

📥 Commits

Reviewing files that changed from the base of the PR and between bd84f08 and 3a02b7b.

📒 Files selected for processing (11)
  • core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt
  • feature/favourites/presentation/build.gradle.kts
  • feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/FavouritesViewModel.kt
  • feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/components/FavouriteRepositoryItem.kt
  • feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/mappers/FavouriteRepositoryMapper.kt
  • feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/model/FavouriteRepository.kt
  • feature/starred/presentation/build.gradle.kts
  • feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/StarredReposViewModel.kt
  • feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/components/StarredRepositoryItem.kt
  • feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/mappers/StarredRepoToUiMapper.kt
  • feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/model/StarredRepositoryUi.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt

Walkthrough

Adds detection and rendering of a "self-owned ✓" badge: presentation UI models gain isCurrentUserOwner; Home/Search/Details/Favourites/Starred viewmodels observe ProfileRepository to set it; RepositoryCard and AppHeader render OfficialBadge; localized self_owned_badge strings and whatsnew notes added.

Changes

Self-owned repository badge

Layer / File(s) Summary
UI model field and badge component
core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/model/DiscoveryRepositoryUi.kt, core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt, core/presentation/src/commonMain/composeResources/files/whatsnew/17.json
DiscoveryRepositoryUi adds isCurrentUserOwner. RepositoryCard introduces OfficialBadge (Verified icon, primary tint, localized contentDescription) and conditionally renders it next to owner name. Whatsnew release notes updated.
Multilingual string resources
core/presentation/src/commonMain/composeResources/values/strings.xml, core/presentation/src/commonMain/composeResources/values-*/strings-*.xml
Adds self_owned_badge string across default and locale files (ar, bn, es, fr, hi, it, ja, ko, pl, ru, tr, zh-rCN) for localized badge text.
Home feature: ownership detection wiring
feature/home/presentation/build.gradle.kts, feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt
Adds projects.feature.profile.domain dependency and injects ProfileRepository; caches current user login, observes profileRepository.getUser(), and sets isCurrentUserOwner during mapReposToUi and on user changes.
Search feature: ownership detection wiring
feature/search/presentation/build.gradle.kts, feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt
Adds projects.feature.profile.domain dependency and injects ProfileRepository; observes current user and sets isCurrentUserOwner on mapped search and explore results via case-insensitive owner-login comparison.
Details feature: ownership state and header badge
feature/details/presentation/build.gradle.kts, 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/AppHeader.kt, feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/Header.kt, composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/di/ViewModelsModule.kt
Adds projects.feature.profile.domain dependency, extends DetailsState with isCurrentUserOwner, injects ProfileRepository into DetailsViewModel and observes user to update state, extends AppHeader API and conditionally renders OfficialBadge, and wires DI for DetailsViewModel.
Favourites: ownership propagation and UI
feature/favourites/presentation/build.gradle.kts, feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/FavouritesViewModel.kt, feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/mappers/FavouriteRepositoryMapper.kt, feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/model/FavouriteRepository.kt, feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/components/FavouriteRepositoryItem.kt
Adds projects.feature.profile.domain dependency, combines favourites with current user, propagates isCurrentUserOwner through mapper and model, and conditionally renders OfficialBadge in favourite item UI.
Starred: ownership propagation and UI
feature/starred/presentation/build.gradle.kts, feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/StarredReposViewModel.kt, feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/mappers/StarredRepoToUiMapper.kt, feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/model/StarredRepositoryUi.kt, feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/components/StarredRepositoryItem.kt
Adds projects.feature.profile.domain dependency, includes current user in starred load pipeline, passes isCurrentUserOwner into mapper and StarredRepositoryUi, and conditionally renders OfficialBadge in starred item UI.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through code to leave a mark,
A tiny check that sparkles bright and neat,
"You own this repo" in many tongues,
On lists and details the small badge meets,
A verified hop — a joyful, jaunty feat!

🚥 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%. 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 accurately describes the primary change: adding a self-owned badge (verified icon) displayed on Home and Search repository cards when the signed-in user owns the repo.
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/e20-self-owned-badge

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

  • Adds a "self-owned ✓" badge (OfficialBadge / Icons.Filled.Verified) to RepositoryCard, AppHeader, StarredRepositoryItem, FavouriteRepositoryItem, and the Details surface. Badge state is derived reactively via ProfileRepository.getUser() — either through a combine-based observer (Home, Search, Details) or a combined flow (Starred, Favourites).
  • Unresolved bug: the search pagination dedup existing.copy(...) block (lines 415–422 in SearchViewModel) does not copy isCurrentUserOwner from the freshly-mapped page result, so any repo already present in state from a prior page will permanently show false for the badge even after login.
  • OfficialBadge is named inconsistently with its semantics (self_owned_badge contentDescription, isCurrentUserOwner flag); renaming to SelfOwnedBadge / OwnerBadge would prevent future confusion with a real third-party verification badge.

Confidence Score: 3/5

Merge-blocking P1 bug in search pagination dedup causes the owner badge to permanently show false for page-1 repos when the user is already signed in.

One confirmed P1 logic bug drops the ceiling to 4; the bug affects all multi-page search sessions where the user is already signed in (the typical case), pulling the score down to 3.

feature/search/presentation/.../SearchViewModel.kt — pagination dedup merge at lines 415–422 must be fixed before merge.

Important Files Changed

Filename Overview
feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt Adds observeCurrentUser() + currentUserLogin cache for badge stamping, but the pagination dedup existing.copy() still omits isCurrentUserOwner, leaving page-1 repos permanently unbadged after sign-in.
feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt Adds reactive observeCurrentUser() observer and currentUserLogin cache; patches all loaded repos on login/logout events. Implementation is consistent and correct.
core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt Adds OfficialBadge composable and conditional render; weight modifier correctly added. Naming is misleading relative to actual self-ownership semantics.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt Adds reactive observeCurrentUserForBadge() using combine of profileRepository.getUser() and the state-mapped owner login with distinctUntilChanged; correctly prevents infinite update loops.
feature/starred/presentation/src/commonMain/kotlin/zed/rainxch/starred/presentation/StarredReposViewModel.kt Extends the combine from 2 to 3 flows (starred + favourites + user) to derive isCurrentUserOwner reactively; clean implementation.
feature/favourites/presentation/src/commonMain/kotlin/zed/rainxch/favourites/presentation/FavouritesViewModel.kt Converts single-flow mapping to combine with profileRepository.getUser() for reactive badge derivation; correctly applies flowOn(Dispatchers.Default) downstream.

Sequence Diagram

sequenceDiagram
    participant PR as ProfileRepository
    participant HVM as HomeViewModel
    participant SVM as SearchViewModel
    participant DVM as DetailsViewModel
    participant FVM as FavouritesViewModel
    participant SRVM as StarredReposViewModel
    participant UI as UI Layer

    PR->>HVM: getUser() flow emit
    HVM->>HVM: "currentUserLogin = user?.username"
    HVM->>HVM: patch isCurrentUserOwner on all loaded repos
    HVM-->>UI: "DiscoveryRepositoryUi(isCurrentUserOwner=true/false)"

    PR->>SVM: getUser() flow emit
    SVM->>SVM: "currentUserLogin = user?.username"
    SVM->>SVM: patch isCurrentUserOwner on all loaded repos
    Note over SVM: pagination dedup existing.copy() does NOT forward isCurrentUserOwner
    SVM-->>UI: "DiscoveryRepositoryUi(isCurrentUserOwner=?)"

    PR->>DVM: "combine(getUser(), _state.map{ownerLogin})"
    DVM->>DVM: "isOwner = login == ownerLogin (ignoreCase)"
    DVM-->>UI: "DetailsState(isCurrentUserOwner=true/false)"

    PR->>FVM: combine(getAllFavorites(), getUser())
    FVM->>FVM: map with isCurrentUserOwner per repo
    FVM-->>UI: "FavouriteRepository(isCurrentUserOwner=true/false)"

    PR->>SRVM: combine(getAllStarred(), getAllFavorites(), getUser())
    SRVM->>SRVM: map with isCurrentUserOwner per repo
    SRVM-->>UI: "StarredRepositoryUi(isCurrentUserOwner=true/false)"

    UI->>UI: RepositoryCard / OfficialBadge renders conditionally
Loading

Comments Outside Diff (1)

  1. feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt, line 415-422 (link)

    P1 isCurrentUserOwner dropped in pagination dedup merge

    When the same repo appears on multiple pages, the existing.copy(...) block refreshes isInstalled, isUpdateAvailable, isFavourite, isStarred, and repository, but silently discards the freshly-computed isCurrentUserOwner from r. A repo on page 1 that arrives before the user is signed in will always remain false even after observeCurrentUser() has stamped later pages, because the existing value from page 1 wins the merge.

Reviews (4): Last reviewed commit: "fix(presentation): weight owner Text so ..." | 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.

🧹 Nitpick comments (2)
feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt (1)

371-385: ⚡ Quick win

Consider caching the current user login to avoid repeated profile lookups.

mapReposToUi(...) calls profileRepository.getUser().first() every time it maps repositories, which occurs during initial loads, pagination, category switches, and topic supplements. This repeated suspending call could be optimized by caching the current user login once during initialization.

♻️ Example caching approach

Add a cached property:

 class HomeViewModel(
     ...
     private val profileRepository: ProfileRepository,
 ) : ViewModel() {
     private var hasLoadedInitialData = false
+    private var currentUserLogin: String? = null
     ...

Initialize it in syncSystemState() or loadPlatform():

 private fun syncSystemState() {
     viewModelScope.launch {
         try {
+            currentUserLogin = profileRepository.getUser().first()?.username
             val result = syncInstalledAppsUseCase()
             ...

Then use the cached value in mapReposToUi:

-val currentLogin = profileRepository.getUser().first()?.username
+val currentLogin = currentUserLogin
🤖 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/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt`
around lines 371 - 385, mapReposToUi currently calls
profileRepository.getUser().first() each time which is expensive; add a cached
nullable String property (e.g., cachedCurrentLogin) on HomeViewModel, set it
once during initialization (e.g., in syncSystemState() or loadPlatform() by
calling profileRepository.getUser().first() there) and then update mapReposToUi
to use cachedCurrentLogin instead of calling
profileRepository.getUser().first(); ensure cachedCurrentLogin is updated
whenever user state changes and remove the inline
profileRepository.getUser().first() calls in mapReposToUi to avoid repeated
suspending lookups.
feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt (1)

354-369: ⚡ Quick win

Consider caching the current user login to avoid repeated profile lookups.

Every search operation calls profileRepository.getUser().first() to determine ownership. This suspending call is repeated in appendExploreResults (line 781) as well. If the user performs multiple searches or explores additional pages, this profile lookup happens repeatedly.

Consider fetching the current user login once during initialization or when the auth state changes, then reusing the cached value for ownership comparisons throughout the session.

♻️ Example caching approach

Add a cached property:

 class SearchViewModel(
     ...
     private val profileRepository: ProfileRepository,
 ) : ViewModel() {
     private var hasLoadedInitialData = false
+    private var currentUserLogin: String? = null
     ...

Initialize it in syncSystemState() or create a dedicated function:

 private fun syncSystemState() {
     viewModelScope.launch {
         try {
+            currentUserLogin = profileRepository.getUser().first()?.username
             val result = syncInstalledAppsUseCase()
             ...

Then use the cached value:

-val currentLogin = profileRepository.getUser().first()?.username
+val currentLogin = currentUserLogin
🤖 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/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt`
around lines 354 - 369, profileRepository.getUser().first() is being called
repeatedly to compute currentLogin (used when building DiscoveryRepositoryUi and
in appendExploreResults); cache the current username once (e.g., a private var
cachedCurrentLogin) during initialization or inside syncSystemState() and update
it on auth changes, then replace direct calls to
profileRepository.getUser().first() with the cachedCurrentLogin when computing
isCurrentUserOwner in the DiscoveryRepositoryUi mapping and in
appendExploreResults so the suspending profile lookup is not performed on every
search/page load.
🤖 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.

Nitpick comments:
In
`@feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt`:
- Around line 371-385: mapReposToUi currently calls
profileRepository.getUser().first() each time which is expensive; add a cached
nullable String property (e.g., cachedCurrentLogin) on HomeViewModel, set it
once during initialization (e.g., in syncSystemState() or loadPlatform() by
calling profileRepository.getUser().first() there) and then update mapReposToUi
to use cachedCurrentLogin instead of calling
profileRepository.getUser().first(); ensure cachedCurrentLogin is updated
whenever user state changes and remove the inline
profileRepository.getUser().first() calls in mapReposToUi to avoid repeated
suspending lookups.

In
`@feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt`:
- Around line 354-369: profileRepository.getUser().first() is being called
repeatedly to compute currentLogin (used when building DiscoveryRepositoryUi and
in appendExploreResults); cache the current username once (e.g., a private var
cachedCurrentLogin) during initialization or inside syncSystemState() and update
it on auth changes, then replace direct calls to
profileRepository.getUser().first() with the cachedCurrentLogin when computing
isCurrentUserOwner in the DiscoveryRepositoryUi mapping and in
appendExploreResults so the suspending profile lookup is not performed on every
search/page load.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b56d9024-2a6b-4b1d-b3dd-6fe327305bd7

📥 Commits

Reviewing files that changed from the base of the PR and between ab5a617 and 5f3de6d.

📒 Files selected for processing (20)
  • 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
  • core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt
  • core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/model/DiscoveryRepositoryUi.kt
  • feature/home/presentation/build.gradle.kts
  • feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt
  • feature/search/presentation/build.gradle.kts
  • feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

@rainxchzed rainxchzed merged commit 872b4e3 into main May 12, 2026
1 check passed
@rainxchzed rainxchzed deleted the feat/e20-self-owned-badge branch May 12, 2026 14:44
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