feat(maestro): empty panes render an inline spawn draft form (#57)
This commit is contained in:
@@ -236,6 +236,9 @@ mv = createMultiView({
|
||||
infoFor,
|
||||
onDisconnect: closePane,
|
||||
renderWelcome,
|
||||
// A draft-pane submit (#57) fills its leaf in place; the shell route may flip
|
||||
// from catalog (no sessions) to attach.
|
||||
onOpenSessionsChange: updateShellRoute,
|
||||
onLayoutChange: scheduleSave,
|
||||
});
|
||||
let contentShowsMv = false;
|
||||
|
||||
+36
-3
@@ -1,8 +1,9 @@
|
||||
// The multi-view renderer/controller (issues #30 splits, #31 tabs, #33
|
||||
// highlights). Owns the tab bar + canvas inside the content pane, builds the
|
||||
// active tab's split tree into DOM, and mounts a live AttachConnection into each
|
||||
// session leaf via the pool. Empty leaves (freshly split, not yet filled) show a
|
||||
// placeholder. Drag-to-dock is a later issue (#32); mobile projection is #35.
|
||||
// session leaf via the pool. Empty leaves are draft panes (#57): they render
|
||||
// the spawn form inline, and submitting replaces the draft with that session's
|
||||
// terminal in place. Drag-to-dock is issue #32; mobile projection is #35.
|
||||
|
||||
import type { SidebarHandle } from "./catalog.js";
|
||||
import type { ConnectionPool } from "./connections.js";
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
} from "./layout.js";
|
||||
import { sessionColor } from "./sessioncolor.js";
|
||||
import type { AttachInfo } from "./attach.js";
|
||||
import { createSpawnForm, type SpawnFormHandle } from "./spawnForm.js";
|
||||
|
||||
export interface MultiViewOptions {
|
||||
pool: ConnectionPool;
|
||||
@@ -33,6 +35,9 @@ export interface MultiViewOptions {
|
||||
onDisconnect: (sessionId: string) => void;
|
||||
/** Welcome content for an empty tab. */
|
||||
renderWelcome: () => HTMLElement;
|
||||
/** Called when a draft-pane submit turns an empty pane into a session pane,
|
||||
* so the shell can re-evaluate its route (sidebar vs attach layout). */
|
||||
onOpenSessionsChange?: () => void;
|
||||
/** Called after any layout change (structure, focus, or divider resize) so the
|
||||
* shell can persist it (issue #34). */
|
||||
onLayoutChange?: () => void;
|
||||
@@ -60,6 +65,10 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
const { pool, layout, sidebar, infoFor, onDisconnect, renderWelcome } = opts;
|
||||
const persist = () => opts.onLayoutChange?.();
|
||||
|
||||
// Draft-pane forms (#57) carry global listeners (dir autocomplete), so each
|
||||
// render tears down the previous generation before rebuilding the canvas.
|
||||
const draftForms = new Set<SpawnFormHandle>();
|
||||
|
||||
const el = document.createElement("div");
|
||||
el.className = "mv";
|
||||
const tabBar = document.createElement("div");
|
||||
@@ -133,7 +142,26 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
const host = document.createElement("div");
|
||||
host.className = "mv-pane-host";
|
||||
if (!leaf.sessionId) {
|
||||
host.innerHTML = `<div class="mv-pane-empty">Empty pane — pick a session from the sidebar.</div>`;
|
||||
// Draft pane (#57): an empty pane is a spawn form. Values hang off the
|
||||
// leaf (never persisted) so each draft keeps its own half-filled state;
|
||||
// submitting fills this leaf with the new session in place.
|
||||
const form = createSpawnForm({
|
||||
seed: leaf.draft ?? undefined,
|
||||
class: "mv-draft",
|
||||
onChange: (values) => {
|
||||
leaf.draft = values;
|
||||
},
|
||||
onSuccess: (session) => {
|
||||
leaf.sessionId = session.id;
|
||||
leaf.draft = null;
|
||||
const t = activeTab(layout);
|
||||
if (leaves(t.root).includes(leaf)) t.focusedLeafId = leaf.id;
|
||||
render();
|
||||
opts.onOpenSessionsChange?.();
|
||||
},
|
||||
});
|
||||
draftForms.add(form);
|
||||
host.appendChild(form.el);
|
||||
}
|
||||
|
||||
const tools = document.createElement("div");
|
||||
@@ -326,6 +354,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
// Fill the focused pane (VS Code style: open replaces the active group).
|
||||
const target = (tab.focusedLeafId && findLeaf(tab.root, tab.focusedLeafId)) || leaves(tab.root)[0];
|
||||
target.sessionId = sessionId;
|
||||
target.draft = null;
|
||||
tab.focusedLeafId = target.id;
|
||||
render();
|
||||
}
|
||||
@@ -333,6 +362,8 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
// ---- top-level render ----
|
||||
|
||||
function render(): void {
|
||||
for (const f of draftForms) f.destroy();
|
||||
draftForms.clear();
|
||||
renderTabs();
|
||||
canvas.replaceChildren();
|
||||
const tab = activeTab(layout);
|
||||
@@ -569,6 +600,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
if (source.kind === "pane" && self) {
|
||||
const dragged = findLeaf(tab.root, self)!;
|
||||
[dragged.sessionId, target.sessionId] = [target.sessionId, dragged.sessionId];
|
||||
[dragged.draft, target.draft] = [target.draft, dragged.draft];
|
||||
tab.focusedLeafId = target.id;
|
||||
} else if (sessionId) {
|
||||
const loc = locateSession(layout, sessionId);
|
||||
@@ -576,6 +608,7 @@ export function createMultiView(opts: MultiViewOptions): MultiView {
|
||||
const t2 = findLeaf(tab.root, targetLeafId);
|
||||
if (t2) {
|
||||
t2.sessionId = sessionId;
|
||||
t2.draft = null;
|
||||
tab.focusedLeafId = t2.id;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user