StudioBranchesPage

tcmts.studio.StudioBranchesPage · #s4heoxkp · term · runs on the server

uses process

The codebase's branches, and what to do with them: its own first (New branch makes one from it), then the others, then agents' sessions (those older than a day folded away), each with when it last changed, how many names differ from the codebase's own, a contribution proposing it, and, on hover, Merge (Land it, onto main), Work on it and Delete. A merge the studio started shows what it says as it runs (made again every two seconds while it does).

import { branchList } from "#codebase/tcmts/cli/changes";
import { liveMcpServers } from "#codebase/tcmts/cli/mcp";
import { ago, at, contributionHref, Fold, servedCodebase, shareNow } from "#codebase/tcmts/share";
import {
  BranchActions, BranchLink, Live, mergeRuns, mergeWaiting, NewBranchButton, StudioFrame, studioShare,
} from "#codebase/tcmts/studio";

export async function StudioBranchesPage() {
  studioShare();
  const s = shareNow();
  if (!s.signedIn)
    return (
      <StudioFrame tab="branches" title="Sign in">
        {null}
      </StudioFrame>
    );
  const cb = servedCodebase().cb;
  const own = cb.store.branch().name;
  const canSwitch = !process.env.TCM_BRANCH;
  const waiting = !!mergeWaiting(cb);
  const working = new Set(liveMcpServers(cb.lay.root).flatMap((m) => (m.branch ? [m.branch] : [])));
  const open = cb.store.contributions("open");
  const agents = (b: string) => /^(session|agent)\//.test(b);
  const items = branchList(cb);
  const recent = (b: { name: string; ts?: number }) =>
    working.has(b.name) || Date.now() - (b.ts ?? 0) < 86_400_000;
  const run = mergeRuns.at(-1);
  const running = !!run && run.ended === undefined;
  const row = (b: (typeof items)[number]) => {
    const c = open.find((x) => x.source === b.name);
    return (
      <div className="lrow branchrow" key={b.name}>
        <div>
          {working.has(b.name) && <span className="live busy" title="an agent is working on it" />}
          {b.current ? <b className="mono">{b.name}</b> : <BranchLink branch={b.name} />}
          <div className="muted" style={{ fontSize: 13 }}>
            {b.ts ? `changed ${ago(b.ts)}` : "no history"}
            {b.current
              ? " · the codebase's own: the one the studio changes"
              : b.differs
                ? ` · ${b.differs} name${b.differs === 1 ? "" : "s"} differ from ${own}`
                : ` · as ${own} is`}
            {c && (
              <>
                {" · "}
                <a href={contributionHref(c.id)}>{`#${c.id} ${c.title}`}</a>
              </>
            )}
          </div>
        </div>
        {!b.current && (
          <BranchActions
            name={b.name}
            own={own}
            canSwitch={canSwitch}
            merging={running || waiting}
            keep={b.name === "main" || working.has(b.name)}
          />
        )}
      </div>
    );
  };
  const branches = items.filter((b) => !agents(b.name) || b.current);
  const sessions = items.filter((b) => !b.current && agents(b.name) && recent(b));
  const older = items.filter((b) => !b.current && agents(b.name) && !recent(b));
  return (
    <StudioFrame tab="branches" title="Branches">
      {running && <Live every={2000} />}
      <div className="pagehead">
        <h2 className="title">Branches</h2>
        <span className="muted">{`${items.length} branch${items.length === 1 ? "" : "es"}`}</span>
        <span className="right actions">
          <NewBranchButton from={own} canSwitch={canSwitch} />
        </span>
      </div>
      {run && (
        <div className="box">
          <div className="boxhead">
            <b className="mono">{`tcm merge ${run.theirs}`}</b>
            <span className={`muted${run.code && !(waiting && run.into === own) ? " bad" : ""}`}>
              {running
                ? `into ${run.into}, running, ${Math.round((Date.now() - run.started) / 1000)}s`
                : run.code && waiting && run.into === own
                  ? `into ${run.into}, waiting apart, ${ago(run.ended!)}`
                  : run.code
                    ? `didn't merge, ${ago(run.ended!)}`
                    : `into ${run.into}, ${ago(run.ended!)}`}
            </span>
          </div>
          <pre className="code applog testlog">{run.lines.slice(-40).join("\n") || "…"}</pre>
        </div>
      )}
      {waiting && (
        <div className="notice">
          {`A merge into ${own} waits apart, not finished: `}
          <a href={at("/fix")}>Needs fixing</a> says what's left to settle.
        </div>
      )}
      <div className="box">{branches.map(row)}</div>
      {(sessions.length > 0 || older.length > 0) && (
        <div className="box">
          <div className="boxhead">
            <b>Agents' sessions</b>
            <span className="muted">each session's work, on a branch of its own</span>
          </div>
          {sessions.map(row)}
          {older.length > 0 && (
            <div className="relline">
              <Fold
                head={
                  <span className="muted">{`${older.length} older session${older.length === 1 ? "" : "s"}, not changed in a day`}</span>
                }
              >
                {older.map(row)}
              </Fold>
            </div>
          )}
        </div>
      )}
    </StudioFrame>
  );
}

uses

used by