feat(ui): browse another branch in the Repo box #114

Merged
deadstyle merged 8 commits from feature/107-browse-branch into develop 2026-10-09 18:12:25 -05:00
Owner

Summary

In [6] Repo, enter (or l) on a branch in the Branches tab now shows that branch on the Files tab, starting at the root. The Files breadcrumb reads <branch>:<path> (for example develop:src/ui) on every branch, the default branch included. The pick lasts until the repo changes, which resets Files to the default branch. o opens the entry on the shown branch. This is the PM's option 1 on #107.

Changes

  • forge.TreeReader: ListTree(ctx, r, ref, dir) and ReadFile(ctx, r, ref, path). Ref "" means the default branch.
  • Forgejo/Gitea and GitHub adapters send the ref as a url.Values-encoded ref query parameter, omitted when it's empty, so feature/x or a&b#c/d reach the forge intact.
  • forgetest.Fake: tree and file seeding is keyed by ref (SetTreeAt, SetFileAt). SetTree and SetFile seed the default branch.
  • core.Key gains Path, and the tree and preview caches are keyed by (repo, ref, path), so one branch's listing is never served for another.
  • UI: details.filesRef holds the chosen branch, which browseBranch sets from the Branches tab, and changing the repo resets it. Tree and preview results for a stale ref are dropped. A pick always refetches the root and shows the cached listing meanwhile. The crumb carries the branch prefix.
  • docs/design.md records the PM's answer, and docs/architecture.md shows the new TreeReader signatures.

Decisions

  • Decided: an empty ref means the default branch. Requiring a concrete name would make Files wait for the branch list on first open, and break Files on hosts that can't list branches.
  • Decided: picking the default branch's row stores "", not its name, so the default branch has a single cache entry.
  • Decided: l and right arrow pick too, not only enter. They're the same key everywhere in navigation, per the keymap in design.md.
  • Decided: at the repo root the crumb is <branch>:, matching git's rev:path notation.
  • Decided: until the default branch's name is known (the branch list is still loading, or the host can't list branches), the crumb shows the path alone rather than a guessed name.
  • Assumed: a chosen branch that's deleted on the forge stays selected, and its root shows — empty —, until the repo changes or another branch is picked.

Test plan

  • make check: exit 0. That covers format, lint (0 issues), the race suite across all packages, govulncheck, the build, shellcheck plus test-install.sh (25/25) and the Windows vet/build.
  • New: internal/forge/{gitea,github}/files_ref_test.go (ref on the wire and round-tripped, empty ref sends no parameter, 404/409 → not found, WebURL kept), forgetest/fake_ref_test.go, core/files_ref_test.go (no cross-branch cache hits), and ui/files_branch_test.go. The UI tests cover enter/l picks, the crumb on picked and default branches and while loading, persistence across tabs, boxes and refresh, reset on repo change, o on a branch, stale-result dropping, a sanitized branch name, and a re-pick refetching.
  • Every new test was proven by mutation: it went red against a deliberately broken implementation.

Refs #107

🤖 Generated with Claude Code

https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY

## Summary In `[6]` Repo, `enter` (or `l`) on a branch in the Branches tab now shows that branch on the Files tab, starting at the root. The Files breadcrumb reads `<branch>:<path>` (for example `develop:src/ui`) on every branch, the default branch included. The pick lasts until the repo changes, which resets Files to the default branch. `o` opens the entry on the shown branch. This is the PM's option 1 on #107. ## Changes - `forge.TreeReader`: `ListTree(ctx, r, ref, dir)` and `ReadFile(ctx, r, ref, path)`. Ref `""` means the default branch. - Forgejo/Gitea and GitHub adapters send the ref as a `url.Values`-encoded `ref` query parameter, omitted when it's empty, so `feature/x` or `a&b#c/d` reach the forge intact. - `forgetest.Fake`: tree and file seeding is keyed by ref (`SetTreeAt`, `SetFileAt`). `SetTree` and `SetFile` seed the default branch. - `core.Key` gains `Path`, and the tree and preview caches are keyed by (repo, ref, path), so one branch's listing is never served for another. - UI: `details.filesRef` holds the chosen branch, which `browseBranch` sets from the Branches tab, and changing the repo resets it. Tree and preview results for a stale ref are dropped. A pick always refetches the root and shows the cached listing meanwhile. The crumb carries the branch prefix. - `docs/design.md` records the PM's answer, and `docs/architecture.md` shows the new `TreeReader` signatures. ## Decisions - Decided: an empty ref means the default branch. Requiring a concrete name would make Files wait for the branch list on first open, and break Files on hosts that can't list branches. - Decided: picking the default branch's row stores `""`, not its name, so the default branch has a single cache entry. - Decided: `l` and right arrow pick too, not only `enter`. They're the same key everywhere in navigation, per the keymap in design.md. - Decided: at the repo root the crumb is `<branch>:`, matching git's `rev:path` notation. - Decided: until the default branch's name is known (the branch list is still loading, or the host can't list branches), the crumb shows the path alone rather than a guessed name. - Assumed: a chosen branch that's deleted on the forge stays selected, and its root shows `— empty —`, until the repo changes or another branch is picked. ## Test plan - `make check`: exit 0. That covers format, lint (0 issues), the race suite across all packages, govulncheck, the build, shellcheck plus `test-install.sh` (25/25) and the Windows vet/build. - New: `internal/forge/{gitea,github}/files_ref_test.go` (ref on the wire and round-tripped, empty ref sends no parameter, 404/409 → not found, `WebURL` kept), `forgetest/fake_ref_test.go`, `core/files_ref_test.go` (no cross-branch cache hits), and `ui/files_branch_test.go`. The UI tests cover `enter`/`l` picks, the crumb on picked and default branches and while loading, persistence across tabs, boxes and refresh, reset on repo change, `o` on a branch, stale-result dropping, a sanitized branch name, and a re-pick refetching. - Every new test was proven by mutation: it went red against a deliberately broken implementation. Refs #107 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Enter or l on a branch in the Branches tab shows that branch's tree on
the Files tab, starting at the root. The choice holds until the repo
changes or another branch is picked. The breadcrumb ends in
<branch>:<path> on every branch, the default branch included.

forge.TreeReader takes a ref ("" is the default branch) in ListTree and
ReadFile. Both adapters send it as a url.Values ref query only when it
is set. core keys tree and preview entries by ref and path, so one
branch's listing never answers for another's. Results for a branch
other than the one shown are dropped.

Refs #107
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
browseBranch seeds from PeekTree but now always returns loadTree for the
root, so a re-pick after a push refreshes. Fix the browse-persistence
tests to return to the Files tab after re-entering [6], and add a
refetch test.

Refs #107

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
test(ui): enter a directory whose listing is still loading on the chosen branch
All checks were successful
ci / check (pull_request) Successful in 1m22s
86cc256e0c
Refs #107

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTZrh5vn4LGcjFQzRVp5TY
deadstyle merged commit c17fe27396 into develop 2026-10-09 18:12:25 -05:00
deadstyle deleted branch feature/107-browse-branch 2026-10-09 18:12:25 -05:00
Sign in to join this conversation.
No description provided.