Skip to content

feat(m2): Browse — Discover, Search, Artist profile + redaction retirement - #44

Merged
cestercian merged 31 commits into
mainfrom
feature/m2-browse
Jul 26, 2026
Merged

feat(m2): Browse — Discover, Search, Artist profile + redaction retirement#44
cestercian merged 31 commits into
mainfrom
feature/m2-browse

Conversation

@cestercian

@cestercian cestercian commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • M2 Browse vertical slice: Artists + Search repositories (with Fake twins), Discover rails, Search with filters/pagination, Artist profile (hero/score/packages/reviews). Wired into client tabs; Book/Message dock CTAs stubbed until M3/M4.
  • Product-truth fix: Retire chat redaction (deleted Redaction.kt); docs/roadmap/checklists updated for Airbnb trust + request→accept booking. Tick F0–F2.
  • Parity tracker: docs/PARITY_CHECKLIST.md marks Discover/Search/Artist profile done.

Test plan

  • ./gradlew :app:assembleDebug green
  • ./gradlew :app:testDebugUnitTest green
  • Emulator/instrumented UI verification (not available on this machine — operator step)
  • Smoke Discover rails + Search debounce against artistant-dev
  • Open Artist profile from Discover/Search; confirm Book/Message stubs navigate or no-op as designed

Follow-ups

  • M3 Booking funnel (BookingsRepository, request→accept, Checkout/Confirmed “Request sent.”)
  • M4 Messages (verbatim bodies, system rows, FCM)
  • Wire Artist profile Book/Message dock when those features land

Closes the M2 Browse milestone (see docs/IMPLEMENTATION_ROADMAP.md).

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added artist discovery rails with featured, regional, new, and category-based listings.
    • Added artist search with text, city, category, price, score, sorting, facets, and pagination.
    • Added artist profile pages with packages, reviews, ratings, availability, pricing, and booking/message actions.
    • Added navigation from Discover and Search results to artist profiles.
  • Documentation

    • Updated product and migration guidance for the request → accept booking flow and trust-focused messaging.
    • Added an Android parity checklist and refreshed implementation status.
  • Chores

    • Removed chat redaction behavior; messages are now handled verbatim with trust-oriented guidance.

cestercian and others added 8 commits July 26, 2026 05:50
Durable tracker for screens/repos/stores/services with Jul-2026 product
deltas (redaction obsolete, request→accept booking).

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
AGP 8.9.1 nags on compileSdk 36; combination builds fine — silence the
sync noise until AGP catches up.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Redaction was scrapped on iOS Jul 2026 (mig 0071). Delete Redaction.kt +
tests; replace moat copy with Airbnb-style trust + request→accept booking
across CLAUDE/README/docs; mark F0–F2 done; fix README status.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Id-keyed hydrating ArtistsRepository and search_artists/facets SearchRepository
ported from iOS; Hilt binds; unit tests for cache/filter/cursor.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Wire DiscoverViewModel (concurrent search_artists rails) into client tabs,
replacing the Placeholder. Artist taps navigate to a profile stub route.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Debounced search with facet rails and pagination; artist profile hydrates
full stitch + reviews; Book/Message CTAs stubbed for M3/M4. Parity checklist
updated.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 113 files, which is 13 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb6c85af-6608-49d0-a009-284787be0b51

📥 Commits

Reviewing files that changed from the base of the PR and between a3b92d6 and 7fd6e5b.

📒 Files selected for processing (113)
  • CLAUDE.md
  • app/build.gradle.kts
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/in/artistant/app/ArtistantApplication.kt
  • app/src/main/java/in/artistant/app/MainActivity.kt
  • app/src/main/java/in/artistant/app/core/config/AppEnvironment.kt
  • app/src/main/java/in/artistant/app/core/di/RepositoryModule.kt
  • app/src/main/java/in/artistant/app/data/model/Booking.kt
  • app/src/main/java/in/artistant/app/data/model/GigRequest.kt
  • app/src/main/java/in/artistant/app/data/model/Message.kt
  • app/src/main/java/in/artistant/app/data/model/SearchTypes.kt
  • app/src/main/java/in/artistant/app/data/payments/PaymentResult.kt
  • app/src/main/java/in/artistant/app/data/repository/AccountRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ArtistLinksRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ArtistMediaRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/BookingsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/FakeArtistsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/MessagesRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/PackagesRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ReportsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/RequestsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ReviewsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/SamplesRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/SavedArtistsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ScoreRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/TechRiderRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/WizardProfileDraft.kt
  • app/src/main/java/in/artistant/app/designsystem/component/MonthCalendar.kt
  • app/src/main/java/in/artistant/app/designsystem/component/ScoreRing.kt
  • app/src/main/java/in/artistant/app/designsystem/component/Sparkline.kt
  • app/src/main/java/in/artistant/app/domain/score/ScoreBands.kt
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileViewModel.kt
  • app/src/main/java/in/artistant/app/feature/artisthome/ArtistHomeLogic.kt
  • app/src/main/java/in/artistant/app/feature/artisthome/ArtistHomeScreen.kt
  • app/src/main/java/in/artistant/app/feature/artisthome/ArtistHomeViewModel.kt
  • app/src/main/java/in/artistant/app/feature/availability/ManageAvailabilityScreen.kt
  • app/src/main/java/in/artistant/app/feature/availability/ManageAvailabilityViewModel.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingDetailScreen.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingDetailViewModel.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingDraftStore.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingScreen.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingSlots.kt
  • app/src/main/java/in/artistant/app/feature/booking/BookingViewModel.kt
  • app/src/main/java/in/artistant/app/feature/booking/CheckoutScreen.kt
  • app/src/main/java/in/artistant/app/feature/booking/CheckoutViewModel.kt
  • app/src/main/java/in/artistant/app/feature/booking/ConfirmedScreen.kt
  • app/src/main/java/in/artistant/app/feature/booking/RequestQuoteScreen.kt
  • app/src/main/java/in/artistant/app/feature/booking/RequestQuoteViewModel.kt
  • app/src/main/java/in/artistant/app/feature/booking/ReviewSheet.kt
  • app/src/main/java/in/artistant/app/feature/bookings/BookingsScreen.kt
  • app/src/main/java/in/artistant/app/feature/bookings/BookingsViewModel.kt
  • app/src/main/java/in/artistant/app/feature/epk/EpkScreen.kt
  • app/src/main/java/in/artistant/app/feature/epk/EpkViewModel.kt
  • app/src/main/java/in/artistant/app/feature/gigs/ArtistGigsScreen.kt
  • app/src/main/java/in/artistant/app/feature/gigs/ArtistGigsViewModel.kt
  • app/src/main/java/in/artistant/app/feature/gigs/GigRequestDetailScreen.kt
  • app/src/main/java/in/artistant/app/feature/gigs/GigRequestDetailViewModel.kt
  • app/src/main/java/in/artistant/app/feature/messages/ChatRealtimeLogic.kt
  • app/src/main/java/in/artistant/app/feature/messages/ChatScreen.kt
  • app/src/main/java/in/artistant/app/feature/messages/ChatViewModel.kt
  • app/src/main/java/in/artistant/app/feature/messages/MessagesScreen.kt
  • app/src/main/java/in/artistant/app/feature/messages/MessagesViewModel.kt
  • app/src/main/java/in/artistant/app/feature/messages/ThreadDetailsSheet.kt
  • app/src/main/java/in/artistant/app/feature/paywall/EntitlementStore.kt
  • app/src/main/java/in/artistant/app/feature/paywall/PaywallScreen.kt
  • app/src/main/java/in/artistant/app/feature/paywall/PaywallViewModel.kt
  • app/src/main/java/in/artistant/app/feature/profile/ProfileScreen.kt
  • app/src/main/java/in/artistant/app/feature/profile/ProfileViewModel.kt
  • app/src/main/java/in/artistant/app/feature/saved/SavedStore.kt
  • app/src/main/java/in/artistant/app/feature/score/ScoreBreakdownSheet.kt
  • app/src/main/java/in/artistant/app/feature/score/ScoreExplainerScreen.kt
  • app/src/main/java/in/artistant/app/feature/score/ScoreHistorySheet.kt
  • app/src/main/java/in/artistant/app/feature/search/SearchFilterSheet.kt
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt
  • app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt
  • app/src/main/java/in/artistant/app/feature/signup/NotifPermissionScreen.kt
  • app/src/main/java/in/artistant/app/feature/wizard/WizardLogic.kt
  • app/src/main/java/in/artistant/app/feature/wizard/WizardScreen.kt
  • app/src/main/java/in/artistant/app/feature/wizard/WizardViewModel.kt
  • app/src/main/java/in/artistant/app/navigation/ArtistTabsScaffold.kt
  • app/src/main/java/in/artistant/app/navigation/ArtistantNavHost.kt
  • app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt
  • app/src/main/java/in/artistant/app/navigation/PushEntryPoint.kt
  • app/src/main/java/in/artistant/app/navigation/Routes.kt
  • app/src/main/java/in/artistant/app/navigation/TabRouter.kt
  • app/src/main/java/in/artistant/app/platform/auth/SessionManager.kt
  • app/src/main/java/in/artistant/app/platform/billing/PlayBillingService.kt
  • app/src/main/java/in/artistant/app/platform/calendar/CalendarSyncPlanner.kt
  • app/src/main/java/in/artistant/app/platform/calendar/CalendarSyncService.kt
  • app/src/main/java/in/artistant/app/platform/media/UploadQueue.kt
  • app/src/main/java/in/artistant/app/platform/media/VideoTrimmer.kt
  • app/src/main/java/in/artistant/app/platform/media/WizardMediaCache.kt
  • app/src/main/java/in/artistant/app/platform/push/ArtistantMessagingService.kt
  • app/src/main/java/in/artistant/app/platform/push/PushService.kt
  • app/src/main/java/in/artistant/app/ui/RootViewModel.kt
  • app/src/main/res/xml/file_paths.xml
  • app/src/test/java/in/artistant/app/data/repository/BookingsRepositoryLogicTest.kt
  • app/src/test/java/in/artistant/app/data/repository/MessagesRepositoryLogicTest.kt
  • app/src/test/java/in/artistant/app/data/repository/SavedArtistsRepositoryLogicTest.kt
  • app/src/test/java/in/artistant/app/data/repository/WizardPublishReposLogicTest.kt
  • app/src/test/java/in/artistant/app/domain/score/ScoreBreakdownLogicTest.kt
  • app/src/test/java/in/artistant/app/feature/artisthome/ArtistHomeLogicTest.kt
  • app/src/test/java/in/artistant/app/feature/booking/CheckoutViewModelLogicTest.kt
  • app/src/test/java/in/artistant/app/feature/messages/ChatRealtimeLogicTest.kt
  • app/src/test/java/in/artistant/app/feature/wizard/WizardViewModelLogicTest.kt
  • app/src/test/java/in/artistant/app/navigation/PushPayloadRouterTest.kt
  • app/src/test/java/in/artistant/app/platform/calendar/CalendarSyncPlannerTest.kt
  • docs/FEATURE_CHECKLIST.md
  • docs/PARITY_CHECKLIST.md
  • gradle/libs.versions.toml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds the Android artist browsing flow: Supabase-backed artist, search, and review repositories; Discover, Search, and Artist Profile screens; navigation wiring; fake repositories and tests; and documentation updates for parity, booking, trust UI, and verbatim chat behavior.

Changes

Browse features

Layer / File(s) Summary
Artist data and repository contracts
app/src/main/java/in/artistant/app/data/model/*, app/src/main/java/in/artistant/app/data/repository/*, app/src/main/java/in/artistant/app/core/di/RepositoryModule.kt
Adds artist/search models, Supabase and fake repositories, artist hydration and caching, review loading, and Hilt bindings.
Search repository and state flow
app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt, app/src/main/java/in/artistant/app/data/repository/FakeSearchRepository.kt, app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt, app/src/test/java/in/artistant/app/{data/repository,feature/search}/*
Implements filtering, facets, pagination, RPC fallback handling, debounced search state, and related tests.
Discover and artist profile flows
app/src/main/java/in/artistant/app/feature/{discover,artist}/*, app/src/test/java/in/artistant/app/feature/discover/*
Adds concurrent Discover rails, artist profile loading, package selection, reviews, profile rendering, and feature logic tests.
Browse UI and navigation wiring
app/src/main/java/in/artistant/app/designsystem/component/*, app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt, app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt
Adds artist tiles, empty states, search controls/results, explicit tab destinations, artist profile routing, and route-aware bottom navigation.

Product truth and migration documentation

Layer / File(s) Summary
Onboarding and project status documentation
CLAUDE.md, README.md, gradle.properties
Updates project status, planning-document references, product description, maintainer test-scope wording, and compile SDK warning configuration.
Migration and API behavior documentation
docs/*
Updates parity, architecture, roadmap, checklist, message API, project structure, risks, screen inventory, booking flow, and chat guidance to reflect verbatim messaging, trust UI, and request-based booking.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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 captures the main browse features added and the redaction retirement work.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/m2-browse

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.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the Android browse vertical slice and expands supporting marketplace infrastructure.

  • Adds Discover rails, filtered and paginated artist search, and artist profile flows.
  • Adds repositories, models, navigation, and UI for later booking, messaging, media, calendar, push, and subscription milestones.
  • Retires chat redaction and updates product documentation for request-to-accept booking.

Confidence Score: 3/5

The PR is not yet safe to merge because filtered search still fails against pre-0073 backend deployments.

The compatibility retry preserves the new RPC parameter names with null values, so PostgREST continues resolving against the extended signature rather than falling back to the legacy search function.

Files Needing Attention: app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt

Important Files Changed

Filename Overview
app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt Implements RPC-backed artist search, facets, pagination, cover resolution, extended filters, and a legacy-server retry path.
app/src/main/java/in/artistant/app/feature/discover/DiscoverViewModel.kt Builds and exposes the Discover feed state from the new artist repository.
app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt Coordinates debounced search, filters, facets, and pagination for the Search screen.
app/src/main/java/in/artistant/app/feature/artist/ArtistProfileViewModel.kt Loads artist profile data and exposes profile-screen state and actions.
app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt Wires Discover, Search, artist profiles, and client-tab navigation together.

Reviews (14): Last reviewed commit: "docs: flip remaining PARITY rows for sco..." | Re-trigger Greptile

cestercian and others added 2 commits July 26, 2026 06:15
Port iOS request→accept booking create (pending_confirm), accept PATCH,
decline/cancel via cancel-booking EF, and gig_requests quote loop — with
Fake twins, Hilt binds, and reviews insert for ReviewSheet.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Client Booking→Checkout→Confirmed (“Request sent.”) flow, Bookings tab,
role-aware detail Accept/Decline, RequestQuote + ReviewSheet, wired from
Artist profile Book CTA. Tick F7/F11 parity rows.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>

@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: 8

🧹 Nitpick comments (7)
app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt (2)

111-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant nested condition — simplify.

!snapshot.hasActiveQuery already implies query.isBlank() && city == null && categories.isEmpty(), so the inner if is always true when the outer one is; this can be collapsed into a single check.

♻️ Proposed simplification
-        if (!snapshot.hasActiveQuery && snapshot.query.isBlank()) {
-            // Empty browse: still show top results so the tab isn't blank.
-            // Matching iOS empty-state rails when query+filters are empty — we
-            // leave results empty and show facets; only search when active.
-            if (snapshot.query.isBlank() && snapshot.city == null && snapshot.categories.isEmpty()) {
-                _state.update {
-                    it.copy(results = emptyList(), canLoadMore = false, loadError = null, isLoading = false)
-                }
-                nextCursor = null
-                return
-            }
-        }
+        if (!snapshot.hasActiveQuery) {
+            // Empty browse: matching iOS empty-state rails — leave results
+            // empty and show facets; only search when a query/filter is active.
+            _state.update {
+                it.copy(results = emptyList(), canLoadMore = false, loadError = null, isLoading = false)
+            }
+            nextCursor = null
+            return
+        }
🤖 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 `@app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt` around
lines 111 - 124, Collapse the nested condition in runSearch into a single guard
using !snapshot.hasActiveQuery, preserving the existing empty-state update,
nextCursor reset, and early return behavior.

125-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Raw exception message shown to the user; inconsistent with DiscoverViewModel's friendly-copy pattern.

loadError = t.message ?: "Search failed." surfaces raw transport/RPC error text, while DiscoverViewModel.messageFor maps the same class of errors to friendly copy. Consider reusing a similar mapping here for a consistent UX and to avoid leaking backend/RPC details.

🤖 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 `@app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt` around
lines 125 - 181, Update the exception handling in the search coroutine to map
failures through the same friendly-error pattern used by
DiscoverViewModel.messageFor instead of assigning t.message directly to
loadError. Preserve the existing fallback behavior and stale-generation guard
while ensuring backend or RPC details are not exposed to users.
app/src/main/java/in/artistant/app/data/model/Artist.kt (1)

67-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Raw hex Color(...) literals bypass the design-token system.

ArtistGradient hardcodes six palettes with raw hex Color(0xFF...) values rather than referencing AppColors. Per coding guidelines, raw hex values shouldn't be used directly in Kotlin/Compose code. If these brand gradients are meant to be content data rather than themeable UI, please confirm this is an accepted exception; otherwise consider sourcing the stops from AppColors.

🤖 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 `@app/src/main/java/in/artistant/app/data/model/Artist.kt` around lines 67 -
83, The ArtistGradient palettes currently use raw hex Color literals; replace
those stops with the corresponding AppColors design-token references. Update
only the palettes inside ArtistGradient, preserving the six palette order,
gradient stop order, and palette(index) clamping behavior.

Source: Coding guidelines

app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt (1)

116-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated artist_media cover-resolution logic across two repositories. Both files independently query artist_media filtered by artist_id/kind=photo, order by position, group by artist, and take the first row's storagePath through coverUrl() — the same rule implemented twice.

  • app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt#L116-L125: extract this query+mapping into a shared internal helper (e.g., a function taking a list of ids and returning Map<String, String>) that both repositories call.
  • app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt#L163-L183: replace resolveCovers with a call to the shared helper extracted from ArtistsRepository.kt, instead of re-implementing the same query.
🤖 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 `@app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt`
around lines 116 - 125, Extract the shared artist cover query and first-cover
mapping from ArtistsRepository into an internal helper accepting artist IDs and
returning Map<String, String>, preserving the artist_media filters, ordering,
grouping, and coverUrl() behavior. Update ArtistsRepository.kt lines 116-125 to
call the helper, and replace SearchRepository.kt lines 163-183 resolveCovers
implementation with the same shared helper call; both sites must use the
centralized logic.
app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt (2)

53-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a conditional over return@Scaffold in the bottomBar slot.

It compiles, but a non-local-looking return inside a composable slot reads as if it aborts the Scaffold call. Wrapping the NavigationBar in if (showBottomBar) { ... } states the intent plainly.

🤖 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 `@app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt` around
lines 53 - 54, Update the bottomBar slot in ClientTabsScaffold to conditionally
render the existing NavigationBar only when showBottomBar is true, removing the
return@Scaffold statement while preserving the current hidden and visible
behavior.

80-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hand-built "artist/$id" duplicates the route template and skips encoding.

Both call sites (Lines 81 and 87) reconstruct the path that Line 42 already declares, so the template and its usages can drift. Add one builder next to ARTIST_PROFILE_ROUTE and encode the argument.

♻️ Proposed refactor
 private const val ARTIST_PROFILE_ROUTE = "artist/{artistId}"
+
+private fun artistProfileRoute(artistId: String): String =
+    "artist/${Uri.encode(artistId)}"
-                DiscoverScreen(onArtistClick = { id -> nav.navigate("artist/$id") })
+                DiscoverScreen(onArtistClick = { id -> nav.navigate(artistProfileRoute(id)) })
-                SearchScreen(onArtistClick = { id -> nav.navigate("artist/$id") })
+                SearchScreen(onArtistClick = { id -> nav.navigate(artistProfileRoute(id)) })
🤖 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 `@app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt` around
lines 80 - 98, Define a shared artist-profile route builder next to
ARTIST_PROFILE_ROUTE that accepts an artist ID and URL-encodes it, then update
the DiscoverScreen and SearchScreen onArtistClick navigation calls to use this
builder instead of constructing "artist/$id" directly.
app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt (1)

88-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Color.White / Color.Black literals sidestep AppColors.

The scrim (Lines 62-69) and the on-image text colors are hardcoded. Even for over-photo content, these should be named tokens (e.g. colors.onMedia, colors.scrimTop) so the locked dark-only direction stays centrally controlled.

As per coding guidelines: "In Kotlin/Jetpack Compose UI, use AppColors, AppType, Space, Size, and Radii; never use raw hex, dp, or sp values."

🤖 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 `@app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt`
around lines 88 - 125, Replace the hardcoded Color.White and Color.Black values
in ArtistTile’s scrim and on-image text styling with the appropriate AppColors
tokens, such as colors.onMedia and colors.scrimTop. Use the existing theme color
access pattern and preserve the current contrast, alpha values, and layout
behavior.

Source: Coding guidelines

🤖 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 `@app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt`:
- Around line 45-46: Replace the raw tile dimensions and 1.dp borders with
shared AppTheme.dimens Size tokens: add the missing tile geometry and hairline
tokens in AppTheme.dimens, then use them in ArtistTile, ArtistProfileScreen, and
both affected SearchScreen sites. Update
app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt lines
45-46, app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt
lines 211-215, and
app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt lines 105 and
270; preserve the existing dimensions and border behavior through the new
tokens.

In `@app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt`:
- Around line 243-246: Update the review Row containing review.name and the
repeated star glyphs to expose a semantics contentDescription stating the review
rating out of five, and mark the star Text as decorative so screen readers do
not announce individual glyphs.

In `@app/src/main/java/in/artistant/app/feature/artist/ArtistProfileViewModel.kt`:
- Around line 42-60: Guard the network calls in refresh around
artistsRepository.find and artistsRepository.ensureFull with runCatching (or
equivalent exception handling), ensuring any failure updates _state with
isLoading = false and an appropriate loadError while preserving cached artist
data when available. Keep the existing successful full-artist flow and reviews
handling unchanged, and ensure exceptions cannot leave ArtistProfileViewModel
stuck loading.

In `@app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt`:
- Around line 262-275: Update the Chip composable’s Modifier chain to add button
semantics via clickable’s Role.Button and enforce the minimum touch height with
defaultMinSize using the existing AppTheme dimens Size token; preserve the
current styling, click behavior, and spacing.
- Around line 139-147: Update the snapshotFlow in LaunchedEffect(listState) so
near-end detection requires a measured, non-empty list: ensure totalItemsCount
is greater than zero and visibleItemsInfo is not empty before comparing the last
visible index. Preserve the existing threshold and loadMore() behavior after
measurement.
- Around line 162-168: Remove the redundant width = 160.dp argument from the
ArtistTile call in the search layout, keeping modifier = Modifier.weight(1f) as
the source of the tile’s width. Do not change the existing height or click
behavior.

In `@docs/FEATURE_CHECKLIST.md`:
- Around line 65-74: Update the F2 notification-permission checklist item to
list only POST_NOTIFICATIONS as completed, and explicitly state that FCM token
registration is deferred to M4. Remove the contradictory “FCM register”
completion wording while preserving the existing permission UI status and F9
dependency.

In `@README.md`:
- Around line 7-10: Update the README status banner to indicate that M2 Browse
is complete rather than stating it is next, while preserving the existing
references to the parity checklist and implementation roadmap.

---

Nitpick comments:
In `@app/src/main/java/in/artistant/app/data/model/Artist.kt`:
- Around line 67-83: The ArtistGradient palettes currently use raw hex Color
literals; replace those stops with the corresponding AppColors design-token
references. Update only the palettes inside ArtistGradient, preserving the six
palette order, gradient stop order, and palette(index) clamping behavior.

In `@app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt`:
- Around line 116-125: Extract the shared artist cover query and first-cover
mapping from ArtistsRepository into an internal helper accepting artist IDs and
returning Map<String, String>, preserving the artist_media filters, ordering,
grouping, and coverUrl() behavior. Update ArtistsRepository.kt lines 116-125 to
call the helper, and replace SearchRepository.kt lines 163-183 resolveCovers
implementation with the same shared helper call; both sites must use the
centralized logic.

In `@app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt`:
- Around line 88-125: Replace the hardcoded Color.White and Color.Black values
in ArtistTile’s scrim and on-image text styling with the appropriate AppColors
tokens, such as colors.onMedia and colors.scrimTop. Use the existing theme color
access pattern and preserve the current contrast, alpha values, and layout
behavior.

In `@app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt`:
- Around line 111-124: Collapse the nested condition in runSearch into a single
guard using !snapshot.hasActiveQuery, preserving the existing empty-state
update, nextCursor reset, and early return behavior.
- Around line 125-181: Update the exception handling in the search coroutine to
map failures through the same friendly-error pattern used by
DiscoverViewModel.messageFor instead of assigning t.message directly to
loadError. Preserve the existing fallback behavior and stale-generation guard
while ensuring backend or RPC details are not exposed to users.

In `@app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt`:
- Around line 53-54: Update the bottomBar slot in ClientTabsScaffold to
conditionally render the existing NavigationBar only when showBottomBar is true,
removing the return@Scaffold statement while preserving the current hidden and
visible behavior.
- Around line 80-98: Define a shared artist-profile route builder next to
ARTIST_PROFILE_ROUTE that accepts an artist ID and URL-encodes it, then update
the DiscoverScreen and SearchScreen onArtistClick navigation calls to use this
builder instead of constructing "artist/$id" directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57522390-4593-45ee-869d-8505d3bdd76f

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba0609 and a3b92d6.

📒 Files selected for processing (34)
  • CLAUDE.md
  • README.md
  • app/src/main/java/in/artistant/app/core/di/RepositoryModule.kt
  • app/src/main/java/in/artistant/app/data/model/Artist.kt
  • app/src/main/java/in/artistant/app/data/model/SearchTypes.kt
  • app/src/main/java/in/artistant/app/data/repository/ArtistsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/FakeArtistsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/FakeSearchRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/ReviewsRepository.kt
  • app/src/main/java/in/artistant/app/data/repository/SearchRepository.kt
  • app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt
  • app/src/main/java/in/artistant/app/designsystem/component/EmptyState.kt
  • app/src/main/java/in/artistant/app/domain/chat/Redaction.kt
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileViewModel.kt
  • app/src/main/java/in/artistant/app/feature/discover/DiscoverScreen.kt
  • app/src/main/java/in/artistant/app/feature/discover/DiscoverViewModel.kt
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt
  • app/src/main/java/in/artistant/app/feature/search/SearchViewModel.kt
  • app/src/main/java/in/artistant/app/navigation/ClientTabsScaffold.kt
  • app/src/test/java/in/artistant/app/data/repository/BrowseRepositoryTest.kt
  • app/src/test/java/in/artistant/app/domain/chat/RedactionTest.kt
  • app/src/test/java/in/artistant/app/feature/discover/DiscoverFeedLogicTest.kt
  • app/src/test/java/in/artistant/app/feature/search/SearchViewModelLogicTest.kt
  • docs/ANDROID_MIGRATION_PLAN.md
  • docs/API_MAPPING.md
  • docs/ARCHITECTURE.md
  • docs/FEATURE_CHECKLIST.md
  • docs/IMPLEMENTATION_ROADMAP.md
  • docs/PARITY_CHECKLIST.md
  • docs/PROJECT_STRUCTURE.md
  • docs/RISKS_AND_DECISIONS.md
  • docs/SCREEN_INVENTORY.md
  • gradle.properties
💤 Files with no reviewable changes (2)
  • app/src/test/java/in/artistant/app/domain/chat/RedactionTest.kt
  • app/src/main/java/in/artistant/app/domain/chat/Redaction.kt

Comment on lines +45 to +46
width: Dp = 192.dp,
height: Dp = 252.dp,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Raw dp literals across the new browse UI bypass the design-token system. Four sites hardcode dimensions instead of reading Size/Radii tokens; the shared root cause is missing hairline and tile-geometry tokens in AppTheme.dimens. Add them once, then reference them everywhere.

  • app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt#L45-L46: replace the 192.dp / 252.dp parameter defaults with AppTheme.dimens.size tile tokens.
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt#L211-L215: replace the width = 1.dp border with a hairline Size token.
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt#L105-L105: replace the .height(1.dp) divider with the same hairline token.
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt#L270-L270: replace the 1.dp chip border width with the same hairline token.

As per coding guidelines: "In Kotlin/Jetpack Compose UI, use AppColors, AppType, Space, Size, and Radii; never use raw hex, dp, or sp values."

📍 Affects 3 files
  • app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt#L45-L46 (this comment)
  • app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt#L211-L215
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt#L105-L105
  • app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt#L270-L270
🤖 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 `@app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt`
around lines 45 - 46, Replace the raw tile dimensions and 1.dp borders with
shared AppTheme.dimens Size tokens: add the missing tile geometry and hairline
tokens in AppTheme.dimens, then use them in ArtistTile, ArtistProfileScreen, and
both affected SearchScreen sites. Update
app/src/main/java/in/artistant/app/designsystem/component/ArtistTile.kt lines
45-46, app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt
lines 211-215, and
app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt lines 105 and
270; preserve the existing dimensions and border behavior through the new
tokens.

Source: Coding guidelines

Comment on lines +243 to +246
Row(horizontalArrangement = Arrangement.spacedBy(space.sm)) {
Text(review.name, style = AppTheme.type.callout, color = colors.ink)
Text("★".repeat(review.rating.coerceIn(0, 5)), style = AppTheme.type.caption, color = colors.warm)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Star glyph rating is not screen-reader accessible.

"★".repeat(...) announces as a run of star characters. Add a semantics description on the Row (e.g. Modifier.semantics { contentDescription = "Rated ${review.rating} out of 5" }) and mark the glyph Text as decorative.

🤖 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 `@app/src/main/java/in/artistant/app/feature/artist/ArtistProfileScreen.kt`
around lines 243 - 246, Update the review Row containing review.name and the
repeated star glyphs to expose a semantics contentDescription stating the review
rating out of five, and mark the star Text as decorative so screen readers do
not announce individual glyphs.

Comment on lines +42 to +60
fun refresh() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, loadError = null) }
val cached = artistsRepository.find(artistId)
if (cached != null) {
_state.update { it.copy(artist = cached) }
}
val full = artistsRepository.ensureFull(artistId)
if (full == null) {
_state.update {
it.copy(
isLoading = false,
loadError = if (it.artist == null) "Artist not found." else null,
)
}
return@launch
}
val popularIdx = full.packages.indexOfFirst { it.popular }.takeIf { it >= 0 } ?: 0
val reviews = runCatching { reviewsRepository.listForArtist(artistId) }.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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unhandled exceptions from find/ensureFull leave the screen stuck on the spinner.

Lines 45 and 49 are unguarded network calls. If either throws, the coroutine dies before any isLoading = false update, so ArtistProfileScreen renders the indefinite CircularProgressIndicator branch with no retry path. The reviews call on Line 60 already uses runCatching; apply the same treatment to the artist fetches.

🛡️ Proposed fix
             _state.update { it.copy(isLoading = true, loadError = null) }
-            val cached = artistsRepository.find(artistId)
+            val cached = runCatching { artistsRepository.find(artistId) }.getOrNull()
             if (cached != null) {
                 _state.update { it.copy(artist = cached) }
             }
-            val full = artistsRepository.ensureFull(artistId)
-            if (full == null) {
+            val fullResult = runCatching { artistsRepository.ensureFull(artistId) }
+            val full = fullResult.getOrNull()
+            if (full == null) {
                 _state.update {
                     it.copy(
                         isLoading = false,
-                        loadError = if (it.artist == null) "Artist not found." else null,
+                        loadError = when {
+                            it.artist != null -> null
+                            fullResult.isFailure -> "Couldn't load this artist. Try again."
+                            else -> "Artist not found."
+                        },
                     )
                 }
                 return@launch
             }
📝 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
fun refresh() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, loadError = null) }
val cached = artistsRepository.find(artistId)
if (cached != null) {
_state.update { it.copy(artist = cached) }
}
val full = artistsRepository.ensureFull(artistId)
if (full == null) {
_state.update {
it.copy(
isLoading = false,
loadError = if (it.artist == null) "Artist not found." else null,
)
}
return@launch
}
val popularIdx = full.packages.indexOfFirst { it.popular }.takeIf { it >= 0 } ?: 0
val reviews = runCatching { reviewsRepository.listForArtist(artistId) }.getOrDefault(emptyList())
fun refresh() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, loadError = null) }
val cached = runCatching { artistsRepository.find(artistId) }.getOrNull()
if (cached != null) {
_state.update { it.copy(artist = cached) }
}
val fullResult = runCatching { artistsRepository.ensureFull(artistId) }
val full = fullResult.getOrNull()
if (full == null) {
_state.update {
it.copy(
isLoading = false,
loadError = when {
it.artist != null -> null
fullResult.isFailure -> "Couldn't load this artist. Try again."
else -> "Artist not found."
},
)
}
return@launch
}
val popularIdx = full.packages.indexOfFirst { it.popular }.takeIf { it >= 0 } ?: 0
val reviews = runCatching { reviewsRepository.listForArtist(artistId) }.getOrDefault(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 `@app/src/main/java/in/artistant/app/feature/artist/ArtistProfileViewModel.kt`
around lines 42 - 60, Guard the network calls in refresh around
artistsRepository.find and artistsRepository.ensureFull with runCatching (or
equivalent exception handling), ensuring any failure updates _state with
isLoading = false and an appropriate loadError while preserving cached artist
data when available. Keep the existing successful full-artist flow and reviews
handling unchanged, and ensure exceptions cannot leave ArtistProfileViewModel
stuck loading.

Comment on lines +139 to +147
LaunchedEffect(listState) {
snapshotFlow {
val info = listState.layoutInfo
val last = info.visibleItemsInfo.lastOrNull()?.index ?: 0
last >= info.totalItemsCount - 3
}
.distinctUntilChanged()
.collect { nearEnd -> if (nearEnd) viewModel.loadMore() }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pagination fires before the list has been measured.

On the first frame totalItemsCount is 0 and visibleItemsInfo is empty, so the expression evaluates 0 >= -3true and loadMore() is called immediately for a list that was never scrolled. Gate on a measured, non-empty list.

🐛 Proposed fix
                     snapshotFlow {
                         val info = listState.layoutInfo
-                        val last = info.visibleItemsInfo.lastOrNull()?.index ?: 0
-                        last >= info.totalItemsCount - 3
+                        val last = info.visibleItemsInfo.lastOrNull()?.index ?: return@snapshotFlow false
+                        info.totalItemsCount > 0 && last >= info.totalItemsCount - 3
                     }
📝 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
LaunchedEffect(listState) {
snapshotFlow {
val info = listState.layoutInfo
val last = info.visibleItemsInfo.lastOrNull()?.index ?: 0
last >= info.totalItemsCount - 3
}
.distinctUntilChanged()
.collect { nearEnd -> if (nearEnd) viewModel.loadMore() }
}
LaunchedEffect(listState) {
snapshotFlow {
val info = listState.layoutInfo
val last = info.visibleItemsInfo.lastOrNull()?.index ?: return@snapshotFlow false
info.totalItemsCount > 0 && last >= info.totalItemsCount - 3
}
.distinctUntilChanged()
.collect { nearEnd -> if (nearEnd) viewModel.loadMore() }
}
🤖 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 `@app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt` around
lines 139 - 147, Update the snapshotFlow in LaunchedEffect(listState) so
near-end detection requires a measured, non-empty list: ensure totalItemsCount
is greater than zero and visibleItemsInfo is not empty before comparing the last
visible index. Preserve the existing threshold and loadMore() behavior after
measurement.

Comment on lines +162 to +168
ArtistTile(
artist = artist,
onClick = { onArtistClick(artist.id) },
modifier = Modifier.weight(1f),
width = 160.dp,
height = 220.dp,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Modifier.weight(1f) and width = 160.dp contradict each other.

weight hands ArtistTile an exact width constraint, so the .width(160.dp) applied inside the tile (ArtistTile.kt Line 52) is coerced away. The literal is dead and misleading — drop it and let the weight drive the width.

♻️ Proposed fix
                                 ArtistTile(
                                     artist = artist,
                                     onClick = { onArtistClick(artist.id) },
                                     modifier = Modifier.weight(1f),
-                                    width = 160.dp,
                                     height = 220.dp,
                                 )
📝 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
ArtistTile(
artist = artist,
onClick = { onArtistClick(artist.id) },
modifier = Modifier.weight(1f),
width = 160.dp,
height = 220.dp,
)
ArtistTile(
artist = artist,
onClick = { onArtistClick(artist.id) },
modifier = Modifier.weight(1f),
height = 220.dp,
)
🤖 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 `@app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt` around
lines 162 - 168, Remove the redundant width = 160.dp argument from the
ArtistTile call in the search layout, keeping modifier = Modifier.weight(1f) as
the source of the tile’s width. Do not change the existing height or click
behavior.

Comment on lines +262 to +275
private fun Chip(label: String, selected: Boolean, onClick: () -> Unit) {
val colors = AppTheme.colors
val space = AppTheme.dimens.space
Text(
text = label,
style = AppTheme.type.caption,
color = if (selected) colors.brandInk else colors.ink2,
modifier = Modifier
.border(1.dp, if (selected) colors.brand else colors.line, RoundedCornerShape(AppTheme.dimens.radii.sm))
.background(if (selected) colors.brand else colors.bg)
.clickable(onClick = onClick)
.padding(horizontal = space.md, vertical = space.sm),
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Chip lacks button semantics and a minimum touch target.

A bare clickable Text announces as text, not a control, and the caption-sized chip can fall below the 48dp target. Add role = Role.Button to clickable and Modifier.defaultMinSize(minHeight = ...) from a Size token.

🤖 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 `@app/src/main/java/in/artistant/app/feature/search/SearchScreen.kt` around
lines 262 - 275, Update the Chip composable’s Modifier chain to add button
semantics via clickable’s Role.Button and enforce the minimum touch height with
defaultMinSize using the existing AppTheme dimens Size token; preserve the
current styling, click behavior, and spacing.

Source: Coding guidelines

Comment thread docs/FEATURE_CHECKLIST.md Outdated
Comment thread README.md
cestercian and others added 6 commits July 26, 2026 06:19
Role-aware Accept/Decline surfaces for artists; client profile Request a
quote CTA; ArtistTabsScaffold wired for Home/Gigs + detail routes.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
… banner

Explicit-column messages repo (no redaction), find-or-create thread from
Artist profile, Airbnb-style safety banner. Realtime subscribe + FCM deferred.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
AccountRepository wraps delete-account + data-export EFs; ProfileScreen
on client tab and artist Home Settings; legal URL hooks in AppEnvironment.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Eleven-step wizard matching iOS order; Publish upserts artists +
setup_complete; incomplete artists route to wizard; EPK tab read-only
shell. CameraX media upload and packages/samples table writes deferred.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
PaywallScreen + EntitlementStore compile and navigate when the flag is
flipped; default off so v1 matchmaker behaviour is unchanged. Docs tick.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Point the session bootstrap at the open parity PR: booking request→accept,
messages without redaction, wizard/EPK scaffold, profile DPDP, inert paywall.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
@cestercian

Copy link
Copy Markdown
Owner Author

Progress update (same branch)

Beyond the original M2 Browse scope, this PR now also carries substantial M3–M7 parity work (still compile + unit-test verified only — no emulator here):

  • M3 Booking request→accept funnel, Bookings tab, artist Home/Gigs Accept/Decline, Request quote
  • M4 Messages inbox + Chat (verbatim bodies, system rows, trust banner; Realtime/FCM deferred)
  • M5 Wizard scaffold + EPK shell (media upload deferred)
  • M6 Profile: sign-out, delete-account, data-export
  • M7 Inert Paywall behind subscriptionsEnabled=false

Gate: assembleDebug + testDebugUnitTest green (~81 tests).

cestercian and others added 9 commits July 26, 2026 06:27
Wire postgres INSERT channel per open thread, collapse send/RETURNING/Realtime
echo races, and add tap-to-retry delivery states — matching iOS MessageStore.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Register FCM via claim_device_token (0069/0075), route artistant_* payloads into
pending thread/booking/gig channels, and wire both role scaffolds to consume them.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Port saved_artists optimistic hearts and the accordion SearchFilterSheet with
price_histogram + 0073 filter dims; wire profile save and filter badge.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Finish the publish path beyond the scaffold: replace_packages /
replace_tech_rider / published=true sync, gallery+SAF picks staged in
WizardMediaCache, and serial UploadQueue for cover/samples after go-live.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Artist availability chips write days_available / default_time_slots with a
seed-failure guard; Profile toggles CalendarContract mirroring of confirmed
gigs (owner-gated map, planner unit-tested).

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
ScoreRepository reads artists metric_* columns + score_history; self
explainer from Home, client breakdown sheet from profile score chip.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
EPK writes packages/tech/links/samples; chat Details sheet reports via
public.reports with local soft-fail log when the table is absent.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Update PARITY_CHECKLIST + FEATURE_CHECKLIST + CLAUDE current-state after the
M5/M6 parity wave on feature/m2-browse.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
cestercian and others added 6 commits July 26, 2026 13:03
Shared Canvas ring with New-tier nil handling, history sparkline + delta sheet,
and Home/profile wiring so Bookability Score matches iOS presentation.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Persist upload snapshots across process death, kick drains via WorkManager,
add TakePicture cover capture + Media3 ≤10s trim so wizard media survives kills.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
EPK photo grid + reorder_artist_media; gig-request clash card from busy days;
Profile calendar picker among writable calendars.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Wire BillingClient behind subscriptionsEnabled; tick FEATURE/PARITY/CLAUDE for
the end-to-end polish wave.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
Keep our M2–M8 ports (request→accept, verbatim chat, UploadQueue,
PlayBilling). Take additive main wins: #12 OAuth deepLinkError surface,
DeepLinkRouter/P2b push channels, PostHog/Sentry no-op wrappers, design
extras. Drop main's parallel feature ports that conflicted.

Co-Authored-By: Cursor Grok 4.5 <noreply@cursor.com>
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Too many files changed for review. (225 files found, 200 file limit)

Bypass the limit by tagging @greptile-apps to review.

@cestercian
cestercian merged commit 32d2d00 into main Jul 26, 2026
1 check passed
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