69 Commits

Author SHA1 Message Date
71c8457578 TASK-045: sky blue + white redesign, UbiSam logo
Rebuilds the Material3 light/dark palette around a sky-blue primary, adds the UBGiteaTopBar wordmark to the repository list, and recolors the launcher icon background to match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 13:17:31 +09:00
39be1b71dc TASK-044: offline cache for the 전체 저장소 repository list
Closes one of docs/product.md's open product decisions ("whether
repositories must be cached for offline browsing"), at the human product
owner's explicit request.

A new file-backed RepositoryListCache persists the ALL/no-organization-
filter repository list after every successful load, using a dedicated
plain DTO (not the domain RepositorySummary directly, since
RepositoryCoordinates/RepositoryRef validate through a private-constructor
factory Gson's reflection would otherwise bypass). RepositoryListViewModel
falls back to it whenever a live fetch of that view fails with a
network-classified error (cold start or mid-session), seeding
repositorySnapshots so pagination keeps working from the cache. A
page-level banner makes the fallback impossible to mistake for a live
view. The cache is cleared on logout.

Deliberately scoped to just the landing list -- not favorites,
organization filters, or repository internals (branches/files/history) --
confirmed with the human product owner after they live-tested the
fallback via airplane mode and asked about extending it further; decided
to hold that off.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 11:05:25 +09:00
fbec743e5f TASK-043: fix empty 즐겨찾기 offering 로그아웃 instead of a way back
The FAVORITES-mode empty state ("nothing starred yet") shared its
MessageState call with the ALL-mode empty state ("account has zero
repositories"), inheriting a hardcoded 로그아웃 action that only made sense
for the latter. Gives FAVORITES-mode its own action: "전체 저장소 보기",
wired to the same onShowAllRepositories the tab row already uses.

Live bug report: "현재 화면 즐겨찾기가 안되어있는데 로그아웃할거냐는
버튼이 왜 있지?" Live-verified the fix on-device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:58:05 +09:00
390dcf0d8b docs: record file-viewer design-review spot-check (no changes)
Reviewed the text/image/PDF/CSV file-viewer implementations in
CodeBrowserScreen.kt as part of the ongoing design-review pass; live
spot-checked the text viewer on a real .gitignore. No bug or design gap
found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:48:48 +09:00
ca999a7bb8 TASK-042 follow-up: colored status badges on NotificationsScreen
Closes the known limitation from the previous commit: NotificationsScreen
had the identical plain-text state-label gap, in a separate screen/enum
(NotificationSubjectState, which also has an UNKNOWN case). Adds its own
StatusBadge composable with the same color roles, mapping UNKNOWN to the
neutral surfaceVariant treatment VisibilityBadge already uses for private.

Live-verified on-device: notifications 전체 list shows all three real
colors side by side (닫힘/병합됨/열림).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:44:00 +09:00
471456258b TASK-042: colored status badges for issue/PR state (열림/닫힘/병합됨)
Replaces the plain-text 열림/닫힘/병합됨 label on issue/PR list rows and
detail headers with a compact colored badge, reusing the VisibilityBadge
pattern TASK-039 established for 공개/비공개. OPEN reuses the app's green
primaryContainer, CLOSED reuses the baseline errorContainer, MERGED reuses
the teal tertiaryContainer -- all existing theme roles, so dark mode is
handled automatically with no new hardcoded colors.

Found while verifying issue/PR body rendering for real content (confirmed
correct, no bug) during the design-review pass. Live-verified on-device in
both light and dark mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 22:40:33 +09:00
39cbad522e TASK-041: fix favorite star reverting after switching tabs
toggleFavorite kept starredCoordinatesCache in sync with a successful
star/unstar but never patched repositorySnapshots (the per-mode
full-repository-list cache), so a starred repository's count reverted
to its pre-toggle cached value on the next visit to that mode, and a
newly-starred repository could be silently missing from an
already-cached favorites list.

Fix: two new helpers (adjustCachedStarCounts, removeFromCachedSnapshot)
patch every cached snapshot containing the toggled repository, and a
star invalidates just the FAVORITES snapshot key if already cached.
Chosen over a full cache-clear-and-refetch after live testing showed
Gitea's own starsCount can lag slightly behind a star/unstar on its
plain repository-listing endpoint.

Also fixes a related gap: unstarring from the FAVORITES tab wasn't
updating starredCoordinatesCache at all (only the two ALL-mode
branches were).

Two new regression tests; all existing tests pass unmodified.
Build/test/lint green, live-verified on device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 19:42:08 +09:00
a07094e0a5 TASK-040 follow-up: live-verify overflow menu (docs only)
Installed the previously code-only overflow-menu change once wireless
debugging resumed. Confirmed the hamburger button and its dropdown
(설정/알림/로그아웃) in portrait, the foldable's unfolded landscape layout,
and dark mode; 설정 correctly navigates to Settings. No source changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 19:17:50 +09:00
2b9a6e091e TASK-040: collapse repository-list header buttons into an overflow menu
Replace the three always-visible 설정/알림/로그아웃 OutlinedButtons on the
repository list's top bar with a single hamburger-style overflow menu
("☰" IconButton + DropdownMenu). Same three callbacks, presentation-only
change, no icon library (plain Unicode glyph, matching this app's
established convention).

Build/test/lint green. Not yet live-verified on device — wireless
debugging was intentionally paused for this round per request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 19:00:13 +09:00
a26e86f650 TASK-039 follow-up: record extended design review (no code changes)
Reviewed the code browser file tree, commit history list, and per-file
diff view in light and dark mode. The diff view already does proper
git-style red/green color-coding in a side-by-side before/after layout
and holds up cleanly in dark mode; the file tree and commit list are
already consistent with the rest of the app. Nothing to fix here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 18:53:47 +09:00
9663d9ab9c TASK-039: design pass — app icon, visibility badge, header truncation, PR label
- Add a custom adaptive launcher icon (background/foreground/monochrome
  layers), replacing Android's bare default. Original hand-drawn git-branch
  glyph on the app's own established primary green, not a reproduction of
  Gitea's logo.
- Add a compact VisibilityBadge for the repository list's public/private
  label, replacing an unstyled full-width text line.
- Truncate the references screen's repository-name header to 2 lines with
  an ellipsis instead of letting a long owner/name wrap to 3 lines and push
  the branch/tag list down.
- Change "Pull Request" -> "PR" in strings_workitems.xml and
  strings_notifications.xml, matching the "PR" abbreviation already used
  everywhere else in the app and fixing a visible line-wrap on the live
  notifications list.

Build/test/lint green, live-verified on device (light and dark mode).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 18:42:51 +09:00
9a8f855e5e TASK-038: Settings screen with opt-in reactive auth retry and favorite-push toggle
Two features gated behind a new Settings screen so neither silently
changes existing behavior:

- Reactive auth retry (opt-in, default off): a live 401 despite
  proactive refresh already judging the token fine now optionally
  retries once with a forcibly-refreshed token before falling back to
  re-login. ReactiveTokenProvider is a separate interface from the
  existing fun interface BearerTokenProvider (not an addition to it),
  so every existing test fake keeps compiling; a new shared
  callAuthenticated helper (used by all four Gitea API classes instead
  of four duplicated retry implementations) checks for it and falls
  back to the old single-attempt behavior otherwise.
- Favorite push notifications (on/off, default on): TASK-036's
  background checker can now be turned off entirely, closing that
  task's own 'no in-app way to disable this' limitation.

Every new constructor parameter defaults to preserving old behavior
exactly, so no existing call site or test needed to change.

Live-verified: Settings screen opens from a new button, both switches
toggle and persist across navigation, no crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 18:11:12 +09:00
75f887c497 TASK-028 follow-up: visible feedback for an out-of-range page-jump value
The Issues/PR numbered-pagination page-jump field silently clamped any
typed number to [1, totalPages] only on submit -- typing '999' would
quietly land on the last page with no explanation. The field now shows
isError/supportingText whenever the currently-typed value is out of
range, making the clamp visible before the tap. Submitting still
clamps exactly as before; this only adds visible feedback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:41:51 +09:00
683305e6c8 TASK-037: already-starred repositories show filled on the ALL list's first load
Closes a limitation accepted since TASK-018/TASK-030: a repository
already starred before the current session showed outlined until
directly toggled. loadStarredCoordinatesOnce fetches the account's
full starred set once per session (one request for the whole list, not
N+1 per row) via the same /user/starred endpoint already used for the
FAVORITES tab, mirroring loadOrganizationsOnce's caching shape.
toggleFavorite keeps the cache in sync on success; refreshRepositories
and logout invalidate/clear it the same way the equivalent
repositorySnapshots/cachedOrganizations caches already are.

Live-verified: a repository independently confirmed starred via the
FAVORITES tab showed filled on the ALL list immediately after a fresh
app launch, no interaction needed, screenshot-confirmed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:36:15 +09:00
18e59d960c TASK-036 follow-up: deep-link the favorite-push notification
Tapping the notification now opens the app directly at that
repository's file browser on its default branch, instead of just the
app's normal entry point. The push-detection step only tracks one
scalar 'latest push time' per repository, not which branch produced
it, so the deep link targets the repository's default branch rather
than the specific pushed branch -- still a correct, useful 'take me to
this repository' action.

ListFavoritedRepositoriesUseCase now returns full RepositorySummary
(not just coordinates) so defaultBranch is available; MainActivity
bridges the notification's PendingIntent extras into its existing
Route/routeStack navigation via a mutableStateOf Activity property read
by a LaunchedEffect, mirroring the existing OAuth-callback consumption
pattern.

Live-verified via a simulated notification-tap intent (adb shell am
start with the exact extras FavoritePushNotifier sets): opened directly
at the target repository's file browser on the requested branch,
screenshot-confirmed, no crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:26:23 +09:00
b1a00233b0 TASK-036: push notification when a favorited repository gets a new push
Gitea's own notification feed doesn't fire for plain pushes (only
issue/PR activity), so this app now periodically checks each favorited
repository's most recent push time itself and posts a local
notification when it advances past what was last seen.

- FavoritePushDetector: pure comparison logic, never notifies on a
  repository's first sighting (only a real advance counts), avoiding a
  notification storm the first time this feature ever runs.
- GetLatestPushEpochMillisUseCase: extracts the same 'newest branch
  commit time' heuristic RepositoryListViewModel already uses for the
  repository list's own last-push row, as a shared, reusable use case.
- SharedPreferencesLastSeenPushStore: one long per favorited repo, not
  Room (docs/architecture.md reserves Room for offline caching).
- FavoritePushCheckWorker: 15-minute WorkManager periodic job,
  network-constrained, no-ops cleanly with no session or no favorites.
- POST_NOTIFICATIONS requested at runtime on API 33+; a denial
  degrades safely, the worker still runs and updates its baseline.

No deep link from the notification to the specific repository yet --
tapping it opens the app normally. End-to-end delivery (a real push
landing, 15 minutes elapsing) is unit-tested and the scheduling/
permission wiring is live-confirmed (prompt appeared, was granted, no
crash), but an actual notification firing hasn't been observed within
this same live-testing session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 17:15:35 +09:00
9e4cbe3e89 TASK-035: remove redundant per-file Commit button from the code browser
The header's 'Commit' button (onViewHistory) just switched
BranchDetailScreen's pager to page 1 -- the exact same action the
top-level 'Commit' tab's own onClick already performs. No path/file
info ever flowed through it, so it was never actually file-specific
history, just a duplicate shortcut that TASK-031's rename made obvious.
Removed the button, its parameter threading, and the now-unused
codebrowser_history string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:55:21 +09:00
bec12e4f46 TASK-034 third round: make favorite star/count update optimistic
Every state/count change in toggleFavorite was applied only after the
network round trip to this app's home-NAS-hosted Gitea completed, so a
tap visibly did nothing until that finished -- reported as feeling
laggy. The star/count now update synchronously on tap, before the
coroutine launches; a failed request rolls the optimistic change back
instead of leaving the display stuck at it. FAVORITES-list unstar (row
removal) stays non-optimistic, since reverting a removed row is more
jarring than reverting a color/count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:49:44 +09:00
2e8c9e7987 TASK-034 follow-up: repeat tap on a starred row now un-stars instead of no-op
The prior fix for double-counting used an early-return guard that made
a repeat tap on an already-Starred row do nothing -- fixed the count
bug but also made the star impossible to undo from the ALL list.
Replaced it with a real toggle: an already-starred-this-session tap
now calls unstar() and decrements the count back down instead of being
inert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:42:17 +09:00
6dbb37d067 TASK-034: fix favorite star count not incrementing after a successful star
toggleFavorite's ALL-list success branch updated favoriteAction to
Starred but never touched starsCount, so a row kept showing its
pre-star count even after the server had actually accepted it. Now
increments starsCount by 1 in the same update. Also guards against a
repeat tap on an already-Starred row (the ALL list can't tell a
repo's already-starred state on load, so the star stays tappable)
double-incrementing the displayed count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 16:37:17 +09:00
b38ddc99e3 TASK-033 correction: starring needs write:user AND write:repository together
Two live-confirmed 403 responses (not source reading, which had already
given one wrong answer) showed /user/starred/{owner}/{repo} requires
BOTH scopes at once: requesting write:repository alone got
required=[write:user]; requesting write:user alone then got
required=[write:repository]. Both are now requested.

Added debug-build-only HTTP status/body logging (HttpTransport
.requireSuccess) and toggleFavorite failure logging, which is how this
was actually diagnosed -- ground truth from the server beats re-reading
Go route source through a summarizing tool. Required testOptions {
unitTests { isReturnDefaultValues = true } } in build.gradle.kts so
those Log calls don't throw inside plain JVM unit tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 00:08:20 +09:00
72de43d273 docs: TASK-033 follow-up, diagnose login server_error after scope change
Traced the live 'denied:server_error' login failure to Gitea 1.23.5's
own GrantApplicationOAuth: it refuses to silently widen an existing
OAuth2 grant's scope (grant.Scope != form.Scope -> ErrorCodeServerError,
'a grant exists with different scope'). No code change -- this is
Gitea's own intended behavior. Fix is to revoke UBGitea's existing
authorization from Gitea's Settings > Applications page before retrying
login, so a fresh grant is created under the new scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:46:19 +09:00
07436d4c83 TASK-033: request write:repository OAuth scope so starring actually works
Gitea's PUT/DELETE /user/starred/{owner}/{repo} require write:repository
token scope (verified against Gitea's route registration source), but
this app's OAuthClientConfig only ever requested read-only scopes,
enforced by a hard require() -- every star/unstar attempt was guaranteed
to fail server-side (403), surfaced as the reported red 'failed' star.

Adds write:repository as one narrow, explicit exception to the read-only
invariant (every other write scope, including the pre-existing unused
write:user, is still rejected). Requires one manual re-login on already
logged-in devices before it takes effect, since a refresh token carries
forward the scope it was originally granted under.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:42:20 +09:00
f3adf007d9 TASK-032: fix reversed ALL/FAVORITES swipe direction on repository list
modeSwipeGesture's onDragEnd had its comparison signs backwards relative
to this app's drag-follows-finger convention (already correct in
BranchDetailScreen's HorizontalPager): swiping right moved ALL ->
FAVORITES and swiping left moved FAVORITES -> ALL. Swapped the two
branch conditions so a leftward drag reveals FAVORITES (the right-hand
tab) and a rightward drag reveals ALL (the left-hand tab).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:38:06 +09:00
c75b348d08 TASK-031: rename History label to Commit, EUC-KR decode fallback, HTTP timeout fix
- '히스토리' label -> '커밋' across the branch-detail tab, code-browser
  history button, and the one permission-denied sentence that used the
  word, matching what that screen actually shows.
- SourceContentClassifier now tries EUC-KR (strictly, same discipline as
  its existing UTF-8 attempt) before giving up, so legacy Korean-encoded
  .cpp/.h source files render instead of failing as UnsupportedEncoding.
  A byte sequence invalid in both charsets still correctly falls through.
- OkHttpTransport's default client is now one shared, longer-timeout
  OkHttpClient (15s/30s/15s) instead of a fresh unconfigured OkHttpClient()
  per call site -- fixes a large commit history/directory listing on this
  app's home-NAS-hosted Gitea being misreported as offline purely because
  it took longer than the previous 10s default to answer, and lets calls
  to the same host reuse one connection pool instead of ~8 separate ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:25:23 +09:00
f693dbdb5b TASK-030: Gitea-green theme, pagination footer height fix, repo language/star
Replaces Compose Material3's unthemed default (purple) with a real color
scheme built from Gitea's own brand green, verified against Gitea's source
(--color-logo: #609926). Fixes a live-reported bug where pagination footer
button heights visibly varied by pinning one explicit height on every
button across all four screens' footers, and by sharing one
SimplePaginationFooter for the plain previous/next case. Each repository
row now shows its Gitea-reported primary language and a tappable star
toggle wired to the existing toggleFavorite action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 23:10:59 +09:00
50d1b41f47 TASK-029: OAuth refresh-token support, stop re-prompting for consent
The app captured Gitea's OAuth refresh token on login but never used it --
an expired access token forced a full new browser-based login (and consent
screen) every time. Now silently exchanges the stored refresh token for a
new access token in the background, proactively before it goes stale,
shared via one AuthSession singleton across all five features' API clients
instead of each independently reading a possibly-stale token.

Verified Gitea's refresh_token grant shape against its own source
(routers/web/auth/oauth2_provider.go) before implementing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:52:03 +09:00
bb83c215d3 TASK-027 follow-up: PDF viewer with swipe/pinch-zoom, reusing image gesture code
Opening a .pdf file (detected by %PDF- magic bytes) now renders its pages via
Android's built-in PdfRenderer -- no new dependency -- with the same
swipe-between-pages, pinch-zoom, pan, and double-tap-to-toggle-zoom
interaction as the image viewer. The shared gesture logic was extracted into
a ZoomableSwipeableBox composable so it's genuinely reused, not duplicated.
maxInputBytes raised 1MB -> 10MB so real-world photos and PDFs aren't
rejected as Oversized before ever being classified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:22:21 +09:00
8bf3930c3d TASK-027 follow-up: pinch-to-zoom and pan for the image viewer
Pinch-zoom (1x-5x), pan while zoomed, and double-tap to toggle zoom, added
to the image viewer. Combined with the existing swipe-navigate gesture in
one hand-rolled awaitEachGesture loop that branches on live pointer count,
rather than stacking a second competing pointerInput detector -- avoiding
the same class of tab-swipe/content-scroll gesture conflict TASK-025 hit
twice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 22:01:09 +09:00
2818dab09a TASK-027/028: image+CSV viewer with gallery swipe, numbered Issues/PR pagination
TASK-027: opening an image (PNG/JPEG/GIF/BMP/WebP, detected by magic bytes)
now decodes and shows it, with swipe navigation to the previous/next image
in the same folder; opening a .csv file shows a scrollable table instead of
raw text. Both reuse the tab-swipe/content-scroll gesture-conflict fix so
neither ever triggers the Files/History tab to change underneath it.

TASK-028: the Issues/PR list shows up to 5 numbered page buttons plus a
[ page / total ] jump field, backed by Gitea's X-Total-Count header
(verified against Gitea's own source) and a server-side type=issues filter
so the count excludes pull requests. Every other paginated list is
unchanged, per this session's earlier scoping decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 21:50:24 +09:00
aef7e2fa44 TASK-025 follow-up: fix tab-swipe vs. code-scroll gesture conflict
The Files/History HorizontalPager and the diff view's own horizontal code
scroll were both trying to consume the same drag, causing jittery scrolling
and text intermittently scrolling out of view. Disable the pager's own
swipe gesture whenever the current tab is showing scrollable source text,
re-enable it everywhere else. Root cause diagnosed live by the human
product owner from watching the behavior directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 21:11:56 +09:00
0eff1ffc76 TASK-025 follow-up: fix diff-view horizontal-scroll direction
The manually-driven scroll offset from the previous jitter fix had the delta
sign backwards: dragging left revealed the start of the line instead of
further content, opposite of every other horizontally-scrollable surface on
Android. Verified and fixed via scripted adb swipes + screenshots.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 20:59:02 +09:00
e219016998 TASK-026: run OAuth login/consent in a Custom Tab instead of the external browser
Login previously left the app entirely for a separate browser task. Now uses
androidx.browser Custom Tabs, which overlays the app's own activity instead of
switching away, and returns automatically on redirect. Deliberately not a
WebView, per RFC 8252: an in-app WebView could read the Gitea login form's
credentials directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 20:27:23 +09:00
e2c25d8662 TASK-025 follow-up: fix diff-view horizontal-scroll jitter
Sharing one ScrollState across every row in the diff panels made each row's
layout pass overwrite the shared max-scroll bound with its own line width,
so the visible offset snapped back and forth mid-drag ("글자가 춤을 추네").
Replace it with a manually-driven offset clamped against one bound computed
once per file, instead of recomputed per row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 01:50:32 +09:00
8cf7acd9ac TASK-025: tabbed branch detail screen, repo commit time, swipeable tabs, and live-testing follow-up fixes
- Branch/tag tap opens one screen with swipeable Files/History tabs (Route.BranchDetail replaces Route.CodeBrowser/Route.History)
- Repository rows show last-commit time; repository All/Favorites tabs are swipeable
- Fix overlapping commit-detail layout (BoxWithConstraints sibling stacking)
- Fix BranchDetailScreen tab row jammed against status bar
- Fix diff view horizontal scroll not working
- Simplify references-loading message; remove in-app back buttons in favor of system back gesture; replace manual refresh buttons with pull-to-refresh

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 01:39:17 +09:00
4b3c11d849 Add TASK-024: history screen redesign
Live, iterative round of changes driven by the human product owner
testing on their own device over wireless adb:
- Tapping a branch/tag opens commit history instead of the file browser
- Selected reference row is color-coded instead of a text label
- Branch/tag rows and commit rows show commit date/time with time zone
- Tapping a changed file shows a parsed before/after diff, client-side
  (new UnifiedDiffParser, no extra network call)
- The two diff panels scroll vertically in sync and share one
  horizontal scroll position
- Diff lines are colored by text only (red/green)
- On a >=600dp-wide screen (a Z Fold's inner screen unfolded) the file
  list and selected diff show side by side
- Removed the branch/tag compare feature entirely, per direct request

Also fixes a real bug found via live device debugging: getCommitDetail
requested the wrong URL path (missing "git/"), causing every commit
tap to 404.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 00:32:42 +09:00
7da04a77ae Add TASK-023: tolerate a literal JSON null list response
Gitea's Go handlers serialize a nil slice as the JSON literal `null`
rather than `[]` for some list endpoints when there are no items
(e.g. a repository with no branches). parseList treated that as a
parse failure and threw ApiFailure.InvalidResponse, surfacing as a
misleading "server error" on a completely successful 200 OK.

Diagnosed live against the human product owner's real Gitea instance
via wireless adb (repositories CSOT-T8-iPTN-AMA / CSOT-T8-MACRO_SA):
confirmed via the Gitea container's own request log that the call
was a genuine 200 OK, then used temporary debug-only logging to
narrow the cause to the literal null body. Fixed at the shared
parseList helper so every sibling list endpoint benefits, not just
the one that was reported.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 23:04:43 +09:00
b46dd3a72b Complete TASK-022: tolerate incomplete Gitea branch timestamps
Continues Codex's paused in-progress fix in this worktree (confirmed
safe to pick up by the human product owner). A missing or malformed
branch commit timestamp no longer fails the whole branch page; the
latest-branch picker now filters to branches with a known timestamp
before selecting the max, and reports Failed (not Empty) when none
are known. Adds the one missing test case from the in-progress
coverage (all branches with unknown timestamps) plus an explicit
test proving the reference screen never depended on the field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 22:19:39 +09:00
ca8620958a feat: sort repositories by recent update 2026-08-26 21:48:07 +09:00
5e414a663a fix: use saveable repository list keys 2026-08-26 21:33:07 +09:00
5778dc9b2a fix: recover safely from OAuth callback failures 2026-08-26 18:41:17 +09:00
31b43c6466 Add TASK-016: partial release hardening
Fixes the recorded P3 CodeBrowserViewModel Main-dispatcher issue,
scans the whole repo for secrets, and reviews the manifest/backup/
token-storage security posture. Minification and any authenticated-
screen device regression are explicitly left blocked: this agent
cannot enter a password to complete Gitea login, confirmed with the
human product owner rather than silently skipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 16:44:59 +09:00
f14be3ac90 Add TASK-015: read-only Gitea notifications
Signed-in users can view notifications (unread-only or all), paginated,
and jump from an issue/PR notification to that repository's work items.
No mark-read/unread/pinned write endpoint is exposed, matching the
existing single-write-scope posture from ADR-004.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:55:19 +09:00
4fe3bf1c9f Implement TASK-014: organizations and repository org filter
Adds read-only GET /user/orgs and GET /orgs/{org}/repos to
GiteaRepositoryBrowserApi (a third interface, OrganizationsRepository),
reusing existing repository mapping rather than duplicating it.

Repository list gains an organization filter chip row (all + one per
org), keeping the existing recent-push sort and paging. Organizations
are fetched once per session and cached; a failure to load them
degrades to an empty filter row instead of blocking the repository
list itself. Filtering only applies to the all-repositories mode,
since Gitea star is not organization-scoped.

Closes MVP outcome #2 in docs/product.md. No task file existed
beforehand; created it first per AGENTS.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:34:22 +09:00
7756ec4d7e Implement TASK-013: typed application navigation
Replace MainActivity's three independent booleans (browsingCode/
browsingHistory/browsingWorkItems) with one typed List<Route> back
stack, restorable through configuration change and process death.

Route holds only stable primitives (owner/name/ref/ref-kind), never
tokens or content. A genuine LIFO stack, not just a refactor: back now
correctly unwinds through arbitrarily nested screens instead of
relying on which boolean happened to still be true underneath another.

No task file existed for this task beforehand (only proposed in an
earlier handoff document); created it first per AGENTS.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:19:56 +09:00
c397fbeea9 Implement TASK-008: pull-request and issue read-only UI
Separate issue and PR list/detail tabs, paginated, with comments
(issues) and reviews+comments (PRs). Deleted/unavailable authors
render a fixed placeholder instead of crashing or inventing identity;
every status is a text label, never color-only.

Reuses TASK-010 directly; no shared/domain file touched. Both list
types dedupe by number per page. Entry point is a new button on the
repository references screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:08:26 +09:00
429668bf26 Implement TASK-007: commit history and change viewer UI
Paginated commit history for the current ref, commit detail with
distinguished added/modified/deleted/renamed file status, bounded and
explicitly-truncated patch text, and a ref comparison flow validating
one entered ref against the currently open ref as base.

Reuses TASK-004's listCommits and this session's own TASK-009
HistoryRepository; no shared/domain file touched. Entry point is a new
history button on the existing code browser header.

Self-review caught and fixed a bug before writing tests: a failed
commit-detail fetch discarded which commit had failed, so retry
silently reloaded the whole list instead of re-fetching the commit the
user tried to open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:56:43 +09:00
3c17476e92 Implement TASK-009: commit history and diff read-only API contracts
Implemented directly at human product owner's direction, independent
of Codex's separate uncommitted attempt at the same task (not read or
referenced here).

- CommitDetail (reusing CommitSummary) with per-file added/modified/
  deleted/renamed/other status; deleted-author and missing-rename-path
  cases handled without crashing or inventing data.
- BoundedPatch for commit and comparison diffs, sourced from Gitea's
  .diff raw-text endpoints (JSON commit/compare objects only carry
  file status, not diff content); byte limit then line limit, both
  constructor-injected and validated positive.
- Comparison uses one combined base...head path segment, built by
  concatenating both refs before a single addPathSegment call so each
  is escaped exactly once; pagination computed locally since Gitea's
  compare endpoint has no server-side paging.
- Reuses Page<T> and ApiFailure; no put/delete/postForm call anywhere
  in the file (read-only, as required).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:17:54 +09:00
7d122d4388 Implement TASK-010: pull-request and issue read-only API contracts
Frozen domain/data contracts for issues, pull requests, reviews, and
comments so TASK-008's UI can be built without touching shared layers.

- Separate immutable IssueSummary/Detail and PullRequestSummary/Detail
  models; WorkItemAuthor.Unavailable instead of inventing an identity
  for deleted/null authors.
- PullRequestState derived from Gitea's merged flag + open/closed
  state (no single field already encodes "merged").
- listIssues drops entries with a non-null pull_request field so
  Gitea's issue-list endpoint (which also returns PRs) never
  duplicates listPullRequests, done client-side rather than trusting
  an unverified query parameter.
- Reuses existing Page<T> and ApiFailure rather than new parallel
  types. Read-only: no put/delete/postForm call anywhere in the file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 14:08:21 +09:00
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