-
-
@@ -192,6 +197,9 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
const cwdRawWrap = form.querySelector(".cwd-raw-wrap") as HTMLElement;
const cwdRaw = form.querySelector(".cwd-raw") as HTMLInputElement;
const wtEnabled = form.querySelector("input[name=worktree_enabled]") as HTMLInputElement;
+ const wtToggle = form.querySelector(".wt-toggle") as HTMLButtonElement;
+ const wtCollapsible = form.querySelector(".wt-collapsible") as HTMLElement;
+ const wtSummary = form.querySelector(".wt-summary") as HTMLElement;
const wtHint = form.querySelector(".wt-hint") as HTMLElement;
const wtBody = form.querySelector(".wt-body") as HTMLElement;
const wtRepo = form.querySelector(".wt-repo") as HTMLElement;
@@ -464,6 +472,7 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
// off. Turning it back on by hand still tries (and fails with git's
// own message), which is deliberate (#91 non-goal).
wtEnabled.checked = !project.is_worktree;
+ updateWtSummary();
if (project.is_worktree) {
inferredBaseRepo = null;
wtBody.hidden = true;
@@ -569,6 +578,18 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
return p;
}
+ // ---- worktree: disclosure (#93), reuses the sidebar's folder-header ---
+ let advancedCollapsed = true;
+ function setAdvancedCollapsed(collapsed: boolean): void {
+ advancedCollapsed = collapsed;
+ wtToggle.setAttribute("aria-expanded", String(!collapsed));
+ wtCollapsible.hidden = collapsed;
+ }
+ function updateWtSummary(): void {
+ wtSummary.textContent = wtEnabled.checked ? "creates a worktree" : "no worktree";
+ }
+ wtToggle.addEventListener("click", () => setAdvancedCollapsed(!advancedCollapsed));
+
// ---- worktree (branch-only; base_repo inferred via scan or /gitroot) --
// The project scan already reports repo membership, so a picked project
// resolves its base repo locally (#61); only the free-text escape hatch
@@ -649,6 +670,7 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
// Publish the untick too, or the next render seeds the box checked with
// no inferred repo and submitting fails.
wtEnabled.checked = false;
+ updateWtSummary();
emit();
wtBody.hidden = true;
wtHint.hidden = false;
@@ -722,6 +744,7 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
branchSelect.addEventListener("change", () => emit());
wtEnabled.addEventListener("change", () => {
+ updateWtSummary();
emit();
if (wtEnabled.checked) {
resolveBaseRepo();
@@ -735,6 +758,7 @@ export function createSpawnForm(opts: SpawnFormOptions): SpawnFormHandle {
// The toggle defaults ON (#61): the operator almost always wants a
// worktree, and every non-repo target unticks it via the gitroot probe.
wtEnabled.checked = prefill.worktreeEnabled ?? true;
+ updateWtSummary();
if (prefill.worktreeBranch) branchInput.value = prefill.worktreeBranch;
setBranchMode(branchMode);
diff --git a/web/src/spawnFormWorktree.test.ts b/web/src/spawnFormWorktree.test.ts
index bb04b96..ff5ca1e 100644
--- a/web/src/spawnFormWorktree.test.ts
+++ b/web/src/spawnFormWorktree.test.ts
@@ -180,6 +180,59 @@ describe("spawnForm worktree defaults and branch picker (#61)", () => {
expect(modes.at(-1)).toBe("existing");
});
+ it("collapses the advanced (worktree) section by default (#93)", async () => {
+ const form = await mount();
+ const toggle = form.querySelector
(".wt-toggle")!;
+ expect(toggle.getAttribute("aria-expanded")).toBe("false");
+ expect(form.querySelector(".wt-collapsible")!.hidden).toBe(true);
+ });
+
+ it("states in the collapsed header that a worktree will be created by default (#93)", async () => {
+ const form = await mount();
+ expect(form.querySelector(".wt-summary")!.textContent).toMatch(/creates? a worktree/i);
+ });
+
+ it("states in the collapsed header that no worktree will be created for a worktree row (#91, #93)", async () => {
+ urls = stubFetch({
+ projects: [
+ {
+ path: "/home/user/wt-91",
+ name: "wt-91",
+ is_worktree: true,
+ base_repo: "/home/user/maestro",
+ last_activity: "2026-08-20T10:00:00Z",
+ },
+ ],
+ });
+ handle = createSpawnForm({ onSuccess() {} });
+ document.body.appendChild(handle.el);
+ for (let i = 0; i < 40; i++) await Promise.resolve();
+
+ handle.el
+ .querySelectorAll(".project-row")
+ .forEach((row) => row.dataset.cwd === "/home/user/wt-91" && row.click());
+ await drain();
+
+ expect(handle.el.querySelector(".wt-summary")!.textContent).toMatch(/no worktree/i);
+ // Still collapsed — picking a project must not force it open.
+ expect(handle.el.querySelector(".wt-toggle")!.getAttribute("aria-expanded")).toBe(
+ "false",
+ );
+ });
+
+ it("expanding the advanced section does not reorder the fields above it (#93)", async () => {
+ const form = await mount();
+ const before = [...form.querySelectorAll(".field")];
+
+ form.querySelector(".wt-toggle")!.click();
+
+ expect(form.querySelector(".wt-toggle")!.getAttribute("aria-expanded")).toBe(
+ "true",
+ );
+ expect(form.querySelector(".wt-collapsible")!.hidden).toBe(false);
+ expect([...form.querySelectorAll(".field")]).toStrictEqual(before);
+ });
+
it("does not repopulate a destroyed form when the branch list lands late", async () => {
const gate: { release?: () => void } = {};
urls = stubFetch({ gateBranches: (r) => (gate.release = r) });
diff --git a/web/src/styles.css b/web/src/styles.css
index afe24c3..5aef4a8 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -1286,18 +1286,33 @@ textarea {
.worktree {
border: 1px solid var(--border);
border-radius: var(--r-2);
- padding: var(--sp-3);
+ padding: 0 var(--sp-3);
margin: 0;
display: flex;
flex-direction: column;
- gap: var(--sp-3);
background: color-mix(in oklab, var(--surface-2) 50%, transparent);
}
-.worktree legend {
- padding: 0 6px;
- color: var(--ink);
- font-size: var(--fs-13);
+.worktree .wt-toggle {
+ margin: 0 calc(var(--sp-3) * -1);
+ padding-left: var(--sp-3);
+ width: calc(100% + var(--sp-3) * 2);
+}
+
+.wt-summary {
+ color: var(--muted);
+ font-size: var(--fs-12);
+}
+
+.wt-collapsible {
+ display: flex;
+ flex-direction: column;
+ gap: var(--sp-3);
+ padding-bottom: var(--sp-3);
+}
+
+.wt-collapsible[hidden] {
+ display: none;
}
.form-error {