Skip to content

Commit 744ef8f

Browse files
author
Test
committed
refactor: remove StackTreeDataProvider and implement StackWebViewProvider
- Deleted StackTreeDataProvider.ts to streamline stack management. - Introduced StackWebViewProvider.ts to handle rendering of the current branch's PR stack. - Added webview HTML generation and state management for the stack view. - Implemented React components for stack display, including StackList, StackRow, and StackTargetChip. - Created context menu for stack actions and integrated logging for user interactions. - Updated TypeScript configuration and Webpack setup to include new stack-related components.
1 parent f351771 commit 744ef8f

53 files changed

Lines changed: 3276 additions & 615 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/rules/architecture.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ Business logic decoupled from VS Code UI:
2626
- `JiraService` — fetches Jira issue data via REST API using stored token
2727
- `RefDetailsCache` — caches ref→commit lookups to avoid redundant git calls
2828
- `PRReviewWorktreeStore` — persists active PR-review worktree paths across sessions
29-
- `StackStore` — Memento wrapper over `context.workspaceState` persisting the detected stack forest (`{ branch, parent, source }`); manual entries always win over `github`/`heuristic` detection on merge (`mergeStackEntries`)
30-
- `stackDetectionService` — detects chains of dependent branches ("stacks") from two sources, merged: GitHub PR base chains (authoritative, via `GitHubClient.listOpenPullRequests`) and a local ancestry heuristic (`git merge-base --is-ancestor` / `git rev-list --count`, offline fallback). Pure functions (`computeLocalAncestryParents`, `computeGithubParents`, `mergeDetectedParents`) are unit-tested against fixtures; `detectStacks` is the async orchestrator
31-
- `stackTopology` — pure forest/tree helpers (`buildStackForest`, `topoOrder`, `findStackContaining`) shared by `StackTreeDataProvider` and the status bar indicator
29+
- `StackStore` — Memento wrapper over `context.workspaceState`; now scoped to the local-ancestry **fallback** path only (`{ branch, parent, source: 'heuristic' | 'manual' }`), plus manual overrides (`mergeStackEntries`). PR-derived stacks are never persisted as edges here — see `prStack`/`stackModel` below
30+
- `stackDetectionService` — the local ancestry heuristic (`computeLocalAncestryParents`, `detectLocalAncestryStacks` via `git merge-base --is-ancestor` / `git rev-list --count`, `heuristicEntriesFrom`). Offline-only; consulted by `StackService` solely as a fallback, never merged with PR data
31+
- `stackTopology` — pure forest/tree helpers (`buildStackForest`, `topoOrder`, `findStackContaining`) used by the heuristic fallback path
32+
- `prStack` — pure PR-chain walker: `buildPrStack(prs, currentBranch, { trunk })` walks `head -> base` links both directions from the current branch and returns the ordered chain plus the target branch (which may be trunk or any other branch); no `localBranches` filter, so remote-only chain members and chains targeting trunk are both valid
33+
- `stackModel` — the `StackView` shape both the Stacks webview and the status bar render (`stackViewFromPrStack` / `stackViewFromForest`, `indicatorBranchesOf`, `stackInfoMapOf`)
34+
- `PrStackCache` — Memento-backed cache of the raw open-PR list per repository (TTL-based `isPrCacheFresh`), so a refresh fetches PRs at most once and a stale/offline fetch can still render from the last known list
35+
- `StackService` — orchestrates a refresh: GitHub PR chains are authoritative and fetched once (`GitHubClient.listOpenPullRequestsOrThrow`); the heuristic only runs when GitHub data is genuinely unusable (no remote, or the API call failed with no fresh cache) — the two sources are structurally never merged into one view
3236

3337
## Git Integration (`src/common/git/`)
3438

@@ -48,20 +52,21 @@ Two distinct abstractions — don't conflate them:
4852
| `autoStashAndPop` | Stash is popped onto target branch (destructive) |
4953
| `autoStashAndApply` | Stash applied to target branch, original preserved |
5054

51-
## Stacks Tree View & Status Bar Indicator
55+
## Stacks View & Status Bar Indicator
5256

53-
`StackTreeDataProvider` (`src/view/`, view id `git-smart-checkout.stacks`, same activity-bar container as Worktrees) follows the two-phase render pattern from `WorktreeTreeDataProvider`: fast phase 1 builds the forest topology from `StackStore` alone, phase 2 asynchronously enriches each branch with PR number/state, ahead/behind vs. parent, and a needs-restack flag (`NOT git merge-base --is-ancestor <parent-tip> <branch-tip>`). Detection itself is driven externally from `extension.ts` (`refreshStacks`, debounced on git state/config changes), not by the provider.
57+
`StackWebViewProvider` (`src/view/`, view id `git-smart-checkout.stacks`, same activity-bar container as Worktrees) renders **only the stack containing the current branch** — data is pushed in via `setStack(view)`, the provider itself has no git/GitHub access. Detection is driven externally from `extension.ts` (`refreshStacks`, debounced on git state/config changes) via `StackService.refresh()`, which fetches the open-PR list **once** per refresh (shared by the webview and the status bar), unlike the tree-view era which fetched per branch.
5458

55-
`StatusBarManager` owns a second status bar item (priority = mode item's priority + 1, so it renders immediately to its left) showing `$(layers) <position>/<size>` for the current branch's stack, gated by `shouldShowStackIndicator` (pure, unit-tested) — hidden when stacks are disabled, the status bar is off, HEAD is detached, or the branch isn't stacked.
59+
`StatusBarManager` owns a second status bar item (priority = mode item's priority + 1, so it renders immediately to its left) showing `$(layers) <position>/<size>` for the current branch's stack (`<size>` counts stacked PRs/branches only — the target is excluded, so sitting on the target itself shows no position), gated by `shouldShowStackIndicator` (pure, unit-tested) — hidden when stacks are disabled, the status bar is off, HEAD is detached (`isDetached` is a real signal from `StackService`, not derived from data presence), or the branch isn't stacked.
5660

5761
## WebView Integration
5862

59-
Two React-based webviews in `src/view/` (providers) + `src/webview/Apps/` (React roots):
63+
Three React-based webviews in `src/view/` (providers) + `src/webview/Apps/` (React roots):
6064

6165
- **PR Clone** (`PrCloneWebViewProvider``Apps/PR/`) — form: target branch, feature branch name, description with Markdown preview, Create/Cancel
6266
- **PR Commits** (`PrCommitsWebViewProvider``Apps/Commits/`) — commit list with per-commit selection for cherry-pick
67+
- **Stacks** (`StackWebViewProvider``Apps/Stacks/`) — the current branch's PR stack (`StackList`/`StackRow`/`StackTargetChip`/`ContextMenu` components); click a row to check it out via `git-smart-checkout.checkoutBranch` (argument-only, not palette-visible — reuses `AutoStashService` through the shared `checkoutRefWithStash` tail), right-click for "Open PR in Browser". The target chip additionally shows a read-only `⇡N ⇣N` ahead/behind indicator (`StackView.targetAheadBehind`, sourced from `%(upstream:track)` the same way `WorktreeTreeDataProvider` does) and a "Fetch latest changes" icon button that runs `git-smart-checkout.fetchBranch` (argument-only; `GitExecutor.fetchSpecificBranch`) then re-refreshes
6368

64-
Webpack builds separate bundles (`main.js`, `commits.js`) via `webpack.webview.config.js`. WebView↔extension communication uses `postMessage` / `onDidReceiveMessage`. VS Code CSS variables handle theming.
69+
`src/view/webviewHtml.ts` (`buildWebviewHtml`) is the single place that rewrites a built webview's HTML for use in a `WebviewView`: asset URIs, script nonce, and CSP injection — shared by all three providers. Webpack builds separate bundles (`main.js`, `commits.js`, `stacks.js`) via `webpack.webview.config.js`. WebView↔extension communication uses `postMessage` / `onDidReceiveMessage`. VS Code CSS variables handle theming.
6570

6671
### Markdown Preview
6772

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ settings.local.json
2424
.claude/skills/
2525
changed-files.jsonl
2626
plans/
27+
.claude/
2728

2829
# .agents
2930
.agents

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ All commands are grouped under the `GSC:` prefix in the Command Palette.
4040
| Open a terminal in a selected worktree's directory | `GSC: Open Worktree Dev Terminal...` | [Open worktree dev terminal](docs/open-worktree-dev-terminal.md) |
4141
| Remove several Git worktrees at once with a single confirmation | `GSC: Remove Multiple Worktrees...` | [Remove multiple worktrees](docs/remove-multiple-worktrees.md) |
4242
| Browse all worktrees for the open repositories in a dedicated "Worktrees" view (activity bar), with dirty/ahead-behind/PR-review status and inline Open/Terminal/Copy WIP/Remove actions | *(tree view — right-click an item for Add to Workspace, Copy Path, Reveal in Finder/Explorer)* ||
43-
| Detect chains of dependent local branches ("stacks") from GitHub PR base chains and/or local ancestry, and browse them in a dedicated "Stacks" view (activity bar) with PR number, ahead/behind, and needs-restack status per branch. A status bar indicator (`$(layers) <position>/<size>`) shows where the current branch sits in its stack; click it to reveal the Stacks view. | *(tree view — passive, zero-config)* | |
43+
| Detect the chain of open GitHub PRs the current branch belongs to (a "stack"), and browse it in a dedicated "Stacks" view (activity bar) showing each PR's title and branch plus the branch the stack is ultimately targeting. Click a row (or its target chip) to check it out using the currently selected checkout/stash strategy; right-click for "Open PR in Browser". Falls back to a local-ancestry heuristic when there's no GitHub remote. A status bar indicator (`$(layers) <position>/<size>`) appears only while the current branch is part of a stack; click it to reveal the Stacks view. | `GSC: Refresh Stacks` | [Stacks](docs/stacks.md) |
4444
| Create a new PR from selected commits in another GitHub PR | `GSC: Clone Pull Request...` | [GitHub PR clone](docs/github-pr-clone.md) |
4545
| Generate and optionally push a tag from a reusable template | `GSC: Create Tag from Template...` | [Create tag from template](docs/create-tag-from-template.md) |
4646
| Create and check out a branch from a template (Jira, file, regex, scripts) | `GSC: Create Branch from Template...` | [Create branch from template](docs/create-branch-from-template.md) |
@@ -83,8 +83,8 @@ Click a setting ID to open that setting in VS Code.
8383
| ⚙️ [`git-smart-checkout.pushTagWithoutConfirmation`](vscode://settings/git-smart-checkout.pushTagWithoutConfirmation) (Push tag without confirmation) | `boolean` | Pushes the created Git tag to the remote without asking for confirmation. |
8484
| ⚙️ [`git-smart-checkout.tagRemote`](vscode://settings/git-smart-checkout.tagRemote) (Tag remote) | `string` | Git remote used when pushing created tags. |
8585
| ⚙️ [`git-smart-checkout.telemetry.enabled`](vscode://settings/git-smart-checkout.telemetry.enabled) (Telemetry enabled) | `boolean` | Enables anonymous Git Smart Checkout analytics while respecting VS Code's global telemetry settings. |
86-
| ⚙️ [`git-smart-checkout.stacks.enabled`](vscode://settings/git-smart-checkout.stacks.enabled) (Stacks enabled) | `boolean` | Detects and visualizes stacks of dependent branches (Stacks view and status bar indicator). Default `true`. |
87-
| ⚙️ [`git-smart-checkout.stacks.detection`](vscode://settings/git-smart-checkout.stacks.detection) (Stacks detection mode) | `string` | How stacks are detected. Available values: `auto` (default; GitHub PR base chains, falling back to the local ancestry heuristic), `github` (PR base chains only), `local` (local ancestry heuristic only, no GitHub API calls), `manual` (no automatic detection). |
86+
| ⚙️ [`git-smart-checkout.stacks.enabled`](vscode://settings/git-smart-checkout.stacks.enabled) (Stacks enabled) | `boolean` | Detects and visualizes the current branch's PR stack (Stacks view and status bar indicator). Default `true`. |
87+
| ⚙️ [`git-smart-checkout.stacks.detection`](vscode://settings/git-smart-checkout.stacks.detection) (Stacks detection mode) | `string` | How stacks are detected. Available values: `auto` (default; GitHub PR chains, falling back to the local ancestry heuristic only when GitHub data is unavailable), `github` (PR chains only, no heuristic fallback), `local` (local ancestry heuristic only, no GitHub API calls), `manual` (no automatic detection). |
8888

8989
## Telemetry
9090

docs/stacks.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Stacks
2+
3+
Command: `Git Smart Checkout: Refresh Stacks`
4+
5+
Detects the chain of open GitHub pull requests the current branch belongs to (a "stack") and shows it in the "Stacks" view (Git Smart Checkout activity bar container), alongside a status bar indicator.
6+
7+
## What Counts as a Stack
8+
9+
A stack is a chain of open PRs linked `head -> base`, ending at a **target** branch that has no open PR of its own (often `main`, but it can be any branch — a release branch, another feature branch, etc.). For example:
10+
11+
```
12+
PR #52 vradchuk/feature-1-for-release-v1.1 (head -> base: test/feature-1-for-release-v1)
13+
PR #12 test/feature-1-for-release-v1 (head -> base: test/fake-release-v1)
14+
test/fake-release-v1 <- target (no open PR)
15+
```
16+
17+
Rules:
18+
19+
- A single PR onto the default branch (trunk) is **not** a stack — that's just an ordinary PR.
20+
- A single PR onto any other branch **is** a stack (the target chip is the point).
21+
- Sitting on trunk itself never shows a stack.
22+
- If a branch is the base of more than one open PR (a fork in the stack), the PR with the lowest number is followed; the others aren't shown.
23+
24+
## The Stacks View
25+
26+
Shows the stack containing the **current branch**, top PR first, target branch as a chip at the bottom. Each row shows the PR title and `#number · branch`; the current branch/target is highlighted.
27+
28+
- **Click a row (or the target chip)** to check out that branch, using whichever [stash mode](switch-mode.md) is currently active — including the manual prompt if that's what's configured.
29+
- **Right-click a row** (or use the inline external-link icon) for "Open PR in Browser".
30+
- The target chip shows **⇡N ⇣N** (commits waiting to be pushed / commits to pull), read directly from the branch's upstream tracking info — the same convention as the Worktrees view — whenever the target branch has an upstream configured. Next to it, a **fetch icon (⟳, "Fetch latest changes")** fetches the target branch from its remote (updating the remote-tracking ref only, no checkout, no merge) and refreshes the indicator.
31+
- When the current branch isn't part of any stack, the view shows an empty state with a Refresh button.
32+
33+
## Status Bar Indicator
34+
35+
`$(layers) <position>/<size>` appears immediately to the left of the stash-mode item, but **only** while the current branch is part of a detected stack — it's hidden when stacks are disabled, the extension's status bar is off, HEAD is detached, or the branch simply isn't stacked. The count is of **stacked PRs (or heuristic branches) only** — the target branch doesn't count toward `<size>`, so sitting on the target itself shows no position (there's no PR there). Its tooltip lists the chain top-to-bottom with PR numbers/titles, plus the target branch on its own line. Click it to reveal the Stacks view.
36+
37+
## Detection Sources
38+
39+
Controlled by `git-smart-checkout.stacks.detection`:
40+
41+
| Value | Behavior |
42+
| --- | --- |
43+
| `auto` (default) | GitHub PR chains; falls back to the local ancestry heuristic only when GitHub data is genuinely unavailable (no GitHub remote/token, or the API call fails with no usable cache). |
44+
| `github` | GitHub PR chains only — no heuristic fallback. |
45+
| `local` | Local ancestry heuristic only (no GitHub API calls); useful offline or without a GitHub remote. |
46+
| `manual` | No automatic detection. |
47+
48+
GitHub PR chains are **authoritative** — the two sources are never merged into one stack. The heuristic (closest ancestor branch by commit distance) is a fallback for when there's no usable PR data at all, so it never grafts extra branches onto a genuine PR stack.
49+
50+
Disable everything with:
51+
52+
```json
53+
"git-smart-checkout.stacks.enabled": false
54+
```

package.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
{
6060
"id": "git-smart-checkout.stacks",
6161
"name": "Stacks",
62-
"type": "tree",
62+
"type": "webview",
6363
"icon": "$(layers)",
6464
"when": "git-smart-checkout.hasRepository"
6565
},
@@ -90,6 +90,12 @@
9090
"title": "Checkout Previous Branch (With Stash)",
9191
"category": "GSC"
9292
},
93+
{
94+
"command": "git-smart-checkout.stacks.refresh",
95+
"title": "Refresh Stacks",
96+
"category": "GSC",
97+
"icon": "$(refresh)"
98+
},
9399
{
94100
"command": "git-smart-checkout.copyBranchName",
95101
"title": "Copy Current Branch Name to Clipboard",
@@ -349,6 +355,11 @@
349355
"when": "view == git-smart-checkout.worktrees",
350356
"group": "navigation@3"
351357
},
358+
{
359+
"command": "git-smart-checkout.stacks.refresh",
360+
"when": "view == git-smart-checkout.stacks",
361+
"group": "navigation@1"
362+
},
352363
{
353364
"command": "git-smart-checkout.prCancelCloneMenu",
354365
"when": "view == git-smart-checkout.prClone && git-smart-checkout.isCloning == true && git-smart-checkout.isConflict == true",
@@ -690,7 +701,7 @@
690701
"git-smart-checkout.stacks.enabled": {
691702
"type": "boolean",
692703
"default": true,
693-
"description": "Detect and visualize stacks of dependent branches (Stacks tree view and status bar indicator)."
704+
"description": "Detect and visualize the current branch's PR stack (Stacks view and status bar indicator)."
694705
},
695706
"git-smart-checkout.stacks.detection": {
696707
"type": "string",
@@ -701,8 +712,8 @@
701712
"manual"
702713
],
703714
"markdownEnumDescriptions": [
704-
"Use GitHub PR base chains when available, falling back to the local ancestry heuristic",
705-
"Only use GitHub PR base chains",
715+
"Use GitHub PR chains when available, falling back to the local ancestry heuristic only when GitHub data is unavailable",
716+
"Only use GitHub PR chains (no heuristic fallback)",
706717
"Only use the local ancestry heuristic (no GitHub API calls)",
707718
"Disable automatic detection; rely entirely on manually-set stack parents"
708719
],

src/analytics/analytics.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export enum AnalyticsEvent {
3636
BranchStashRestored = 'branch_stash_restored',
3737
StatusBarMenuOpened = 'status_bar_menu_opened',
3838
SettingsOpened = 'settings_opened',
39+
CheckoutStackBranch = 'checkout_stack_branch',
40+
FetchStackBaseBranch = 'fetch_stack_base_branch',
3941
}
4042

4143
let client: PostHog | null = null;

0 commit comments

Comments
 (0)