santree 0.5.0 → 0.5.1
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useReducer, useCallback, useRef, useState } from "react";
|
|
3
3
|
import { Text, Box, useInput, useStdout, useApp } from "ink";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
@@ -28,7 +28,7 @@ import ReviewList from "../lib/dashboard/ReviewList.js";
|
|
|
28
28
|
import ReviewDetailPanel, { buildReviewActions } from "../lib/dashboard/ReviewDetailPanel.js";
|
|
29
29
|
import { CommitOverlay, PrCreateOverlay } from "../lib/dashboard/Overlays.js";
|
|
30
30
|
import { MultilineTextArea } from "../lib/dashboard/MultilineTextArea.js";
|
|
31
|
-
import DiffOverlay, { flattenTreeFiles, computeDiffLayout } from "../lib/dashboard/DiffOverlay.js";
|
|
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
33
|
export const description = "Interactive dashboard of your Linear issues";
|
|
34
34
|
const execAsync = promisify(exec);
|
|
@@ -193,11 +193,15 @@ function Tab({ active, label, mode }) {
|
|
|
193
193
|
* Single-line global keymap shown at the bottom-left of the dashboard. The
|
|
194
194
|
* `E workspace` hint only appears when the action is meaningful
|
|
195
195
|
* (`SANTREE_EDITOR` is `code`/`cursor` and a `.code-workspace` file exists in
|
|
196
|
-
* the repo root).
|
|
196
|
+
* the repo root). When the diff overlay is active, the keymap switches to
|
|
197
|
+
* diff-specific bindings since the global ones don't apply.
|
|
197
198
|
*/
|
|
198
|
-
function CommandBar({ showWorkspace }) {
|
|
199
|
+
function CommandBar({ showWorkspace, mode = "default", }) {
|
|
199
200
|
const dot = _jsx(Text, { dimColor: true, children: " · " });
|
|
200
201
|
const Key = ({ k }) => (_jsx(Text, { color: "cyan", bold: true, children: k }));
|
|
202
|
+
if (mode === "diff") {
|
|
203
|
+
return (_jsxs(Text, { children: [_jsx(Key, { k: "j/k" }), _jsx(Text, { dimColor: true, children: " file" }), dot, _jsx(Key, { k: "\u21E7\u2191\u2193" }), _jsx(Text, { dimColor: true, children: " scroll" }), dot, _jsx(Key, { k: "g/G" }), _jsx(Text, { dimColor: true, children: " top/bot" }), dot, _jsx(Key, { k: "q" }), _jsx(Text, { dimColor: true, children: " close" })] }));
|
|
204
|
+
}
|
|
201
205
|
return (_jsxs(Text, { children: [_jsx(Key, { k: "j/k" }), _jsx(Text, { dimColor: true, children: " nav" }), dot, _jsx(Key, { k: "\u21E7\u2191\u2193" }), _jsx(Text, { dimColor: true, children: " scroll" }), dot, _jsx(Key, { k: "1/2" }), _jsx(Text, { dimColor: true, children: " tabs" }), showWorkspace ? (_jsxs(_Fragment, { children: [dot, _jsx(Key, { k: "E" }), _jsx(Text, { dimColor: true, children: " workspace" })] })) : null, dot, _jsx(Key, { k: "R" }), _jsx(Text, { dimColor: true, children: " refresh" }), dot, _jsx(Key, { k: "q" }), _jsx(Text, { dimColor: true, children: " quit" })] }));
|
|
202
206
|
}
|
|
203
207
|
// ── Component ─────────────────────────────────────────────────────────
|
|
@@ -218,7 +222,7 @@ export default function Dashboard() {
|
|
|
218
222
|
const repoRootRef = useRef(null);
|
|
219
223
|
const stateRef = useRef(state);
|
|
220
224
|
stateRef.current = state;
|
|
221
|
-
const draggingRef = useRef(
|
|
225
|
+
const draggingRef = useRef(null);
|
|
222
226
|
const [termSize, setTermSize] = useState({
|
|
223
227
|
columns: stdout?.columns ?? 80,
|
|
224
228
|
rows: stdout?.rows ?? 24,
|
|
@@ -264,6 +268,13 @@ export default function Dashboard() {
|
|
|
264
268
|
const leftWidthRef = useRef(leftWidth);
|
|
265
269
|
leftWidthRef.current = leftWidth;
|
|
266
270
|
const rightWidth = innerWidth - leftWidth - separatorWidth;
|
|
271
|
+
// Diff overlay's left pane width — null means "use the default formula"
|
|
272
|
+
// (computed inside computeDiffLayout). Becomes a number once the user drags
|
|
273
|
+
// the divider, and persists across overlay open/close while the dashboard
|
|
274
|
+
// session is alive.
|
|
275
|
+
const [diffLeftWidth, setDiffLeftWidth] = useState(null);
|
|
276
|
+
const diffLeftWidthRef = useRef(diffLeftWidth);
|
|
277
|
+
diffLeftWidthRef.current = diffLeftWidth;
|
|
267
278
|
// Header (1) + tab strip (1) + 2 borders + command bar (1, inside box) = 5 rows
|
|
268
279
|
const contentHeight = Math.max(3, rows - 5);
|
|
269
280
|
const LIST_FOOTER_HEIGHT = 0;
|
|
@@ -333,11 +344,20 @@ export default function Dashboard() {
|
|
|
333
344
|
const sepW = 3;
|
|
334
345
|
// Release — stop dragging
|
|
335
346
|
if (isRelease && draggingRef.current) {
|
|
336
|
-
draggingRef.current =
|
|
347
|
+
draggingRef.current = null;
|
|
337
348
|
return;
|
|
338
349
|
}
|
|
339
350
|
// Drag — resize if actively dragging
|
|
340
351
|
if (isDrag && draggingRef.current) {
|
|
352
|
+
if (draggingRef.current === "diff") {
|
|
353
|
+
// DiffOverlay starts at abs col 2 with width=innerWidth; its
|
|
354
|
+
// 1-col divider sits at relative col (leftWidth+1) → abs col
|
|
355
|
+
// (leftWidth+2). Setting newLeft = col - 2 keeps it under the
|
|
356
|
+
// cursor; clampDiffLeftWidth enforces pane minimums.
|
|
357
|
+
const innerW = Math.max(40, cols - 2);
|
|
358
|
+
setDiffLeftWidth(clampDiffLeftWidth(col - 2, innerW));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
341
361
|
// col is 1-based; outer border consumes col 1, so left pane spans cols 2..(lw+1).
|
|
342
362
|
// Setting newLeft = col - 1 keeps the divider at the user's cursor.
|
|
343
363
|
const newLeft = Math.max(minW, Math.min(col - 1, cols - 2 - sepW - minW));
|
|
@@ -361,12 +381,15 @@ export default function Dashboard() {
|
|
|
361
381
|
files: s.diffFiles,
|
|
362
382
|
fileIndex: s.diffFileIndex,
|
|
363
383
|
fileScrollOffset: s.diffFileScrollOffset,
|
|
384
|
+
leftWidthOverride: diffLeftWidthRef.current ?? undefined,
|
|
364
385
|
});
|
|
365
386
|
// Body's first line is at absolute row 6 (title + tab + top border + overlay title + rule)
|
|
366
387
|
const bodyRow = row - 6;
|
|
367
388
|
if (bodyRow < 0 || bodyRow >= layout.bodyHeight)
|
|
368
389
|
return;
|
|
369
|
-
|
|
390
|
+
// DiffOverlay starts at abs col 2; left pane occupies abs cols
|
|
391
|
+
// 2..(leftWidth+1).
|
|
392
|
+
if (col <= layout.leftWidth + 1) {
|
|
370
393
|
const maxIdx = s.diffFiles.length - 1;
|
|
371
394
|
if (maxIdx < 0)
|
|
372
395
|
return;
|
|
@@ -414,7 +437,7 @@ export default function Dashboard() {
|
|
|
414
437
|
}
|
|
415
438
|
if (!isPress)
|
|
416
439
|
return;
|
|
417
|
-
// Diff overlay click: select file row in left pane
|
|
440
|
+
// Diff overlay click: drag divider, or select file row in left pane
|
|
418
441
|
{
|
|
419
442
|
const s = stateRef.current;
|
|
420
443
|
if (s.overlay === "diff") {
|
|
@@ -427,8 +450,17 @@ export default function Dashboard() {
|
|
|
427
450
|
files: s.diffFiles,
|
|
428
451
|
fileIndex: s.diffFileIndex,
|
|
429
452
|
fileScrollOffset: s.diffFileScrollOffset,
|
|
453
|
+
leftWidthOverride: diffLeftWidthRef.current ?? undefined,
|
|
430
454
|
});
|
|
431
|
-
|
|
455
|
+
// Divider sits at abs col leftWidth+2 (DiffOverlay starts at
|
|
456
|
+
// abs col 2; divider at relative col leftWidth+1). Allow ±1
|
|
457
|
+
// tolerance — a 1-col target is hard to hit precisely.
|
|
458
|
+
const diffDivAbsCol = layout.leftWidth + 2;
|
|
459
|
+
if (col >= diffDivAbsCol - 1 && col <= diffDivAbsCol - 1 + DIFF_DIVIDER_WIDTH + 1) {
|
|
460
|
+
draggingRef.current = "diff";
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (col > layout.leftWidth + 1)
|
|
432
464
|
return;
|
|
433
465
|
const bodyRow = row - 6;
|
|
434
466
|
if (bodyRow < 0 || bodyRow >= layout.bodyHeight)
|
|
@@ -447,7 +479,7 @@ export default function Dashboard() {
|
|
|
447
479
|
const divStart = lw + 2;
|
|
448
480
|
const divEnd = lw + 1 + sepW;
|
|
449
481
|
if (col >= divStart && col <= divEnd) {
|
|
450
|
-
draggingRef.current =
|
|
482
|
+
draggingRef.current = "main";
|
|
451
483
|
return;
|
|
452
484
|
}
|
|
453
485
|
// Left-click press: select item in left pane (cols 2..lw+1)
|
|
@@ -1292,6 +1324,7 @@ export default function Dashboard() {
|
|
|
1292
1324
|
files: state.diffFiles,
|
|
1293
1325
|
fileIndex: state.diffFileIndex,
|
|
1294
1326
|
fileScrollOffset: state.diffFileScrollOffset,
|
|
1327
|
+
leftWidthOverride: diffLeftWidth ?? undefined,
|
|
1295
1328
|
});
|
|
1296
1329
|
const totalLines = state.diffContent ? state.diffContent.split("\n").length : 0;
|
|
1297
1330
|
const maxScroll = Math.max(0, totalLines - layout.bodyHeight);
|
|
@@ -1851,17 +1884,22 @@ export default function Dashboard() {
|
|
|
1851
1884
|
const defaultBranch = getDefaultBranch();
|
|
1852
1885
|
const label = branch === defaultBranch ? `${branch} (default)` : branch;
|
|
1853
1886
|
return (_jsx(Text, { children: _jsxs(Text, { color: selected ? "cyan" : undefined, bold: selected, children: [selected ? "> " : " ", label] }) }, branch));
|
|
1854
|
-
}), _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 })) : 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
|
|
1887
|
+
}), _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 })) : 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
|
|
1855
1888
|
.split("\n")
|
|
1856
1889
|
.slice(-(contentHeight - 1))
|
|
1857
|
-
.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 })) })] })), _jsxs(Box, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview }) })] })] })] }));
|
|
1890
|
+
.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 })) })] })), _jsxs(Box, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: state.overlay === "diff" ? "diff" : "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay }) })] })] })] }));
|
|
1858
1891
|
}
|
|
1859
1892
|
/**
|
|
1860
1893
|
* Renders the per-issue action key hints (Resume / Editor / View diff / …)
|
|
1861
1894
|
* lifted out of the detail panels so they sit on the same row as the global
|
|
1862
1895
|
* command bar. Empty when nothing is selected.
|
|
1863
1896
|
*/
|
|
1864
|
-
function ActionRow({ activeTab, selectedIssue, selectedReview, }) {
|
|
1897
|
+
function ActionRow({ activeTab, selectedIssue, selectedReview, overlay, }) {
|
|
1898
|
+
// During the diff overlay, none of the per-issue actions apply (View diff
|
|
1899
|
+
// is what got us here, Commit/PR/etc. need the detail panel context). Keep
|
|
1900
|
+
// the row blank so the diff-specific CommandBar reads cleanly.
|
|
1901
|
+
if (overlay === "diff")
|
|
1902
|
+
return _jsx(Text, { children: " " });
|
|
1865
1903
|
const items = activeTab === "reviews"
|
|
1866
1904
|
? selectedReview
|
|
1867
1905
|
? buildReviewActions(selectedReview)
|
|
@@ -14,6 +14,11 @@ interface Props {
|
|
|
14
14
|
error: string | null;
|
|
15
15
|
/** Theme-adapted selection background. Falls back to dark navy. */
|
|
16
16
|
selectionBg?: string;
|
|
17
|
+
/**
|
|
18
|
+
* User-set left pane width (from divider drag). Falls back to the default
|
|
19
|
+
* formula when undefined. Always clamped against pane minimums.
|
|
20
|
+
*/
|
|
21
|
+
leftWidthOverride?: number;
|
|
17
22
|
}
|
|
18
23
|
interface RenderedRow {
|
|
19
24
|
prefix: string;
|
|
@@ -39,12 +44,18 @@ export interface DiffLayout {
|
|
|
39
44
|
* Shared between DiffOverlay (rendering) and the dashboard mouse handler
|
|
40
45
|
* (mapping click coords back to file indices).
|
|
41
46
|
*/
|
|
47
|
+
export declare const DIFF_LEFT_MIN = 20;
|
|
48
|
+
export declare const DIFF_RIGHT_MIN = 20;
|
|
49
|
+
export declare const DIFF_DIVIDER_WIDTH = 1;
|
|
50
|
+
export declare function defaultDiffLeftWidth(width: number): number;
|
|
51
|
+
export declare function clampDiffLeftWidth(leftWidth: number, width: number): number;
|
|
42
52
|
export declare function computeDiffLayout(opts: {
|
|
43
53
|
width: number;
|
|
44
54
|
height: number;
|
|
45
55
|
files: DiffFile[];
|
|
46
56
|
fileIndex: number;
|
|
47
57
|
fileScrollOffset: number;
|
|
58
|
+
leftWidthOverride?: number;
|
|
48
59
|
}): DiffLayout;
|
|
49
|
-
export default function DiffOverlay({ width, height, ticketId, baseBranch, files, fileIndex, fileScrollOffset, content, contentScrollOffset, loadingFiles, loadingContent, error, selectionBg, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
export default function DiffOverlay({ width, height, ticketId, baseBranch, files, fileIndex, fileScrollOffset, content, contentScrollOffset, loadingFiles, loadingContent, error, selectionBg, leftWidthOverride, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
50
61
|
export {};
|
|
@@ -112,12 +112,24 @@ export function flattenTreeFiles(files) {
|
|
|
112
112
|
* Shared between DiffOverlay (rendering) and the dashboard mouse handler
|
|
113
113
|
* (mapping click coords back to file indices).
|
|
114
114
|
*/
|
|
115
|
+
export const DIFF_LEFT_MIN = 20;
|
|
116
|
+
export const DIFF_RIGHT_MIN = 20;
|
|
117
|
+
export const DIFF_DIVIDER_WIDTH = 1;
|
|
118
|
+
export function defaultDiffLeftWidth(width) {
|
|
119
|
+
return Math.min(48, Math.max(24, Math.floor(width * 0.32)));
|
|
120
|
+
}
|
|
121
|
+
export function clampDiffLeftWidth(leftWidth, width) {
|
|
122
|
+
const max = Math.max(DIFF_LEFT_MIN, width - DIFF_DIVIDER_WIDTH - DIFF_RIGHT_MIN);
|
|
123
|
+
return Math.max(DIFF_LEFT_MIN, Math.min(leftWidth, max));
|
|
124
|
+
}
|
|
115
125
|
export function computeDiffLayout(opts) {
|
|
116
126
|
const headerHeight = 2;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const
|
|
127
|
+
// Keymap footer lives in the dashboard's global CommandBar — don't reserve
|
|
128
|
+
// a row here or we'd render two stacked keymap rows.
|
|
129
|
+
const bodyHeight = Math.max(3, opts.height - headerHeight);
|
|
130
|
+
const requestedLeft = opts.leftWidthOverride ?? defaultDiffLeftWidth(opts.width);
|
|
131
|
+
const leftWidth = clampDiffLeftWidth(requestedLeft, opts.width);
|
|
132
|
+
const rightWidth = Math.max(DIFF_RIGHT_MIN, opts.width - leftWidth - DIFF_DIVIDER_WIDTH);
|
|
121
133
|
const rows = [];
|
|
122
134
|
const tree = buildTree(opts.files);
|
|
123
135
|
renderTree(tree, 0, rows, { value: 0 });
|
|
@@ -194,8 +206,15 @@ function truncateVisible(s, max) {
|
|
|
194
206
|
return out + "…";
|
|
195
207
|
}
|
|
196
208
|
// ── Component ─────────────────────────────────────────────────────────
|
|
197
|
-
export default function DiffOverlay({ width, height, ticketId, baseBranch, files, fileIndex, fileScrollOffset, content, contentScrollOffset, loadingFiles, loadingContent, error, selectionBg = "#1e3a5f", }) {
|
|
198
|
-
const layout = computeDiffLayout({
|
|
209
|
+
export default function DiffOverlay({ width, height, ticketId, baseBranch, files, fileIndex, fileScrollOffset, content, contentScrollOffset, loadingFiles, loadingContent, error, selectionBg = "#1e3a5f", leftWidthOverride, }) {
|
|
210
|
+
const layout = computeDiffLayout({
|
|
211
|
+
width,
|
|
212
|
+
height,
|
|
213
|
+
files,
|
|
214
|
+
fileIndex,
|
|
215
|
+
fileScrollOffset,
|
|
216
|
+
leftWidthOverride,
|
|
217
|
+
});
|
|
199
218
|
const { bodyHeight, leftWidth, rightWidth, rows, effectiveScroll, selectedRowIdx } = layout;
|
|
200
219
|
const visibleRows = rows.slice(effectiveScroll, effectiveScroll + bodyHeight);
|
|
201
220
|
// Right pane: split content into lines and slice for scroll. If the content
|
|
@@ -239,5 +258,5 @@ export default function DiffOverlay({ width, height, ticketId, baseBranch, files
|
|
|
239
258
|
// so usable column count is rightWidth - 1.
|
|
240
259
|
const cell = truncateVisible(line.text || " ", Math.max(1, rightWidth - 1));
|
|
241
260
|
return (_jsx(Text, { color: line.color, bold: line.bold, dimColor: line.dim, children: cell }, i));
|
|
242
|
-
})) })] })
|
|
261
|
+
})) })] })] }));
|
|
243
262
|
}
|
|
@@ -9,20 +9,20 @@ function checksIndicator(checks) {
|
|
|
9
9
|
return { text: "\u2713", color: "green" };
|
|
10
10
|
return { text: "\u25cf", color: "yellow" };
|
|
11
11
|
}
|
|
12
|
-
const FOOTER_HEIGHT = 2;
|
|
13
12
|
const HEADER_ROWS = 1;
|
|
14
13
|
export function getReviewListRowCount(flatReviews) {
|
|
15
14
|
return HEADER_ROWS + flatReviews.length;
|
|
16
15
|
}
|
|
17
16
|
export default function ReviewList({ flatReviews, selectedIndex, scrollOffset, height, width, selectionBg = "#1e3a5f", }) {
|
|
18
|
-
|
|
17
|
+
// Keymap footer lives in the dashboard's global CommandBar \u2014 use the full
|
|
18
|
+
// pane height for the list so we don't render two stacked keymap rows.
|
|
19
|
+
const listHeight = height;
|
|
19
20
|
const numColWidth = 6;
|
|
20
21
|
const authorColWidth = 12;
|
|
21
22
|
const changesColWidth = 10;
|
|
22
23
|
const checksColWidth = 2;
|
|
23
24
|
const fixedWidth = 2 + numColWidth + 1 + authorColWidth + 1 + changesColWidth + 1 + checksColWidth;
|
|
24
25
|
const titleMaxWidth = Math.max(width - fixedWidth, 10);
|
|
25
|
-
const footerRule = "\u2500".repeat(width);
|
|
26
26
|
const totalRows = HEADER_ROWS + flatReviews.length;
|
|
27
27
|
const visibleStart = scrollOffset;
|
|
28
28
|
const visibleEnd = Math.min(visibleStart + listHeight, totalRows);
|
|
@@ -49,5 +49,5 @@ export default function ReviewList({ flatReviews, selectedIndex, scrollOffset, h
|
|
|
49
49
|
const bg = selected ? selectionBg : undefined;
|
|
50
50
|
rows.push(_jsxs(Box, { width: width, children: [_jsxs(Text, { backgroundColor: bg, bold: selected, children: [cursor, " "] }), _jsx(Text, { backgroundColor: bg, color: pr.isDraft ? "gray" : "green", children: num.padEnd(numColWidth) }), _jsx(Text, { backgroundColor: bg, children: " " }), _jsx(Text, { backgroundColor: bg, bold: selected, children: title.padEnd(titleMaxWidth) }), _jsx(Text, { backgroundColor: bg, dimColor: true, children: author.padStart(authorColWidth) }), _jsx(Text, { backgroundColor: bg, children: " " }), _jsxs(Text, { backgroundColor: bg, children: [_jsx(Text, { color: "green", children: `+${item.additions}` }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { color: "red", children: `-${item.deletions}` }), "".padStart(Math.max(0, changesColWidth - changes.length))] }), _jsx(Text, { backgroundColor: bg, children: " " }), _jsx(Text, { backgroundColor: bg, color: selected ? (ci.color === "gray" ? "gray" : ci.color) : ci.color, children: ci.text.padStart(checksColWidth) })] }, `${pr.number}`));
|
|
51
51
|
}
|
|
52
|
-
return (
|
|
52
|
+
return (_jsx(Box, { flexDirection: "column", width: width, height: height, children: _jsx(Box, { flexDirection: "column", height: listHeight, children: rows }) }));
|
|
53
53
|
}
|