Skip to content

Git

The desktop app's git pickers and git bar. A branch and pull request menu, the where menu for a new session, a folder picker for a remote host with clone on send, the checkout's live status with sync, and the repository import, on a GitHost.

zidane-ui/git holds the desktop app's git pickers. BranchMenu switches between pull requests and branches, and starts a branch or a worktree. WherePicker chooses where a new session runs: this computer, a server or a shared machine, in the folder, a worktree or a sandbox. RemoteFolderPicker picks a folder on a remote host. GitBar shows where the checkout stands under a composer, and useImportRepo clones a repository. A GitHost provides the data and runs the actions. The previews below run on a fixture host.

WherePicker: a worktree on this computer
WhereMenu: places, isolation and the branch
Empty uses the suggested name.
RemoteFolderPicker: no folder picked yet
RemoteFolderBrowser: a server's workspace roots
BranchMenu: opens from the branch

Mount it#

TSX
import { createServerClient, createServerGitHost } from 'zidane-ui/adapters'
import { GitProvider, RemoteFolderPicker } from 'zidane-ui/git'

const client = createServerClient({ url, token })
const git = createServerGitHost({ client })

export function FolderStep({ folder, setFolder }) {
  return (
    <GitProvider host={git}>
      <RemoteFolderPicker hostName="box" folder={folder} onChange={setFolder} />
    </GitProvider>
  )
}

AppHostProvider (zidane-ui/app) puts host.git in a GitProvider for you. WherePicker reads no host: it shows the hosts, the environment kinds and the draft you pass.

The where menu#

WherePicker lists the places first: this computer, the saved servers and the machines shared with the draft's host. Then it lists the isolation kinds of the picked place. A kind that takes a branch (a worktree) adds a branch field. An empty field uses the suggested name. WhereMenu is the same menu without the pill, for a surface of your own.

The draft keeps only an explicit pick. resolveDraftEnvironment(descriptors, draft) turns it into the environment a session is created with: the pick while it is usable, else the default kind, else Local. withMachines(descriptors) orders a host's kinds before its machines' kinds and marks an offline machine's kinds unavailable. GitHost.environments lists the kinds when the host can. useEnvironmentChoices(cwd, hostId) reads them for a draft, withMachines applied, and reads them again on GitHost.subscribeEnvironments (the desktop app: a settings write or the window's focus; zidane-server: settings:changed and a reconnect).

The folder picker#

RemoteFolderPicker is a pill and its menu. RemoteFolderBrowser is the menu alone, for a surface of your own. The menu lists the roots, the folder's path (each step opens it), its folders and .., then Use. Type in its field to filter the folder it shows. Enter opens the one match. A text that starts with / is a path, and Enter opens it. When nothing matches, Enter searches the folders below by name (GitHost.searchFolders). New folder creates one in the folder shown (GitHost.createFolder) while the listing allows it (listing.allows.createFolders). A shared machine (machineId) gets the filter and the path only. A new session's composer on a server shows ServerScopeToggle (zidane-ui/chat/composer) instead, the project menu of this computer over the server's folders, with this browser behind "Select directory…".

Clone on send#

A new session on a server can start from a project on this computer. While no folder on the server is picked, useDraftTarget reads the project's origin (GitHost.originUrl) and lists the server's first root. resolveRemoteClone(origin, root) (zidane-ui/git/headless) says what a send would clone:

stateWhenThe pill shows
cloneNo folder of the repository's name in the first rootwebheim · will clone
existsA folder of that name is therewebheim
offThe server takes no clones (listing.allows.clone is false)Default folder; the clone row shows cloning off, greyed out

ServerScopeToggle shows it: pass draft.remoteClone and draft.cloning as clone and cloning. The clone is the menu's first row, picked while no folder is. A listing without allows gets no clone row. createServerGitHost and the desktop app fill allows from the hello's clone and createFolders flags, or, on an older server that sends neither, from the methods its hello lists; allows.searchFolders says whether the browser offers its search. On send, useSendPrompt clones first (GitHost.cloneOnServer) into <first root>/<repository name>, then creates the session there. A folder that already holds the same repository is used as it is. With cloning off, nothing is cloned and the session starts in the first root. When the clone fails, no session is made: the error shows on the draft and the composer keeps the text.

The branch menu#

BranchMenu opens from a control you pass as anchorRef. Pull requests waiting on your review come first, then yours, then the others, then the local branches. Each section shows a few rows until you expand it or type. Typing filters the loaded rows at once and asks the host to search every open pull request. A branch with an open pull request shows once, under it. A typed name that matches nothing can start a branch, or a worktree when you pass worktree.

A branch that another worktree has checked out is refused. The host names the worktree and the chats there. resolveChat picks the chat to offer and onOpenChat opens it.

The git bar#

GitBar is one quiet line under a composer: the branch (it opens BranchMenu, or the switcher you pass as BranchMenu), the commits to push and pull (SyncChip: a click syncs, or publishes a branch with no remote copy, after a confirmation confirmSync can turn off), the branch's open pull request, and the uncommitted changes (onShowChanges). In a narrow composer the extras step aside, the pull request first. It slides away for a chat without a folder and outside a git checkout, and holds its place, faded, while a folder it has not seen is read.

useGitBar(cwd, sessionId) is its data: status refreshed when the refs move (subscribeRefs), when the session's work lands (subscribeActivity), on focus and on a slow timer while the window has focus; branchPullRequest followed per branch. Facts are kept per folder, so another chat in the same folder shows them at once. syncAction, syncSummary and syncTooltip (zidane-ui/git/headless) are the chip's wording.

TSX
<GitBar cwd={folder} sessionId={id} running={running} onShowChanges={() => showDiffs()} />
GitBar: one commit to push, the branch's pull request, uncommitted changes
GitBar: a branch with no remote copy yet, to publish
GitBar while the agent works: no switching, no syncing
ImportRepoDialog: paste zidane-ai/zidane

Import repository

Paste a GitHub or git URL, or owner/repo.

Clone into/Users/me/src

Import a repository#

useImportRepo() opens ImportRepoDialog on the modal layer and resolves with the cloned folder, or null. The preview under the field is the host's own parse (clone.parse), so it is what the host will clone. Once the field names a GitHub repository and holds still, a card shows what it is (clone.info): its preview image, the owner's avatar, stars, language, the branch a clone checks out and the description, as the skill browser shows a source. useImportRepo({ hostId }) clones on that saved server instead (cloneOnServer, into its first root), with the same card. The progress comes from clone.onProgress; Stop or Esc calls clone.cancel. pickFolder adds Change… for the parent folder. Without GitHost.clone it opens nothing: hide the control that calls it.

Pick a host#

HostFromOffers
createServerGitHost({ client })zidane-ui/adaptersRemote folders (fs.listDirs, machines.listDirs), environment kinds (environments.list), worktree moves (sessions.moveEnvironment), and the checkout in a folder of the server's workspace roots: branches, pull requests, status and the branch's pull request (git.branches, git.pullRequests, git.status, git.branchPullRequest), switching, new branches, sync and the repository import (git.checkout, git.createBranch, git.sync, git.clone with its progress and git.cloneCancel). It also creates and searches the server's folders (fs.mkdir, fs.searchDirs), and fills listing.allows from the hello's createFolders and clone. Cloning, new folders and search need a chat token, the other writes an admin one. It leaves out originUrl and cloneOnServer: a browser has no project of its own to clone. subscribeRefs follows the server's watch of the checkout (git.watch, git:refs, taken again on each new connection). Each member is offered when the connection may call its method (build the host after the handshake).
createFixtureGitHost(options)zidane-ui/git/testingEverything, in memory, for demos and tests
Your ownzidane-ui/git/headlessAny subset of the members below
MemberGives
branches(cwd), checkout(cwd, target, sessionId)The branch menu. Without both, it renders nothing.
pullRequests({ cwd, search, fresh })The pull request sections. { kind: 'unavailable' } shows a note; { kind: 'rate-limited', retryAt } says when it resumes.
createBranch(cwd, name, sessionId)"Start branch" for a typed name
startWorktree(sessionId, branch)"Start a worktree" and "Start as worktree"
subscribeRefs(cwd, onChange)An open menu refreshes when the refs move
listFolders({ hostId, machineId, path })The remote folder picker. listing.allows says whether New folder and clone on send apply there
createFolder({ hostId, parent, name })New folder in the remote folder picker
searchFolders({ hostId, query, path })The remote folder picker's search of the folders below
originUrl(cwd)Clone on send: the origin of the local project
cloneOnServer(hostId, input, parent)Clone on send: the clone on the server
environments({ cwd, hostId })The kinds the where menu shows
subscribeEnvironments({ cwd, hostId }, onChange)The kinds are read again when they may have changed
status(cwd)Where the checkout stands (branch, ahead and behind, changes)
pickFolder()A native folder dialog (the desktop app); Change… in the import dialog
subscribeActivity(sessionId, onChange)The git bar refreshes when the session's tool calls and terminal commands finish
branchPullRequest(cwd, branch, fresh)The git bar's pull request
sync(cwd, sessionId)The git bar's Sync and Publish. A refusal comes back as { ok: false, error, conflicts? }
clone (parse, defaultParent, info, clone, onProgress, cancel)The repository import, and its card (info)

Errors come back as values: { code: 'failed', message } shows the host's words, { code: 'unavailable' } the gitUnavailable label.

Test your host#

TypeScript
import { describeGitHostContract } from 'zidane-ui/git/testing/contract'

describeGitHostContract('my host', backend => makeHarness(backend), ['listFolders', 'environments'])
Entry pointHolds
zidane-ui/gitGitProvider, useGitHost, useGitLabels, BranchMenu, WherePicker, WhereMenu, RemoteFolderPicker, RemoteFolderBrowser, GitBar, SyncChip, useGitBar, ImportRepoDialog, useImportRepo, useEnvironmentChoices
zidane-ui/git/headlessGitHost, the branch menu's model (switcherSections, switcherCreates, prMatches, switcherCacheOf), the draft model (resolveDraftEnvironment, resolveDraftDescriptor, withMachines, environmentKindIcon), clone on send (resolveRemoteClone), the sync chip's wording (syncAction, syncSummary, syncTooltip), the labels
zidane-ui/git/testingcreateFixtureGitHost, demo branches, pull requests, kinds, folders, origins, the branches' pull requests and a repository to import
zidane-ui/git/testing/contractdescribeGitHostContract