fix(maestro): keep the keyboard in the list while previewing
Two mounts were taking focus out of the sidebar mid-walk: the render that put the multi-view on screen before the preview applied, and the render that restores the previous pane when a preview ends. Both now mount without focusing, so j/k keeps walking. Also marks the preview with a badge on the canvas and drops the close button from its tab.
This commit is contained in:
+10
-2
@@ -114,8 +114,9 @@ let previewTimer: number | undefined;
|
||||
function schedulePreview(sessionId: string | null, source: "key" | "hover"): void {
|
||||
clearTimeout(previewTimer);
|
||||
previewTimer = window.setTimeout(() => {
|
||||
showMultiView();
|
||||
ensureMultiView();
|
||||
mv?.previewSession(sessionId);
|
||||
updateShellRoute();
|
||||
}, PREVIEW_DELAY[source]);
|
||||
}
|
||||
|
||||
@@ -262,11 +263,18 @@ mv = createMultiView({
|
||||
onLayoutChange: scheduleSave,
|
||||
});
|
||||
let contentShowsMv = false;
|
||||
function showMultiView(): void {
|
||||
/** Put the multi-view in the content pane, without rendering it. A preview
|
||||
* renders once, on its own terms (`docs/PREVIEW.md`); a render here would
|
||||
* first re-mount the pane it is about to replace, and that mount takes the
|
||||
* keyboard away from the list being walked. */
|
||||
function ensureMultiView(): void {
|
||||
if (!contentShowsMv) {
|
||||
content.replaceChildren(mv.el);
|
||||
contentShowsMv = true;
|
||||
}
|
||||
}
|
||||
function showMultiView(): void {
|
||||
ensureMultiView();
|
||||
mv.render();
|
||||
updateShellRoute();
|
||||
}
|
||||
|
||||
+20
-5
@@ -110,6 +110,20 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
tempTabId: string | null;
|
||||
}
|
||||
let preview: Preview | null = null;
|
||||
/// Set while a preview-driven render runs: ending a preview re-mounts the
|
||||
/// pane that was there, and that mount must not pull the keyboard out of the
|
||||
/// sidebar the operator is still walking.
|
||||
let quietRender = false;
|
||||
|
||||
/** Render without any mount taking the keyboard. */
|
||||
function renderQuietly(): void {
|
||||
quietRender = true;
|
||||
try {
|
||||
render();
|
||||
} finally {
|
||||
quietRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
const isTempTab = (tabId: string) => preview?.tempTabId === tabId;
|
||||
|
||||
@@ -152,7 +166,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
function previewSession(sessionId: string | null): void {
|
||||
if (preview?.sessionId === sessionId) return;
|
||||
if (sessionId === null) {
|
||||
if (restorePreview()) render();
|
||||
if (restorePreview()) renderQuietly();
|
||||
return;
|
||||
}
|
||||
restorePreview();
|
||||
@@ -176,7 +190,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
layout.activeTabId = tab.id;
|
||||
preview = { sessionId, prevActiveTabId, prevFocus: null, tempTabId: tab.id };
|
||||
}
|
||||
render();
|
||||
renderQuietly();
|
||||
}
|
||||
|
||||
/** Any user action that changes the layout for real ends the preview first,
|
||||
@@ -385,7 +399,8 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
}
|
||||
render();
|
||||
});
|
||||
if (layout.tabs.length > 1) {
|
||||
// A preview tab is not the operator's to close: it goes on its own.
|
||||
if (layout.tabs.length > 1 && !isTempTab(t.id)) {
|
||||
const close = document.createElement("span");
|
||||
close.className = "mv-tab-close";
|
||||
close.textContent = "✕";
|
||||
@@ -555,7 +570,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
// terminal, or j/k would stop walking the list after the first step.
|
||||
pool.setVisible(
|
||||
hosts.map((h) => ({ id: h.id, pane: h.host, info: h.info })),
|
||||
{ focus: !preview },
|
||||
{ focus: !preview && !quietRender },
|
||||
);
|
||||
syncFocus();
|
||||
persist();
|
||||
@@ -579,7 +594,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
// Only the focused leaf's session is live on mobile; the rest park.
|
||||
pool.setVisible(
|
||||
focused.sessionId ? [{ id: focused.sessionId, pane: pane.host, info: infoFor(focused.sessionId) }] : [],
|
||||
{ focus: !preview },
|
||||
{ focus: !preview && !quietRender },
|
||||
);
|
||||
if (!preview) pool.setFocus(focused.sessionId ?? null);
|
||||
sidebar.setHighlights({ open: realOpenSessions(), focused: focused.sessionId ?? null });
|
||||
|
||||
@@ -2890,6 +2890,21 @@ input.leaf-rename:focus {
|
||||
border-style: dashed;
|
||||
font-style: italic;
|
||||
}
|
||||
.mv.mv-previewing .mv-canvas::before {
|
||||
content: "preview";
|
||||
position: absolute;
|
||||
top: var(--sp-2);
|
||||
right: var(--sp-3);
|
||||
z-index: var(--z-sticky);
|
||||
padding: 1px var(--sp-2);
|
||||
border-radius: var(--r-1);
|
||||
font: 500 var(--fs-11) / 1.5 var(--font-mono);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--accent-ink);
|
||||
background: var(--accent);
|
||||
opacity: 0.85;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mv.mv-previewing .mv-canvas::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user