Skip to content

feat: Book Club plugin integration (server-driven, feature-flagged)#17

Merged
fabiodalez-dev merged 4 commits into
mainfrom
feat/book-club-integration
Jul 6, 2026
Merged

feat: Book Club plugin integration (server-driven, feature-flagged)#17
fabiodalez-dev merged 4 commits into
mainfrom
feat/book-club-integration

Conversation

@fabiodalez-dev

Copy link
Copy Markdown
Owner

Surfaces the Book Club plugin of the Pinakes server in the app, consuming the /api/v1/bookclub bridge (server PRs #215/#216 on fabiodalez-dev/Pinakes).

What's in

  • Discovery, double-gated: GET /api/v1/bookclub/health probe ANDed with the core health app_access_enabled; the flag parse-defaults to false, so the section is invisible against servers without the plugin. Single entry point (Profile row), gated.
  • Screens: Book Club home (personal dashboard + clubs directory) and club detail (books, polls, meetings) — Hilt ViewModels with SavedStateHandle nav args, collectAsStateWithLifecycle, keyed LazyColumn items.
  • Actions: join, vote (simple/multi/weighted; web deep-link for ballot modes the app can't render), RSVP with seat gating, reading progress.
  • Graceful degradation (review fix): a 404 re-probes the plugin health — if the plugin was deactivated server-side the feature flag flips immediately (entry point hides) and the screen shows a terminal empty state instead of a retryable error. A 404 for a single missing club keeps the normal error path.
  • i18n: 57 book_club_* keys in all four locales (it/en/fr/de, 400 keys each, parity verified), placeholder parity included.
  • Contract verified field-by-field against the PHP bridge (envelope, snake_case names, nullability, ISO-8601 timestamps, action semantics).

Verification

67 unit tests + assembleRelease (R8) green.

⚠️ Merge after the server-side PRs, and note the bridge PRs are being amended (login-gate fix + HTTPS enforcement) — the endpoint contract is unchanged.

claude and others added 4 commits July 6, 2026 10:40
Integrate the server-side Book Club plugin's mobile surface
(/api/v1/bookclub/*) so members can use it from the Android app whenever
the plugin (and its `mobile` module) is active for their instance.

- Auto-discovery + gating: probe GET /api/v1/bookclub/health (public, no
  token) alongside /health; cache the flag in an encrypted store and show
  a Book Club entry in Profile only when the plugin answers 2xx (404 hides
  it). Confirm-before-showing, like public registration.
- Data layer: BookClubApi (Retrofit) + BookClubRepository + BookClubStore,
  reusing the same base URL and bearer token as the core client. The plugin
  uses a different envelope ({success,data,error}); a dedicated bookClubCall
  maps it into the shared ApiResult/ErrorCodes.
- Screens: Book Club home (reading dashboard, your clubs, discover
  directory) and club detail (reading list with state chips + progress,
  polls, meetings). Actions wired end-to-end: join, vote (simple/multi/
  weighted, ballot pre-seeded from my_option_ids), RSVP and reading
  progress. Guests are read-only; advanced poll modes and proposing a
  title deep-link to the web page per the API contract.
- i18n: new strings added to all four locale JSONs (it/en/fr/de), in parity.

Verified: assembleDebug, lintDebug and assembleRelease (R8/minify) all
BUILD SUCCESSFUL.
Adversarial review of the app against the live Book Club + mobile-api PHP
sources (10 confirmed findings, all fixed):

Compatibility with the PHP API:
- canJoin now mirrors the server's rejoin rules: members with status
  'left'/'suspended' see the Join button again (server re-admits them;
  only 'banned' is blocked) instead of a dead-end header.
- Polls whose closes_at has passed render as Closed: the mobile API never
  lazy-closes expired polls, so the old ballot could only fail with 409
  poll_closed.
- The 'Going' RSVP chip disables when a meeting is full (yes_count >=
  seats) unless the user is already going, mirroring the server's 409
  no_seats rule; maybe/no stay enabled.
- DateFormat now parses raw MySQL DATETIME (Y-m-d H:i:s): review cards,
  the device list and book club dates no longer show the raw DB string.
- ErrorCodes.APP_DISABLED ('app_disabled') matched nothing the server
  emits; replaced with APP_ACCESS_DISABLED ('app_access_disabled').
- The Book Club section also hides when /health reports
  app_access_enabled=false (the plugin's public health answers 200
  regardless of the gate).

Correctness:
- Home 'Available now' shelf: restored the server-side available=true
  query; the cached unfiltered first page is only the offline fallback,
  so availability beyond the newest 40 titles shows again.
- forgetInstance() purges the Room catalog cache and the in-memory ETag
  cache — library A's titles/covers can no longer surface under library B.
- A late availability probe can no longer resurrect the Book Club flag
  after 'change library': probe results are guarded by instance URL.
- A Book Club dashboard fetch failure now surfaces a snackbar instead of
  silently dropping the 'Your reading' section.

Efficiency & structure:
- Core /health and the plugin probe run concurrently (was serial — it
  doubled the post-login spinner and every startup refresh).
- Book Club availability folded into FeatureStore/InstanceFeatures (drops
  the fourth EncryptedSharedPreferences file and its Keystore cost).
- bookClubCall reuses the core error pipeline (parseErrorBody, status
  fallbacks, Retry-After) — also fixes blank messages on bodiless errors;
  BookClubError replaced by the shared ApiError; NO_AUTH deduplicated.
- Dead response DTOs removed (VoteResult/RsvpResult/ProgressResult/
  BookClubHealth → Envelope<Unit>); web deep-link URL built in the
  repository instead of string-concatenation in the composable.

Verified: testDebugUnitTest, lintDebug, assembleDebug and assembleRelease
(R8) all BUILD SUCCESSFUL.
…ver-side

Review findings on the integration branch:
- A 404 on clubs/detail now re-probes /bookclub/health (confirmGone): when the
  plugin is really gone it flips the feature flag immediately - hiding the
  Profile entry without waiting for the next app-foreground health refresh -
  and the screen shows a friendly terminal empty state instead of a retryable
  error banner. A 404 for a single missing club keeps the normal error path.
- pollWebUrl returns an empty string when the instance origin is unknown, so
  the deep-link guard skips instead of firing an intent on a relative URL.
- 2 new i18n keys (gone title/subtitle) added to all 4 locales (400 keys each,
  parity verified).

67 unit tests + assembleRelease green.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 47 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f6d8bfb-6756-4f64-adcb-54b9e69f677c

📥 Commits

Reviewing files that changed from the base of the PR and between 79a0aa3 and 303a225.

📒 Files selected for processing (32)
  • .gitignore
  • README.md
  • STATUS.md
  • app/src/main/java/com/pinakes/app/data/model/BookClubModels.kt
  • app/src/main/java/com/pinakes/app/data/network/ApiResult.kt
  • app/src/main/java/com/pinakes/app/data/network/BookClubApi.kt
  • app/src/main/java/com/pinakes/app/data/network/BookClubResult.kt
  • app/src/main/java/com/pinakes/app/data/network/NetworkModule.kt
  • app/src/main/java/com/pinakes/app/data/repository/AuthRepository.kt
  • app/src/main/java/com/pinakes/app/data/repository/BookClubRepository.kt
  • app/src/main/java/com/pinakes/app/data/repository/CatalogRepository.kt
  • app/src/main/java/com/pinakes/app/data/store/FeatureStore.kt
  • app/src/main/java/com/pinakes/app/data/sync/CatalogSyncWorker.kt
  • app/src/main/java/com/pinakes/app/di/AppModule.kt
  • app/src/main/java/com/pinakes/app/ui/common/AppViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/common/DateFormat.kt
  • app/src/main/java/com/pinakes/app/ui/navigation/MainScaffold.kt
  • app/src/main/java/com/pinakes/app/ui/navigation/PinakesNavHost.kt
  • app/src/main/java/com/pinakes/app/ui/navigation/Routes.kt
  • app/src/main/java/com/pinakes/app/ui/screens/bookclub/BookClubHomeScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/bookclub/BookClubHomeViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/screens/bookclub/BookClubUi.kt
  • app/src/main/java/com/pinakes/app/ui/screens/bookclub/ClubDetailScreen.kt
  • app/src/main/java/com/pinakes/app/ui/screens/bookclub/ClubDetailViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/screens/home/HomeViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/screens/login/LoginViewModel.kt
  • app/src/main/java/com/pinakes/app/ui/screens/profile/ProfileScreen.kt
  • app/src/test/java/com/pinakes/app/CatalogSyncWorkerTest.kt
  • i18n/de.json
  • i18n/en.json
  • i18n/fr.json
  • i18n/it.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/book-club-integration

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.

@fabiodalez-dev fabiodalez-dev merged commit fe0bc84 into main Jul 6, 2026
1 of 2 checks passed
@fabiodalez-dev fabiodalez-dev deleted the feat/book-club-integration branch July 6, 2026 16:31
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.

2 participants