Skip to content

feat(discovery): expand to 11 regions with language pickers and richer recommendations - #953

Merged
ashwkun merged 4 commits into
masterfrom
feat/catalog-v3-shared-region-feed-onboarding
Jul 28, 2026
Merged

feat(discovery): expand to 11 regions with language pickers and richer recommendations#953
ashwkun merged 4 commits into
masterfrom
feat/catalog-v3-shared-region-feed-onboarding

Conversation

@ashwkun

@ashwkun ashwkun commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Expand chart/discovery support to 11 storefronts (USA, India, UK, plus France, Germany, Netherlands, Singapore, Spain, Brazil, Russia, Indonesia) with matching content-language allowlists.
  • New Settings / onboarding UI to pick content region and languages (English locked, up to 4; recommended set seeds on region change) for charts and personalized recommendations.
  • Richer recommendations: larger candidate pool and Qwen3 embeddings-backed discovery (catalog/v3 path; retired live content/sections/v1).
  • Shared discovery poster cards and UI refresh across Home, Explore, and search/onboarding suggestion surfaces (taste-lane chips, equal-height posters, vibe headers).
  • Centralize ContentRegions / language helpers in :core:model aligned with the proxy.

Listener impact

What changes in the user’s life

  • Charts and recommendations work in seven additional countries beyond the original core markets, with languages that match each storefront (e.g. French in France, Hindi in India, Chinese in Singapore).
  • A clearer country + language picker in Settings (and onboarding) so listeners control where charts come from and which languages recommendations prefer.
  • Recommendations feel stronger and more relevant — bigger pool to pick from, powered by the updated Qwen embedding model on the discovery backend.
  • Home, Explore, and search/suggestion screens look more consistent: refreshed cards, mood/vibe browsing, and an easier “Designed for you” onboarding grid.

Test plan

  • Settings → Library: switch among the 11 regions; confirm language chips reset to that country’s recommended set
  • Toggle content languages (English stays locked; max 4); charts/recs respect the selection after refresh
  • Home “Based on Your Taste” / Explore For You / search idle moods render and open correctly
  • Fresh onboarding → Designed for you: lane chips, select grid, detail sheet, finish
  • Similar episodes on episode info still load
  • Wait for PR testDebugUnitTest + coderabbit-threads-resolved green

Drop the live sections/v1 client in favor of catalog/v3, move ContentRegions
and discovery poster cards into shared cores, redesign onboarding suggestion
lanes, and clear detekt LongMethod/parameter debt for CI.
@ashwkun ashwkun added user-impact-medium Noticeable but not headline — include in README when space allows backend-change Touches backend/proxy/infra — pairable with any user-impact level labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ashwkun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4cd500be-7beb-4791-885b-e9d99a0584c9

📥 Commits

Reviewing files that changed from the base of the PR and between 76be9b2 and d349c99.

📒 Files selected for processing (7)
  • core/model/src/test/java/cx/aswin/boxlore/core/model/ContentLanguageSelectionTest.kt
  • feature/onboarding/README.md
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingDiscoveryLocale.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelAi.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelGenre.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelSearch.kt
  • feature/onboarding/src/test/java/cx/aswin/boxlore/feature/onboarding/OnboardingDiscoveryLocaleTest.kt
📝 Walkthrough

Walkthrough

This PR adds region-aware content language preferences, propagates them through discovery and recommendation APIs, introduces shared design-system components, removes the content-sections v1 contract, redesigns onboarding suggestions and welcome animations, and makes OPML import dialogs full-screen.

Changes

Content language and discovery

Layer / File(s) Summary
Region and language preferences
core/model/*, core/prefs/*, app/src/main/java/.../navigation/*, feature/home/.../settings/*, core/designsystem/.../RegionSegmentedSelector.kt
Adds canonical region and language normalization, persistence, settings wiring, and region-language picker UI.
Catalog and network contracts
core/catalog/*, core/network/*
Adds language-aware requests and repository APIs, introduces SimilarEpisodesQuery, and removes the content/sections/v1 models and endpoint.
Discovery caller propagation
feature/home/*, feature/explore/*, core/playback/*, core/downloads/*, feature/info/*
Passes current content languages into Home, Explore, personalized recommendations, curated content, similar episodes, downloads, and smart queue requests.
Shared discovery components
core/designsystem/.../components/*, feature/home/.../components/*
Adds shared media cards and spacing tokens, moves reusable cards into the design system, and updates Home consumers.
Onboarding redesign
feature/onboarding/src/main/java/.../*
Adds lane-based podcast suggestions, selectable grid cards, detail sheets, regional onboarding language requests, and revised welcome entrance and marquee animation behavior.
Application shell and documentation
app/src/main/java/.../OpmlImportDialog.kt, */README.md, docs/recommendation-system.md
Makes OPML import dialogs full-screen and updates module and recommendation-system documentation for the revised contracts and UI structure.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (5 errors, 1 warning)

Check name Status Explanation Resolution
Unresolved Review Threads ❌ Error The invalid-language rejection test is still missing, and there are no hermetic tests covering the onboarding request country/language payloads. Add/restore the missing tests (e.g., assertNull for invalid language codes and request-capture tests for onboarding viewmodels), then mark or dismiss those threads.
Architecture Compliance ❌ Error FAIL: PodcastRepository drops content_sections_cache for content_catalog_cache via plain SharedPreferences, but ARCHITECTURE.md says SharedPreferences filenames must stay stable. Add a migration bridge (e.g. PrefsFileMigrator) or keep the old file name so existing installs retain the cache state.
Module Readme Updated ❌ Error core/downloads and core/playback changed src/main Kotlin, but their README.md files were not modified in the PR. Add edits to core/downloads/README.md and core/playback/README.md (or remove those production Kotlin changes) to satisfy the module-README rule.
Jvm Tests For Changed Logic ❌ Error Helper tests exist, but the new ViewModel/repository language plumbing in Home/Explore/Info/Downloads/SmartQueue/UserPrefs has no hermetic JVM coverage. Add JVM tests for contentLanguages propagation, request construction, and cancellation/error paths in those components, or extract testable helpers.
Title check ❌ Error The title follows Conventional Commits and matches the change, but it exceeds the ~72 character limit. Shorten it to fit the length limit, e.g. "feat(discovery): add language pickers for 11 regions".
Docstring Coverage ⚠️ Warning Docstring coverage is 14.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Description check ✅ Passed The description is clearly related to the changeset and summarizes the main discovery, settings, onboarding, and recommendation updates.
✨ Finishing Touches
  • 🛠️ update changelog

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

Collapse FeedMediaCard chrome slots and extract region/onboarding
helpers so Sonar new-code quality gate can pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
core/playback/src/main/java/cx/aswin/boxlore/core/playback/SmartQueueSources.kt (1)

160-210: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add the required playback README update and JVM tests.

This changes Smart Queue’s discovery inputs, but the cohort includes neither a core/playback/README.md update nor tests for preference success, fallback on preference failure, and cancellation propagation.

As per coding guidelines, “Whenever production Kotlin under core//src/main/ changed, the matching module README.md must also be modified” and “must add or extend hermetic JVM unit tests”; as per path instructions, “Require hermetic JVM tests for new/changed engines, DAOs math, and pure helpers.”

🤖 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/playback/src/main/java/cx/aswin/boxlore/core/playback/SmartQueueSources.kt`
around lines 160 - 210, Update core/playback/README.md to document the Smart
Queue language inputs, and add hermetic JVM tests covering
getPersonalizedRecommendations and getSimilarEpisodes when preferences load
successfully, when loading fails and languages fall back to null, and when
CancellationException propagates. Target the production methods in
SmartQueueSources and verify repository calls receive the expected languages.

Sources: Coding guidelines, Path instructions

feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/ExploreViewModel.kt (1)

727-741: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Refresh Explore recommendations when content languages change.

fetchPersonalizedRecommendations() now samples contentLanguagesStream, but the collector at Lines 315-321 only observes region and subscriptions. Changing languages therefore leaves the existing recommendations stale until another trigger occurs.

Include contentLanguagesStream in that collector and add a regression test.

Proposed fix
 combine(
     userPrefs.regionStream,
+    userPrefs.contentLanguagesStream,
     subscriptionRepository.subscribedPodcastIds,
-) { _, _ -> }.collect {
+) { _, _, _ -> }.collect {
     fetchPersonalizedRecommendations()
 }
🤖 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/explore/src/main/java/cx/aswin/boxlore/feature/explore/ExploreViewModel.kt`
around lines 727 - 741, Update the collector that triggers
fetchPersonalizedRecommendations() to observe contentLanguagesStream alongside
region and subscription changes, ensuring language updates refresh Explore
recommendations. Add a regression test verifying that changing content languages
invokes the recommendation refresh.

Source: Path instructions

core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt (1)

680-694: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add hermetic JVM coverage for the changed language-propagation paths.

  • core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt#L680-L694: test selected-language forwarding and timeout fallback.
  • core/downloads/src/main/java/cx/aswin/boxlore/core/downloads/SmartDownloadManager.kt#L182-L190: test forwarding languages to personalized recommendations.
  • feature/info/src/main/java/cx/aswin/boxlore/feature/info/EpisodeInfoViewModel.kt#L426-L443: test construction of the language-aware similar-episodes query.
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeViewModelLoadData.kt#L90-L98: test language-change flow restart and downstream propagation.

As per path instructions, substantially changed production Kotlin must add or extend hermetic JVM tests under the corresponding src/test directory.

🤖 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/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt`
around lines 680 - 694, Add or extend hermetic JVM tests under the corresponding
src/test directories for all four changed paths:
AutoBrowseTreeBuilder.getCuratedChildren must cover selected-language forwarding
and the timeout fallback; SmartDownloadManager must cover forwarding languages
to personalized recommendations; EpisodeInfoViewModel must cover construction of
the language-aware similar-episodes query; and HomeViewModelLoadData must cover
language-change flow restart and downstream propagation. Update
core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt:680-694,
core/downloads/src/main/java/cx/aswin/boxlore/core/downloads/SmartDownloadManager.kt:182-190,
feature/info/src/main/java/cx/aswin/boxlore/feature/info/EpisodeInfoViewModel.kt:426-443,
and
feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeViewModelLoadData.kt:90-98
only as needed to support the tests.

Source: Path instructions

🤖 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/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepository.kt`:
- Around line 546-549: Update the catch block in the similar-episode loading
flow to rethrow CancellationException before handling other exceptions. Preserve
the existing error logging and emptyList fallback for non-cancellation failures,
matching the cancellation handling in getNetworkEpisodesPaginated,
getRssPodcastDetails, and getPersonalizedRecommendations.
- Around line 516-539: Add a MockWebServer-backed regression test in
PodcastRepositoryCatalogTest.kt for getSimilarEpisodes, using an RSS podcast ID
and negative episode ID, and assert the serialized SimilarEpisodesRequest
normalizes those IDs to "0", canonicalizes the country, and includes the
expanded query languages. Follow existing catalog test setup and
request-assertion patterns.

In
`@core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryRecommendations.kt`:
- Around line 17-20: Remove the ad-hoc lowercase-and-length validation for
resolvedCountry in the recommendations request construction. Reuse the already
canonical country supplied by PodcastRepository.getPersonalizedRecommendations,
or apply ContentRegions.canonicalize consistently, so valid values such as
global-style storefronts are not replaced with "us".

In
`@core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentMappingHelpersTest.kt`:
- Around line 49-63: Extend resolveContentLanguagesForQuery tests with
emptyList() and an unmapped country such as "fr", asserting the expected
fallback behavior. Add a duplicate and mixed-case language input case to verify
deduplication and stable ordering, while preserving the existing null and
mapped-country assertions.

In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/EqualHeightPosterGrid.kt`:
- Around line 17-21: Complete the shared-grid migration by removing the
duplicate EqualHeightPosterGrid implementation from HomeFeedRecommendations and
updating its usages to import and call
core.designsystem.components.EqualHeightPosterGrid. Preserve the existing Home
grid behavior and parameters while relying solely on the shared component.

In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt`:
- Around line 37-48: Reduce the parameter count of FeedMediaCard by grouping its
related display data and/or composable slots into a small dedicated model,
keeping the API within the configured Sonar limit. Update the call sites and
preserve the existing title, subtitle, click, modifier, line-limit, badge, and
overlay behavior.

In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedPosterSpacing.kt`:
- Around line 11-15: Update TitleLineHeight and textFootHeight so the reserved
poster-foot height scales with the title typography’s font scale, deriving the
line height from the actual text style in a density-aware Compose context
instead of using a fixed 18.dp value; preserve the minimum one-line behavior.

In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt`:
- Around line 191-205: Update the hint Surface in the
showOffMarketHint/offMarket block to use
MaterialTheme.colorScheme.secondaryContainer directly as its color, removing the
translucent copy alpha while preserving the existing shape, content color, and
layout.
- Around line 323-392: Reduce cognitive complexity in RegionPickerOptionRow by
extracting selected/unselected presentation values and recommended-label
rendering into focused helper functions or composables. Keep
RegionPickerOptionRow responsible for layout and selection behavior, while
preserving the existing colors, typography, and conditional suggested-language
display.
- Around line 132-206: Add hermetic JVM coverage for the language-selection
behavior used by ContentLanguageChipRow and its LanguageChipSection transitions.
Extract the selection transition logic into a testable production symbol if
necessary, then add or extend the related Test.kt under src/test to verify
English locking, the MAX_LANGUAGES limit, deselection at capacity,
normalization, and off-market selections. Ensure tests cover both valid
transitions and unchanged selections when constraints reject an update.
- Around line 387-390: Update the RadioButton inside the region selector to use
a null onClick handler, relying on the parent selectable action for interaction
and exposing only one accessible control. Preserve the existing selected state
and onSelect behavior on the parent.

In
`@core/prefs/src/main/java/cx/aswin/boxlore/core/prefs/UserPreferencesRepository.kt`:
- Around line 96-112: Serialize setRegion and setContentLanguages with one
repository mutex, and move setContentLanguages’ region lookup inside the same
dataStore.edit preferences snapshot instead of using regionStream.first().
Preserve setRegion’s language reset and ensure concurrent writes cannot
overwrite it. Add or extend the related repository Test.kt with a regression
test for the ordering, including default and malformed stored-language cases.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt`:
- Around line 92-101: Update the OutlinedCard modifier in the onboarding
suggestion card, including the corresponding card instance around the second
referenced section, to expose its subscription state through accessibility
semantics. Mark each card as selected when isSubscribed is true and assign an
appropriate selection-control role, while preserving the existing click behavior
and visuals.
- Around line 156-178: Update the IconButton containing Icons.Rounded.Info to
provide a 48.dp minimum interactive touch target while preserving the existing
36.dp visual icon/circle sizing. Avoid allowing the card’s subscription toggle
to receive taps within the expanded info-button target.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionsScreen.kt`:
- Around line 86-92: The isLoading calculation in AiSuggestionsScreen must not
treat every empty, error-free state as an active load. Gate the second
empty-state clause on an in-flight flag such as uiState.isLoadingPodcasts or
uiState.isAiLoading, while preserving the existing first clause, so completed
empty responses render the “No suggestions yet” state.
- Around line 327-405: Reduce cognitive complexity in SuggestionsLaneChip and
SuggestionsFinishBar by extracting their conditional logic into focused helpers:
provide a laneChipColors(selected) holder for container, content, border, and
badge colors, and a pure finishCtaLabel(uiState, selectedCount): String for CTA
text. Update both composables to consume these helpers, and add unit coverage
for finishCtaLabel under src/test while preserving existing behavior.
- Around line 211-224: In the charts branch of onToggleAll within
AiSuggestionsScreen, simplify the lane.podcasts forEach toggle logic by
combining the two conditional checks into one condition based on whether
allSelected differs from each podcast’s selected state. Preserve the existing
behavior of toggling only podcasts that need to change, while leaving the
non-charts path unchanged.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingScreen.kt`:
- Around line 326-344: Update the onboarding entrance interaction logic around
chromeInteractive and the CTA click handlers so welcome actions are not ignored
during the full WelcomeEntranceMs animation. Allow the CTAs to respond once
their reveal segment completes (about 0.72 progress), or have the first
interaction snap entranceProgress to 1f before executing the action; apply the
same behavior to the additional CTA logic near the referenced second location.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelGenre.kt`:
- Around line 83-86: Canonicalize the country field wherever onboarding requests
pair it with recommendedLanguages: update OnboardingViewModelGenre.kt lines
83-86, OnboardingViewModelSearch.kt lines 320-322 and 424-426, and
OnboardingViewModelAi.kt lines 353-356 to use
ContentRegions.canonicalize(currentRegion). In the OPML request at
OnboardingViewModelSearch.kt lines 424-426, hoist the single _uiState.value read
into a local so country and languages use the same state snapshot.

In
`@feature/onboarding/src/test/java/cx/aswin/boxlore/feature/onboarding/OnboardingSuggestionsLanesTest.kt`:
- Around line 39-57: Add tests in OnboardingSuggestionsLanesTest covering
purposeForCurriculumTitle for a blank title and for a title matching no rule,
asserting each fallback purpose, and cover chartsPurpose when no genres are
selected, asserting its empty-selection result. Keep the existing lane and
clampIndex tests unchanged.

---

Outside diff comments:
In
`@core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt`:
- Around line 680-694: Add or extend hermetic JVM tests under the corresponding
src/test directories for all four changed paths:
AutoBrowseTreeBuilder.getCuratedChildren must cover selected-language forwarding
and the timeout fallback; SmartDownloadManager must cover forwarding languages
to personalized recommendations; EpisodeInfoViewModel must cover construction of
the language-aware similar-episodes query; and HomeViewModelLoadData must cover
language-change flow restart and downstream propagation. Update
core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt:680-694,
core/downloads/src/main/java/cx/aswin/boxlore/core/downloads/SmartDownloadManager.kt:182-190,
feature/info/src/main/java/cx/aswin/boxlore/feature/info/EpisodeInfoViewModel.kt:426-443,
and
feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeViewModelLoadData.kt:90-98
only as needed to support the tests.

In
`@core/playback/src/main/java/cx/aswin/boxlore/core/playback/SmartQueueSources.kt`:
- Around line 160-210: Update core/playback/README.md to document the Smart
Queue language inputs, and add hermetic JVM tests covering
getPersonalizedRecommendations and getSimilarEpisodes when preferences load
successfully, when loading fails and languages fall back to null, and when
CancellationException propagates. Target the production methods in
SmartQueueSources and verify repository calls receive the expected languages.

In
`@feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/ExploreViewModel.kt`:
- Around line 727-741: Update the collector that triggers
fetchPersonalizedRecommendations() to observe contentLanguagesStream alongside
region and subscription changes, ensuring language updates refresh Explore
recommendations. Add a regression test verifying that changing content languages
invokes the recommendation refresh.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 87f3618a-32a4-4c76-a274-bc3cece54566

📥 Commits

Reviewing files that changed from the base of the PR and between 01bfbda and c60e25b.

📒 Files selected for processing (75)
  • app/README.md
  • app/src/main/java/cx/aswin/boxlore/navigation/NavGraphLibrarySettingsDestinations.kt
  • app/src/main/java/cx/aswin/boxlore/navigation/NavGraphWiring.kt
  • app/src/main/java/cx/aswin/boxlore/ui/BoxLoreAppRoot.kt
  • app/src/main/java/cx/aswin/boxlore/ui/libraryimport/OpmlImportDialog.kt
  • core/catalog/README.md
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepository.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentCache.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentMapping.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryRecommendations.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/SimilarEpisodesQuery.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/backup/LibraryBackupManager.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/ContentSectionsCachePolicy.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/ContentSignalEnrichment.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/GroupedContentSectionProvider.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentMappingHelpersTest.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/content/ContentSectionsCachePolicyTest.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/content/ContentSignalEnrichmentTest.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/content/GroupedContentSectionsTest.kt
  • core/designsystem/README.md
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/BoxLoreLogo.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/CuratedEpisodeCard.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/EqualHeightPosterGrid.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedPosterSpacing.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt
  • core/designsystem/src/test/java/cx/aswin/boxlore/core/designsystem/components/EqualHeightPosterGridTest.kt
  • core/designsystem/src/test/java/cx/aswin/boxlore/core/designsystem/components/RegionAliasHelpersTest.kt
  • core/downloads/src/main/java/cx/aswin/boxlore/core/downloads/SmartDownloadManager.kt
  • core/model/README.md
  • core/model/src/main/java/cx/aswin/boxlore/core/model/ContentRegion.kt
  • core/model/src/test/java/cx/aswin/boxlore/core/model/ContentRegionsTest.kt
  • core/network/README.md
  • core/network/src/main/java/cx/aswin/boxlore/core/network/BoxLoreApi.kt
  • core/network/src/main/java/cx/aswin/boxlore/core/network/model/OnboardingModels.kt
  • core/network/src/main/java/cx/aswin/boxlore/core/network/model/PodcastIndexModels.kt
  • core/network/src/main/java/cx/aswin/boxlore/core/network/model/SyncModels.kt
  • core/network/src/test/java/cx/aswin/boxlore/core/network/BoxLoreApiContractTest.kt
  • core/network/src/test/java/cx/aswin/boxlore/core/network/model/ContentSectionsV1RequestSerializationTest.kt
  • core/network/src/test/resources/fixtures/content_sections_v1.json
  • core/playback/src/main/java/cx/aswin/boxlore/core/playback/SmartQueueSources.kt
  • core/playback/src/main/java/cx/aswin/boxlore/core/playback/service/auto/AutoBrowseTreeBuilder.kt
  • core/prefs/README.md
  • core/prefs/src/main/java/cx/aswin/boxlore/core/prefs/UserPreferenceKeys.kt
  • core/prefs/src/main/java/cx/aswin/boxlore/core/prefs/UserPreferencesRepository.kt
  • docs/recommendation-system.md
  • feature/explore/README.md
  • feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/ExploreScreen.kt
  • feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/ExploreViewModel.kt
  • feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/components/ExploreBrowseHeaders.kt
  • feature/explore/src/main/java/cx/aswin/boxlore/feature/explore/components/ExploreCards.kt
  • feature/home/README.md
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeFeedEditorialRows.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeFeedRecommendations.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/HomeViewModelLoadData.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/components/BecauseYouLikeSection.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/components/ForYouSection.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/components/HomeFeedSpacing.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/components/PodcastCard.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/logic/HomeHeroLogic.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/settings/SettingsScreen.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/settings/pages/LibrarySettingsPage.kt
  • feature/home/src/test/java/cx/aswin/boxlore/feature/home/logic/HomeHeroLogicBranchesTest.kt
  • feature/info/README.md
  • feature/info/src/main/java/cx/aswin/boxlore/feature/info/EpisodeInfoViewModel.kt
  • feature/onboarding/README.md
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionRowCard.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionsScreen.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingScreen.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingSuggestionsLanes.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelAi.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelGenre.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelSearch.kt
  • feature/onboarding/src/test/java/cx/aswin/boxlore/feature/onboarding/OnboardingSuggestionsLanesTest.kt
💤 Files with no reviewable changes (10)
  • core/network/src/test/resources/fixtures/content_sections_v1.json
  • core/network/src/test/java/cx/aswin/boxlore/core/network/model/ContentSectionsV1RequestSerializationTest.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/content/ContentSignalEnrichmentTest.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/ContentSignalEnrichment.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/ContentSectionsCachePolicy.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/content/ContentSectionsCachePolicyTest.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionRowCard.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/content/GroupedContentSectionProvider.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentCache.kt
  • core/network/src/test/java/cx/aswin/boxlore/core/network/BoxLoreApiContractTest.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt (1)

37-124: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add FeedMediaCard test coverage.
FeedMediaCard now owns title-line clamping, subtitle/no-subtitle layout, fixed-height title-only rendering, and imageChrome placement; add a focused FeedMediaCardTest under core/designsystem/src/test.

🤖 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/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt`
around lines 37 - 124, The new FeedMediaCard behavior lacks focused tests. Add
FeedMediaCardTest under core/designsystem/src/test covering titleMaxLines
clamping, subtitle and no-subtitle rendering, fixed-height title-only layout,
and imageChrome placement; use the existing Compose testing conventions and
target the FeedMediaCard composable directly.

Source: Path instructions

🤖 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/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionsScreen.kt`:
- Around line 517-538: The search-flow branch in suggestionsFinishCtaLabel must
not return “Start without subscribing” when any podcast is selected. After
checking selected curriculum recommendations, use a generic subscribe label
whenever uiState.subscribedPodcastIds is non-empty, while preserving the
existing recommended-count label when applicable and the non-search flow
behavior.

---

Outside diff comments:
In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt`:
- Around line 37-124: The new FeedMediaCard behavior lacks focused tests. Add
FeedMediaCardTest under core/designsystem/src/test covering titleMaxLines
clamping, subtitle and no-subtitle rendering, fixed-height title-only layout,
and imageChrome placement; use the existing Compose testing conventions and
target the FeedMediaCard composable directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d1fd58f-eb5a-47e3-927f-b530af4846a4

📥 Commits

Reviewing files that changed from the base of the PR and between c60e25b and f5907e5.

📒 Files selected for processing (5)
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/CuratedEpisodeCard.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedMediaCard.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt
  • feature/home/src/main/java/cx/aswin/boxlore/feature/home/components/PodcastCard.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionsScreen.kt

Rethrow cancellation in similar-episode fetches, canonicalize regions
consistently, fix empty-suggestions loader, extract language-toggle
logic with tests, and polish a11y/region UI nits.
@ashwkun ashwkun added user-impact-high Listeners clearly notice this change — prioritize README and notification and removed user-impact-medium Noticeable but not headline — include in README when space allows labels Jul 28, 2026
@ashwkun ashwkun changed the title feat: retire content/sections/v1 and share region, feed, onboarding UX feat: 11 regions, language pickers, and richer discovery recommendations Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt (1)

422-438: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve paragraph boundaries while stripping HTML.

<p>First</p><p>Second</p> becomes FirstSecond, producing unreadable detail-sheet copy. Normalize block/break tags to whitespace before stripping remaining tags, and add hermetic regression tests for tags and entities.

Proposed fix
 internal fun String.stripHtml(): String {
-    val withoutTags = this.replace(Regex("<[^>]*>"), "")
+    val normalized =
+        replace(Regex("(?i)<\\s*(br\\s*/?|/p|/div|/li)\\s*>"), "\n")
+    val withoutTags = normalized.replace(Regex("<[^>]*>"), "")

As per coding guidelines, substantially changed production logic must add hermetic JVM unit tests; as per path instructions, changed main-source behavior requires related src/test coverage.

🤖 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/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt`
around lines 422 - 438, Update String.stripHtml so block and break tags such as
paragraph boundaries are first normalized to whitespace, then strip remaining
HTML tags and retain the existing entity decoding and trimming. Add hermetic JVM
tests under the related src/test path covering paragraph/break-tag preservation
and the existing entity conversions.

Sources: Coding guidelines, Path instructions

core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt (1)

423-435: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disable the locked English chip.

English is marked locked but remains enabled and clickable; applyToggle then silently returns null. Expose it as non-actionable instead.

Proposed fix
-                    enabled = isEnglish || canSelectMore,
+                    enabled = !isEnglish && canSelectMore,
🤖 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/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt`
around lines 423 - 435, Update the LanguagePreferenceChip configuration in the
language selector so the locked English option is not enabled or actionable.
Keep English identified as locked, but exclude isEnglish from the enabled
condition and prevent its onClick toggle path from being invoked, while
preserving selection behavior for other languages.
🤖 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/model/src/test/java/cx/aswin/boxlore/core/model/ContentLanguageSelectionTest.kt`:
- Around line 8-89: Add a hermetic JVM test in ContentLanguageSelectionTest
covering applyToggle with an invalid language code such as "xx", and assert that
it returns null. Keep the test focused on rejection of codes outside the
allowlist.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelAi.kt`:
- Around line 353-354: Add hermetic JVM unit coverage for the repeated
onboarding request payload wiring. In OnboardingViewModelAi.kt:353-354,
OnboardingViewModelGenre.kt:84-85, and OnboardingViewModelSearch.kt:321-322 and
:425-426, capture requests with a fake API or extract a pure builder, then
assert region aliases produce the same canonical country and recommended
languages for curriculum, genre-synthesis, search recommendation, and OPML
recommendation requests.

---

Outside diff comments:
In
`@core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt`:
- Around line 423-435: Update the LanguagePreferenceChip configuration in the
language selector so the locked English option is not enabled or actionable.
Keep English identified as locked, but exclude isEnglish from the enabled
condition and prevent its onClick toggle path from being invoked, while
preserving selection behavior for other languages.

In
`@feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt`:
- Around line 422-438: Update String.stripHtml so block and break tags such as
paragraph boundaries are first normalized to whitespace, then strip remaining
HTML tags and retain the existing entity decoding and trimming. Add hermetic JVM
tests under the related src/test path covering paragraph/break-tag preservation
and the existing entity conversions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2e47a48d-8586-4c9c-8d1f-120d50997d71

📥 Commits

Reviewing files that changed from the base of the PR and between f5907e5 and 76be9b2.

📒 Files selected for processing (15)
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepository.kt
  • core/catalog/src/main/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryRecommendations.kt
  • core/catalog/src/test/java/cx/aswin/boxlore/core/catalog/PodcastRepositoryContentMappingHelpersTest.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/FeedPosterSpacing.kt
  • core/designsystem/src/main/java/cx/aswin/boxlore/core/designsystem/components/RegionSegmentedSelector.kt
  • core/model/README.md
  • core/model/src/main/java/cx/aswin/boxlore/core/model/ContentLanguageSelection.kt
  • core/model/src/test/java/cx/aswin/boxlore/core/model/ContentLanguageSelectionTest.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionCards.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/AiSuggestionsScreen.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingScreen.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelAi.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelGenre.kt
  • feature/onboarding/src/main/java/cx/aswin/boxlore/feature/onboarding/OnboardingViewModelSearch.kt
  • feature/onboarding/src/test/java/cx/aswin/boxlore/feature/onboarding/OnboardingSuggestionsLanesTest.kt

@ashwkun ashwkun changed the title feat: 11 regions, language pickers, and richer discovery recommendations feat(discovery): expand to 11 regions with language pickers and richer recommendations Jul 28, 2026
…ge toggles

Extract discoveryLocaleForRegion for canonical country/languages payloads
and add hermetic coverage CodeRabbit requested on PR 953.
@sonarqubecloud

Copy link
Copy Markdown

@ashwkun
ashwkun enabled auto-merge (squash) July 28, 2026 14:51
@ashwkun
ashwkun merged commit 72cfc27 into master Jul 28, 2026
6 checks passed
@ashwkun
ashwkun deleted the feat/catalog-v3-shared-region-feed-onboarding branch July 28, 2026 14:58
ashwkun added a commit that referenced this pull request Jul 28, 2026
Split the thin one-liner into listener-facing New features / Improvements
and clarify Unreleased changelog entries so future sync-readme runs have
cleaner source material.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend-change Touches backend/proxy/infra — pairable with any user-impact level user-impact-high Listeners clearly notice this change — prioritize README and notification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant