Ubisam-MobileGit/tasks/025-branch-detail-tabs.md
choiminyeong 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

10 KiB

TASK-025: Tabbed branch detail screen (Files + History), repository row commit time, swipeable repository/favorites tabs, and live-testing follow-up fixes

Status: implemented; all required checks pass; device verification pending Owner: Claude Reviewer: Codex

Outcome

Three more live requests from the same testing session:

  1. Tapping a branch/tag now opens one screen with two swipeable tabs — 파일 (Files, the code browser, default) and 히스토리 (History) — over the same ref, restoring file/folder browsing (removed from the UI by TASK-024) while keeping the direct-to-history behavior the human product owner asked for, by making history the second tab instead of the only destination.
  2. Each repository row on the repository list now shows its last-commit time (reusing the existing updatedAtEpochMillis field and the shared formatInstant helper from TASK-024 — no new API data needed).
  3. The repository list's 전체(All)/즐겨찾기(Favorites) tabs are now swipeable (swipe right from All → Favorites, swipe left from Favorites → All), not just tap-to-switch.

Plus six more follow-up fixes/changes found or requested during the same live-testing pass on the same branch, kept in this task rather than split out since they're all small, tightly related touch-ups to screens this task already modifies or reuses:

  1. Overlap bug fix: the narrow-layout (non-Z-Fold-unfolded) branch of CommitDetailScreen's BoxWithConstraints had Header(...) and its content as bare siblings instead of children of a Column, so BoxWithConstraints (which behaves like Box — it does not arrange multi-child branches vertically on its own) rendered them stacked on top of each other. Reported twice by the human product owner via screenshot before being root-caused; fixed by wrapping each branch's contents in Column(Modifier.fillMaxSize()) { ... }.
  2. Status-bar padding fix: BranchDetailScreen's TabRow was jammed against the status bar because, unlike every other top-level screen in the app, it wasn't wrapped in its own Scaffold. Fixed with Modifier.statusBarsPadding() on the outer Column (not a nested Scaffold, to avoid double-padding since CodeBrowserScreen/HistoryScreen already have their own Scaffolds internally).
  3. Diff horizontal-scroll fix: DiffSideLine (in the History tab's file-diff view) had Modifier.fillMaxWidth().horizontalScroll(state) and no softWrap = false, so long lines wrapped instead of overflowing — nothing to scroll. Fixed by removing fillMaxWidth() and adding softWrap = false, matching the already-working PatchText composable's modifier pattern.
  4. Loading-text simplification: the references-loading message on the repository screen previously interpolated the repository name into a longer sentence; simplified to a plain "로딩 중" per direct request, and the now-unused %1$s placeholder removed from repositories_references_loading.
  5. In-app back buttons removed: every screen-header back button (CodeBrowserScreen's BrowserHeader, HistoryScreen's Header, NotificationsScreen's FilterTabs, WorkItemsScreen's ModeTabs and DetailHeader, RepositoryListScreen's ReferencesContent header) removed, since every screen already has a BackHandler wired to the system back gesture and the human product owner asked to rely on that instead. WorkItemsScreen's DetailHeader composable (which consisted only of a back button) removed entirely along with all 5 call sites. Now-unused history_back/notifications_back/workitems_back/codebrowser_back string resources removed; repositories_back kept, since it's a distinct recovery action on the references-error state's secondary button, not a header back button.
  6. Pull-to-refresh replaces manual refresh buttons: RepositoryListScreen's RepositoryContent and ReferencesContent each had a manual "새로고침" OutlinedButton; both replaced with PullToRefreshBox (Material3, @OptIn(ExperimentalMaterial3Api::class)) wrapping their LazyColumns, so pulling down from the top refreshes instead.

Scope

  • Included: ui/navigation/Route.kt/RouteStackSaver.kt (Route.CodeBrowser/Route.History replaced by one Route.BranchDetail), new feature/branchdetail/BranchDetailScreen.kt, MainActivity.kt wiring, strings_branchdetail.xml, plus the follow-up touch-ups (items 4-9 above) to feature/history/HistoryScreen.kt, feature/codebrowser/CodeBrowserScreen.kt, feature/notifications/NotificationsScreen.kt, feature/workitems/WorkItemsScreen.kt, feature/repositories/RepositoryListScreen.kt, and their string resources.
  • Excluded: No change to CodeBrowserViewModel/HistoryViewModel internals — both are reused exactly as they were, including their own internal drill-down (subfolder navigation, commit detail) and their own per-state BackHandlers. This screen only adds the TabRow + HorizontalPager chrome around them. No change to actual refresh/pagination/network logic — item 9 only changes the trigger UI, not what onRefresh does.

Design notes

  • Both tabs' ViewModels (codeBrowserViewModel, historyViewModel) are opened together when Route.BranchDetail becomes current, and closed together when either tab's internal back-handling has nothing left to unwind (onCodeBrowserBack/onHistoryBack both close both ViewModels and pop the route) — a single shared exit point rather than two independently-tracked one.
  • HorizontalPager's default beyondViewportPageCount (0) means only the currently visible tab is composed, so each tab's own internal BackHandlers never conflict with each other — the system back button always applies to whichever tab is actually on screen, with no extra coordination code needed.
  • CodeBrowserScreen's existing "히스토리" header button is kept, repurposed to animate the pager to the History tab instead of pushing a route — a working existing affordance, not dead code.
  • Route.CodeBrowser and Route.History are fully removed (not left dead), since Route.BranchDetail is now the only way either screen is reached.

Acceptance criteria

  • Tapping a branch/tag opens Route.BranchDetail, defaulting to the Files tab.
  • Files tab supports folder drill-down and reading file contents (the restored TASK-006 code browser, unmodified).
  • Swiping left/right between tabs works (HorizontalPager); tapping a tab also works (TabRow).
  • System back: within a subfolder/commit-detail, back unwinds that tab's own internal state first; only at each tab's own root does back exit the branch-detail screen entirely.
  • RouteEncoding/RouteStackSaver round-trip Route.BranchDetail including a ref containing a slash, and fall back to Repositories on truncated/malformed persisted data.
  • Commit-detail screen renders correctly (no overlapping content) in the narrow/folded layout.
  • BranchDetailScreen's tab row sits below the status bar, not jammed against it.
  • Diff view supports horizontal scroll on long lines (synced across before/after panels).
  • References-loading message reads a plain "로딩 중".
  • No in-app back buttons remain on any screen header; system back gesture still exits each screen/subview correctly.
  • Pulling down from the top of the repository list or references list triggers a refresh; no manual refresh button remains.
  • Build, unit tests, and lint pass.
  • Full device confirmation of the tab UI, swipe gesture, back-button removal, and pull-to-refresh together in one pass.

Required checks

./gradlew assembleDebug
./gradlew testDebugUnitTest
./gradlew lintDebug

Evidence

  • feature/branchdetail/BranchDetailScreen.kt (new): TabRow + HorizontalPager(pageCount = { 2 }), page 0 = CodeBrowserScreen, page 1 = HistoryScreen, both passed through unmodified with their full existing parameter lists.
  • ui/navigation/Route.kt: CodeBrowser/History replaced by BranchDetail(owner, name, refKind, ref). RouteStackSaver.kt's RouteEncoding updated to match (TYPE_BRANCH_DETAIL replaces TYPE_CODE_BROWSER/TYPE_HISTORY).
  • MainActivity.kt: onReferenceSelected now maps the tapped reference's ReferenceKind (branch/tag) to CodeReferenceKind and pushes one Route.BranchDetail, matching what previously only fed Route.CodeBrowser's refKind field — this mapping had been temporarily lost when TASK-024 redirected the tap straight to Route.History (which didn't need a ref kind); this task restores it.
  • RouteEncodingTest.kt updated: code browser route round-trips and history route round-trips tests replaced by one branch detail route round-trips including a ref containing a slash test; the truncated-route and full-back-stack tests updated to match.
  • HistoryScreen.kt's CommitDetailScreen: each BoxWithConstraints branch now wraps Header(...) + content in Column(Modifier.fillMaxSize()) { ... } instead of leaving them as bare siblings.
  • BranchDetailScreen.kt: outer Column gained Modifier.statusBarsPadding().
  • HistoryScreen.kt's DiffSideLine: Modifier.horizontalScroll(horizontalScrollState).padding(...) with softWrap = false, no fillMaxWidth().
  • strings_repositories.xml: repositories_references_loading simplified to a fixed "로딩 중" string (no %1$s placeholder); call site in RepositoryListScreen.kt updated to match.
  • Back buttons removed from CodeBrowserScreen.BrowserHeader, HistoryScreen.Header, NotificationsScreen.FilterTabs, WorkItemsScreen.ModeTabs; WorkItemsScreen.DetailHeader composable and its 5 call sites removed entirely; ReferencesContent's header back button removed. history_back/notifications_back/workitems_back/codebrowser_back string resources removed as now-unused.
  • RepositoryListScreen.kt: RepositoryContent/ReferencesContent wrapped in PullToRefreshBox (@OptIn(ExperimentalMaterial3Api::class)), manual "새로고침" OutlinedButtons removed from both.
  • ./gradlew compileDebugKotlin: BUILD SUCCESSFUL (verified after adding the @OptIn(ExperimentalMaterial3Api::class) fix for PullToRefreshBox).
  • ./gradlew assembleDebug testDebugUnitTest lintDebug --rerun-tasks: BUILD SUCCESSFUL in 3m 22s, 53 actionable tasks executed, all unit tests passing, 0 lint errors.
  • Live device: build installed over wireless adb (adb-RFCYA0RD1CZ-ztOzAA._adb-tls-connect._tcp, Galaxy Z Fold); human product owner asked to confirm tab switching, folder browsing, overlap fix, status-bar padding, diff horizontal scroll, back-button removal, and pull-to-refresh together — result pending as of this commit.

Review findings

None yet — pending Codex/human review.