dsh-files-git
August 20, 2026 · View on GitHub
English | 简体中文
A Files & Changes panel plugin for the DSH Web UI: an all-in-one file browser / search / preview / editor plus Git operations (status, staging, commit, pull, push, branches, history, diffs, …) for the current session's workspace directory — rendered as a centered modal panel, so everyday file and version-control work happens without ever leaving the WebUI.
- Zero runtime dependencies (the host half uses Node built-ins only) — installs offline;
- UI text follows the DSH language setting, with built-in Chinese / English;
- Security-conscientious: RPC is loopback-trusted only, file browsing is confined to the workspace root.
The panel: file tree, search, and Markdown preview
Table of Contents
- Features
- Requirements
- Installation (standard flow)
- Quick Start
- Configuration
- Internationalization (i18n)
- Security Model
- Development
- Uninstall
- FAQ
- Contributing
- License
Features
File browsing & preview
- Lazy file tree: expand/collapse directories with file sizes and Git status badges (staged / modified / untracked / conflicted);
- Workspace-wide search: git repositories are indexed via
git ls-files --cached --others --exclude-standard(respects.gitignore); non-git directories fall back to filtering the current directory. Flat result list, double-click to preview (deep nested files included); - Search history: keywords are recorded after a 1s pause (or Enter / blur); reopen from the dropdown and clear at will;
- Content preview: plain-text files render in full (no truncation under 512KB); a Preview/Source toggle renders Markdown and syntax-highlights code (chunked async rendering for large files — never blocks the main thread); files open in Source view by default;
- In-panel editing: a CodeMirror 6 editor with automatic language matching for 16 languages (js/ts/jsx/tsx/vue/json/md/py/java/go/rs/c/cpp/php/sql/yaml/scss, …), following the DSH light/dark theme; saving writes back to disk and refreshes Git status; the editor core and language packs lazy-load from CDN (esm.sh first, jsdelivr fallback, with retries) — zero bundle-size cost;
- Quick actions: hover any entry to reveal in file explorer, copy path, or copy name; breadcrumb segments are clickable, with a "Open directory" button on the far right.
Image files preview inline, with open-in-browser / open-in-editor actions
Git operations
- Info bar: current branch, ahead/behind counts as high-contrast capsules (⬆ push / ⬇ pull);
- Action bar: pull (optional
--rebase), push, fetch, force-push (--force-with-lease, double-confirmed); each action shows a live output module — progress while running, green on success, red on failure — kept until dismissed; - Branch selector: all branches grouped current → local → remote (searchable); checkout / merge / create-from / update / rename;
- Change list: conflicted / staged / unstaged / untracked groups rendered as
a directory tree (aggregated counts; whole-directory stage / unstage /
untrack / track / ignore); untracked directories auto-expand into real file
lists; per-file stage / unstage / add-to-
.gitignore; type-colored status badges; View all diffs toggles between unstaged / staged; - Diff preview: click a change row to expand its diff on the right (3:7 split, draggable divider, double-click to reset), word-level highlight (LCS) + line coloring with graceful degradation for huge diffs; untracked files render as all-green new-file diffs;
- Commit: commit selected / commit all, optional
--amend,Ctrl+Entershortcut; - History: collapsed by default into an IDEA-style bar; expand into a scrollable list; click a commit for the detail view (changed files + diff, 3:7 split); row menu offers View changes / Revert commit / Reset to commit (soft/hard — dangerous ops double-confirmed);
- Auto refresh: silent 5s polling (while the page is visible and idle), snapshot-deduped, external changes never interrupt your current operation.
Panel experience
- Modal panel: same interaction as the settings dialog; header button for one-click fullscreen (fullscreen by default, configurable and persisted);
- Suspend: the "suspend" (↑) button — or moving the mouse out — slides the panel out of view, leaving a frosted handle at the top; hover to instantly restore the full state (tab, preview, scroll, search, git status). Only close (× / Esc) truly unmounts. The panel belongs to exactly one workspace at a time;
- Artifact links → panel preview (opt-in, off by default): when enabled, clicking produced-file chips / file mentions in the conversation previews them inside the panel instead of invoking a local app — in-workspace files navigate to their directory; out-of-workspace files preview read-only by absolute path (512KB cap);
- Frosted-glass visuals: panel at 86% base color +
blur(30px), popovers (search history, branch list, context menus) frosted too; theme-adaptive text colors stay legible in light/dark skins; - Focus trap & scroll lock: Tab cycles inside the panel; wheel events don't leak through; while a popover is open only it scrolls;
- Dual smart entry points: once a session is engaged, the button sits in the header bar (left of "Session log"); on a brand-new workspace with no conversation yet (blank session) it automatically switches to a ghost button at the right end of the row above the composer — strictly synchronized with the header's visibility, never both at once.
Performance
- Input isolation: typing a commit message re-renders only the commit box;
- Stable references +
React.memo:useGitresults, change rows, file rows, history blocks, diff cards and the panel shell compare by content — polling or a single checkbox rebuilds only the affected rows; - Lazy loading: the CodeMirror core + language packs fetch from CDN on first "Edit" click;
- Highlight/Markdown results memoized per preview content — dragging dividers never re-runs them;
- Scroll isolation via
contain: contenton list/preview/diff containers; - Host-side parallelized directory listing + browser RPC auto-retry (2 retries, 20s timeout).
Requirements
| Dependency | Notes |
|---|---|
| DSH | dsh web (Web UI mode, --profile web) |
| Git | A git on PATH (or an absolute path via configuration); 2.30+ recommended (--force-with-lease / restore --staged) |
| Browser | A modern Chromium / Firefox / Safari (the panel uses backdrop-filter, color-mix) |
| Network (optional) | Only the first use of in-panel editing needs esm.sh / jsdelivr for CodeMirror; offline keeps the "open in editor" fallback |
Installation (standard flow)
1. Get the plugin
# Option 1: clone this repository
git clone https://github.com/leanderli/dsh-files-git.git
# Put it under the dsh plugins directory (any stable path works)
mkdir -p ~/.dsh/plugins
mv dsh-files-git ~/.dsh/plugins/
On Windows
~is%USERPROFILE%(e.g.C:\Users\you\.dsh\plugins\dsh-files-git). The path is an example — any directory that will not be deleted or moved works (see the warning below).
2. Register it into the web profile
dsh plugin --profile web add ~/.dsh/plugins/dsh-files-git
This appends dsh-files-git to dsh.profile.bundles; its cordis.patch.yml
(bundle patch) mounts the files-git plugin row on the next boot.
3. Restart dsh web
dsh web
4. Verify
- Open the WebUI and enter (or create) any workspace session;
- Engaged session → a Files & Changes button appears in the header bar, left of Session log;
- Brand-new workspace without a conversation → a ghost button appears at the right end of the row above the composer;
- Open the panel — the Files tab should list the current workspace; the Git tab activates inside git repositories.
⚠️ Do not delete or move the plugin source directory after installation: the profile stores a symlink (
link:absolute-path); a missing source directory breaksdsh webstartup. To uninstall usedsh plugin --profile web remove dsh-files-git— never just delete the directory.
Quick Start
- Open the panel: click Files & Changes (entry points above). The panel targets the current session's workspace directory automatically (no manual path) and follows session/workspace switches.
- Files tab: click directories to expand; click files to preview; the
preview header toggles Preview/Source, Edit (CodeMirror) and
Open in editor (system default app);
.gitis hidden by default. - Git tab:
- Stage: check change files (or directory rows / select-all) → Commit all / Commit selected;
- Pull / Push / Fetch / Rebase: one click on the action bar, live output;
- Diff: click a change row to expand; drag the divider;
- History: click the collapsed bar → click a commit for details → row menu for revert/reset.
- Suspend: click "↑" or move the mouse out to slide the panel away; hover the top handle to restore it instantly.
Without a workspace the panel shows "no current workspace"; the Git tab hides itself in non-git directories.
Configuration
Works out of the box. To override, patch the files-git row in the profile's
cordis.patch.yml:
- id: files-git
config:
gitPath: /usr/bin/git # absolute path to git (default: auto-resolved from PATH, skipping .git-ai dirs)
defaultRoot: /path/to/repo # fallback when the client sends no repo (rarely needed)
In-panel settings (⚙ settings tab, persisted in browser localStorage):
| Setting | Default | Description |
|---|---|---|
| Open fullscreen by default | Fullscreen | Default panel size on open |
| Clicking produced files / file links | Off | Preview conversation artifacts inside the panel instead of opening the system app |
The ⚙ settings tab, with the branch selector popover open
Internationalization (i18n)
- UI text follows the DSH language setting (Settings → General → Language); Chinese / English are built in and switch instantly (no refresh needed);
- Known boundary: host-side RPC error messages stay in Chinese (the host cannot sense the browser language); git command output is English / locale-mixed.
Security Model
- Loopback fence: the
/git-apichannel registers withauthority: "loopback"behind the same browser trust fence as/api— only loopback origins (127.0.0.1 / localhost) may call it; LAN-origin requests are rejected; - Workspace confinement: file browsing (
list/read/ the relative-path branch ofwrite) is confined to the workspace root —resolve+realpathdouble containment checks reject.., absolute paths and symlink escapes; - No shell injection: every git command runs via an argv array (never string-joined), so messages/paths cannot inject shell syntax;
- Fail fast:
GIT_TERMINAL_PROMPT=0makes credential prompts fail fast instead of hanging; the Windows credential manager (GCM) still works; - Explicitly trusted exceptions:
readPath(absolute-path read-only, 512KB cap) and theabsbranch ofwriteare not workspace-constrained — they exist solely for files the panel has previewed/edited; the browser only ever sends back real paths it just read. See SECURITY.md.
Development
Architecture
- Host half (
lib/index.js): registersPOST /git-api/*RPC endpoints over the sharedconnectionchannel; runs git commands and file browsing; zero runtime dependencies; - Browser half (
lib/client.js): a self-contained React panel registered intoconversation.session.header.utilities(header button),conversation.input.dock(blank-session button) andshell.overlay(modal layer).
Source layout
The dsh client module loader accepts exactly one bundle per plugin and its
require cannot resolve relative paths — so the source lives as readable
fragments under lib/src/ (sharing one factory scope), stitched by the build
script:
lib/
client.js ← shipped bundle (do not edit by hand; generated by build.cjs)
build.cjs ← assembler: node build.cjs (re-split current bundle + assemble)
node build.cjs --rebuild (assemble from src/ only)
src/ ← source fragments (shared factory scope, dependency order)
styles.js CSS (DSH-token driven)
icons.js SVG icons
store.js overlay/hidden global state
i18n.js zh/en dictionaries (following the DSH locale)
utils.js RPC + shared UI atoms (btn/chip/lbtn/link/fmtSize)
triggers.js header button + blank-session ghost trigger
hooks.js useGit (state/actions/polling)
diffutil.js diff parsing + LCS word-level highlight
editor.js CodeMirror 6 editor (CDN lazy-load: core + 16 language packs + one-dark)
ui.js memoized sub-views (change rows/history/diff panes)
gitview.js branch selector/confirm dialog/Git tab
filebrowser.js file browser/search/preview/settings
overlay.js FilePanelBody + FilePanelOverlay (suspend/auto-suspend)
index.js apply()/inject entry
Local workflow
git clone https://github.com/leanderli/dsh-files-git.git
cd dsh-files-git
# 1. Register into your local web profile via link (once)
dsh plugin --profile web add "$PWD"
# 2. Hack on lib/src/ fragments
# 3. Rebuild the shipped bundle
node lib/build.cjs --rebuild
# 4. Restart dsh web (client bundles load at startup)
dsh web
The lib/client.js bundle contains #region section comments for direct reading.
⚠️ Slot choice: the panel must not register into the
detailsslot — that is a singleton slot owned by the built-indsh-client-ui-conversationtool detail panel; a second entry throws and takes the whole Web client down. The panel usesshell.overlay(a list slot that allows multiple entries).
Uninstall
dsh plugin --profile web remove dsh-files-git # official way; never just delete the source directory
FAQ
Q: The button does nothing / the Web UI fails to start?
Check whether the plugin source directory was moved or deleted (the profile
holds a symlink) and whether a singleton slot like details was used; roll
back with dsh plugin --profile web remove dsh-files-git before debugging.
Q: Git commands randomly fail on Windows (exit code 0xC0000142)?
A known Windows DLL-initialization hiccup under heavy git-process concurrency;
the host already retries once automatically. If it persists, pin gitPath
via configuration.
Q: Git operations are rejected when the WebUI is accessed from another device on the LAN?
Expected — /git-api trusts loopback origins only. Access from the local
machine, or tunnel (e.g. SSH port-forward) to loopback for remote use.
Q: The Edit button is disabled / loading fails? In-panel editing lazy-loads CodeMirror from CDN (esm.sh / jsdelivr) and is unavailable offline; the "open in editor" fallback remains. Binary files and read-truncated (>512KB) files are not editable.
Contributing
Issues and PRs are welcome! See CONTRIBUTING.md. For security vulnerabilities please follow SECURITY.md instead of opening a public issue.
License
MIT © leanderli