Skip to content

feat: include starred repos in auto-link suggestions#686

Open
rainxchzed wants to merge 1 commit into
mainfrom
feat/637-starred-in-autolink
Open

feat: include starred repos in auto-link suggestions#686
rainxchzed wants to merge 1 commit into
mainfrom
feat/637-starred-in-autolink

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 28, 2026

Closes #637 (method 3).

@FumiyaSenro asked that when linking an already-installed app, the app should also look through the user's starred repos — most apps worth tracking are ones they've already starred, and the manual-URL fallback is annoying (nobody remembers repo URLs).

Rather than bloating the link sheet with a separate "pick from starred" step, this folds starred matching into the existing auto-match path. ExternalImportRepositoryImpl.resolveMatches (the single resolver behind both the "Scan GitHub Apps" link sheet AND the Obtainium import scan) now adds a 5th candidate source:

  • Fetches StarredRepository.getAllStarred() once per resolve.
  • Scores each starred repo against the device app by normalized name + package tail (alphanumeric-only, case-insensitive): exact match 0.78, substring match 0.60.
  • Emits RepoMatchSuggestion(source = STARRED, stars, description) merged with manifest / fingerprint / backend / Forgejo results.
  • Dedupe changed to sort-then-distinct so the highest-confidence entry survives when the same repo comes from multiple sources (previously distinctBy kept whichever source ran first, which could drop a higher-confidence starred hit).

Both flows surface starred matches automatically — no new screen, no extra button. Suggestions carry a "Starred" provenance chip.

Changes:

  • RepoMatchSource.STARRED + SuggestionSource.STARRED.
  • ExternalImportRepositoryImpl gains starredRepository dep (wired in SharedModule).
  • match_source_starred string + all 12 locales; chip label in LinkAppBottomSheet.
  • What's-new bullet (en + 12 locales).

Compile-verified :core:data + :feature:apps:presentation (Android) + :composeApp (jvm). Method 2 (reworking the "Add from Starred" screen to link-in-place) intentionally left out to keep this scoped.

Summary by CodeRabbit

  • New Features

    • When linking an installed GitHub app, the application now searches and suggests your starred repositories as potential matches, reducing the need to manually paste URLs.
    • Added "Starred" label to identify repository suggestions originating from your starred collection.
  • Documentation

    • Updated release notes and translations across multiple languages to document new features.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Walkthrough

This PR adds starred repository support to the external app import workflow. When scanning GitHub apps, the feature now fetches the user's starred repositories, normalizes and scores them against candidate labels, and surfaces matching starred repos as auto-suggestions alongside matches from manifest, fingerprint, backend, and Forgejo sources.

Changes

Starred Repository Match Feature

Layer / File(s) Summary
Domain model & dependency wiring
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/RepoMatchSource.kt, core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt
RepoMatchSource enum adds STARRED constant; Koin wiring injects StarredRepository into ExternalImportRepositoryImpl.
Data layer star matching
core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt
Fetches starred repos via injected StarredRepository, normalizes candidate tokens via normalizeMatchToken(), scores against starred repos using exact/contains confidence thresholds (with MIN_MATCH_TOKEN_LEN filtering), appends suggestions to existing sources, dedupes by host/owner/repo key, and sorts by confidence.
Presentation integration
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/model/RepoSuggestionUi.kt, feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/ExternalImportViewModel.kt, feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/components/LinkAppBottomSheet.kt
SuggestionSource enum adds STARRED; view model maps RepoMatchSource.STARRED to UI layer; MatchSourceChip renders starred source label using localized string.
Localization & release notes
core/presentation/src/commonMain/composeResources/values*/strings*.xml, core/presentation/src/commonMain/composeResources/files/whatsnew/{*,ar,bn,es,fr,hi,it,ja,ko,pl,ru,tr,zh-CN}/19.json, docs/release-notes/1.9.0.md
Adds match_source_starred string resource across English and 11 language variants; updates v1.9.0 release notes in all locales to document the new starred-repo matching feature and its integration with the "Scan GitHub Apps" workflow; creates main release notes documentation.

Sequence Diagram

sequenceDiagram
  participant ImportVM as ExternalImportViewModel
  participant ExternalImportRepository
  participant StarredRepository
  participant MatchScoringEngine as Match Scoring
  
  ImportVM->>ExternalImportRepository: resolveMatches(...)
  ExternalImportRepository->>StarredRepository: getAllStarred()
  StarredRepository-->>ExternalImportRepository: starred repo list
  ExternalImportRepository->>MatchScoringEngine: score candidates vs starred repos
  MatchScoringEngine->>MatchScoringEngine: normalizeMatchToken() per repo label/name
  MatchScoringEngine->>MatchScoringEngine: apply exact/contains confidence thresholds
  MatchScoringEngine-->>ExternalImportRepository: starred match suggestions
  ExternalImportRepository->>ExternalImportRepository: combine with manifest/fingerprint/backend matches
  ExternalImportRepository->>ExternalImportRepository: sort by confidence, dedupe by host/owner/repo
  ExternalImportRepository-->>ImportVM: resolved suggestions with STARRED source
  ImportVM->>ImportVM: toUi() map STARRED to SuggestionSource.STARRED
Loading

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly Related PRs

  • OpenHub-Store/GitHub-Store#679: Registers v19 release notes in KnownWhatsNewVersionCodes.ALL so the updated "What's New" JSON files can be loaded in the app.
  • OpenHub-Store/GitHub-Store#177: Implements the StarredRepository interface and data types that ExternalImportRepositoryImpl depends on for fetching the starred repos list.

Poem

🐰 A starred repo shines so bright,
Now matching apps becomes a light—
No paste, no search, just one swift scan,
Your favorites found in every plan! ✨

🚥 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 title accurately describes the main feature: including starred repositories in auto-link suggestions for app linking.
Linked Issues check ✅ Passed The PR implements the core requirement from #637: leveraging starred repos in the auto-link matching flow integrated into 'Scan GitHub Apps' without requiring manual URL entry.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: starred-repo matching integration, domain/UI updates, localization strings, and what's-new documentation; no unrelated code modifications detected.

✏️ 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/637-starred-in-autolink

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.

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: 5

🤖 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/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt`:
- Around line 270-272: The current use of runCatching around
starredRepository.getAllStarred().first() in ExternalImportRepositoryImpl (the
starred variable) swallows CancellationException and converts cancellations into
emptyList(); change it to explicitly preserve cancellation by replacing
runCatching with a try/catch that rethrows CancellationException and only
catches/logs non-cancellation exceptions, returning emptyList() on those; locate
the starredRepository.getAllStarred().first() call in
ExternalImportRepositoryImpl.kt and implement the try { val starred = ... }
catch (e: CancellationException) { throw e } catch (e: Exception) { Logger.d {
"starred fetch for match failed: ${e.message}" }; emptyList() } pattern.

In `@core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml`:
- Line 598: The Polish label for the starred source is ambiguous; update the
string resource named match_source_starred to use a clearer phrase for source
provenance by replacing its value "Z gwiazdką" with "Oznaczone gwiazdką" so it
matches the rest of the locale and reads unambiguously in the UI.

In `@core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml`:
- Line 598: The Russian label for the starred source is awkward; update the
string resource with name "match_source_starred" to use the consistent
terminology by replacing its value "Со звездой" with "Из избранного" so it
matches other locale strings that use "избранные" for starred repos.

In `@docs/release-notes/1.9.0.md`:
- Around line 5-48: The English release notes are missing the starred-repo
matching feature; insert a new subsection titled "### 🌟 Starred Repository
Matching" into the "New Features" section immediately after the "Desktop
Maturity" subsection and add the three bullets: "Linking installed apps now
matches against **your starred repositories**", "'Scan GitHub Apps'
automatically suggests starred repos as candidates", and "Reduces manual URL
pasting when linking apps you've already starred" so the English markdown
mirrors the localized JSON entries.
- Line 3: The document uses an h3 heading ("### The biggest visual overhaul
since launch. New Geist typography, hero app headers, redesigned Home cards with
platform glyphs, refreshed Library with updates banner + \"Ready to install\",
Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get
dedicated screens. Desktop finally feels native — window state persists, Windows
11 + macOS dark title bars, fluid content widths. Root installs work again on
modern Magisk via libsu rewrite.") immediately after the h1, skipping h2; change
that h3 to h2 (replace the leading "###" with "##") in
docs/release-notes/1.9.0.md so heading levels increment by one and restore
correct document structure and accessibility.
🪄 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: 2c85cba7-4ee5-4eaa-b33e-4606bc4cdf47

📥 Commits

Reviewing files that changed from the base of the PR and between 1636530 and f750cfa.

📒 Files selected for processing (33)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/RepoMatchSource.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/19.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
  • docs/release-notes/1.9.0.md
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/components/LinkAppBottomSheet.kt
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/ExternalImportViewModel.kt
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/model/RepoSuggestionUi.kt

Comment on lines +270 to +272
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve cancellation when reading starred repos

Line 270 uses runCatching, which catches CancellationException and turns cancellation into emptyList(). This can keep cancelled scans running.

Proposed fix
-        val starred = runCatching { starredRepository.getAllStarred().first() }
-            .onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
-            .getOrDefault(emptyList())
+        val starred = try {
+            starredRepository.getAllStarred().first()
+        } catch (e: CancellationException) {
+            throw e
+        } catch (e: Exception) {
+            Logger.d { "starred fetch for match failed: ${e.message}" }
+            emptyList()
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())
val starred = try {
starredRepository.getAllStarred().first()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Logger.d { "starred fetch for match failed: ${e.message}" }
emptyList()
}
🤖 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
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt`
around lines 270 - 272, The current use of runCatching around
starredRepository.getAllStarred().first() in ExternalImportRepositoryImpl (the
starred variable) swallows CancellationException and converts cancellations into
emptyList(); change it to explicitly preserve cancellation by replacing
runCatching with a try/catch that rethrows CancellationException and only
catches/logs non-cancellation exceptions, returning emptyList() on those; locate
the starredRepository.getAllStarred().first() call in
ExternalImportRepositoryImpl.kt and implement the try { val starred = ... }
catch (e: CancellationException) { throw e } catch (e: Exception) { Logger.d {
"starred fetch for match failed: ${e.message}" }; emptyList() } pattern.

<string name="match_source_fingerprint">Podpis</string>
<string name="match_source_search">Wyszukiwanie</string>
<string name="match_source_manual">Ręczne</string>
<string name="match_source_starred">Z gwiazdką</string>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a clearer Polish label for starred source.

Z gwiazdką is ambiguous in UI (can read like “with an asterisk”). For source provenance, use a clearer phrase like Oznaczone gwiazdką to match the rest of the locale.

Suggested fix
-    <string name="match_source_starred">Z gwiazdką</string>
+    <string name="match_source_starred">Oznaczone gwiazdką</string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<string name="match_source_starred">Z gwiazdką</string>
<string name="match_source_starred">Oznaczone gwiazdką</string>
🤖 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 `@core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml`
at line 598, The Polish label for the starred source is ambiguous; update the
string resource named match_source_starred to use a clearer phrase for source
provenance by replacing its value "Z gwiazdką" with "Oznaczone gwiazdką" so it
matches the rest of the locale and reads unambiguously in the UI.

<string name="match_source_fingerprint">Подпись</string>
<string name="match_source_search">Поиск</string>
<string name="match_source_manual">Вручную</string>
<string name="match_source_starred">Со звездой</string>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Russian label is awkward and inconsistent with existing terminology.

Со звездой reads unnatural here. This locale already uses “избранные” for starred repos, so the source chip should be aligned (e.g., Из избранного).

Suggested fix
-    <string name="match_source_starred">Со звездой</string>
+    <string name="match_source_starred">Из избранного</string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<string name="match_source_starred">Со звездой</string>
<string name="match_source_starred">Из избранного</string>
🤖 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 `@core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml`
at line 598, The Russian label for the starred source is awkward; update the
string resource with name "match_source_starred" to use the consistent
terminology by replacing its value "Со звездой" with "Из избранного" so it
matches other locale strings that use "избранные" for starred repos.

@@ -0,0 +1,52 @@
# 🚀 GitHub Store 1.9 — Design Refresh & Desktop Maturity

### The biggest visual overhaul since launch. New Geist typography, hero app headers, redesigned Home cards with platform glyphs, refreshed Library with updates banner + "Ready to install", Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels native — window state persists, Windows 11 + macOS dark title bars, fluid content widths. Root installs work again on modern Magisk via libsu rewrite.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix heading level skip.

Line 3 uses h3 (###) directly after the h1 title on line 1, skipping h2. Markdown heading levels should increment by one level at a time for proper document structure and accessibility.

📝 Proposed fix
-### The biggest visual overhaul since launch. New Geist typography, hero app headers, redesigned Home cards with platform glyphs, refreshed Library with updates banner + "Ready to install", Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels native — window state persists, Windows 11 + macOS dark title bars, fluid content widths. Root installs work again on modern Magisk via libsu rewrite.
+## The biggest visual overhaul since launch. New Geist typography, hero app headers, redesigned Home cards with platform glyphs, refreshed Library with updates banner + "Ready to install", Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels native — window state persists, Windows 11 + macOS dark title bars, fluid content widths. Root installs work again on modern Magisk via libsu rewrite.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### The biggest visual overhaul since launch. New Geist typography, hero app headers, redesigned Home cards with platform glyphs, refreshed Library with updates banner + "Ready to install", Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels native — window state persists, Windows 11 + macOS dark title bars, fluid content widths. Root installs work again on modern Magisk via libsu rewrite.
## The biggest visual overhaul since launch. New Geist typography, hero app headers, redesigned Home cards with platform glyphs, refreshed Library with updates banner + "Ready to install", Apple-style menus everywhere. Tablet two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels native — window state persists, Windows 11 + macOS dark title bars, fluid content widths. Root installs work again on modern Magisk via libsu rewrite.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 3-3: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 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 `@docs/release-notes/1.9.0.md` at line 3, The document uses an h3 heading ("###
The biggest visual overhaul since launch. New Geist typography, hero app
headers, redesigned Home cards with platform glyphs, refreshed Library with
updates banner + \"Ready to install\", Apple-style menus everywhere. Tablet
two-pane lands. Inner Details pages get dedicated screens. Desktop finally feels
native — window state persists, Windows 11 + macOS dark title bars, fluid
content widths. Root installs work again on modern Magisk via libsu rewrite.")
immediately after the h1, skipping h2; change that h3 to h2 (replace the leading
"###" with "##") in docs/release-notes/1.9.0.md so heading levels increment by
one and restore correct document structure and accessibility.

Comment on lines +5 to +48
## ✨ New Features

### 🎨 Design Overhaul
- New **Geist** typography across the app
- Hero app header on Details with clickable owner avatar + ✓ verification badge
- Redesigned Home cards now show **every platform** a repo ships installers for
- Refreshed Library with **Updates banner** and **Ready to install** section
- Apple-style dropdown menus (`GhsDropdownMenu`) across all overflow surfaces
- Real Apple + Tux icons for macOS / Linux platform indicators
- Native Compose contribution calendar on developer profiles (hidden for orgs)
- Clickable @mentions and clickable company in bios

### 📱 Tablet Two-Pane
- Home / Search / Library list on the left, repo opens on the right
- Draggable divider, persists across sessions
- Inner Details (About, What's New) slide *within* the right pane

### 🖥 Desktop Maturity
- **Window state persists** — size, position, maximized survive across launches (#664)
- **Windows 11 + macOS** title bars match system dark mode (#663)
- **Fluid content width** — Compact / Wide / Extra wide scale as 55% / 75% / 95% of window
- Real GitHub Store logo in side drawer (no more "G" placeholder)
- MenuBar with About / Feedback / Licenses / Privacy

## 🐛 Bug Fixes
- **Mirror + direct download race** corrupting destination file (#667)
- **Root installer** on Android 14+ / Magisk 27+ rewritten on **libsu** (#651)
- Linked apps now show **"Update to X"** immediately instead of stale "Install vX"
- Multi-flavor APK repos no longer show false "Update" CTA (#638)
- Long release tags no longer wrap into one-char vertical date columns
- URL paste verifies repo exists before showing match
- Dynamic color contrast on Search TextField across all palettes
- README + release-notes keep scroll position on return

## ⚡ Performance
- Markdown no longer re-renders on every download progress tick (~10×/sec → 0)
- Chunked progressive markdown — large READMEs paint first screen in <100ms
- `LazyColumn.animateItem()` across discovery / search / library
- Direction-aware bottom-nav transitions based on tab index

## 🧹 Cleanups
- Anonymous telemetry removed completely
- App info moved from Tweaks → Profile (where users look for it)
- Discovery platforms moved into Tweaks → Sources
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add missing starred repository matching feature documentation.

The primary feature of this PR—starred repository matching in the external app import workflow—is documented in all 12 localized JSON release notes (line 15 in each file) but is completely absent from this English markdown release notes file. Users reading docs/release-notes/1.9.0.md will not learn about this significant new capability.

📝 Suggested addition to the "New Features" section

Insert a new subsection after "Desktop Maturity" (around line 27):

### 🌟 Starred Repository Matching
- Linking installed apps now matches against **your starred repositories**
- "Scan GitHub Apps" automatically suggests starred repos as candidates
- Reduces manual URL pasting when linking apps you've already starred
🤖 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 `@docs/release-notes/1.9.0.md` around lines 5 - 48, The English release notes
are missing the starred-repo matching feature; insert a new subsection titled
"### 🌟 Starred Repository Matching" into the "New Features" section immediately
after the "Desktop Maturity" subsection and add the three bullets: "Linking
installed apps now matches against **your starred repositories**", "'Scan GitHub
Apps' automatically suggests starred repos as candidates", and "Reduces manual
URL pasting when linking apps you've already starred" so the English markdown
mirrors the localized JSON entries.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR folds the user's GitHub starred repos into the existing resolveMatches auto-match pipeline as a fifth candidate source, surfacing them in both the "Scan GitHub Apps" link sheet and the Obtainium import scan without adding new UI steps. Deduplication is upgraded from distinctBy (first-wins) to sort-then-distinct (highest-confidence-wins) to handle the same repo appearing from multiple sources.

  • ExternalImportRepositoryImpl.resolveMatches gains a starredMatches() helper that fetches the local Room starred cache once, normalizes app label and package tail, and scores each starred repo (exact=0.78, substring=0.60) before merging with manifest/fingerprint/backend/Forgejo results.
  • RepoMatchSource.STARRED and SuggestionSource.STARRED are added; LinkAppBottomSheet.MatchSourceChip and ExternalImportViewModel.toUi() handle the new enum values; 13-locale strings and what's-new content are included.

Confidence Score: 3/5

The core matching logic in ExternalImportRepositoryImpl needs a fix before this is safe to ship — specifically how it builds needles from the package name tail.

The pkgTail needle derivation takes the segment after the last . in the package name and uses it with contains matching against every starred repo. Apps following the widespread .android naming convention produce a generic needle "android" that substring-matches any starred repo whose name includes "android" — a category that covers a large fraction of an Android developer's starred list. Because 0.60 falls inside the preselect window, a completely unrelated repo would appear as the preselected suggestion for those apps when no better source has a match. The auto-link threshold of 0.85 keeps these matches from auto-linking, but preselection means users would need to actively dismiss the wrong choice.

core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt — specifically starredMatches() and the pkgTail needle construction.

Important Files Changed

Filename Overview
core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/ExternalImportRepositoryImpl.kt Core change: adds starred-repo matching to resolveMatches(). Contains a P1 false-positive issue with generic pkgTail needles (e.g. "android") and two P2 efficiency concerns (no skip-threshold guard, repeated DB reads per rescan).
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/RepoMatchSource.kt Adds STARRED enum value to RepoMatchSource. Straightforward, no issues.
core/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.kt Wires starredRepository into ExternalImportRepositoryImpl via Koin. Correct single-line change.
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/components/LinkAppBottomSheet.kt Adds STARRED case to MatchSourceChip and the matching string resource lookup. Correct and complete.
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/model/RepoSuggestionUi.kt Adds STARRED to SuggestionSource enum and maps it in ExternalImportViewModel.toUi(). No issues.
feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/import/ExternalImportViewModel.kt Adds STARRED case to the RepoMatchSource → SuggestionSource mapping in toUi(). No issues; the confidence thresholds (AUTO_LINK_THRESHOLD=0.85) are safely above STARRED_EXACT_CONFIDENCE=0.78.

Sequence Diagram

sequenceDiagram
    participant VM as ExternalImportViewModel
    participant Repo as ExternalImportRepositoryImpl
    participant FP as SigningFingerprintDao
    participant API as ExternalMatchApi
    participant Forgejo as ForgejoClientRegistry
    participant Star as StarredRepository (Room)

    VM->>Repo: resolveMatches(candidates)
    Repo->>FP: lookup(signingFingerprint) per candidate
    FP-->>Repo: fingerprintHits (confidence 0.92)
    Repo->>API: match(batch) [backend]
    API-->>Repo: backendResults
    Repo->>Forgejo: "searchRepositories(query) [skipped if existing >= 0.7]"
    Forgejo-->>Repo: forgejoHits
    Repo->>Star: getAllStarred().first()
    Star-->>Repo: starred list (Room cache)
    Note over Repo: starredMatches(): normalize label + pkgTail,<br/>score each starred repo (exact=0.78, contains=0.60)
    Repo->>Repo: sortedByDescending(confidence).distinctBy(owner/repo)
    Repo-->>VM: List of RepoMatchResult (deduped, highest confidence wins)
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(apps): include starred repos in aut..." | Re-trigger Greptile

Comment on lines +298 to +328
private fun starredMatches(
candidate: ExternalAppCandidate,
starred: List<zed.rainxch.core.domain.model.StarredRepository>,
): List<RepoMatchSuggestion> {
if (starred.isEmpty()) return emptyList()
val label = normalizeMatchToken(candidate.appLabel)
val pkgTail = normalizeMatchToken(candidate.packageName.substringAfterLast('.'))
val needles = listOf(label, pkgTail).filter { it.length >= MIN_MATCH_TOKEN_LEN }
if (needles.isEmpty()) return emptyList()

return starred.mapNotNull { repo ->
val repoName = normalizeMatchToken(repo.repoName)
if (repoName.length < MIN_MATCH_TOKEN_LEN) return@mapNotNull null
val confidence = needles.maxOf { needle ->
when {
needle == repoName -> STARRED_EXACT_CONFIDENCE
needle.contains(repoName) || repoName.contains(needle) -> STARRED_CONTAINS_CONFIDENCE
else -> 0.0
}
}
if (confidence <= 0.0) return@mapNotNull null
RepoMatchSuggestion(
owner = repo.repoOwner,
repo = repo.repoName,
confidence = confidence,
source = RepoMatchSource.STARRED,
stars = repo.stargazersCount,
description = repo.repoDescription,
sourceHost = null,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Generic pkgTail produces false-positive preselected suggestions

pkgTail is derived by taking everything after the last . in the package name. For any app with a .android suffix — an extremely common Android convention (com.twitter.android, com.snapchat.android, com.example.android) — the normalized tail is "android" (7 chars, passes MIN_MATCH_TOKEN_LEN). The contains check on line 314 then produces a STARRED_CONTAINS_CONFIDENCE = 0.60 match for every starred repo whose normalized name contains "android" (e.g. google/android-architecture-samples, JakeWharton/android-times-square, any *-android-* repo). Because 0.60 falls inside PRESELECT_MIN..PRESELECT_MAX = [0.5, 0.85], the wrong repo is surfaced as the preselected suggestion in the review card for every such commercial app that has no manifest/fingerprint/backend match. The same problem arises for other common suffixes: mobile, lite, beta, debug, release all pass the 4-char minimum and will substring-match large numbers of starred repos.

Fix in Claude Code

Comment on lines +270 to +272
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())
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 No skip-threshold guard on starred matching — unlike Forgejo search (which skips candidates with existing confidence ≥ 0.7), starredMatches() runs unconditionally for every candidate on every resolveMatches() call. For a user with 1,000+ starred repos and 50+ candidates that already have fingerprint/backend hits, this is 50,000+ in-memory iterations that produce only noise (the low-confidence starred entries won't win the dedupe). Adding the same guard used for Forgejo would eliminate this work at zero correctness cost.

Suggested change
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())
val hasHighConfidenceMatch = { packageName: String ->
val existing = listOfNotNull(
candidates.firstOrNull { it.packageName == packageName }?.manifestHint?.confidence,
fingerprintHits[packageName]?.confidence,
backendResults[packageName]?.maxOfOrNull { it.confidence },
forgejoHits[packageName]?.maxOfOrNull { it.confidence },
).maxOrNull() ?: 0.0
existing >= FORGEJO_SEARCH_SKIP_THRESHOLD
}
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())

Fix in Claude Code

Comment on lines +270 to +272
val starred = runCatching { starredRepository.getAllStarred().first() }
.onFailure { Logger.d { "starred fetch for match failed: ${it.message}" } }
.getOrDefault(emptyList())
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 Repeated full DB reads in per-package rescans

getAllStarred().first() is called on every resolveMatches() invocation, including inside rescanSinglePackage() which passes a single-element list. When the PackageMonitor detects a burst of installs (or the user retriggers individual rescans), each call issues a full Room query for the entire starred table. Hoisting the fetch outside resolveMatches or caching the starred list at the instance level (similar to how candidateSnapshot works) would avoid these redundant reads.

Fix in Claude Code

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