Ubisam-MobileGit/docs/decisions/004-favorites-write-user-scope.md
choiminyeong 525a0aa2ff Add Favorites tab backed by Gitea star (first write endpoint)
TASK-018, human product owner priority-1 request. Adds write:user
OAuth scope (ADR-004) and relaxes OAuthClientConfig's read-only guard
to an explicit, hard-coded write-scope allow-list rather than a
general write-scope allowance. GiteaRepositoryBrowserApi gains star/
unstar/listStarred via a new RepositoryFavorites interface, reusing
its existing repository mapping. RepositoryListViewModel tracks an
ALL/FAVORITES mode; toggling favorite confirms against the server
before updating UI state rather than acting optimistically.

No create/edit/merge/close/comment endpoint is introduced. Favorite
state is never cached locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 13:51:04 +09:00

3.4 KiB

ADR-004: Favorites use Gitea star via a new write:user scope

Status

Accepted — 2026-08-26

Context

The human product owner asked for a Favorites tab. Two designs were considered: a device-local favorite flag (no server write, no new scope) or Gitea's native star feature (GET/PUT/DELETE /user/starred/{owner}/{repo}), which syncs with the Gitea web UI and across devices but requires a write call. ADR-002 explicitly required product approval, an ADR revision, and explicit reauthorization UX before any new scope was added — this ADR is that revision.

The human product owner chose Gitea-native star after being told explicitly that it requires the app's first write endpoint, that no NAS/Gitea server restart or configuration is needed (starring is a standard, already-live Gitea REST endpoint), and that the only consequence is a fresh login to pick up the new scope.

Decision

  • Add write:user to the app's requested OAuth scopes. Per Gitea's OAuth2 provider documentation, write:user grants read/write access to user-scoped operations including repository subscriptions, which covers starring.
  • OAuthClientConfig's scope guard is relaxed from "every scope must be read-only" to "every scope must be read-only or appear in an explicit, hard-coded EXPLICITLY_APPROVED_WRITE_SCOPES allow-list." Today that list contains exactly write:user. Adding any other write scope later requires the same kind of explicit human decision and ADR, not just adding an enum value.
  • Star/unstar are the only write calls added. No create/edit/merge/close/comment/delete/admin endpoint is introduced by this or any related task (TASK-018).
  • Star/unstar require no confirmation dialog: both are immediately reversible (tap again to undo) and non-destructive (no data loss, no effect on other users), matching Gitea's and GitHub's own web UI convention and AGENTS.md's confirmation requirement, which is scoped to destructive operations.

Alternatives considered

  • Device-local-only favorites: rejected by the human product owner in favor of cross-device/web parity, despite being the option with zero new scope and zero server interaction.
  • A generic "allow any write scope" guard relaxation: rejected — kept the allow-list explicit and enumerable so a future write scope cannot be added without deliberately touching this decision again.

Consequences

  • Existing signed-in users must log in again once before they can use Favorites; their existing token only carries the old read-only scopes.
  • If this Gitea instance's OAuth application registration restricts requestable scopes independently of what the client requests (a server-admin setting, not something requiring a restart), the first login attempt with the new scope will surface as an explicit authorization error rather than silently degrading; this is a known, accepted risk to verify on-device rather than something assumed away.
  • Favorite state is not cached locally; the starred set is always read fresh from Gitea, consistent with the product's existing no-offline-cache posture.

Evidence