BranchActions
uses useState
What to do with another branch: merge it into the studio's own (tcm merge; on main, that lands it, with its tests
run first), as one entry if asked, with what it's for; work on it (tcm switch, where the studio can); delete it
(tcm branch --delete), asking first, unless it's to keep (main, a session an agent is working on).
import { navigate } from "#codebase/tcmts/react"; import { at, toasts } from "#codebase/tcmts/share"; import { removeBranch, startMerge, switchBranch, toldOf } from "#codebase/tcmts/studio"; import { useState } from "react"; import { Button, Checkbox, Dialog, DialogTrigger, Heading, Label, Popover, TextArea, TextField, } from "react-aria-components"; export function BranchActions({ name, own, canSwitch, merging, keep = false, }: { name: string; own: string; canSwitch: boolean; merging: boolean; keep?: boolean; }) { const landing = own === "main"; const [message, setMessage] = useState(""); const [squash, setSquash] = useState(/^(session|agent)\//.test(name)); const [busy, setBusy] = useState(false); return ( <span className="actions rowacts"> {!merging && ( <DialogTrigger> <Button className="btn small">{landing ? "Land it" : "Merge it in"}</Button> <Popover className="menu-pop getit" placement="bottom end" offset={6}> <Dialog className="getit-body" aria-label={`Merge ${name}`}> {({ close }) => ( <form className="prodform" onSubmit={async (e) => { e.preventDefault(); setBusy(true); try { await startMerge(name, squash, message); close(); navigate(at("/branches")); } catch (err) { toasts.add( { title: `Couldn't merge ${name}`, description: (err as Error).message, tone: "bad", }, { timeout: 9000 }, ); } setBusy(false); }} > <Heading slot="title" className="getit-title"> {landing ? `Land ${name} on main` : `Merge ${name} into ${own}`} </Heading> <p> {landing ? "main is merged into it first, there, then its tests run; it lands only if they pass. One that conflicts or stops typechecking doesn't land: settle it on its branch." : `What conflicts, or stops typechecking, waits apart: ${own} stays as it is until Needs fixing has nothing left.`} </p> <TextField className="fieldrow" value={message} onChange={setMessage}> <Label>What it's for</Label> <TextArea className="field" rows={3} placeholder="A line saying what and why" /> </TextField> <Checkbox className="check" isSelected={squash} onChange={setSquash}> <span className="box-mark" aria-hidden="true" /> As one entry (its own entries stay on its branch) </Checkbox> <div className="actions"> <Button className="btn" onPress={close}> Cancel </Button> <Button className="btn primary" type="submit" isPending={busy}> {landing ? "Land it" : "Merge"} </Button> </div> </form> )} </Dialog> </Popover> </DialogTrigger> )} {canSwitch && ( <Button className="btn small" onPress={() => void toldOf(switchBranch(name), `Working on ${name}`, `Couldn't switch to ${name}`) } > Work on it </Button> )} {!keep && ( <DialogTrigger> <Button className="btn small danger">Delete</Button> <Popover className="menu-pop getit" placement="bottom end" offset={6}> <Dialog className="getit-body" aria-label={`Delete ${name}`}> {({ close }) => ( <> <Heading slot="title" className="getit-title"> Delete {name}? </Heading> <p> Its names go; the history it made stays (what was merged from it is kept where it was merged). </p> <div className="actions"> <Button className="btn" onPress={close}> Keep it </Button> <Button className="btn danger" onPress={() => { close(); void toldOf( removeBranch(name), `Deleted ${name}`, `Couldn't delete ${name}`, ); }} > Delete </Button> </div> </> )} </Dialog> </Popover> </DialogTrigger> )} </span> ); }
uses
- tcmts.react.navigate
- tcmts.share.at
- tcmts.share.toasts
- tcmts.studio.removeBranch
- tcmts.studio.startMerge
- tcmts.studio.switchBranch
- tcmts.studio.toldOf