feat(lanes): show Add Lane auto-setup summary before closing modal

The three setup calls (profile/agents/mcp) fired after worktree
creation but their outcome was only logged to the console. Keep the
modal open with a ✓/✗ summary for a few seconds (or until dismissed)
so the user actually sees what happened, matching the original F5 design.
This commit is contained in:
2026-08-06 13:31:56 +07:00
parent 77f3805083
commit 7fa39687fe
5 changed files with 92 additions and 8 deletions
+56 -6
View File
@@ -58,6 +58,25 @@ export function AddLaneModal({
setSetupResult(null);
};
// The setup summary (below) is shown for a few seconds before the modal
// auto-closes, so the user actually sees whether profile/agents/mcp
// succeeded instead of the modal vanishing the instant the lane exists.
const closeTimerRef = useRef<number | null>(null);
const finishAndClose = useCallback(() => {
if (closeTimerRef.current !== null) {
window.clearTimeout(closeTimerRef.current);
closeTimerRef.current = null;
}
reset();
onClose();
}, [onClose]);
useEffect(
() => () => {
if (closeTimerRef.current !== null) window.clearTimeout(closeTimerRef.current);
},
[]
);
// Look up the repo's branches once the path settles - debounced so every
// keystroke while typing a path doesn't fire a request against a path that
// isn't finished yet.
@@ -128,9 +147,9 @@ export function AddLaneModal({
});
}
reset();
setBusy(false);
onAdded(result.lane);
onClose();
closeTimerRef.current = window.setTimeout(finishAndClose, 3000);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setBusy(false);
@@ -144,9 +163,8 @@ export function AddLaneModal({
// into after the very first character. useCallback keeps the identity stable
// across renders so only mount/unmount (and a real onClose change) refocuses.
const handleCancel = useCallback(() => {
reset();
onClose();
}, [onClose]);
finishAndClose();
}, [finishAndClose]);
return (
<ConfirmModal
@@ -156,7 +174,7 @@ export function AddLaneModal({
cancelLabel={t("destructive.cancel")}
destructive={false}
busy={busy}
disabled={!sourceRepo.trim() || !title.trim() || !branches}
disabled={!!setupResult || !sourceRepo.trim() || !title.trim() || !branches}
onConfirm={submit}
onCancel={handleCancel}
>
@@ -214,6 +232,38 @@ export function AddLaneModal({
<p className="text-[10px] text-status-warning">{branchesError}</p>
)}
{setupResult && (
<div className="rounded-md border border-border-light bg-surface-0 p-2 space-y-1">
<p className="text-[10px] font-medium text-fg-secondary">{t("addLaneSetupTitle")}</p>
{(
[
{
label: t("addLaneSetupProfile"),
ok: setupResult.profile !== "failed",
skipped: setupResult.profile === "skipped",
},
{ label: t("addLaneSetupAgents"), ok: setupResult.agents === "ok" },
{ label: t("addLaneSetupMcp"), ok: setupResult.mcp === "ok" },
] as const
).map((row) => (
<p key={row.label} className="flex items-center gap-1.5 text-[11px] text-fg-primary">
<span
className={
"skipped" in row && row.skipped
? "text-fg-muted"
: row.ok
? "text-status-success"
: "text-status-danger"
}
>
{"skipped" in row && row.skipped ? "" : row.ok ? "✓" : "✗"}
</span>
{row.label}
</p>
))}
</div>
)}
{error && (
<p role="alert" className="text-xs text-status-danger">
{error}