santree 0.6.2 → 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/README.md +7 -7
- package/dist/commands/dashboard.js +465 -54
- package/dist/commands/issue/setup.d.ts +2 -0
- package/dist/commands/issue/setup.js +108 -0
- package/dist/commands/issue/switch.d.ts +1 -0
- package/dist/commands/issue/switch.js +2 -2
- package/dist/commands/worktree/work.js +1 -1
- package/dist/lib/ai.js +4 -0
- package/dist/lib/dashboard/DetailPanel.d.ts +5 -2
- package/dist/lib/dashboard/DetailPanel.js +24 -3
- 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 +17 -3
- package/dist/lib/dashboard/data.d.ts +2 -0
- package/dist/lib/dashboard/data.js +56 -54
- package/dist/lib/dashboard/types.d.ts +80 -2
- package/dist/lib/dashboard/types.js +97 -1
- package/dist/lib/multiplexer/cmux.js +0 -15
- package/dist/lib/multiplexer/none.js +0 -3
- package/dist/lib/multiplexer/tmux.js +0 -8
- package/dist/lib/multiplexer/types.d.ts +0 -1
- package/dist/lib/session-signal.d.ts +5 -3
- package/dist/lib/session-signal.js +5 -22
- package/dist/lib/trackers/config.js +1 -1
- package/dist/lib/trackers/index.d.ts +11 -0
- package/dist/lib/trackers/index.js +26 -0
- package/dist/lib/trackers/local/frontmatter.d.ts +12 -0
- package/dist/lib/trackers/local/frontmatter.js +91 -0
- package/dist/lib/trackers/local/index.d.ts +2 -0
- package/dist/lib/trackers/local/index.js +102 -0
- package/dist/lib/trackers/local/store.d.ts +30 -0
- package/dist/lib/trackers/local/store.js +203 -0
- package/dist/lib/trackers/types.d.ts +26 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text, Box, useInput } from "ink";
|
|
4
|
+
import Spinner from "ink-spinner";
|
|
5
|
+
import { findMainRepoRoot } from "../../lib/git.js";
|
|
6
|
+
import { setRepoTracker, getIssueTracker } from "../../lib/trackers/index.js";
|
|
7
|
+
import { setRepoLinearOrg } from "../../lib/trackers/linear/index.js";
|
|
8
|
+
import { readLinearAuthStore } from "../../lib/trackers/auth-store.js";
|
|
9
|
+
import { getAuthenticatedUser, getCurrentRepoNwo } from "../../lib/trackers/github/auth.js";
|
|
10
|
+
export const description = "Pick and configure the issue tracker for this repo";
|
|
11
|
+
const TRACKERS = [
|
|
12
|
+
{ kind: "local", label: "Local", hint: "built-in, file-based — no account needed" },
|
|
13
|
+
{ kind: "linear", label: "Linear", hint: "OAuth workspace" },
|
|
14
|
+
{ kind: "github", label: "GitHub", hint: "GitHub Issues via gh CLI" },
|
|
15
|
+
];
|
|
16
|
+
export default function IssueSetup() {
|
|
17
|
+
const [phase, setPhase] = useState("checking");
|
|
18
|
+
const [message, setMessage] = useState("");
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [trackerIdx, setTrackerIdx] = useState(0);
|
|
21
|
+
const [orgs, setOrgs] = useState([]);
|
|
22
|
+
const [orgIdx, setOrgIdx] = useState(0);
|
|
23
|
+
const [repoRoot, setRepoRoot] = useState(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const root = findMainRepoRoot();
|
|
26
|
+
if (!root) {
|
|
27
|
+
setError("Not inside a git repository");
|
|
28
|
+
setPhase("error");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
setRepoRoot(root);
|
|
32
|
+
setPhase("pick-tracker");
|
|
33
|
+
}, []);
|
|
34
|
+
function finish(label) {
|
|
35
|
+
setMessage(label);
|
|
36
|
+
setPhase("done");
|
|
37
|
+
}
|
|
38
|
+
async function chooseTracker(kind) {
|
|
39
|
+
const root = repoRoot;
|
|
40
|
+
if (kind === "local") {
|
|
41
|
+
setRepoTracker(root, "local");
|
|
42
|
+
finish(`Active tracker for this repo: ${getIssueTracker(root).displayName}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (kind === "linear") {
|
|
46
|
+
const store = readLinearAuthStore();
|
|
47
|
+
const list = Object.entries(store).map(([slug, tokens]) => ({
|
|
48
|
+
slug,
|
|
49
|
+
name: tokens.org_name,
|
|
50
|
+
}));
|
|
51
|
+
if (list.length === 0) {
|
|
52
|
+
setError("No authenticated Linear workspaces. Run: santree linear auth");
|
|
53
|
+
setPhase("error");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (list.length === 1) {
|
|
57
|
+
setRepoLinearOrg(root, list[0].slug);
|
|
58
|
+
setRepoTracker(root, "linear");
|
|
59
|
+
finish(`Linked to ${list[0].name} (${list[0].slug})`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setOrgs(list);
|
|
63
|
+
setOrgIdx(0);
|
|
64
|
+
setPhase("pick-org");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// github
|
|
68
|
+
setPhase("checking");
|
|
69
|
+
const user = await getAuthenticatedUser();
|
|
70
|
+
if (!user) {
|
|
71
|
+
setError("GitHub CLI not authenticated. Run: santree github auth");
|
|
72
|
+
setPhase("error");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
setRepoTracker(root, "github");
|
|
76
|
+
const nwo = await getCurrentRepoNwo(root);
|
|
77
|
+
finish(`Active tracker: GitHub (@${user.login}${nwo ? ` · ${nwo}` : ""})`);
|
|
78
|
+
}
|
|
79
|
+
useInput((_input, key) => {
|
|
80
|
+
if (phase === "pick-tracker") {
|
|
81
|
+
if (key.upArrow)
|
|
82
|
+
setTrackerIdx((i) => Math.max(0, i - 1));
|
|
83
|
+
else if (key.downArrow)
|
|
84
|
+
setTrackerIdx((i) => Math.min(TRACKERS.length - 1, i + 1));
|
|
85
|
+
else if (key.return)
|
|
86
|
+
void chooseTracker(TRACKERS[trackerIdx].kind);
|
|
87
|
+
}
|
|
88
|
+
else if (phase === "pick-org") {
|
|
89
|
+
if (key.upArrow)
|
|
90
|
+
setOrgIdx((i) => Math.max(0, i - 1));
|
|
91
|
+
else if (key.downArrow)
|
|
92
|
+
setOrgIdx((i) => Math.min(orgs.length - 1, i + 1));
|
|
93
|
+
else if (key.return) {
|
|
94
|
+
const org = orgs[orgIdx];
|
|
95
|
+
setRepoLinearOrg(repoRoot, org.slug);
|
|
96
|
+
setRepoTracker(repoRoot, "linear");
|
|
97
|
+
finish(`Linked to ${org.name} (${org.slug})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (phase === "done" || phase === "error") {
|
|
103
|
+
const timer = setTimeout(() => process.exit(phase === "error" ? 1 : 0), 100);
|
|
104
|
+
return () => clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}, [phase]);
|
|
107
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Issue tracker setup" }) }), phase === "checking" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Checking\u2026" })] })), phase === "pick-tracker" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select the issue tracker for this repo:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: TRACKERS.map((t, i) => (_jsxs(Text, { children: [i === trackerIdx ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), t.label, " ", _jsxs(Text, { dimColor: true, children: ["\u2014 ", t.hint] })] }, t.kind))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), phase === "pick-org" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select a Linear workspace to link:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: orgs.map((org, i) => (_jsxs(Text, { children: [i === orgIdx ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), org.name, " (", org.slug, ")"] }, org.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), phase === "done" && _jsxs(Text, { color: "green", children: ["\u2713 ", message] }), phase === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] }));
|
|
108
|
+
}
|
|
@@ -7,9 +7,9 @@ import { findMainRepoRoot } from "../../lib/git.js";
|
|
|
7
7
|
import { setRepoTracker, getIssueTracker } from "../../lib/trackers/index.js";
|
|
8
8
|
export const description = "Switch the active issue tracker for this repo";
|
|
9
9
|
export const args = z.tuple([
|
|
10
|
-
z.enum(["linear", "github"]).describe(argument({
|
|
10
|
+
z.enum(["linear", "github", "local"]).describe(argument({
|
|
11
11
|
name: "kind",
|
|
12
|
-
description: "Tracker kind: linear or
|
|
12
|
+
description: "Tracker kind: linear, github, or local (built-in)",
|
|
13
13
|
})),
|
|
14
14
|
]);
|
|
15
15
|
export default function IssueSwitch({ args }) {
|
|
@@ -99,5 +99,5 @@ export default function Work({ options }) {
|
|
|
99
99
|
setError(err instanceof Error ? err.message : "Failed to launch agent");
|
|
100
100
|
}
|
|
101
101
|
}, [status, aiContext, mode]);
|
|
102
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Work" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : getModeColor(mode), paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: getModeColor(mode), color: "white", bold: true, children: ` ${getModeLabel(mode)} ` })] })] }), _jsxs(Box, { marginTop: 1, children: [status === "loading" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading..." })] })), status === "fetching" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Fetching issue from tracker..." })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Launching Claude..." }), _jsxs(Text, { dimColor: true, children: [" ", "claude", mode === "plan" ? "
|
|
102
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Work" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status === "error" ? "red" : getModeColor(mode), paddingX: 1, width: "100%", children: [branch && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "branch:" }), _jsx(Text, { color: "cyan", bold: true, children: branch })] })), ticketId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "ticket:" }), _jsx(Text, { color: "blue", bold: true, children: ticketId })] })), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "mode:" }), _jsx(Text, { backgroundColor: getModeColor(mode), color: "white", bold: true, children: ` ${getModeLabel(mode)} ` })] })] }), _jsxs(Box, { marginTop: 1, children: [status === "loading" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading..." })] })), status === "fetching" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Fetching issue from tracker..." })] })), status === "launching" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Launching Claude..." }), _jsxs(Text, { dimColor: true, children: [" ", "claude --permission-mode ", mode === "plan" ? "plan" : "auto", " ", `"<${getModeLabel(mode)} prompt for ${ticketId}>"`] })] })), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] })] }));
|
|
103
103
|
}
|
package/dist/lib/ai.js
CHANGED
|
@@ -185,6 +185,10 @@ export function launchAgent(prompt, opts) {
|
|
|
185
185
|
throw new Error("Claude CLI not found. Install: npm install -g @anthropic-ai/claude-code");
|
|
186
186
|
}
|
|
187
187
|
const args = [];
|
|
188
|
+
// Plan mode uses Claude Code's `--permission-mode plan` (read-only,
|
|
189
|
+
// restrictive); implement runs use `auto`. Auto-acceptance of non-mutating
|
|
190
|
+
// tools while planning is governed by the user's `useAutoModeDuringPlan`
|
|
191
|
+
// setting in ~/.claude/settings.json, not by santree.
|
|
188
192
|
args.push("--permission-mode", opts?.planMode ? "plan" : "auto");
|
|
189
193
|
if (opts?.sessionId) {
|
|
190
194
|
if (opts.resume) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashboardIssue } from "./types.js";
|
|
1
|
+
import type { DashboardIssue, DashboardTab } from "./types.js";
|
|
2
2
|
interface Props {
|
|
3
3
|
issue: DashboardIssue | null;
|
|
4
4
|
scrollOffset: number;
|
|
@@ -18,6 +18,9 @@ export type IssueActionItem = {
|
|
|
18
18
|
* `trackerName` is the active tracker's `displayName` ("Linear" / "GitHub"),
|
|
19
19
|
* surfaced as the open-in-browser action label so the panel doesn't hardcode
|
|
20
20
|
* a vendor name. */
|
|
21
|
-
export declare function buildIssueActions(di: DashboardIssue, trackerName: string
|
|
21
|
+
export declare function buildIssueActions(di: DashboardIssue, trackerName: string, opts?: {
|
|
22
|
+
tab?: DashboardTab;
|
|
23
|
+
canMutate?: boolean;
|
|
24
|
+
}): IssueActionItem[];
|
|
22
25
|
export default function DetailPanel({ issue, scrollOffset, height, width, creatingForTicket, creationLogs, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
23
26
|
export {};
|
|
@@ -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) {
|
|
@@ -57,9 +57,26 @@ function fileColor(xy) {
|
|
|
57
57
|
* `trackerName` is the active tracker's `displayName` ("Linear" / "GitHub"),
|
|
58
58
|
* surfaced as the open-in-browser action label so the panel doesn't hardcode
|
|
59
59
|
* a vendor name. */
|
|
60
|
-
export function buildIssueActions(di, trackerName) {
|
|
60
|
+
export function buildIssueActions(di, trackerName, opts) {
|
|
61
61
|
const { worktree, pr, issue } = di;
|
|
62
62
|
const items = [];
|
|
63
|
+
// Issues tab = backlog/planning. No worktree actions here (commit / PR /
|
|
64
|
+
// diff / fix live on the Trees tab). Offer Work (start → creates a
|
|
65
|
+
// worktree, moving the row to Trees) plus issue CRUD when the active
|
|
66
|
+
// tracker supports mutation (built-in Local only — feature-detected via
|
|
67
|
+
// `canMutate`, never a kind string check).
|
|
68
|
+
if (opts?.tab === "issues") {
|
|
69
|
+
items.push({ key: "w", label: "Work", color: "cyan" });
|
|
70
|
+
if (opts.canMutate) {
|
|
71
|
+
items.push({ key: "n", label: "New", color: "cyan" });
|
|
72
|
+
items.push({ key: "e", label: "Edit", color: "cyan" });
|
|
73
|
+
items.push({ key: "d", label: "Delete", color: "red" });
|
|
74
|
+
}
|
|
75
|
+
if (issue.url) {
|
|
76
|
+
items.push({ key: "o", label: trackerName, color: "gray" });
|
|
77
|
+
}
|
|
78
|
+
return items;
|
|
79
|
+
}
|
|
63
80
|
// The synthetic "Main repo" row is special: no PR/Switch/Resume/Remove,
|
|
64
81
|
// no work-launching (you're already on it). Only commit / diff /
|
|
65
82
|
// editor — the actions that make sense for "I have changes in main and
|
|
@@ -127,7 +144,11 @@ export default function DetailPanel({ issue, scrollOffset, height, width, creati
|
|
|
127
144
|
const contentRows = height - 1;
|
|
128
145
|
const startIdx = Math.max(0, logLines.length - contentRows);
|
|
129
146
|
const visible = logLines.slice(startIdx, startIdx + contentRows);
|
|
130
|
-
|
|
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)))] }));
|
|
131
152
|
}
|
|
132
153
|
if (!issue) {
|
|
133
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,13 +20,27 @@ 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
|
+
{
|
|
31
|
+
title: "Tabs & keys",
|
|
32
|
+
rows: [
|
|
33
|
+
{ glyph: "1", color: "cyan", meaning: "Issues tab — backlog / planning" },
|
|
34
|
+
{ glyph: "2", color: "cyan", meaning: "Trees tab — worktrees in progress" },
|
|
35
|
+
{ glyph: "3", color: "cyan", meaning: "Reviews tab — PRs awaiting your review" },
|
|
36
|
+
{ glyph: "Tab", color: "cyan", meaning: "Cycle Issues → Trees → Reviews" },
|
|
37
|
+
{ glyph: "t", color: "cyan", meaning: "Switch / set up the issue tracker" },
|
|
38
|
+
{ glyph: "n", color: "cyan", meaning: "New issue (built-in tracker, Issues tab)" },
|
|
39
|
+
{ glyph: "e", color: "cyan", meaning: "Edit issue (built-in tracker, Issues tab)" },
|
|
40
|
+
{ glyph: "d", color: "red", meaning: "Delete issue (built-in) / remove worktree (Trees)" },
|
|
41
|
+
{ glyph: "w", color: "cyan", meaning: "Start work (creates a worktree → Trees tab)" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
30
44
|
{
|
|
31
45
|
title: "Issue list",
|
|
32
46
|
rows: [
|
|
@@ -2,6 +2,8 @@ import type { DashboardIssue, ProjectGroup, EnrichedReviewPR } from "./types.js"
|
|
|
2
2
|
export declare function loadDashboardData(repoRoot: string): Promise<{
|
|
3
3
|
groups: ProjectGroup[];
|
|
4
4
|
flatIssues: DashboardIssue[];
|
|
5
|
+
treeGroups: ProjectGroup[];
|
|
6
|
+
flatTrees: DashboardIssue[];
|
|
5
7
|
}>;
|
|
6
8
|
export declare function loadReviewsData(repoRoot: string): Promise<{
|
|
7
9
|
flatReviews: EnrichedReviewPR[];
|
|
@@ -203,16 +203,6 @@ export async function loadDashboardData(repoRoot) {
|
|
|
203
203
|
parent.children.push(di);
|
|
204
204
|
childTicketIds.add(ticketId);
|
|
205
205
|
}
|
|
206
|
-
// Group by project (excluding children — they'll appear nested under parents)
|
|
207
|
-
const groupMap = new Map();
|
|
208
|
-
for (const di of enriched) {
|
|
209
|
-
if (childTicketIds.has(di.issue.identifier))
|
|
210
|
-
continue;
|
|
211
|
-
const key = di.issue.projectName ?? "No Project";
|
|
212
|
-
const list = groupMap.get(key) ?? [];
|
|
213
|
-
list.push(di);
|
|
214
|
-
groupMap.set(key, list);
|
|
215
|
-
}
|
|
216
206
|
// Status type priority: started > unstarted > backlog > triage
|
|
217
207
|
const statusTypePriority = {
|
|
218
208
|
started: 0,
|
|
@@ -220,44 +210,35 @@ export async function loadDashboardData(repoRoot) {
|
|
|
220
210
|
backlog: 2,
|
|
221
211
|
triage: 3,
|
|
222
212
|
};
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
213
|
+
// Group a set of (non-child) issues by project, then by status.
|
|
214
|
+
function buildProjectGroups(issues) {
|
|
215
|
+
const groupMap = new Map();
|
|
226
216
|
for (const di of issues) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
statusMap.set(statusName, {
|
|
234
|
-
name: statusName,
|
|
235
|
-
type: di.issue.state.type,
|
|
236
|
-
issues: [di],
|
|
237
|
-
});
|
|
238
|
-
}
|
|
217
|
+
if (childTicketIds.has(di.issue.identifier))
|
|
218
|
+
continue;
|
|
219
|
+
const key = di.issue.projectName ?? "No Project";
|
|
220
|
+
const list = groupMap.get(key) ?? [];
|
|
221
|
+
list.push(di);
|
|
222
|
+
groupMap.set(key, list);
|
|
239
223
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
issues: topLevelOrphans,
|
|
259
|
-
},
|
|
260
|
-
],
|
|
224
|
+
return [...groupMap.entries()].map(([name, list]) => {
|
|
225
|
+
const statusMap = new Map();
|
|
226
|
+
for (const di of list) {
|
|
227
|
+
const statusName = di.issue.state.name;
|
|
228
|
+
const existing = statusMap.get(statusName);
|
|
229
|
+
if (existing) {
|
|
230
|
+
existing.issues.push(di);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
statusMap.set(statusName, {
|
|
234
|
+
name: statusName,
|
|
235
|
+
type: di.issue.state.type,
|
|
236
|
+
issues: [di],
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const statusGroups = [...statusMap.values()].sort((a, b) => (statusTypePriority[a.type] ?? 99) - (statusTypePriority[b.type] ?? 99));
|
|
241
|
+
return { name, id: list[0]?.issue.projectId ?? null, statusGroups };
|
|
261
242
|
});
|
|
262
243
|
}
|
|
263
244
|
// Flatten with children inserted right after their parent
|
|
@@ -270,21 +251,42 @@ export async function loadDashboardData(repoRoot) {
|
|
|
270
251
|
}
|
|
271
252
|
return result;
|
|
272
253
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
//
|
|
254
|
+
function flatten(g) {
|
|
255
|
+
return g.flatMap((grp) => grp.statusGroups.flatMap((sg) => sg.issues.flatMap(flattenWithChildren)));
|
|
256
|
+
}
|
|
257
|
+
// ── Partition: Issues tab (backlog/planning) vs Trees tab (work in
|
|
258
|
+
// progress). A tracker issue with no worktree is backlog; once it gains a
|
|
259
|
+
// worktree it moves to the Trees tab. Children always have a worktree, so
|
|
260
|
+
// they only ever appear nested in Trees. Main-repo + orphaned worktrees
|
|
261
|
+
// belong to Trees (they're active checkouts, not backlog).
|
|
262
|
+
const backlogIssues = enriched.filter((di) => !di.worktree);
|
|
263
|
+
const treeIssues = enriched.filter((di) => di.worktree);
|
|
264
|
+
const groups = buildProjectGroups(backlogIssues);
|
|
265
|
+
const flatIssues = flatten(groups);
|
|
266
|
+
const treeGroups = buildProjectGroups(treeIssues);
|
|
267
|
+
const topLevelOrphans = orphans.filter((di) => !childTicketIds.has(di.issue.identifier));
|
|
268
|
+
if (topLevelOrphans.length > 0) {
|
|
269
|
+
treeGroups.push({
|
|
270
|
+
name: "Orphaned Worktrees",
|
|
271
|
+
id: null,
|
|
272
|
+
statusGroups: [{ name: "Orphaned", type: "orphaned", issues: topLevelOrphans }],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const flatTrees = flatten(treeGroups);
|
|
276
|
+
// Synthesize a "Main repo" row at the top of the Trees tab so users can
|
|
277
|
+
// commit / view diffs / inspect drift on whatever branch their main
|
|
278
|
+
// checkout happens to be on. state.type === "main" lets the renderer
|
|
279
|
+
// differentiate.
|
|
278
280
|
const mainEntry = await buildMainEntry(repoRoot);
|
|
279
281
|
if (mainEntry) {
|
|
280
|
-
|
|
282
|
+
treeGroups.unshift({
|
|
281
283
|
name: "Main repo",
|
|
282
284
|
id: null,
|
|
283
285
|
statusGroups: [{ name: "Main", type: "main", issues: [mainEntry] }],
|
|
284
286
|
});
|
|
285
|
-
|
|
287
|
+
flatTrees.unshift(mainEntry);
|
|
286
288
|
}
|
|
287
|
-
return { groups, flatIssues };
|
|
289
|
+
return { groups, flatIssues, treeGroups, flatTrees };
|
|
288
290
|
}
|
|
289
291
|
/** Build the synthetic dashboard row for the main repo checkout — the
|
|
290
292
|
* non-worktree clone that the user typically commits master/main from.
|
|
@@ -72,8 +72,19 @@ export interface EnrichedReviewPR {
|
|
|
72
72
|
* Null when the user hasn't set one or the lookup failed. */
|
|
73
73
|
authorName: string | null;
|
|
74
74
|
}
|
|
75
|
-
export type DashboardTab = "issues" | "reviews";
|
|
76
|
-
export type ActionOverlay = "mode-select" | "context-input" | "base-select" | "confirm-delete" | "confirm-setup" | "commit" | "pr-create" | "diff" | "help" | null;
|
|
75
|
+
export type DashboardTab = "issues" | "trees" | "reviews";
|
|
76
|
+
export type ActionOverlay = "mode-select" | "context-input" | "base-select" | "confirm-delete" | "confirm-setup" | "commit" | "pr-create" | "diff" | "help" | "tracker-select" | "issue-form" | "confirm-delete-issue" | null;
|
|
77
|
+
/** Tracker-selection overlay sub-phase: pick a tracker, then (for Linear with
|
|
78
|
+
* multiple authenticated workspaces) pick the org. */
|
|
79
|
+
export type TrackerSelectPhase = "root" | "linear-org";
|
|
80
|
+
/** Issue create/edit form sub-phase. Title and description are captured in
|
|
81
|
+
* two sequential MultilineTextArea steps (reusing the context-input pattern);
|
|
82
|
+
* "saving" blocks input while the tracker writes the file. */
|
|
83
|
+
export type IssueFormPhase = "title" | "description" | "saving";
|
|
84
|
+
export interface TrackerOrgOption {
|
|
85
|
+
slug: string;
|
|
86
|
+
name: string;
|
|
87
|
+
}
|
|
77
88
|
export type DiffFileStatus = "M" | "A" | "D" | "R" | "C" | "U" | "?";
|
|
78
89
|
export interface DiffFile {
|
|
79
90
|
path: string;
|
|
@@ -96,6 +107,21 @@ export interface DashboardState {
|
|
|
96
107
|
reviewSelectedIndex: number;
|
|
97
108
|
reviewListScrollOffset: number;
|
|
98
109
|
reviewDetailScrollOffset: number;
|
|
110
|
+
treeGroups: ProjectGroup[];
|
|
111
|
+
flatTrees: DashboardIssue[];
|
|
112
|
+
treeSelectedIndex: number;
|
|
113
|
+
treeListScrollOffset: number;
|
|
114
|
+
treeDetailScrollOffset: number;
|
|
115
|
+
trackerSelectPhase: TrackerSelectPhase;
|
|
116
|
+
trackerSelectIndex: number;
|
|
117
|
+
trackerSelectOrgs: TrackerOrgOption[];
|
|
118
|
+
trackerSelectMessage: string | null;
|
|
119
|
+
issueFormMode: "create" | "edit" | null;
|
|
120
|
+
issueFormPhase: IssueFormPhase;
|
|
121
|
+
issueFormId: string | null;
|
|
122
|
+
issueFormTitle: string;
|
|
123
|
+
issueFormDescription: string;
|
|
124
|
+
issueFormError: string | null;
|
|
99
125
|
loading: boolean;
|
|
100
126
|
refreshing: boolean;
|
|
101
127
|
error: string | null;
|
|
@@ -120,6 +146,7 @@ export interface DashboardState {
|
|
|
120
146
|
prCreateUrl: string | null;
|
|
121
147
|
prCreateBody: string | null;
|
|
122
148
|
prCreateTitle: string | null;
|
|
149
|
+
prCreateDraft: boolean;
|
|
123
150
|
setupMode: "plan" | "implement" | null;
|
|
124
151
|
baseSelectOptions: string[];
|
|
125
152
|
baseSelectIndex: number;
|
|
@@ -154,9 +181,58 @@ export type DashboardAction = {
|
|
|
154
181
|
type: "SET_DATA";
|
|
155
182
|
groups: ProjectGroup[];
|
|
156
183
|
flatIssues: DashboardIssue[];
|
|
184
|
+
treeGroups: ProjectGroup[];
|
|
185
|
+
flatTrees: DashboardIssue[];
|
|
157
186
|
} | {
|
|
158
187
|
type: "SELECT";
|
|
159
188
|
index: number;
|
|
189
|
+
} | {
|
|
190
|
+
type: "TREE_SELECT";
|
|
191
|
+
index: number;
|
|
192
|
+
} | {
|
|
193
|
+
type: "TREE_SCROLL_LIST";
|
|
194
|
+
offset: number;
|
|
195
|
+
} | {
|
|
196
|
+
type: "TREE_SCROLL_DETAIL";
|
|
197
|
+
offset: number;
|
|
198
|
+
} | {
|
|
199
|
+
type: "TRACKER_SELECT_OPEN";
|
|
200
|
+
} | {
|
|
201
|
+
type: "TRACKER_SELECT_MOVE";
|
|
202
|
+
index: number;
|
|
203
|
+
} | {
|
|
204
|
+
type: "TRACKER_SELECT_PHASE";
|
|
205
|
+
phase: TrackerSelectPhase;
|
|
206
|
+
orgs?: TrackerOrgOption[];
|
|
207
|
+
} | {
|
|
208
|
+
type: "TRACKER_SELECT_MESSAGE";
|
|
209
|
+
message: string | null;
|
|
210
|
+
} | {
|
|
211
|
+
type: "TRACKER_SELECT_CLOSE";
|
|
212
|
+
} | {
|
|
213
|
+
type: "ISSUE_FORM_OPEN";
|
|
214
|
+
mode: "create" | "edit";
|
|
215
|
+
id: string | null;
|
|
216
|
+
title: string;
|
|
217
|
+
description: string;
|
|
218
|
+
} | {
|
|
219
|
+
type: "ISSUE_FORM_PHASE";
|
|
220
|
+
phase: IssueFormPhase;
|
|
221
|
+
} | {
|
|
222
|
+
type: "ISSUE_FORM_TITLE";
|
|
223
|
+
title: string;
|
|
224
|
+
} | {
|
|
225
|
+
type: "ISSUE_FORM_DESC";
|
|
226
|
+
description: string;
|
|
227
|
+
} | {
|
|
228
|
+
type: "ISSUE_FORM_ERROR";
|
|
229
|
+
error: string;
|
|
230
|
+
} | {
|
|
231
|
+
type: "ISSUE_FORM_CLOSE";
|
|
232
|
+
} | {
|
|
233
|
+
type: "ISSUE_DELETE_OPEN";
|
|
234
|
+
} | {
|
|
235
|
+
type: "ISSUE_DELETE_CLOSE";
|
|
160
236
|
} | {
|
|
161
237
|
type: "SCROLL_LIST";
|
|
162
238
|
offset: number;
|
|
@@ -237,6 +313,8 @@ export type DashboardAction = {
|
|
|
237
313
|
type: "PR_CREATE_CONFIRM";
|
|
238
314
|
} | {
|
|
239
315
|
type: "PR_CREATE_EDIT";
|
|
316
|
+
} | {
|
|
317
|
+
type: "PR_CREATE_TOGGLE_DRAFT";
|
|
240
318
|
} | {
|
|
241
319
|
type: "PR_CREATE_DONE";
|
|
242
320
|
url: string;
|