fix(run,lanes): prompt autocomplete, per-lane Active runs, compact Changes header
- The run form's prompt box offers suggestions again: `/` lists the run
cwd's project and user commands and skills plus the CLI built-ins
(claude lanes only), `@` lists files under the cwd; Enter picks while
the list is open and sends otherwise. The 2f39f4e rewrite had left a
bare textarea that ignored both.
- Active runs lists only the current lane's runs and external sessions
(sessions matched to the lane by longest path-boundary cwd), instead of
every lane's.
- The Changes panel's git line and its actions are one compact row in one
card, and the mode/range toolbar is one wrapping row.
- The lane card hides itself when it has nothing to show, instead of
leaving an empty frame where the git row used to be.
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
* per-file stage/unstage/discard it commits — one way to commit, and it only
|
||||
* ever takes what the user staged.
|
||||
*
|
||||
* It renders at the top of the Changes panel (`LaneGitHeader`) rather than in
|
||||
* the lane card's ⋯ menu: that menu holds the rare and dangerous (`reset
|
||||
* It renders at the top of the Changes panel (`LaneGitHeader`), its actions as
|
||||
* icons on the same row as the branch line, rather than in the lane card's ⋯ menu: that menu holds the rare and dangerous (`reset
|
||||
* worktree`), and these are the ordinary moves a lane needs several times an hour.
|
||||
* Nothing here decides what an action may do; the server does (pull is always
|
||||
* --ff-only, switch refuses a dirty tree), and this only reports what it said.
|
||||
@@ -30,6 +30,7 @@ function Btn({
|
||||
busy,
|
||||
disabled,
|
||||
title,
|
||||
compact,
|
||||
onClick,
|
||||
}: {
|
||||
label: string;
|
||||
@@ -37,6 +38,8 @@ function Btn({
|
||||
busy?: boolean;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
/** Icon only; the label becomes the accessible name, the hint the tooltip. */
|
||||
compact?: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
@@ -45,10 +48,13 @@ function Btn({
|
||||
onClick={onClick}
|
||||
disabled={disabled || busy}
|
||||
title={title}
|
||||
className="flex items-center gap-1 rounded border border-border-light px-1.5 py-0.5 text-[11px] text-fg-secondary transition-colors hover:bg-surface-2 hover:text-fg-primary disabled:cursor-not-allowed disabled:opacity-40"
|
||||
aria-label={compact ? label : undefined}
|
||||
className={`flex items-center gap-1 rounded text-[11px] text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg-primary disabled:cursor-not-allowed disabled:opacity-40 ${
|
||||
compact ? "h-6 w-6 justify-center" : "border border-border-light px-1.5 py-0.5"
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
{!compact && label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -58,9 +64,13 @@ export function LaneGitBar({
|
||||
git,
|
||||
onChanged,
|
||||
onNewLane,
|
||||
lead,
|
||||
}: {
|
||||
laneId: number;
|
||||
git: LaneGitFacts;
|
||||
/** Rendered at the start of the action row: the header's branch line, so
|
||||
* the facts and the buttons that act on them read as one row. */
|
||||
lead?: React.ReactNode;
|
||||
/** Called after any action that could have moved the working copy. */
|
||||
onChanged: () => void;
|
||||
/** Open the Add-lane flow for a sibling worktree of this repo, optionally on
|
||||
@@ -165,8 +175,9 @@ export function LaneGitBar({
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-testid="lane-git-bar" className="mb-2 space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<div data-testid="lane-git-bar" className="space-y-1">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{lead}
|
||||
{/* Ahead/behind first: it is the fact that decides which of the
|
||||
buttons beside it the user actually wants. */}
|
||||
{/* Null counts with an upstream named means git could not compare
|
||||
@@ -176,7 +187,7 @@ export function LaneGitBar({
|
||||
<span
|
||||
data-testid="lane-git-tracking"
|
||||
title={git.upstream}
|
||||
className="flex items-center gap-1 font-mono text-[11px] text-fg-muted"
|
||||
className="flex shrink-0 items-center gap-1 font-mono text-[11px] text-fg-muted"
|
||||
>
|
||||
{(git.ahead ?? 0) > 0 && (
|
||||
<span className="flex items-center text-status-warning">
|
||||
@@ -194,43 +205,51 @@ export function LaneGitBar({
|
||||
</span>
|
||||
) : (
|
||||
git.hasRemote && (
|
||||
<span className="font-mono text-[11px] text-fg-muted">{t("gitBar.noUpstream")}</span>
|
||||
<span className="shrink-0 font-mono text-[11px] text-fg-muted">
|
||||
{t("gitBar.noUpstream")}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
|
||||
<Btn
|
||||
label={t("gitBar.fetch")}
|
||||
icon={<RefreshCw className="h-3 w-3" />}
|
||||
busy={busy === "fetch"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.fetchHint")}
|
||||
onClick={() => run("fetch")}
|
||||
/>
|
||||
<Btn
|
||||
label={t("gitBar.pull")}
|
||||
icon={<ArrowDown className="h-3 w-3" />}
|
||||
busy={busy === "pull"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.pullHint")}
|
||||
onClick={() => run("pull")}
|
||||
/>
|
||||
<Btn
|
||||
label={t("gitBar.push")}
|
||||
icon={<Upload className="h-3 w-3" />}
|
||||
busy={busy === "push"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.pushHint")}
|
||||
onClick={() => run("push")}
|
||||
/>
|
||||
<Btn
|
||||
label={t("gitBar.switch")}
|
||||
icon={<GitBranch className="h-3 w-3" />}
|
||||
disabled={!!busy}
|
||||
// A dirty tree is refused server-side; saying so before the click
|
||||
// beats a red box after it.
|
||||
title={dirty > 0 ? t("gitBar.switchDirty") : t("gitBar.switchHint")}
|
||||
onClick={openSwitch}
|
||||
/>
|
||||
<div className="ml-auto flex shrink-0 items-center">
|
||||
<Btn
|
||||
compact
|
||||
label={t("gitBar.fetch")}
|
||||
icon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||
busy={busy === "fetch"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.fetchHint")}
|
||||
onClick={() => run("fetch")}
|
||||
/>
|
||||
<Btn
|
||||
compact
|
||||
label={t("gitBar.pull")}
|
||||
icon={<ArrowDown className="h-3.5 w-3.5" />}
|
||||
busy={busy === "pull"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.pullHint")}
|
||||
onClick={() => run("pull")}
|
||||
/>
|
||||
<Btn
|
||||
compact
|
||||
label={t("gitBar.push")}
|
||||
icon={<Upload className="h-3.5 w-3.5" />}
|
||||
busy={busy === "push"}
|
||||
disabled={!git.hasRemote || !!busy}
|
||||
title={t("gitBar.pushHint")}
|
||||
onClick={() => run("push")}
|
||||
/>
|
||||
<Btn
|
||||
compact
|
||||
label={t("gitBar.switch")}
|
||||
icon={<GitBranch className="h-3.5 w-3.5" />}
|
||||
disabled={!!busy}
|
||||
// A dirty tree is refused server-side; saying so before the click
|
||||
// beats a red box after it.
|
||||
title={dirty > 0 ? t("gitBar.switchDirty") : t("gitBar.switchHint")}
|
||||
onClick={openSwitch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{switching && (
|
||||
|
||||
Reference in New Issue
Block a user