Skip to content

Files

The desktop app's Files and Diffs tabs. A workspace tree beside the open files, with an editor, the working-tree diff, search, find and diagnostics, and the session's edits and the working tree as reviewable diffs, on a FilesHost.

zidane-ui/files holds the desktop app's Files and Diffs tabs. FilesTab shows a folder's tree beside its open files. A file opens in the viewer, or in the editor when the host can write, and a changed file opens on its working-tree diff. The tab searches file names and contents, finds in the file on screen, and shows project-tool and language-server findings. DiffsTab shows what changed: the session's edits and the working tree against HEAD, a file tree beside virtualised diffs you can comment on. A FilesHost provides the files, the git state and the editor preferences. The previews below run on a fixture host.

Files: the tree beside an open file
greeter
Files: a changed file opens on its diff, tree hidden
Files: no folder
No folder
Diffs: the working tree against HEAD
Diffs: a clean working tree

Mount it#

TSX
import { createServerClient, createServerFilesHost } from 'zidane-ui/adapters'
import { FilesProvider, FilesTab } from 'zidane-ui/files'

const client = createServerClient({ url, token })
const files = createServerFilesHost({ client, sessionOf: cwd => openSessionFor(cwd), storage: localStorage })

export function FilesPanel({ session }) {
  return (
    <FilesProvider host={files}>
      <FilesTab scope={{ root: session.cwd, cwd: session.cwd, session: session.id }} />
    </FilesProvider>
  )
}

AppHostProvider (zidane-ui/app) puts host.files in a FilesProvider for you. The tabs fill their parent (flex-1 min-h-0): give them a sized frame, like a side panel's slot.

What the app plugs in#

The tabs read data from the host, and take what only the app knows as props:

PropGives
scopeThe folder (cwd, or workspace when the session's files are elsewhere) and the session it is bound to
layoutWhere the open tabs, the active file, the tree toggles and its width live (FilesLayoutStore). Default: in memory, per mount. createMemoryFilesLayout(initial) makes one.
chatWhere "Add to chat" and review comments go (FilesChatTarget: mention(path), attach(name, text)). Without one they show disabled.
useEditsA hook giving the bound session's edit events: changed files in the tree, the Diffs tab's Session source
useKeyScopeA hook registering next, previous, close and find on the tab's root, for the app's key routing
revealRequest, onRevealTaken, takeRevealPositionA path handed over from elsewhere: a file opens at a position, a folder is revealed in the tree
onOpenExternal⌘/Ctrl-click on a tree row opens the path outside the panel
renderTabGlyphA file tab's glyph (default: its icon)
readOnlyNothing changes: no saves, no file actions (default false)
fileActionsThe tree's new file and folder, rename, move, copy, delete and drops, when the host has entries (default true)
workingTreeRequest, onWorkingTreeTakenDiffsTab: open on the working tree
createParseWorkerDiffsTab: the worker that parses working-tree patches (zidane-ui/code/editor/patch-parse.worker). Default: this thread.

Pick a host#

HostFromOffers
createServerFilesHost({ client, sessionOf, storage })zidane-ui/adaptersListing, reading and writing through a session's workspace (sessions.workspaceListFiles, workspaceReadFile, workspaceWriteFile, workspaceFileSize), its empty folders (sessions.workspaceEmptyDirs), the file actions (sessions.workspaceCreate, workspaceMove, workspaceCopy, workspaceRemove, an admin token; removal is for good; a drop is sent one file per call), folder changes from the session's tool results, editor preferences in storage. A write re-reads the file and refuses when its text moved. In a folder of the server's workspace roots: content search (files.search), git status and the working-tree diff (git.fileStatus, git.diff), project diagnostics (files.diagnostics, an admin token; files.diagnosticsInvalidate on a rerun, a refresh or a tool result, so the next run checks again), each offered when the connection may call it (build the host after the handshake). Media files play from the server's GET /zidane/media (mediaUrl, under a ticket the host registers on each connection: files.mediaTicket), relative to the page's origin: the web app the server serves. No language servers (they run per window, where the editor is).
createFixtureFilesHost(options)zidane-ui/files/testingA folder in memory, its git status and patches, for demos and tests
Your ownzidane-ui/files/headlessAny subset of the members below

The server host does not read the handshake: every member is there whatever the token may call. Gate it yourself, and leave files out of the AppHost unless client.can('sessions.workspaceListFiles'). Built without that check, a server or token without the workspace methods shows a Files tab whose every listing fails. Build your own remote app shows it with the other hosts.

MemberGives
list(cwd, { showIgnored })The tree
read(cwd, path)Open files (null: gone)
write(cwd, path, contents, expectedMtimeMs)Editing: ⌘S saves, refused with { ok: false, reason: 'changed' } when the file moved, which raises the conflict bar. Without it, files are read-only
entriesThe tree's file actions: New file and New folder (named in place), rename (F2), move (drag a row onto a folder; ⌥ copies), Cut / Copy / Paste (⌘X / ⌘C / ⌘V), Duplicate, Delete (⌘⌫, asked first: removal(cwd) says whether it goes to the trash or is gone for good), and files dropped from the file manager onto a folder or the editor (importFiles). Nothing is overwritten: a taken name is refused, a copy takes the next free name (a copy.ts). The open tabs follow a rename or a move
search(cwd, query, options)Search in file contents (without it, names only)
gitStatus(cwd)Changed marks, "Changed only", diff mode for changed files
workingTreeDiff(cwd, { path, context })The working-tree diffs: a changed file's diff, the Diffs tab's Working source
watch(scope, onChange)Refreshes when the folder may have changed (a tool result, the repository moving)
editorPrefsEditing, saving, indentation, wrapping, diagnostics and language-server preferences (createStorageEditorPrefs; useEditorPrefs() reads them)
diagnosticsProject-tool findings, the findings chip, counts in the Diffs file tree
lspLive findings while typing, hovers, completion, go to definition
system"Open in editor" and "Reveal in Finder" for local folders
mediaUrl(source)Images, video, audio and PDFs shown as themselves, before and after in Diffs

Test your host#

TypeScript
import { describeFilesHostContract } from 'zidane-ui/files/testing/contract'

describeFilesHostContract('my host', backend => makeHarness(backend), ['list', 'read', 'write'])
Entry pointHolds
zidane-ui/filesFilesProvider, useFilesHost, useEditorPrefs, useFilesLabels, FilesTab, DiffsTab, the code attachment names (attachmentName, parseCodeAttachmentName, fenceFor, codeAttachmentLabel: an attachmentLabel renderer slot that names a code or terminal excerpt by its source and range), terminal selections as attachments (serializeTerminalExcerpt, terminalRangeLabel, terminalRangeDetail)
zidane-ui/files/headlessFilesHost and its types, the editor preferences (parseEditor, DEFAULT_EDITOR, createStorageEditorPrefs), the view state (FilesScope, filesRootOf, FilesLayout, createMemoryFilesLayout, FilesChatTarget), matchRanges, mediaTypeOfPath, the entry helpers (droppedItems, droppedImportItems, movedPath, topLevelPaths, entryRefusalText), the labels
zidane-ui/files/testingcreateFixtureFilesHost, memoryEntryPort (an EntryPort over memory, for a fake backend), DEMO_FILES
zidane-ui/files/testing/contractdescribeFilesHostContract