santree 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -52
- package/dist/commands/dashboard.d.ts +1 -1
- package/dist/commands/dashboard.js +95 -27
- package/dist/commands/doctor.js +33 -71
- package/dist/commands/github/auth.d.ts +2 -0
- package/dist/commands/github/auth.js +56 -0
- package/dist/commands/github/index.d.ts +1 -0
- package/dist/commands/github/index.js +1 -0
- package/dist/commands/helpers/template.d.ts +1 -0
- package/dist/commands/helpers/template.js +13 -10
- package/dist/commands/issue/index.d.ts +1 -0
- package/dist/commands/issue/index.js +1 -0
- package/dist/commands/issue/open.d.ts +2 -0
- package/dist/commands/{linear → issue}/open.js +13 -11
- package/dist/commands/issue/switch.d.ts +11 -0
- package/dist/commands/issue/switch.js +38 -0
- package/dist/commands/linear/auth.js +23 -10
- package/dist/commands/linear/switch.js +7 -3
- package/dist/commands/pr/create.js +7 -5
- package/dist/commands/worktree/create.js +4 -6
- package/dist/commands/worktree/work.js +1 -1
- package/dist/lib/ai.d.ts +8 -6
- package/dist/lib/ai.js +29 -15
- package/dist/lib/dashboard/DetailPanel.d.ts +5 -2
- package/dist/lib/dashboard/DetailPanel.js +6 -3
- package/dist/lib/dashboard/DiffOverlay.js +8 -1
- package/dist/lib/dashboard/data.js +17 -9
- package/dist/lib/dashboard/types.d.ts +3 -16
- package/dist/lib/git.d.ts +16 -33
- package/dist/lib/git.js +20 -74
- package/dist/lib/metadata.d.ts +3 -0
- package/dist/lib/metadata.js +27 -0
- package/dist/lib/multiplexer/cmux.js +1 -1
- package/dist/lib/multiplexer/types.d.ts +1 -1
- package/dist/lib/prompts.d.ts +4 -3
- package/dist/lib/prompts.js +4 -3
- package/dist/lib/session-signal.d.ts +2 -3
- package/dist/lib/session-signal.js +3 -29
- package/dist/lib/trackers/auth-store.d.ts +16 -0
- package/dist/lib/trackers/auth-store.js +57 -0
- package/dist/lib/trackers/config.d.ts +8 -0
- package/dist/lib/trackers/config.js +21 -0
- package/dist/lib/trackers/github/api.d.ts +3 -0
- package/dist/lib/trackers/github/api.js +90 -0
- package/dist/lib/trackers/github/auth.d.ts +5 -0
- package/dist/lib/trackers/github/auth.js +27 -0
- package/dist/lib/trackers/github/images.d.ts +2 -0
- package/dist/lib/trackers/github/images.js +42 -0
- package/dist/lib/trackers/github/index.d.ts +2 -0
- package/dist/lib/trackers/github/index.js +78 -0
- package/dist/lib/trackers/index.d.ts +12 -0
- package/dist/lib/trackers/index.js +34 -0
- package/dist/lib/trackers/linear/api.d.ts +4 -0
- package/dist/lib/trackers/linear/api.js +128 -0
- package/dist/lib/trackers/linear/auth.d.ts +11 -0
- package/dist/lib/trackers/linear/auth.js +206 -0
- package/dist/lib/trackers/linear/images.d.ts +2 -0
- package/dist/lib/trackers/linear/images.js +44 -0
- package/dist/lib/trackers/linear/index.d.ts +3 -0
- package/dist/lib/trackers/linear/index.js +100 -0
- package/dist/lib/trackers/types.d.ts +52 -0
- package/dist/lib/trackers/types.js +1 -0
- package/package.json +1 -1
- package/prompts/ticket.njk +3 -3
- package/dist/commands/linear/open.d.ts +0 -2
- package/dist/lib/linear.d.ts +0 -83
- package/dist/lib/linear.js +0 -482
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
18
|
Create, switch, and manage Git worktrees with ease.<br/>
|
|
19
|
-
Integrates with GitHub PRs and Linear
|
|
19
|
+
Integrates with GitHub PRs and pluggable issue trackers (Linear, GitHub Issues) via Claude AI.
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
---
|
|
@@ -112,13 +112,29 @@ With the `stw` alias: `stw create`, `stw list`, `stw switch`, `stw work`, `stw c
|
|
|
112
112
|
| `santree pr fix` | Fix PR review comments with AI |
|
|
113
113
|
| `santree pr review` | Review changes against ticket with AI |
|
|
114
114
|
|
|
115
|
-
###
|
|
115
|
+
### Issue trackers
|
|
116
|
+
|
|
117
|
+
Santree supports Linear and GitHub Issues behind a single interface. Each repo picks one. Use the generic `santree issue` commands for tracker-agnostic actions; use `santree linear` / `santree github` for backend-specific auth.
|
|
118
|
+
|
|
119
|
+
#### Generic — `santree issue`
|
|
120
|
+
|
|
121
|
+
| Command | Description |
|
|
122
|
+
| -------------------------------------- | ------------------------------------------------------------ |
|
|
123
|
+
| `santree issue switch <linear\|github>` | Pick the active tracker for this repo |
|
|
124
|
+
| `santree issue open` | Open the current branch's issue in the browser |
|
|
125
|
+
|
|
126
|
+
#### Linear (`santree linear`)
|
|
116
127
|
|
|
117
128
|
| Command | Description |
|
|
118
129
|
| ----------------------- | --------------------------------------------- |
|
|
119
130
|
| `santree linear auth` | Authenticate with Linear (OAuth) |
|
|
120
131
|
| `santree linear switch` | Switch Linear workspace for this repo |
|
|
121
|
-
|
|
132
|
+
|
|
133
|
+
#### GitHub Issues (`santree github`)
|
|
134
|
+
|
|
135
|
+
| Command | Description |
|
|
136
|
+
| --------------------- | ---------------------------------------------------------------------- |
|
|
137
|
+
| `santree github auth` | Verify `gh auth status`, run `gh auth login` if needed, set tracker=github |
|
|
122
138
|
|
|
123
139
|
### Helpers (`santree helpers`)
|
|
124
140
|
|
|
@@ -137,16 +153,75 @@ With the `stw` alias: `stw create`, `stw list`, `stw switch`, `stw work`, `stw c
|
|
|
137
153
|
|
|
138
154
|
| Command | Description |
|
|
139
155
|
| ------------------- | ----------------------------------------------- |
|
|
140
|
-
| `santree dashboard` | Interactive dashboard of all your
|
|
156
|
+
| `santree dashboard` | Interactive dashboard of all your assigned issues |
|
|
141
157
|
| `santree doctor` | Check system requirements and integrations |
|
|
142
158
|
|
|
143
159
|
---
|
|
144
160
|
|
|
145
161
|
## Features
|
|
146
162
|
|
|
163
|
+
### Workflow
|
|
164
|
+
|
|
165
|
+
End-to-end flow, from picking an issue to merging the PR. Everything runs inside the terminal multiplexer; the dashboard orchestrates and the AI agent does the heavy lifting at two distinct stages.
|
|
166
|
+
|
|
167
|
+
```mermaid
|
|
168
|
+
flowchart TB
|
|
169
|
+
subgraph Term["Terminal · tmux / cmux"]
|
|
170
|
+
direction TB
|
|
171
|
+
Dashboard(["① Dashboard<br/>pick issue · add context"]):::action
|
|
172
|
+
Editor["Editor<br/><sub>zed · cursor · vscode · nvim · jetbrains</sub>"]:::tool
|
|
173
|
+
AI(("② / ⑤ AI agent<br/>Claude Code")):::agent
|
|
174
|
+
Pager["③ Diff pager<br/><sub>delta · diff-so-fancy · built-in</sub>"]:::tool
|
|
175
|
+
Decision{good?}:::decision
|
|
176
|
+
Ship(["④ git commit + PR"]):::action
|
|
177
|
+
GitHub[("GitHub<br/>PRs · CI")]:::external
|
|
178
|
+
|
|
179
|
+
Dashboard ==>|implement| AI
|
|
180
|
+
Dashboard -. write context .-> Editor
|
|
181
|
+
Editor -. context · hand-edit .-> AI
|
|
182
|
+
AI ==> Pager
|
|
183
|
+
Pager ==> Decision
|
|
184
|
+
Decision -->|hand-edit| Editor
|
|
185
|
+
Decision -->|resume| AI
|
|
186
|
+
Decision ==>|ship| Ship
|
|
187
|
+
Ship ==> GitHub
|
|
188
|
+
GitHub -->|reviews · CI| AI
|
|
189
|
+
AI -->|push fix| Ship
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
classDef action fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
|
|
193
|
+
classDef tool fill:#f3f4f6,stroke:#9ca3af,color:#374151
|
|
194
|
+
classDef agent fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px,color:#4c1d95
|
|
195
|
+
classDef external fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#78350f
|
|
196
|
+
classDef decision fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Bold edges (`==>`) follow the happy path; thin edges (`-->`) are branches/loops; dotted edges (`-.->`) are optional editor side-trips. Colors group nodes by role: blue actions, purple agent, gray tools, yellow external service, red decision.
|
|
200
|
+
|
|
201
|
+
**Stages, in order:**
|
|
202
|
+
|
|
203
|
+
1. **Pick + add context** — open the dashboard, browse assigned issues, pick one. The inline context box lets you add custom instructions to the prompt; `Ctrl+O` drops into your editor for longer prose.
|
|
204
|
+
2. **AI agent (Claude)** — runs in the worktree's mux window with the rendered ticket + your context. Implements or plans.
|
|
205
|
+
3. **Diff pager (review)** — `[v]` opens the diff overlay. From here you iterate: hand-edit in your editor, or resume the Claude session to keep going.
|
|
206
|
+
4. **Ship** — once the diff looks right, `[C]` for inline commit + push, then `[c]` to create the PR (`--fill` runs Claude non-interactively against the PR template).
|
|
207
|
+
5. **PR feedback loop** — `[f]` (fix) and `[r]` (self-review) re-launch the AI with PR comments + CI failures as input. Patches are pushed; CI re-runs; loop until merged.
|
|
208
|
+
|
|
209
|
+
**Supported tools per stage:**
|
|
210
|
+
|
|
211
|
+
| Stage | Supported today | Planned |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| **Issue tracker** (① Dashboard) | Linear, GitHub Issues | Jira, Shortcut, etc. |
|
|
214
|
+
| **AI agent** (②, ⑤) | Claude Code (`claude` CLI) | OpenAI Codex, OpenCode, Cursor agent |
|
|
215
|
+
| **Diff pager** (③) | delta, diff-so-fancy, any unified-diff pager — built-in colorizer when none set | — |
|
|
216
|
+
| **Editor** (side branch) | Anything taking a path: zed, nvim, jetbrains, cursor, vscode. cursor + vscode also handle `.code-workspace` files via `[E]` workspace shortcut | — |
|
|
217
|
+
| **Forge** (④) | GitHub via `gh` CLI | GitLab, Bitbucket, Gitea |
|
|
218
|
+
| **Terminal multiplexer** (outer frame) | tmux, cmux *(macOS-only — see [#1472](https://github.com/manaflow-ai/cmux/issues/1472))*, none | zellij |
|
|
219
|
+
|
|
220
|
+
The AI agent and issue tracker are already behind interfaces (`lib/trackers/`, plus the `claude` CLI shim in `lib/ai.ts`). Adding a new option in either category means writing one new module and wiring it into the factory — no changes to UI/dashboard code.
|
|
221
|
+
|
|
147
222
|
### Interactive Dashboard
|
|
148
223
|
|
|
149
|
-
`santree dashboard` opens a full-screen TUI to manage all your work in one place. It shows your
|
|
224
|
+
`santree dashboard` opens a full-screen TUI to manage all your work in one place. It shows your assigned issues from the active tracker (Linear or GitHub Issues) grouped by project, with live status for worktrees, PRs, CI checks, and reviews.
|
|
150
225
|
|
|
151
226
|
**Left pane** — issue list. Issues without worktrees show as a single row (priority + ID + title). Issues with worktrees expand into nested sub-rows below the title showing `· diff` (files/adds/deletes/commits-ahead), `· pr` (number, state, CI, review count), and `· session` (state + Claude session ID). Click any row (main or sub) to select; scroll wheel navigates; drag the divider to resize panes.
|
|
152
227
|
|
|
@@ -162,7 +237,7 @@ With the `stw` alias: `stw create`, `stw list`, `stw switch`, `stw work`, `stw c
|
|
|
162
237
|
| `c` | Create PR (fill from commits or open in browser) |
|
|
163
238
|
| `v` | Inline diff overlay — file tree + diff content (mouse + keyboard) |
|
|
164
239
|
| `f` / `r` | Fix PR / Review PR (launches in tmux) |
|
|
165
|
-
| `o` / `p` | Open Linear
|
|
240
|
+
| `o` / `p` | Open issue (in Linear or GitHub) / PR in browser |
|
|
166
241
|
| `d` | Remove worktree |
|
|
167
242
|
|
|
168
243
|
Commit, PR creation, and diff review happen inline without leaving the dashboard. Work, fix, and review open in new tmux windows.
|
|
@@ -177,9 +252,9 @@ Create isolated worktrees for each feature branch. No more stashing or committin
|
|
|
177
252
|
|
|
178
253
|
See PR status directly in your worktree list. Clean up worktrees automatically when PRs are merged or closed.
|
|
179
254
|
|
|
180
|
-
###
|
|
255
|
+
### Issue Tracker Integration
|
|
181
256
|
|
|
182
|
-
Santree fetches
|
|
257
|
+
Santree fetches issue data (title, description, comments, images) from the active tracker — Linear or GitHub Issues — and injects it into prompts when running `santree worktree work`. See [Issue Tracker Integration](#issue-tracker-integration-1) for setup.
|
|
183
258
|
|
|
184
259
|
### Claude AI Integration
|
|
185
260
|
|
|
@@ -210,16 +285,46 @@ npm install
|
|
|
210
285
|
|
|
211
286
|
### Branch Naming
|
|
212
287
|
|
|
213
|
-
|
|
288
|
+
Branch names need to encode the issue ID so santree can link the worktree back to the right ticket. The accepted format depends on the active tracker:
|
|
214
289
|
|
|
215
290
|
```
|
|
291
|
+
# Linear (uppercased letter prefix + dash + digits)
|
|
216
292
|
user/TEAM-123-feature-description
|
|
217
293
|
feature/PROJ-456-add-auth
|
|
294
|
+
|
|
295
|
+
# GitHub Issues (explicit prefix required, to avoid `fix-typo-1` matching)
|
|
296
|
+
feature/issue-42-add-auth
|
|
297
|
+
gh-42-add-auth
|
|
298
|
+
42-add-auth
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
GitHub's parser is strict on purpose: a commit-style branch like `fix-typo-1` will *not* match (no explicit prefix, no slash-led number).
|
|
302
|
+
|
|
303
|
+
### Issue Tracker Integration
|
|
304
|
+
|
|
305
|
+
Each repo picks one tracker. The active tracker is resolved in this order: `SANTREE_TRACKER` env var → per-repo `_tracker.kind` (in `.santree/metadata.json`) → legacy `_linear.org` (treated as Linear) → auto-detect (any Linear creds → Linear, else GitHub).
|
|
306
|
+
|
|
307
|
+
#### Choosing a tracker
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Explicitly pick the active tracker for this repo
|
|
311
|
+
santree issue switch linear
|
|
312
|
+
santree issue switch github
|
|
313
|
+
|
|
314
|
+
# Or let an auth command set it as a side effect:
|
|
315
|
+
santree linear auth # Sets _tracker.kind = "linear" after OAuth
|
|
316
|
+
santree github auth # Sets _tracker.kind = "github" after `gh auth login`
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
For a one-off override (testing, scripting):
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
SANTREE_TRACKER=github santree dashboard
|
|
218
323
|
```
|
|
219
324
|
|
|
220
|
-
|
|
325
|
+
#### Linear
|
|
221
326
|
|
|
222
|
-
Santree fetches Linear ticket data
|
|
327
|
+
Santree fetches Linear ticket data via the GraphQL API (OAuth PKCE).
|
|
223
328
|
|
|
224
329
|
```bash
|
|
225
330
|
# Authenticate with Linear (opens browser for OAuth)
|
|
@@ -233,12 +338,26 @@ santree linear auth --test TEAM-123
|
|
|
233
338
|
|
|
234
339
|
# Log out
|
|
235
340
|
santree linear auth --logout
|
|
341
|
+
|
|
342
|
+
# Switch between authenticated workspaces
|
|
343
|
+
santree linear switch
|
|
236
344
|
```
|
|
237
345
|
|
|
238
346
|
On first run, `santree linear auth` opens your browser to authorize the app with your Linear workspace. Tokens are stored in `$XDG_CONFIG_HOME/santree/auth.json` (defaults to `~/.config/santree/auth.json`) and auto-refresh transparently.
|
|
239
347
|
|
|
240
348
|
If you have multiple workspaces authenticated, running `santree linear auth` in a new repo will let you pick which one to link. Images from tickets are downloaded to a temp directory and cleaned up after Claude exits.
|
|
241
349
|
|
|
350
|
+
#### GitHub Issues
|
|
351
|
+
|
|
352
|
+
Santree uses the existing `gh` CLI — no separate OAuth flow.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Verify gh is authenticated; flips this repo's tracker to GitHub
|
|
356
|
+
santree github auth
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Issues are listed via `gh search issues --assignee=@me --state=open --repo <owner>/<name>` (current repo only — cross-repo issues aren't surfaced today). Priority is derived from labels matching `P0`/`P1`/`P2`/`P3`/`urgent`/`critical`/`high`/`medium`/`low`, falling back to `No priority`. Attached images on `user-images.githubusercontent.com` and `github.com/.../assets/` are downloaded so Claude can read them when filling PR templates.
|
|
360
|
+
|
|
242
361
|
### Claude Code Statusline (Optional)
|
|
243
362
|
|
|
244
363
|
Santree provides a custom statusline for Claude Code showing git info, model, context usage, and cost.
|
|
@@ -286,45 +405,11 @@ To preview the JSON without writing: `santree helpers session-signal install --d
|
|
|
286
405
|
|
|
287
406
|
Verify with `santree doctor` — look for the "Session Signal Hooks" row under Claude Code.
|
|
288
407
|
|
|
289
|
-
### Session Hooks (Optional)
|
|
290
|
-
|
|
291
|
-
Run custom scripts when Claude's session state changes. Create executable scripts in `.santree/hooks/`:
|
|
292
|
-
|
|
293
|
-
```
|
|
294
|
-
.santree/hooks/
|
|
295
|
-
on-waiting.sh # Runs when session needs permission approval
|
|
296
|
-
on-active.sh # Runs when user submits a new prompt
|
|
297
|
-
on-idle.sh # Runs when session finishes and waits for next prompt
|
|
298
|
-
on-exited.sh # Runs when session ends
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Each script receives these environment variables:
|
|
302
|
-
|
|
303
|
-
| Variable | Description |
|
|
304
|
-
|----------|-------------|
|
|
305
|
-
| `SANTREE_TICKET_ID` | e.g. `TEAM-123` |
|
|
306
|
-
| `SANTREE_SESSION_STATE` | `waiting`, `active`, `idle`, or `exited` |
|
|
307
|
-
| `SANTREE_SESSION_ID` | The Claude session ID |
|
|
308
|
-
| `SANTREE_WORKTREE_PATH` | Absolute path to the worktree |
|
|
309
|
-
| `SANTREE_REPO_ROOT` | Absolute path to the main repo |
|
|
310
|
-
| `SANTREE_MESSAGE` | Notification message (only for `waiting`) |
|
|
311
|
-
|
|
312
|
-
Scripts are optional — only executed if they exist and are executable. They run fire-and-forget with a 5-second timeout.
|
|
313
|
-
|
|
314
|
-
**Example** — log when Claude is waiting for approval:
|
|
315
|
-
|
|
316
|
-
```bash
|
|
317
|
-
#!/bin/bash
|
|
318
|
-
# .santree/hooks/on-waiting.sh
|
|
319
|
-
echo "$(date): $SANTREE_TICKET_ID waiting — $SANTREE_MESSAGE" >> /tmp/santree-hooks.log
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Make it executable: `chmod +x .santree/hooks/on-waiting.sh`
|
|
323
|
-
|
|
324
408
|
### Environment Variables
|
|
325
409
|
|
|
326
410
|
| Variable | Effect |
|
|
327
411
|
|---|---|
|
|
412
|
+
| `SANTREE_TRACKER` | Override the active issue tracker for a single invocation: `linear` or `github`. Takes precedence over the per-repo `_tracker.kind`. If unset, falls back to repo config → legacy `_linear.org` → auto-detect. |
|
|
328
413
|
| `SANTREE_EDITOR` | Editor used by `e` (open in editor) actions in the dashboard. Defaults to `code`. Examples: `cursor`, `zed`, `code`, `nvim`. |
|
|
329
414
|
| `SANTREE_MULTIPLEXER` | Terminal multiplexer used by the dashboard and `worktree create --window`. One of `tmux`, `cmux`, `none`. If unset, auto-detects from `$TMUX` / `$CMUX_SURFACE_ID`. cmux is macOS-only and limited by [manaflow-ai/cmux#1472](https://github.com/manaflow-ai/cmux/issues/1472). |
|
|
330
415
|
| `SANTREE_DIFF_TOOL` | Pager used by `worktree diff` (CLI) and the dashboard diff overlay. Passed to git as `-c core.pager=<tool>` for the CLI, and used to pipe content for the overlay. Examples: `delta`, `diff-so-fancy`. Must accept a unified diff on stdin. Names are restricted to `[A-Za-z0-9_\-/.+]`. |
|
|
@@ -384,7 +469,7 @@ Shows a branch-only unified diff against the base branch's merge-base — same s
|
|
|
384
469
|
| -------- | ------------------------------- |
|
|
385
470
|
| `--plan` | Only create implementation plan |
|
|
386
471
|
|
|
387
|
-
Automatically fetches Linear
|
|
472
|
+
Automatically fetches issue data from the active tracker (Linear or GitHub Issues) if authenticated. Degrades gracefully if not.
|
|
388
473
|
|
|
389
474
|
### pr create
|
|
390
475
|
|
|
@@ -426,16 +511,16 @@ Santree currently locks in to specific providers for some integrations and is in
|
|
|
426
511
|
|
|
427
512
|
| Area | Supported | Not yet supported |
|
|
428
513
|
| --- | --- | --- |
|
|
429
|
-
| **Ticket system** | Linear (via `santree linear auth`) | Jira, GitHub Issues, Shortcut, Asana, Linear-equivalents |
|
|
430
514
|
| **Source control / forge** | GitHub (via `gh` CLI) | GitLab, Bitbucket, Gitea, Codeberg, self-hosted Forgejo |
|
|
431
515
|
| **Coding agent** | Claude Code (`claude` CLI) | OpenAI Codex, OpenCode, Cursor agent, Aider, others |
|
|
432
516
|
|
|
433
|
-
These are hardwired in `lib/
|
|
517
|
+
These are hardwired in `lib/github.ts` and `lib/ai.ts` respectively. Adding a second provider means abstracting an interface (similar to `lib/trackers/` or `lib/multiplexer/`) and wiring a selection mechanism.
|
|
434
518
|
|
|
435
519
|
### Multi-provider (already interchangeable)
|
|
436
520
|
|
|
437
521
|
| Area | How to switch | Examples |
|
|
438
522
|
| --- | --- | --- |
|
|
523
|
+
| **Issue tracker** | `santree issue switch <linear\|github>` (or `SANTREE_TRACKER` env var, or as a side effect of `santree linear auth` / `santree github auth`) | Linear (OAuth + GraphQL), GitHub Issues (via `gh` CLI). Adding a third tracker = one new directory under `lib/trackers/`. |
|
|
439
524
|
| **Editor** | `SANTREE_EDITOR` env var (or `--editor` flag on `worktree open`) | `code`, `cursor`, `zed`, `nvim`, `subl`, `webstorm` — any executable that takes a path argument |
|
|
440
525
|
| **Terminal multiplexer** | `SANTREE_MULTIPLEXER` env var | `tmux` (default, all platforms), `cmux` (macOS only — see [#1472](https://github.com/manaflow-ai/cmux/issues/1472)), `none`. Zellij is planned but not implemented. |
|
|
441
526
|
| **Diff renderer** | `SANTREE_DIFF_TOOL` env var | `delta`, `diff-so-fancy`, or any pager that accepts a unified diff. Falls back to plain `git diff` colorization when unset. |
|
|
@@ -502,11 +587,17 @@ source/
|
|
|
502
587
|
├── cli.tsx # Entry point (Pastel app runner)
|
|
503
588
|
├── lib/
|
|
504
589
|
│ ├── ai.ts # Shared AI logic (context, prompt, launch)
|
|
505
|
-
│ ├── git.ts # Git helpers (worktrees, branches
|
|
590
|
+
│ ├── git.ts # Git helpers (worktrees, branches); extractTicketId is a tracker shim
|
|
506
591
|
│ ├── github.ts # GitHub CLI wrapper (PR info, auth, push, checks, reviews)
|
|
507
|
-
│ ├── linear.ts # Linear GraphQL API client (OAuth, tickets, images)
|
|
508
592
|
│ ├── exec.ts # Shell command helpers
|
|
593
|
+
│ ├── metadata.ts # .santree/metadata.json r/w (extracted to break import cycles)
|
|
509
594
|
│ ├── prompts.ts # Nunjucks template renderer
|
|
595
|
+
│ ├── trackers/ # Issue tracker abstraction (Linear, GitHub Issues)
|
|
596
|
+
│ │ ├── types.ts # IssueTracker interface + generic Issue/AssignedIssue types
|
|
597
|
+
│ │ ├── index.ts # getIssueTracker(repoRoot) factory
|
|
598
|
+
│ │ ├── linear/ # OAuth PKCE + GraphQL + image rewriter
|
|
599
|
+
│ │ └── github/ # `gh` CLI wrappers; priority derived from labels
|
|
600
|
+
│ ├── multiplexer/ # tmux/cmux/none abstraction (windows/sessions)
|
|
510
601
|
│ └── dashboard/ # Dashboard UI components
|
|
511
602
|
│ ├── types.ts # State types, action types, phase enums
|
|
512
603
|
│ ├── IssueList.tsx # Left pane — issue list with priority, session, PR, CI columns
|
|
@@ -516,7 +607,9 @@ source/
|
|
|
516
607
|
├── dashboard.tsx # Top-level: interactive dashboard
|
|
517
608
|
├── worktree/ # Worktree management (create, list, switch, etc.)
|
|
518
609
|
├── pr/ # PR lifecycle (create, open, fix, review)
|
|
519
|
-
├── linear/ # Linear
|
|
610
|
+
├── linear/ # Linear-specific OAuth (auth, switch)
|
|
611
|
+
├── github/ # GitHub-specific auth (gh wrapper)
|
|
612
|
+
├── issue/ # Tracker-agnostic actions (switch, open)
|
|
520
613
|
└── helpers/ # Shell init, statusline
|
|
521
614
|
prompts/ # Nunjucks templates: implement, plan, review, fix-pr, fill-pr, ticket
|
|
522
615
|
shell/ # Shell integration templates: init.zsh.njk, init.bash.njk
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const description = "Interactive dashboard of your
|
|
1
|
+
export declare const description = "Interactive dashboard of your assigned issues";
|
|
2
2
|
export default function Dashboard(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -17,7 +17,7 @@ import { extractTicketId } from "../lib/git.js";
|
|
|
17
17
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
18
18
|
import { getPRTemplate } from "../lib/github.js";
|
|
19
19
|
import { renderPrompt, renderDiff, renderTicket } from "../lib/prompts.js";
|
|
20
|
-
import {
|
|
20
|
+
import { getIssueTracker } from "../lib/trackers/index.js";
|
|
21
21
|
import * as os from "os";
|
|
22
22
|
import { initialState, reducer } from "../lib/dashboard/types.js";
|
|
23
23
|
import { loadDashboardData, loadReviewsData } from "../lib/dashboard/data.js";
|
|
@@ -30,7 +30,7 @@ import { CommitOverlay, PrCreateOverlay } from "../lib/dashboard/Overlays.js";
|
|
|
30
30
|
import { MultilineTextArea } from "../lib/dashboard/MultilineTextArea.js";
|
|
31
31
|
import DiffOverlay, { flattenTreeFiles, computeDiffLayout, clampDiffLeftWidth, DIFF_DIVIDER_WIDTH, } from "../lib/dashboard/DiffOverlay.js";
|
|
32
32
|
import { CURRENT_VERSION, CLAUDE_CODE_PACKAGE, getLatestVersion, getCachedLatestVersion, getLatestVersionFor, getCachedLatestVersionFor, isUpdateAvailable, } from "../lib/version.js";
|
|
33
|
-
export const description = "Interactive dashboard of your
|
|
33
|
+
export const description = "Interactive dashboard of your assigned issues";
|
|
34
34
|
const execAsync = promisify(exec);
|
|
35
35
|
// Resolved at module load — cheap. Honors cmux's bundled binary when running
|
|
36
36
|
// inside cmux so the header reflects the binary santree will actually use.
|
|
@@ -42,17 +42,74 @@ const CLAUDE_VERSION = getInstalledClaudeVersion() ?? "";
|
|
|
42
42
|
* R100\told/path\tnew/path
|
|
43
43
|
* For renames/copies, the status code has a similarity suffix we strip.
|
|
44
44
|
*/
|
|
45
|
+
/**
|
|
46
|
+
* Split combined-parameter SGR sequences (e.g. `\x1b[48;2;R;G;B;38;2;R;G;B m`)
|
|
47
|
+
* into separate single-attribute SGRs (`\x1b[48;2;...m\x1b[38;2;...m`).
|
|
48
|
+
*
|
|
49
|
+
* Why: Ink uses `slice-ansi` to clip text horizontally, and `slice-ansi`
|
|
50
|
+
* miscounts visible width on combined RGB bg+fg SGRs — it cuts the line at
|
|
51
|
+
* roughly half the requested visible width. Delta emits exactly this combined
|
|
52
|
+
* form on every styled token, so the diff pane was rendering content cut at
|
|
53
|
+
* arbitrary points (e.g. `from datetime i` instead of `from datetime import
|
|
54
|
+
* timedelta`). Splitting them sidesteps the slice-ansi bug without losing any
|
|
55
|
+
* styling — the terminal renders the two SGRs identically to the combined one.
|
|
56
|
+
*/
|
|
57
|
+
function splitCombinedSgr(s) {
|
|
58
|
+
return s.replace(/\x1b\[([0-9;]+)m/g, (_match, params) => {
|
|
59
|
+
const tokens = params.split(";");
|
|
60
|
+
const groups = [];
|
|
61
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
62
|
+
const t = tokens[i];
|
|
63
|
+
if ((t === "38" || t === "48") && tokens[i + 1] === "2") {
|
|
64
|
+
groups.push([t, "2", tokens[i + 2], tokens[i + 3], tokens[i + 4]].join(";"));
|
|
65
|
+
i += 4;
|
|
66
|
+
}
|
|
67
|
+
else if ((t === "38" || t === "48") && tokens[i + 1] === "5") {
|
|
68
|
+
groups.push([t, "5", tokens[i + 2]].join(";"));
|
|
69
|
+
i += 2;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
groups.push(t);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (groups.length <= 1)
|
|
76
|
+
return `\x1b[${params}m`;
|
|
77
|
+
return groups.map((g) => `\x1b[${g}m`).join("");
|
|
78
|
+
});
|
|
79
|
+
}
|
|
45
80
|
/**
|
|
46
81
|
* Pipe `git diff` output through an external tool (e.g. delta) and return the
|
|
47
82
|
* combined ANSI output. Uses spawn pipes — no shell — so the tool name is safe
|
|
48
83
|
* even though we already validate it in getDiffTool().
|
|
49
84
|
*/
|
|
50
|
-
function runPipedDiff(cwd,
|
|
85
|
+
function runPipedDiff(cwd, gitArgs, tool, themeMode) {
|
|
51
86
|
return new Promise((resolve, reject) => {
|
|
52
|
-
const git = spawn("git", ["-C", cwd,
|
|
87
|
+
const git = spawn("git", ["-C", cwd, ...gitArgs], {
|
|
53
88
|
stdio: ["ignore", "pipe", "pipe"],
|
|
54
89
|
});
|
|
55
|
-
|
|
90
|
+
// Delta's syntax theme defaults are tuned for dark backgrounds — pale
|
|
91
|
+
// Monokai foreground on a light terminal becomes invisible. Force the
|
|
92
|
+
// theme flag matching santree's detected mode so colors stay readable.
|
|
93
|
+
const pagerArgs = tool === "delta" ? [themeMode === "light" ? "--light" : "--dark"] : [];
|
|
94
|
+
// Disable hyperlinks for delta: OSC 8 sequences (`\x1b]8;...`) are not
|
|
95
|
+
// handled by truncateVisible() — its CSI-only regex counts the URL
|
|
96
|
+
// bytes as visible characters, mangling line truncation and breaking
|
|
97
|
+
// terminal rendering of the wrapped text. Delta's CLI rejects an
|
|
98
|
+
// inline `--hyperlinks=false`, so override via GIT_CONFIG_PARAMETERS
|
|
99
|
+
// (delta reads its config from git). Also drop line-numbers — they
|
|
100
|
+
// eat ~6 cols of an already-narrow right pane.
|
|
101
|
+
const pagerEnv = tool === "delta"
|
|
102
|
+
? {
|
|
103
|
+
...process.env,
|
|
104
|
+
GIT_CONFIG_PARAMETERS: "'delta.hyperlinks=false' 'delta.line-numbers=false'",
|
|
105
|
+
}
|
|
106
|
+
: process.env;
|
|
107
|
+
// We use the pager only for its rendering — the dashboard owns
|
|
108
|
+
// scrolling/search itself in Ink, so we capture stdout as a string.
|
|
109
|
+
const pager = spawn(tool, pagerArgs, {
|
|
110
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
111
|
+
env: pagerEnv,
|
|
112
|
+
});
|
|
56
113
|
let out = "";
|
|
57
114
|
let err = "";
|
|
58
115
|
git.stdout.pipe(pager.stdin);
|
|
@@ -72,7 +129,7 @@ function runPipedDiff(cwd, mergeBase, filePath, tool) {
|
|
|
72
129
|
reject(new Error(err || `${tool} exited with code ${code}`));
|
|
73
130
|
}
|
|
74
131
|
else {
|
|
75
|
-
resolve(out);
|
|
132
|
+
resolve(splitCombinedSgr(out));
|
|
76
133
|
}
|
|
77
134
|
});
|
|
78
135
|
});
|
|
@@ -664,16 +721,24 @@ export default function Dashboard() {
|
|
|
664
721
|
if (file.isUntracked) {
|
|
665
722
|
// Untracked files aren't in `git diff` output — fake a "full
|
|
666
723
|
// addition" diff via --no-index against /dev/null. git exits 1
|
|
667
|
-
// when files differ; that's expected, so capture stdout
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
|
|
724
|
+
// when files differ; that's expected, so we capture stdout
|
|
725
|
+
// regardless. Pipe through the configured tool when set so
|
|
726
|
+
// untracked files get the same syntax highlighting as tracked
|
|
727
|
+
// ones; otherwise fall back to spawnAsync + manual colorize.
|
|
728
|
+
if (tool) {
|
|
729
|
+
const content = await runPipedDiff(cwd, ["diff", "--color=always", "--no-index", "--", "/dev/null", file.path], tool, theme.mode);
|
|
730
|
+
dispatch({ type: "DIFF_CONTENT_LOADED", content });
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
const { output } = await spawnAsync("git", ["-C", cwd, "diff", "--no-color", "--no-index", "--", "/dev/null", file.path], { cwd });
|
|
734
|
+
dispatch({ type: "DIFF_CONTENT_LOADED", content: output });
|
|
735
|
+
}
|
|
671
736
|
}
|
|
672
737
|
else if (tool) {
|
|
673
738
|
// Pipe git diff (with colors enabled so the tool can pass them
|
|
674
739
|
// through if desired) into the configured tool. Use spawn pipes
|
|
675
740
|
// rather than shell to avoid quoting concerns.
|
|
676
|
-
const content = await runPipedDiff(cwd, mergeBase, file.path, tool);
|
|
741
|
+
const content = await runPipedDiff(cwd, ["diff", "--color=always", mergeBase, "--", file.path], tool, theme.mode);
|
|
677
742
|
dispatch({ type: "DIFF_CONTENT_LOADED", content });
|
|
678
743
|
}
|
|
679
744
|
else {
|
|
@@ -693,6 +758,7 @@ export default function Dashboard() {
|
|
|
693
758
|
state.diffMergeBase,
|
|
694
759
|
state.diffFileIndex,
|
|
695
760
|
state.diffFiles,
|
|
761
|
+
theme.mode,
|
|
696
762
|
]);
|
|
697
763
|
// ── Actions ───────────────────────────────────────────────────────
|
|
698
764
|
const launchWorkInTmux = useCallback(async (di, mode, worktreePath, contextFile) => {
|
|
@@ -733,13 +799,13 @@ export default function Dashboard() {
|
|
|
733
799
|
type: "SET_ACTION_MESSAGE",
|
|
734
800
|
message: resumeCmd
|
|
735
801
|
? `Resumed session in new window: ${windowName}`
|
|
736
|
-
: `Launched ${mode} in
|
|
802
|
+
: `Launched ${mode} in new window: ${windowName}`,
|
|
737
803
|
});
|
|
738
804
|
}
|
|
739
805
|
else {
|
|
740
806
|
dispatch({
|
|
741
807
|
type: "SET_ACTION_MESSAGE",
|
|
742
|
-
message: `Failed to create
|
|
808
|
+
message: `Failed to create window${created.message ? `: ${created.message}` : ""}`,
|
|
743
809
|
});
|
|
744
810
|
}
|
|
745
811
|
}
|
|
@@ -768,7 +834,7 @@ export default function Dashboard() {
|
|
|
768
834
|
else {
|
|
769
835
|
dispatch({
|
|
770
836
|
type: "SET_ACTION_MESSAGE",
|
|
771
|
-
message: `Worktree created, but
|
|
837
|
+
message: `Worktree created, but window launch failed${created.message ? `: ${created.message}` : ""}`,
|
|
772
838
|
});
|
|
773
839
|
}
|
|
774
840
|
setTimeout(() => refresh(), 3000);
|
|
@@ -1097,12 +1163,14 @@ export default function Dashboard() {
|
|
|
1097
1163
|
dispatch({ type: "PR_CREATE_PHASE", phase: "filling" });
|
|
1098
1164
|
const ticketId = extractTicketId(s.prCreateBranch) ?? "";
|
|
1099
1165
|
const mainRepoRoot = findMainRepoRoot();
|
|
1100
|
-
// Fetch
|
|
1166
|
+
// Fetch issue content from the active tracker (downloads images
|
|
1167
|
+
// inline so Claude can read them via --allowedTools Read).
|
|
1101
1168
|
let ticketContent;
|
|
1102
1169
|
if (ticketId && mainRepoRoot) {
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1170
|
+
const tracker = getIssueTracker(mainRepoRoot);
|
|
1171
|
+
const result = await tracker.getIssue(ticketId, mainRepoRoot);
|
|
1172
|
+
if (result.ok) {
|
|
1173
|
+
ticketContent = renderTicket(result.value, tracker.displayName);
|
|
1106
1174
|
}
|
|
1107
1175
|
}
|
|
1108
1176
|
const commitLog = run(`git log ${base}..HEAD --format="- %s"`, { cwd }) || null;
|
|
@@ -1759,7 +1827,7 @@ export default function Dashboard() {
|
|
|
1759
1827
|
dispatch({
|
|
1760
1828
|
type: "SET_ACTION_MESSAGE",
|
|
1761
1829
|
message: created.ok
|
|
1762
|
-
?
|
|
1830
|
+
? "Launched AI review in new window"
|
|
1763
1831
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
1764
1832
|
});
|
|
1765
1833
|
})();
|
|
@@ -1864,7 +1932,7 @@ export default function Dashboard() {
|
|
|
1864
1932
|
if (!created.ok) {
|
|
1865
1933
|
dispatch({
|
|
1866
1934
|
type: "SET_ACTION_MESSAGE",
|
|
1867
|
-
message: `Failed to switch
|
|
1935
|
+
message: `Failed to switch window${created.message ? `: ${created.message}` : ""}`,
|
|
1868
1936
|
});
|
|
1869
1937
|
}
|
|
1870
1938
|
})();
|
|
@@ -1876,10 +1944,10 @@ export default function Dashboard() {
|
|
|
1876
1944
|
}
|
|
1877
1945
|
return;
|
|
1878
1946
|
}
|
|
1879
|
-
// Open in Linear
|
|
1947
|
+
// Open issue in tracker (Linear/GitHub web UI)
|
|
1880
1948
|
if (input === "o") {
|
|
1881
1949
|
if (!di.issue.url) {
|
|
1882
|
-
dispatch({ type: "SET_ACTION_MESSAGE", message: "No
|
|
1950
|
+
dispatch({ type: "SET_ACTION_MESSAGE", message: "No issue URL available" });
|
|
1883
1951
|
return;
|
|
1884
1952
|
}
|
|
1885
1953
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
@@ -1935,7 +2003,7 @@ export default function Dashboard() {
|
|
|
1935
2003
|
dispatch({
|
|
1936
2004
|
type: "SET_ACTION_MESSAGE",
|
|
1937
2005
|
message: created.ok
|
|
1938
|
-
?
|
|
2006
|
+
? "Launched review in new window"
|
|
1939
2007
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
1940
2008
|
});
|
|
1941
2009
|
})();
|
|
@@ -2002,7 +2070,7 @@ export default function Dashboard() {
|
|
|
2002
2070
|
dispatch({
|
|
2003
2071
|
type: "SET_ACTION_MESSAGE",
|
|
2004
2072
|
message: created.ok
|
|
2005
|
-
?
|
|
2073
|
+
? "Launched PR fix in new window"
|
|
2006
2074
|
: `Failed to launch PR fix${created.message ? `: ${created.message}` : ""}`,
|
|
2007
2075
|
});
|
|
2008
2076
|
})();
|
|
@@ -2063,14 +2131,14 @@ export default function Dashboard() {
|
|
|
2063
2131
|
}), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "j/k to navigate, Enter to select, ESC to cancel" })] }) })) : state.overlay === "confirm-delete" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 3, paddingY: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Remove worktree?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: selectedIssue?.worktree?.branch ?? "" }), selectedIssue?.worktree?.dirty && (_jsx(Text, { color: "yellow", children: "Warning: worktree has uncommitted changes" })), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "red", bold: true, children: "y" }), " Confirm"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "n" }), " Cancel"] })] }) })) : state.overlay === "diff" ? (_jsx(DiffOverlay, { width: innerWidth, height: contentHeight, ticketId: state.diffTicketId ?? "", baseBranch: state.diffBaseBranch ?? "", files: state.diffFiles, fileIndex: state.diffFileIndex, fileScrollOffset: state.diffFileScrollOffset, content: state.diffContent, contentScrollOffset: state.diffContentScrollOffset, loadingFiles: state.diffLoadingFiles, loadingContent: state.diffLoadingContent, error: state.diffError, selectionBg: theme.selectionBg, leftWidthOverride: diffLeftWidth ?? undefined, pendingDiscard: state.diffPendingDiscard })) : state.overlay === "confirm-setup" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 3, paddingY: 1, children: [_jsx(Text, { bold: true, children: "Run setup script?" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: ".santree/init.sh" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "y" }), " Run setup"] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", bold: true, children: "n" }), " Skip"] })] }) })) : (_jsxs(Box, { flexGrow: 1, children: [_jsx(Box, { width: leftWidth, children: state.activeTab === "reviews" ? (_jsx(ReviewList, { flatReviews: state.flatReviews, selectedIndex: state.reviewSelectedIndex, scrollOffset: state.reviewListScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg })) : state.flatIssues.length === 0 ? (_jsx(Box, { width: leftWidth, height: contentHeight, justifyContent: "center", alignItems: "center", children: _jsx(Text, { color: "yellow", children: "No active issues" }) })) : (_jsx(IssueList, { groups: state.groups, flatIssues: state.flatIssues, selectedIndex: state.selectedIndex, scrollOffset: state.listScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg })) }), _jsx(Box, { flexDirection: "column", width: 3, children: Array.from({ length: contentHeight }).map((_, i) => (_jsx(Text, { dimColor: true, children: " │ " }, i))) }), _jsx(Box, { width: rightWidth, children: state.activeTab === "reviews" && state.creatingForTicket ? (_jsxs(Box, { flexDirection: "column", width: rightWidth, height: contentHeight, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Setting up worktree for ", state.creatingForTicket, "..."] }), state.creationLogs
|
|
2064
2132
|
.split("\n")
|
|
2065
2133
|
.slice(-(contentHeight - 1))
|
|
2066
|
-
.map((line, i) => (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: line }) }, i)))] })) : state.activeTab === "reviews" ? (_jsx(ReviewDetailPanel, { item: selectedReview, scrollOffset: state.reviewDetailScrollOffset, height: contentHeight, width: rightWidth })) : state.overlay === "commit" ? (_jsx(CommitOverlay, { width: rightWidth, height: contentHeight, branch: state.commitBranch, ticketId: state.commitTicketId, gitStatus: state.commitGitStatus, phase: state.commitPhase, message: state.commitMessage, error: state.commitError, dispatch: dispatch, onSubmit: handleCommitSubmit })) : state.overlay === "pr-create" ? (_jsx(PrCreateOverlay, { width: rightWidth, height: contentHeight, branch: state.prCreateBranch, ticketId: state.prCreateTicketId, phase: state.prCreatePhase, error: state.prCreateError, url: state.prCreateUrl, body: state.prCreateBody, title: state.prCreateTitle, dispatch: dispatch })) : (_jsx(DetailPanel, { issue: selectedIssue, scrollOffset: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay }) })] })) })] })] }));
|
|
2134
|
+
.map((line, i) => (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: line }) }, i)))] })) : state.activeTab === "reviews" ? (_jsx(ReviewDetailPanel, { item: selectedReview, scrollOffset: state.reviewDetailScrollOffset, height: contentHeight, width: rightWidth })) : state.overlay === "commit" ? (_jsx(CommitOverlay, { width: rightWidth, height: contentHeight, branch: state.commitBranch, ticketId: state.commitTicketId, gitStatus: state.commitGitStatus, phase: state.commitPhase, message: state.commitMessage, error: state.commitError, dispatch: dispatch, onSubmit: handleCommitSubmit })) : state.overlay === "pr-create" ? (_jsx(PrCreateOverlay, { width: rightWidth, height: contentHeight, branch: state.prCreateBranch, ticketId: state.prCreateTicketId, phase: state.prCreatePhase, error: state.prCreateError, url: state.prCreateUrl, body: state.prCreateBody, title: state.prCreateTitle, dispatch: dispatch })) : (_jsx(DetailPanel, { issue: selectedIssue, scrollOffset: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay, trackerName: getIssueTracker(repoRootRef.current).displayName }) })] })) })] })] }));
|
|
2067
2135
|
}
|
|
2068
2136
|
/**
|
|
2069
2137
|
* Renders the per-issue action key hints (Resume / Editor / View diff / …)
|
|
2070
2138
|
* lifted out of the detail panels so they sit on the same row as the global
|
|
2071
2139
|
* command bar. Empty when nothing is selected.
|
|
2072
2140
|
*/
|
|
2073
|
-
function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, }) {
|
|
2141
|
+
function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, trackerName, }) {
|
|
2074
2142
|
// During the diff overlay, none of the per-issue actions apply (View diff
|
|
2075
2143
|
// is what got us here, Commit/PR/etc. need the detail panel context). Keep
|
|
2076
2144
|
// the row blank so the diff-specific CommandBar reads cleanly.
|
|
@@ -2081,7 +2149,7 @@ function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, }) {
|
|
|
2081
2149
|
? buildReviewActions(selectedReview)
|
|
2082
2150
|
: []
|
|
2083
2151
|
: selectedIssue
|
|
2084
|
-
? buildIssueActions(selectedIssue)
|
|
2152
|
+
? buildIssueActions(selectedIssue, trackerName)
|
|
2085
2153
|
: [];
|
|
2086
2154
|
if (items.length === 0)
|
|
2087
2155
|
return _jsx(Text, { children: " " });
|