fix(maestro): fifth-review findings, incl. the guarded-worktree dead end
The cleanup guard plus deterministic paths had created a trap: one untracked file an agent left behind — a log, a .env, a stray artifact — made the guard refuse removal, the directory stayed, and every later spawn of that branch hit PathExists. The branch was burned permanently and the product offered no way to clear it. A leftover tree registered for the same branch is now adopted, so the operator lands back in their own unsaved work. A tree a *live* session holds is still rejected, since two sessions must not share a checkout. Also: - Fast-forward still awaited /api/info, /api/projects and /api/names inline, the exact freeze the draft loaders were restructured to avoid. It posts to the same channel now. - Draft updates were routed by tab index, which shifts when a tab closes: an in-flight load could be dropped or land on the wrong pane. Drafts carry an id and are found by it. - extend_names left the name it had just displayed at the head of the queue, so the next reroll produced it again and the key looked dead. - The fast-forward fallback discarded info.home_dir, so its draft showed unabbreviated paths and seeded "Other directory…" with / — disagreeing with the normal draft entry point. - Web fast-forward mounted via openSession, which replaces the focused pane: pressing it while attached evicted that session from the layout. It reuses an empty pane or splits, as the TUI does. - branchEdited was inferred from the seeded branch value, but in "new" mode that value is the auto-synced session name — so any form rebuild froze the branch against later rerolls. - resolveBaseRepo ignored the worktree checkbox, rebuilding the fieldset expanded under an unticked box and probing gitroot for a worktree the operator had turned off. - short_hash used DefaultHasher, whose algorithm is not stable across Rust releases, in a directory name — a toolchain bump would orphan every worktree under the override root. Now FNV-1a.
This commit is contained in:
@@ -49,6 +49,7 @@ export interface MultiView {
|
||||
/** Bring a session into the layout: focus it if already open, else fill the
|
||||
* focused pane (or open the first pane in an empty tab). */
|
||||
openSession: (sessionId: string) => void;
|
||||
openSessionBeside: (sessionId: string) => void;
|
||||
/** Drop the pane showing `sessionId` (the session was disconnected/killed). */
|
||||
closeSession: (sessionId: string) => void;
|
||||
/** Every session currently shown in some pane, across all tabs. */
|
||||
@@ -378,6 +379,39 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
render();
|
||||
}
|
||||
|
||||
/// Mount a session without evicting one.
|
||||
///
|
||||
/// `openSession` deliberately replaces the focused pane (VS Code style) for
|
||||
/// a sidebar click, but the fast path must never do that: pressing the key
|
||||
/// while attached would drop the running session out of the layout
|
||||
/// entirely. Follows `openDraft`'s rule instead — reuse an empty pane, else
|
||||
/// split beside the focused one (#67 review).
|
||||
function openSessionBeside(sessionId: string): void {
|
||||
const loc = locateSession(layout, sessionId);
|
||||
if (loc) {
|
||||
layout.activeTabId = loc.tab.id;
|
||||
loc.tab.focusedLeafId = loc.leaf.id;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
const tab = activeTab(layout);
|
||||
if (!tab.root || !tab.focusedLeafId || !findLeaf(tab.root, tab.focusedLeafId)) {
|
||||
tab.focusedLeafId = splitLeaf(tab, "", sessionId, "row", true);
|
||||
render();
|
||||
opts.onOpenSessionsChange?.();
|
||||
return;
|
||||
}
|
||||
const target = findLeaf(tab.root, tab.focusedLeafId)!;
|
||||
if (!target.sessionId) {
|
||||
target.sessionId = sessionId;
|
||||
target.draft = null;
|
||||
} else {
|
||||
tab.focusedLeafId = splitLeaf(tab, target.id, sessionId, "row", true);
|
||||
}
|
||||
render();
|
||||
opts.onOpenSessionsChange?.();
|
||||
}
|
||||
|
||||
function openDraft(seed?: Partial<Draft>): void {
|
||||
const tab = activeTab(layout);
|
||||
const draft = (): Draft => ({ command: "", cwd: "", name: "", ...seed });
|
||||
@@ -693,6 +727,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
return {
|
||||
el,
|
||||
openSession,
|
||||
openSessionBeside,
|
||||
openDraft,
|
||||
closeSession,
|
||||
openSessions: () => openSessions(layout),
|
||||
|
||||
Reference in New Issue
Block a user