santree 0.6.3 → 0.7.0
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/dist/commands/dashboard.js +47 -10
- package/dist/lib/dashboard/DetailPanel.js +6 -2
- package/dist/lib/dashboard/IssueList.d.ts +2 -0
- package/dist/lib/dashboard/IssueList.js +9 -1
- package/dist/lib/dashboard/Overlays.d.ts +2 -1
- package/dist/lib/dashboard/Overlays.js +3 -3
- package/dist/lib/dashboard/types.d.ts +3 -0
- package/dist/lib/dashboard/types.js +4 -0
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ import { getInstalledClaudeVersion } from "../lib/version.js";
|
|
|
15
15
|
import { extractTicketId, getStagedDiffContent } from "../lib/git.js";
|
|
16
16
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
17
17
|
import { shellEscape } from "../lib/multiplexer/types.js";
|
|
18
|
+
import Spinner from "ink-spinner";
|
|
18
19
|
import SquirrelLoader from "../lib/squirrel-loader.js";
|
|
19
20
|
import { getPRTemplate } from "../lib/github.js";
|
|
20
21
|
import { renderPrompt, renderDiff, renderTicket } from "../lib/prompts.js";
|
|
@@ -399,7 +400,8 @@ export default function Dashboard() {
|
|
|
399
400
|
}
|
|
400
401
|
try {
|
|
401
402
|
// Re-detect terminal theme alongside data fetch so light↔dark
|
|
402
|
-
// switches propagate within one refresh cycle (≤
|
|
403
|
+
// switches propagate within one refresh cycle (≤5min, or sooner
|
|
404
|
+
// on a manual `R`). Skip the
|
|
403
405
|
// OSC 11 query when a text-input overlay is active — the
|
|
404
406
|
// terminal's response would otherwise leak into the user's
|
|
405
407
|
// commit/PR/context message via Ink's stdin handler.
|
|
@@ -647,16 +649,21 @@ export default function Dashboard() {
|
|
|
647
649
|
await refresh(true);
|
|
648
650
|
};
|
|
649
651
|
init();
|
|
650
|
-
// Auto-refresh every
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
//
|
|
652
|
+
// Auto-refresh every 5 minutes. Each refresh fans out into several
|
|
653
|
+
// `gh pr view`/`gh pr checks` calls per worktree-PR plus the reviews
|
|
654
|
+
// tab, all on the GraphQL API (5000-point/hour budget) — a 30s cadence
|
|
655
|
+
// drained it within the hour when the dashboard was left open. Press
|
|
656
|
+
// `R` for an on-demand refresh between cycles. While the diff overlay
|
|
657
|
+
// is open, also bump the diff refresh tick so new/removed files
|
|
658
|
+
// (created or deleted outside the dashboard) eventually show up.
|
|
659
|
+
// Stage/unstage already patch XY in place, so this is purely about
|
|
660
|
+
// file-set drift.
|
|
654
661
|
refreshTimerRef.current = setInterval(() => {
|
|
655
662
|
refresh();
|
|
656
663
|
if (stateRef.current.overlay === "diff") {
|
|
657
664
|
dispatch({ type: "DIFF_REFRESH_FILES" });
|
|
658
665
|
}
|
|
659
|
-
},
|
|
666
|
+
}, 300_000);
|
|
660
667
|
return () => {
|
|
661
668
|
if (refreshTimerRef.current)
|
|
662
669
|
clearInterval(refreshTimerRef.current);
|
|
@@ -1567,7 +1574,8 @@ export default function Dashboard() {
|
|
|
1567
1574
|
try {
|
|
1568
1575
|
const bodyFile = path.join(os.tmpdir(), `santree-pr-${Date.now()}.md`);
|
|
1569
1576
|
fs.writeFileSync(bodyFile, s.prCreateBody);
|
|
1570
|
-
const
|
|
1577
|
+
const draftFlag = s.prCreateDraft ? " --draft" : "";
|
|
1578
|
+
const { stdout } = await execAsync(`gh pr create --title "${s.prCreateTitle.replace(/"/g, '\\"')}" --base "${base}" --head "${s.prCreateBranch}" --body-file "${bodyFile}"${draftFlag}`, { cwd });
|
|
1571
1579
|
try {
|
|
1572
1580
|
fs.unlinkSync(bodyFile);
|
|
1573
1581
|
}
|
|
@@ -1589,8 +1597,25 @@ export default function Dashboard() {
|
|
|
1589
1597
|
return;
|
|
1590
1598
|
const base = getBaseBranch(s.prCreateBranch);
|
|
1591
1599
|
const cwd = s.prCreateWorktreePath;
|
|
1600
|
+
// Carry the edited title/body into GitHub's compose page so the browser
|
|
1601
|
+
// opens pre-filled (gh passes them as URL query params). Without this,
|
|
1602
|
+
// the fill→"open in browser" path would drop everything the user just
|
|
1603
|
+
// reviewed. Note: very long bodies can be truncated by GitHub's URL
|
|
1604
|
+
// length limit — gh's documented `--web` behavior; the editable compose
|
|
1605
|
+
// page is the fallback. Draft selection lives in the browser dropdown,
|
|
1606
|
+
// since `gh --web` doesn't accept `--draft`.
|
|
1607
|
+
let bodyFile = null;
|
|
1592
1608
|
try {
|
|
1593
|
-
|
|
1609
|
+
let cmd = `gh pr create --web --base "${base}" --head "${s.prCreateBranch}"`;
|
|
1610
|
+
if (s.prCreateTitle) {
|
|
1611
|
+
cmd += ` --title "${s.prCreateTitle.replace(/"/g, '\\"')}"`;
|
|
1612
|
+
}
|
|
1613
|
+
if (s.prCreateBody) {
|
|
1614
|
+
bodyFile = path.join(os.tmpdir(), `santree-pr-${Date.now()}.md`);
|
|
1615
|
+
fs.writeFileSync(bodyFile, s.prCreateBody);
|
|
1616
|
+
cmd += ` --body-file "${bodyFile}"`;
|
|
1617
|
+
}
|
|
1618
|
+
await execAsync(cmd, { cwd });
|
|
1594
1619
|
dispatch({ type: "PR_CREATE_DONE", url: "" });
|
|
1595
1620
|
setTimeout(() => {
|
|
1596
1621
|
dispatch({ type: "PR_CREATE_CANCEL" });
|
|
@@ -1601,6 +1626,14 @@ export default function Dashboard() {
|
|
|
1601
1626
|
const msg = e?.stderr?.trim() || e?.message || "Failed to open in browser";
|
|
1602
1627
|
dispatch({ type: "PR_CREATE_ERROR", error: msg });
|
|
1603
1628
|
}
|
|
1629
|
+
finally {
|
|
1630
|
+
if (bodyFile) {
|
|
1631
|
+
try {
|
|
1632
|
+
fs.unlinkSync(bodyFile);
|
|
1633
|
+
}
|
|
1634
|
+
catch { }
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1604
1637
|
}, [refresh]);
|
|
1605
1638
|
// ── Keyboard ──────────────────────────────────────────────────────
|
|
1606
1639
|
useInput((input, key) => {
|
|
@@ -1682,6 +1715,10 @@ export default function Dashboard() {
|
|
|
1682
1715
|
confirmPrCreate();
|
|
1683
1716
|
return;
|
|
1684
1717
|
}
|
|
1718
|
+
if (input === "d") {
|
|
1719
|
+
dispatch({ type: "PR_CREATE_TOGGLE_DRAFT" });
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1685
1722
|
if (input === "e") {
|
|
1686
1723
|
dispatch({ type: "PR_CREATE_EDIT" });
|
|
1687
1724
|
return;
|
|
@@ -2688,7 +2725,7 @@ export default function Dashboard() {
|
|
|
2688
2725
|
: state.flatIssues[state.selectedIndex]) ?? null;
|
|
2689
2726
|
const selectedReview = state.flatReviews[state.reviewSelectedIndex] ?? null;
|
|
2690
2727
|
const activeTracker = getIssueTracker(repoRootRef.current);
|
|
2691
|
-
return (_jsxs(Box, { width: columns, height: rows, flexDirection: "column", children: [_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "santree" }), _jsxs(Text, { dimColor: true, children: [" ", "v", version] }), updateAvailable && latestVersion ? (_jsxs(Text, { color: "yellow", children: [" ⬆ v", latestVersion, " available — `santree update`"] })) : null, CLAUDE_VERSION ? (_jsxs(Text, { dimColor: true, children: [" · claude ", CLAUDE_VERSION] })) : null, claudeUpdateAvailable && latestClaudeVersion ? (_jsxs(Text, { color: "yellow", children: [" ⬆ ", latestClaudeVersion] })) : null, state.refreshing ?
|
|
2728
|
+
return (_jsxs(Box, { width: columns, height: rows, flexDirection: "column", children: [_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "santree" }), _jsxs(Text, { dimColor: true, children: [" ", "v", version] }), updateAvailable && latestVersion ? (_jsxs(Text, { color: "yellow", children: [" ⬆ v", latestVersion, " available — `santree update`"] })) : null, CLAUDE_VERSION ? (_jsxs(Text, { dimColor: true, children: [" · claude ", CLAUDE_VERSION] })) : null, claudeUpdateAvailable && latestClaudeVersion ? (_jsxs(Text, { color: "yellow", children: [" ⬆ ", latestClaudeVersion] })) : null, state.refreshing ? (_jsxs(Text, { dimColor: true, children: [" · ", _jsx(Spinner, { type: "dots" }), " refreshing"] })) : null, state.actionMessage ? (_jsxs(Text, { color: "yellow", children: [" · ", state.actionMessage] })) : null] }), _jsxs(Box, { paddingX: 1, children: [_jsx(Tab, { active: state.activeTab === "issues", label: `1 Issues (${state.flatIssues.length})`, mode: theme.mode }), _jsx(Text, { children: " " }), _jsx(Tab, { active: state.activeTab === "trees", label: `2 Trees (${state.flatTrees.length})`, mode: theme.mode }), _jsx(Text, { children: " " }), _jsx(Tab, { active: state.activeTab === "reviews", label: `3 Reviews (${state.flatReviews.length})`, mode: theme.mode })] }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: "cyan", flexDirection: "column", children: [state.overlay === "help" ? (_jsx(HelpOverlay, { width: innerWidth, height: contentHeight })) : state.overlay === "tracker-select" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 3, paddingY: 1, children: state.trackerSelectPhase === "linear-org" ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Select a Linear workspace:" }), _jsx(Text, { children: " " }), state.trackerSelectOrgs.map((org, i) => {
|
|
2692
2729
|
const sel = i === state.trackerSelectIndex;
|
|
2693
2730
|
return (_jsxs(Text, { color: sel ? "cyan" : undefined, bold: sel, children: [sel ? "> " : " ", org.name, " (", org.slug, ")"] }, org.slug));
|
|
2694
2731
|
}), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "j/k to navigate, Enter to link, ESC to go back" })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Select an issue tracker for this repo:" }), _jsx(Text, { children: " " }), [
|
|
@@ -2721,7 +2758,7 @@ export default function Dashboard() {
|
|
|
2721
2758
|
: 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
|
|
2722
2759
|
.split("\n")
|
|
2723
2760
|
.slice(-(contentHeight - 1))
|
|
2724
|
-
.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.activeTab === "trees"
|
|
2761
|
+
.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, draft: state.prCreateDraft, dispatch: dispatch })) : (_jsx(DetailPanel, { issue: selectedIssue, scrollOffset: state.activeTab === "trees"
|
|
2725
2762
|
? state.treeDetailScrollOffset
|
|
2726
2763
|
: 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: activeTracker.displayName, canMutate: activeTracker.canMutate === true }) })] })) })] })] }));
|
|
2727
2764
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
function stateColor(type) {
|
|
4
4
|
switch (type) {
|
|
@@ -144,7 +144,11 @@ export default function DetailPanel({ issue, scrollOffset, height, width, creati
|
|
|
144
144
|
const contentRows = height - 1;
|
|
145
145
|
const startIdx = Math.max(0, logLines.length - contentRows);
|
|
146
146
|
const visible = logLines.slice(startIdx, startIdx + contentRows);
|
|
147
|
-
|
|
147
|
+
// Setup-script output is arbitrary-length; clamp each line to the pane
|
|
148
|
+
// width so long lines truncate instead of wrapping/overflowing into the
|
|
149
|
+
// left pane. Ink's default soft-wrap would push the box past `height`.
|
|
150
|
+
const clampLine = (s) => s.length > width ? s.slice(0, Math.max(0, width - 1)) + "…" : s;
|
|
151
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Text, { color: "yellow", bold: true, children: clampLine(`Setting up worktree for ${creatingForTicket}...`) }), visible.map((line, i) => (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: clampLine(line) }) }, i)))] }));
|
|
148
152
|
}
|
|
149
153
|
if (!issue) {
|
|
150
154
|
return (_jsx(Box, { width: width, height: height, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "No issue selected" }) }));
|
|
@@ -64,6 +64,11 @@ export function buildIssueListRows(groups, flatIssues) {
|
|
|
64
64
|
}
|
|
65
65
|
groups.forEach((group, gi) => {
|
|
66
66
|
const totalIssues = group.statusGroups.reduce((sum, sg) => sum + sg.issues.length, 0);
|
|
67
|
+
// Blank line between projects. Modelled as a real row (not a render-only
|
|
68
|
+
// `marginTop`) so `rows[]` indices line up 1:1 with rendered rows —
|
|
69
|
+
// otherwise the dashboard's click→row mapping drifts by one per project.
|
|
70
|
+
if (gi > 0)
|
|
71
|
+
rows.push({ kind: "spacer" });
|
|
67
72
|
rows.push({ kind: "header", name: group.name, count: totalIssues, isFirst: gi === 0 });
|
|
68
73
|
for (const sg of group.statusGroups) {
|
|
69
74
|
rows.push({ kind: "status-header", name: sg.name, type: sg.type, count: sg.issues.length });
|
|
@@ -86,6 +91,9 @@ export default function IssueList({ groups, flatIssues, selectedIndex, scrollOff
|
|
|
86
91
|
const visible = rows.slice(scrollOffset, scrollOffset + height);
|
|
87
92
|
const titleMaxWidth = Math.max(width - LEFT_FIXED - 1 /* leading space */ - RIGHT_FIXED - TITLE_GAP, 10);
|
|
88
93
|
return (_jsx(Box, { flexDirection: "column", width: width, height: height, children: _jsx(Box, { flexDirection: "column", height: height, children: visible.map((row, i) => {
|
|
94
|
+
if (row.kind === "spacer") {
|
|
95
|
+
return _jsx(Box, { height: 1 }, `sp-${i}`);
|
|
96
|
+
}
|
|
89
97
|
if (row.kind === "header") {
|
|
90
98
|
// On the first project header, also render the WT/CI column
|
|
91
99
|
// labels right-aligned to the worktree/CI glyph columns —
|
|
@@ -97,7 +105,7 @@ export default function IssueList({ groups, flatIssues, selectedIndex, scrollOff
|
|
|
97
105
|
const labelPad = row.isFirst
|
|
98
106
|
? Math.max(2, width - namePart.length - labelText.length)
|
|
99
107
|
: 0;
|
|
100
|
-
return (_jsxs(Box, {
|
|
108
|
+
return (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: row.name }), _jsxs(Text, { dimColor: true, children: [" ", row.count] }), row.isFirst && _jsx(Text, { dimColor: true, children: `${" ".repeat(labelPad)}${labelText}` })] }, `h-${i}`));
|
|
101
109
|
}
|
|
102
110
|
if (row.kind === "status-header") {
|
|
103
111
|
return (_jsxs(Box, { children: [_jsxs(Text, { color: stateColor(row.type, row.name), children: [" ", row.name] }), _jsxs(Text, { dimColor: true, children: [" ", row.count] })] }, `sh-${i}`));
|
|
@@ -22,9 +22,10 @@ interface PrCreateOverlayProps {
|
|
|
22
22
|
url: string | null;
|
|
23
23
|
body: string | null;
|
|
24
24
|
title: string | null;
|
|
25
|
+
draft: boolean;
|
|
25
26
|
dispatch: React.Dispatch<DashboardAction>;
|
|
26
27
|
}
|
|
27
|
-
export declare function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, body, title, dispatch, }: PrCreateOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, body, title, draft, dispatch, }: PrCreateOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
28
29
|
interface HelpOverlayProps {
|
|
29
30
|
width: number;
|
|
30
31
|
height: number;
|
|
@@ -20,11 +20,11 @@ export function CommitOverlay({ width, height, branch, ticketId, gitStatus, phas
|
|
|
20
20
|
return (_jsxs(Text, { color: color, children: [" ", line] }, i));
|
|
21
21
|
}), gitStatus.split("\n").length > 8 && (_jsxs(Text, { dimColor: true, children: [" +", gitStatus.split("\n").length - 8, " more"] }))] })) : null, _jsx(Text, { children: " " }), phase === "confirm-stage" && (_jsxs(Text, { children: ["Stage all changes?", " ", _jsx(Text, { color: "cyan", bold: true, children: "y" }), "/", _jsx(Text, { color: "cyan", bold: true, children: "n" })] })), phase === "choose-mode" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "How do you want to write the message?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "f" }), " ", "Fill \u2014 let Claude draft a short message"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "m" }), " ", "Manual \u2014 type it yourself"] })] })), phase === "filling" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Drafting commit message with Claude..."] })), phase === "awaiting-message" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Edit commit message" }), _jsx(Text, { children: " " }), _jsx(MultilineTextArea, { value: message, onChange: (v) => dispatch({ type: "COMMIT_MESSAGE", message: v }), onSubmit: () => onSubmit(message), onCancel: () => dispatch({ type: "COMMIT_CANCEL" }), width: width, height: Math.max(3, Math.min(6, height - 12)), placeholder: "(empty)" }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "Ctrl+D" }), " commit · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+O" }), " editor · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+G" }), " cancel"] })] })), phase === "committing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Committing..."] })), phase === "pushing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Pushing..."] })), phase === "done" && (_jsx(Text, { color: "green", bold: true, children: "Committed and pushed!" })), phase === "error" && _jsx(Text, { color: "red", children: error }), phase !== "awaiting-message" && phase !== "done" && phase !== "error" && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "ESC to cancel" })] }))] }));
|
|
22
22
|
}
|
|
23
|
-
export function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, body, title, dispatch, }) {
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Text, { bold: true, color: "cyan", children: "Create Pull Request" }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.min(width, 50)) }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "branch: " }), _jsx(Text, { children: branch })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "ticket: " }), _jsx(Text, { children: ticketId })] }), _jsx(Text, { children: " " }), phase === "choose-mode" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "How do you want to create this PR?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "f" }), " ", "Fill \u2014 use AI to fill the PR template"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "w" }), " ", "Web \u2014 open in browser to edit manually"] })] })), phase === "pushing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Pushing branch..."] })), phase === "filling" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Filling PR template with AI..."] })), phase === "review" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Edit PR description" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "title: " }), _jsx(Text, { children: title })] }), _jsx(Text, { children: " " }), _jsx(MultilineTextArea, { value: body ?? "", onChange: (v) => dispatch({ type: "PR_CREATE_BODY_CHANGE", body: v }), onSubmit: () => dispatch({ type: "PR_CREATE_CONFIRM" }), onCancel: () => dispatch({ type: "PR_CREATE_CANCEL" }), width: width, height: Math.max(6, height - 10), placeholder: "(empty PR body)" }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "Ctrl+D" }), " send · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+O" }), " editor · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+G" }), " cancel"] })] })), phase === "confirm" && (_jsxs(_Fragment, { children: [
|
|
23
|
+
export function PrCreateOverlay({ width, height, branch, ticketId, phase, error, url, body, title, draft, dispatch, }) {
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, children: [_jsx(Text, { bold: true, color: "cyan", children: "Create Pull Request" }), _jsx(Text, { dimColor: true, children: "─".repeat(Math.min(width, 50)) }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "branch: " }), _jsx(Text, { children: branch })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "ticket: " }), _jsx(Text, { children: ticketId })] }), _jsx(Text, { children: " " }), phase === "choose-mode" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "How do you want to create this PR?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "f" }), " ", "Fill \u2014 use AI to fill the PR template"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", bold: true, children: "w" }), " ", "Web \u2014 open in browser to edit manually"] })] })), phase === "pushing" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Pushing branch..."] })), phase === "filling" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Filling PR template with AI..."] })), phase === "review" && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Edit PR description" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "title: " }), _jsx(Text, { children: title })] }), _jsx(Text, { children: " " }), _jsx(MultilineTextArea, { value: body ?? "", onChange: (v) => dispatch({ type: "PR_CREATE_BODY_CHANGE", body: v }), onSubmit: () => dispatch({ type: "PR_CREATE_CONFIRM" }), onCancel: () => dispatch({ type: "PR_CREATE_CANCEL" }), width: width, height: Math.max(6, height - 10), placeholder: "(empty PR body)" }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "Ctrl+D" }), " send · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+O" }), " editor · ", _jsx(Text, { color: "cyan", bold: true, children: "Ctrl+G" }), " cancel"] })] })), phase === "confirm" && (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, children: ["Create this PR?", " ", _jsxs(Text, { color: draft ? "yellow" : "green", children: ["(", draft ? "draft" : "ready for review", ")"] })] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "title: " }), _jsx(Text, { children: title })] }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [(body ?? "")
|
|
25
25
|
.split("\n")
|
|
26
26
|
.slice(0, Math.max(4, height - 12))
|
|
27
|
-
.map((line, i) => (_jsx(Text, { wrap: "truncate", children: line || " " }, i))), (body ?? "").split("\n").length > Math.max(4, height - 12) && (_jsxs(Text, { dimColor: true, children: ["\u2026+", (body ?? "").split("\n").length - Math.max(4, height - 12), " more lines"] }))] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "y" }), " / ", _jsx(Text, { color: "green", bold: true, children: "Enter" }), " create ", _jsx(Text, { color: "yellow", bold: true, children: "e" }), " keep editing ", _jsx(Text, { color: "cyan", bold: true, children: "w" }), " open in browser ", _jsx(Text, { color: "red", bold: true, children: "ESC" }), " cancel"] })] })), phase === "creating" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Creating PR..."] })), phase === "done" && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", bold: true, children: "PR created!" }), url ? _jsx(Text, { dimColor: true, children: url }) : null] })), phase === "error" && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", children: error }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "w" }), " ", "open in browser ESC cancel"] })] })), phase !== "review" && phase !== "confirm" && phase !== "error" && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "ESC to cancel" })] }))] }));
|
|
27
|
+
.map((line, i) => (_jsx(Text, { wrap: "truncate", children: line || " " }, i))), (body ?? "").split("\n").length > Math.max(4, height - 12) && (_jsxs(Text, { dimColor: true, children: ["\u2026+", (body ?? "").split("\n").length - Math.max(4, height - 12), " more lines"] }))] }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: "y" }), " / ", _jsx(Text, { color: "green", bold: true, children: "Enter" }), " create ", _jsx(Text, { color: "yellow", bold: true, children: "d" }), ` ${draft ? "mark ready" : "mark draft"} `, _jsx(Text, { color: "yellow", bold: true, children: "e" }), " keep editing ", _jsx(Text, { color: "cyan", bold: true, children: "w" }), " open in browser ", _jsx(Text, { color: "red", bold: true, children: "ESC" }), " cancel"] })] })), phase === "creating" && (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), " ", "Creating PR..."] })), phase === "done" && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", bold: true, children: "PR created!" }), url ? _jsx(Text, { dimColor: true, children: url }) : null] })), phase === "error" && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", children: error }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "w" }), " ", "open in browser ESC cancel"] })] })), phase !== "review" && phase !== "confirm" && phase !== "error" && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "ESC to cancel" })] }))] }));
|
|
28
28
|
}
|
|
29
29
|
const LEGEND = [
|
|
30
30
|
{
|
|
@@ -146,6 +146,7 @@ export interface DashboardState {
|
|
|
146
146
|
prCreateUrl: string | null;
|
|
147
147
|
prCreateBody: string | null;
|
|
148
148
|
prCreateTitle: string | null;
|
|
149
|
+
prCreateDraft: boolean;
|
|
149
150
|
setupMode: "plan" | "implement" | null;
|
|
150
151
|
baseSelectOptions: string[];
|
|
151
152
|
baseSelectIndex: number;
|
|
@@ -312,6 +313,8 @@ export type DashboardAction = {
|
|
|
312
313
|
type: "PR_CREATE_CONFIRM";
|
|
313
314
|
} | {
|
|
314
315
|
type: "PR_CREATE_EDIT";
|
|
316
|
+
} | {
|
|
317
|
+
type: "PR_CREATE_TOGGLE_DRAFT";
|
|
315
318
|
} | {
|
|
316
319
|
type: "PR_CREATE_DONE";
|
|
317
320
|
url: string;
|
|
@@ -49,6 +49,7 @@ export const initialState = {
|
|
|
49
49
|
prCreateUrl: null,
|
|
50
50
|
prCreateBody: null,
|
|
51
51
|
prCreateTitle: null,
|
|
52
|
+
prCreateDraft: false,
|
|
52
53
|
setupMode: null,
|
|
53
54
|
baseSelectOptions: [],
|
|
54
55
|
baseSelectIndex: 0,
|
|
@@ -260,6 +261,7 @@ export function reducer(state, action) {
|
|
|
260
261
|
prCreateBranch: action.branch,
|
|
261
262
|
prCreateError: null,
|
|
262
263
|
prCreateUrl: null,
|
|
264
|
+
prCreateDraft: false,
|
|
263
265
|
};
|
|
264
266
|
case "PR_CREATE_PHASE":
|
|
265
267
|
return { ...state, prCreatePhase: action.phase };
|
|
@@ -277,6 +279,8 @@ export function reducer(state, action) {
|
|
|
277
279
|
return { ...state, prCreateBody: action.body };
|
|
278
280
|
case "PR_CREATE_CONFIRM":
|
|
279
281
|
return { ...state, prCreatePhase: "confirm" };
|
|
282
|
+
case "PR_CREATE_TOGGLE_DRAFT":
|
|
283
|
+
return { ...state, prCreateDraft: !state.prCreateDraft };
|
|
280
284
|
case "PR_CREATE_EDIT":
|
|
281
285
|
return { ...state, prCreatePhase: "review" };
|
|
282
286
|
case "PR_CREATE_DONE":
|