Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the 23 views it drove are gone; App.tsx composes the ported components — sidebar, multi-view, command bar, spawn modal, help and restart overlays, daemon link, drawer — around the catalog and layout stores. The pure parts of four deleted modules moved next to the components that still use them: the spawn-form model and spawnSimilar into components/spawn, the transcript's markdown/format helpers and the diff into components/structured, the help overlay's grouping into components/dialogs. Document-level event names now live in uiEvents.ts. Deduplicated two pairs the migration left side by side: the sidebar uses dialogs/KillDialog and common/ContextMenu. It also listens for the command bar's kill/rename events, which nothing had picked up since catalog.ts went away. styles.css stays as the token and base layer, with `#app` renamed to `#root` and the pane-body rules re-keyed off the classes the React panes actually carry. Closes #253
120 lines
4.5 KiB
TypeScript
120 lines
4.5 KiB
TypeScript
// The spawn form's worktree section (#61): create-worktree toggle, then
|
|
// (once a base repo is known) new-branch-vs-existing-branch, mirroring
|
|
// spawnForm.ts's `.adv-group`/`.wt-body`/`.wt-mode` markup.
|
|
|
|
import type { BranchInfo } from "../../types.js";
|
|
import type { WorktreeBranchMode } from "./model.js";
|
|
|
|
export interface BranchPickerProps {
|
|
enabled: boolean;
|
|
onToggleEnabled(enabled: boolean): void;
|
|
/** Status line shown in place of the body — "Checking…", "not a repo",
|
|
* "choose a directory first". `null` once the body itself can render. */
|
|
hint: string | null;
|
|
/** The repo a worktree would branch from, once resolved. */
|
|
baseRepo: string | null;
|
|
mode: WorktreeBranchMode;
|
|
onModeChange(mode: WorktreeBranchMode): void;
|
|
newBranch: string;
|
|
onNewBranchChange(value: string): void;
|
|
/** This cwd's local branches, or `undefined` while unknown/not yet loaded. */
|
|
branches: BranchInfo[] | undefined;
|
|
existingBranch: string;
|
|
onExistingBranchChange(value: string): void;
|
|
}
|
|
|
|
export function BranchPicker(props: BranchPickerProps) {
|
|
return (
|
|
<div
|
|
className={`flex flex-col gap-sp-3 border-l-2 pl-sp-3 ${
|
|
props.enabled ? "border-accent" : "border-border"
|
|
}`}
|
|
>
|
|
<label className="flex cursor-pointer items-center gap-sp-2 text-fs-14 text-ink">
|
|
<input
|
|
type="checkbox"
|
|
data-role="toggle"
|
|
className="size-[18px] accent-accent"
|
|
checked={props.enabled}
|
|
onChange={(e) => props.onToggleEnabled(e.currentTarget.checked)}
|
|
/>
|
|
Create git worktree
|
|
</label>
|
|
|
|
{props.hint && <p className="m-0 text-fs-12 text-muted">{props.hint}</p>}
|
|
|
|
{props.enabled && !props.hint && (
|
|
<div className="flex flex-col gap-sp-3">
|
|
{props.baseRepo && (
|
|
<p className="m-0 font-mono text-fs-12 text-muted">repo: {props.baseRepo}</p>
|
|
)}
|
|
<div role="radiogroup" aria-label="Branch source" className="flex gap-sp-4">
|
|
<label className="flex cursor-pointer items-center gap-sp-2 text-fs-14 text-ink">
|
|
<input
|
|
type="radio"
|
|
name="worktree_branch_mode"
|
|
checked={props.mode === "new"}
|
|
onChange={() => props.onModeChange("new")}
|
|
/>
|
|
New branch
|
|
</label>
|
|
<label className="flex cursor-pointer items-center gap-sp-2 text-fs-14 text-ink">
|
|
<input
|
|
type="radio"
|
|
name="worktree_branch_mode"
|
|
checked={props.mode === "existing"}
|
|
onChange={() => props.onModeChange("existing")}
|
|
/>
|
|
Existing branch
|
|
</label>
|
|
</div>
|
|
|
|
{props.mode === "new" ? (
|
|
<label className="flex flex-col gap-1 text-fs-12 text-muted">
|
|
New branch name
|
|
<input
|
|
name="worktree_branch"
|
|
type="text"
|
|
autoComplete="off"
|
|
placeholder="feat/foo"
|
|
className="w-full rounded-r-2 border border-border bg-surface-2 px-sp-3 py-2 font-mono text-fs-15 text-ink"
|
|
value={props.newBranch}
|
|
onChange={(e) => props.onNewBranchChange(e.currentTarget.value)}
|
|
/>
|
|
</label>
|
|
) : (
|
|
<label className="flex flex-col gap-1 text-fs-12 text-muted">
|
|
Pick a branch
|
|
<select
|
|
className="w-full rounded-r-2 border border-border bg-surface-2 px-sp-3 py-2 font-mono text-fs-15 text-ink"
|
|
value={props.existingBranch}
|
|
onChange={(e) => props.onExistingBranchChange(e.currentTarget.value)}
|
|
>
|
|
{branchOptions(props.branches)}
|
|
</select>
|
|
</label>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function branchOptions(branches: BranchInfo[] | undefined) {
|
|
if (!branches || branches.length === 0) {
|
|
return <option value="">No local branches found.</option>;
|
|
}
|
|
return branches.map((b) => {
|
|
// A branch already checked out anywhere stays visible but unpickable —
|
|
// git refuses a second checkout. That includes the main checkout's own
|
|
// HEAD, which is commonly `main` and would otherwise be the default pick,
|
|
// making every existing-branch spawn fail (#54 review).
|
|
const held = b.worktree ?? (b.is_head ? "the main checkout" : null);
|
|
return (
|
|
<option key={b.name} value={b.name} disabled={held !== null} title={held ?? ""}>
|
|
{held ? `${b.name} — held by ${held}` : b.name}
|
|
</option>
|
|
);
|
|
});
|
|
}
|