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.
Mount it#
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:
| Prop | Gives |
|---|---|
scope | The folder (cwd, or workspace when the session's files are elsewhere) and the session it is bound to |
layout | Where the open tabs, the active file, the tree toggles and its width live (FilesLayoutStore). Default: in memory, per mount. createMemoryFilesLayout(initial) makes one. |
chat | Where "Add to chat" and review comments go (FilesChatTarget: mention(path), attach(name, text)). Without one they show disabled. |
useEdits | A hook giving the bound session's edit events: changed files in the tree, the Diffs tab's Session source |
useKeyScope | A hook registering next, previous, close and find on the tab's root, for the app's key routing |
revealRequest, onRevealTaken, takeRevealPosition | A 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 |
renderTabGlyph | A file tab's glyph (default: its icon) |
readOnly | Nothing changes: no saves, no file actions (default false) |
fileActions | The tree's new file and folder, rename, move, copy, delete and drops, when the host has entries (default true) |
workingTreeRequest, onWorkingTreeTaken | DiffsTab: open on the working tree |
createParseWorker | DiffsTab: the worker that parses working-tree patches (zidane-ui/code/editor/patch-parse.worker). Default: this thread. |
Pick a host#
| Host | From | Offers |
|---|---|---|
createServerFilesHost({ client, sessionOf, storage }) | zidane-ui/adapters | Listing, 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/testing | A folder in memory, its git status and patches, for demos and tests |
| Your own | zidane-ui/files/headless | Any 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.
| Member | Gives |
|---|---|
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 |
entries | The 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) |
editorPrefs | Editing, saving, indentation, wrapping, diagnostics and language-server preferences (createStorageEditorPrefs; useEditorPrefs() reads them) |
diagnostics | Project-tool findings, the findings chip, counts in the Diffs file tree |
lsp | Live 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#
import { describeFilesHostContract } from 'zidane-ui/files/testing/contract'
describeFilesHostContract('my host', backend => makeHarness(backend), ['list', 'read', 'write'])| Entry point | Holds |
|---|---|
zidane-ui/files | FilesProvider, 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/headless | FilesHost 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/testing | createFixtureFilesHost, memoryEntryPort (an EntryPort over memory, for a fake backend), DEMO_FILES |
zidane-ui/files/testing/contract | describeFilesHostContract |