Skip to content

fix(profiles): gate picker management affordances on server authorization - #220

Open
Daicaa wants to merge 1 commit into
Silo-Server:mainfrom
Daicaa:fix/picker-profile-management-gating
Open

fix(profiles): gate picker management affordances on server authorization#220
Daicaa wants to merge 1 commit into
Silo-Server:mainfrom
Daicaa:fix/picker-profile-management-gating

Conversation

@Daicaa

@Daicaa Daicaa commented Aug 11, 2026

Copy link
Copy Markdown

Problem

Part of #219

The "Who's watching?" picker shows Add Profile and Manage Profiles to every
account, but the server only authorizes profile management for an active
primary profile or an admin account. At the picker no profile is active, so
for every non-admin account these affordances can only end in a 403 — shown
as the server's raw error string after the user has already filled in the
create form.

Approach

  • New shared predicate canManageProfilesFromPicker(user, activeProfile) = role == admin || activeProfile.isPrimary beside isActingAdmin, mirroring
    BOTH arms of the server rule. This matters because the two platforms reach
    the picker differently: phone keeps the acting profile across "Switch
    Profile" (a non-admin owner acting as primary IS authorized there — and the
    phone picker is the app's only management surface), while TV clears it, so
    on TV and at first login only the admin arm can hold. Fails closed when
    neither input resolves.
  • Both pickers (phone + TV) hide the Add tile and Manage affordance unless
    authorized; the Add tile stays for an EMPTY grid regardless of role — the
    server exempts first-profile bootstrap, and the picker renders the empty
    state itself.
  • User resolved via AuthRepository.getCurrentUser() through a constructor
    seam mirroring AdminEntryViewModel's pattern; the grant is resolved together
    with the grid under the same identity-scope capture, dropped on scope
    changes/logout, and re-evaluated on every resume. Revoking it also exits
    manage mode and closes any pending delete-confirmation dialog.
  • No Apple-style "borrow the primary context" flow — that's a product
    decision left to the maintainers (see issue).
  • Known minor tradeoffs (deliberate): each picker load serializes one /me
    fetch before the profile list; a transient /me failure hides an admin's
    affordances until the next resume (fail closed, self-heals).

Testing

Built and unit-tested for real (JDK 21 + SDK platform 36); excerpts:

$ ./gradlew :androidApp:compileDebugKotlin :androidTvApp:compileDebugKotlin
BUILD SUCCESSFUL in 50s
$ ./gradlew :shared:testDebugUnitTest :androidApp:testDebugUnitTest :androidTvApp:testDebugUnitTest
BUILD SUCCESSFUL in 39s
# result XMLs: AdminPermissionsTest tests="11" failures="0";
# ProfileSelectionManagementGatingTest tests="8" failures="0";
# TvProfileSelectionManagementGatingTest tests="7" failures="0";
# pre-existing GridScope suites tests="2" failures="0" each

No emulator/device screenshots included — my build host has no KVM, so an
emulator run wasn't practical. Happy to add screenshots if wanted; the change
is limited to hiding two affordances behind the new predicate.

AI Disclosure

  • Tool(s): Claude Code
  • Model(s): claude-fable-5
  • Involvement: AI-assisted (implementation and tests AI-generated from my
    design; I reviewed the diff and ran the builds/tests myself)
  • Adversarial review: an independent AI review (claude-fable-5) of the first
    draft found a BLOCKING design bug: the original predicate was admin-only,
    assuming no acting profile exists at the picker — but the phone app keeps
    the acting profile across Switch Profile, so the gate would have removed
    server-authorized profile management from every non-admin household owner
    (and the phone picker is the only management surface). Fixed by modeling
    both server arms. The review also caught the delete-confirmation dialog
    surviving a permission downgrade, and manage-mode reset asymmetries between
    the phone/TV reset paths — all fixed, each with a regression test. Verified
    sound by the review: first-profile bootstrap, identity-scope/race handling,
    TV grid arithmetic, existing-caller compatibility, and that the tests catch
    an inverted predicate.

Checklist

  • I ran an adversarial AI review of the diff and summarized findings above.
  • I ran the repo verify commands (Gradle compile + unit test tasks; repo
    has no lint config beyond compiler warnings — none new).

Summary by CodeRabbit

  • New Features

    • Profile management controls now respect server-provided permissions on Android and Android TV.
    • Administrators and users acting through the primary profile can manage profiles.
    • The Add Profile option remains available when no profiles exist, supporting initial setup.
    • Management actions and pending deletions are cleared when access or identity scope changes.
  • Tests

    • Added coverage for authorized, unauthorized, unresolved-user, bootstrap, and revoked-access scenarios.

…tion

The Who's watching picker showed Add Profile and Manage Profiles to every
account, but the server only authorizes profile management when the acting
profile is the household primary or the account is admin. At the picker the
phone app keeps the acting profile across Switch Profile while TV clears it,
so on TV and at first login only the admin arm can hold — every other
account got the server's raw 403 after already filling in the create form.

Gate both pickers on a shared canManageProfilesFromPicker(user, activeProfile)
predicate mirroring the server rule. The add tile stays for an empty grid:
the server exempts first-profile bootstrap and the picker renders that state
itself. The grant is resolved with the grid under the same identity-scope
capture, dropped on scope changes, and revoking it also exits manage mode
and closes any pending delete dialog.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Profile picker management now uses a shared authorization rule on Android and Android TV. ViewModels resolve the current user, update management state during profile loading, clear protected state when access changes, and gate management and add-profile controls.

Changes

Profile picker authorization

Layer / File(s) Summary
Shared picker authorization rule
shared/src/commonMain/kotlin/org/siloserver/silo/model/auth/AdminPermissions.kt, shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt
canManageProfilesFromPicker allows admins or users acting through the primary profile to manage profiles. Tests cover authorized, unauthorized, and unresolved states.
Android picker management state and UI
androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/profiles/*, androidApp/src/androidMain/kotlin/org/siloserver/silo/android/di/AndroidModule.kt, androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/profiles/*
The Android ViewModel computes management access from the current user and active profile. The screen gates management controls and preserves add-profile access for empty lists. Tests cover access changes and state cleanup.
TV picker management state and UI
androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/profiles/*, androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/di/AndroidTvModule.kt, androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/profiles/*
The TV ViewModel and screen apply the same authorization and bootstrap rules. The tile grid now includes the add tile only when permitted. Tests cover authorization scenarios and revocation cleanup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProfileSelectionScreen
  participant ProfileSelectionViewModel
  participant AuthRepository
  participant ProfileRepository
  ProfileSelectionScreen->>ProfileSelectionViewModel: request profile load
  ProfileSelectionViewModel->>AuthRepository: resolve current user
  ProfileSelectionViewModel->>ProfileRepository: load profiles and active profile
  ProfileSelectionViewModel->>ProfileSelectionScreen: expose management and add permissions
Loading

Possibly related PRs

Suggested reviewers: quick104, rxwatcher

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% 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 clearly and concisely describes gating profile picker management affordances based on server authorization.
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

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.

@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

🧹 Nitpick comments (1)
shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt (1)

63-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use camelCase test function names.

The new Kotlin test function identifiers use backticked prose names instead of camelCase.

  • shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt#L63-L96: rename the added test functions to camelCase.
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionManagementGatingTest.kt#L48-L181: rename the added test functions to camelCase.
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionManagementGatingTest.kt#L48-L160: rename the added test functions to camelCase.
🤖 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
`@shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt`
around lines 63 - 96, Rename the added backticked test functions to camelCase
identifiers in
shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt
lines 63-96,
androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionManagementGatingTest.kt
lines 48-181, and
androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionManagementGatingTest.kt
lines 48-160; preserve each test’s behavior and coverage.

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
`@androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionViewModel.kt`:
- Line 108: Clear canManageProfiles, isManageMode, and deleteDialogProfile at
the start of the load flow in ProfileSelectionViewModel before
currentUserProvider() runs. Apply the equivalent reset in
TvProfileSelectionViewModel, clearing canManageProfiles, isManageMode, and
deleteCandidate when loading starts.

---

Nitpick comments:
In
`@shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt`:
- Around line 63-96: Rename the added backticked test functions to camelCase
identifiers in
shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt
lines 63-96,
androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionManagementGatingTest.kt
lines 48-181, and
androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionManagementGatingTest.kt
lines 48-160; preserve each test’s behavior and coverage.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a44c73-1a97-43b9-9431-646413219d41

📥 Commits

Reviewing files that changed from the base of the PR and between 3efdbd9 and 129589c.

📒 Files selected for processing (10)
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/di/AndroidModule.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionScreen.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionViewModel.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/profiles/ProfileSelectionManagementGatingTest.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/di/AndroidTvModule.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionViewModel.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/profiles/TvProfileSelectionManagementGatingTest.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/model/auth/AdminPermissions.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/model/auth/AdminPermissionsTest.kt

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