dsh-code-review
English | 中文
dsh-code-review is a browser plugin for the DSH (DeepSeek Harness) web UI that turns git diff output in a conversation into a one-click code review page in its own browser tab — a two-column, human-friendly view (old file on the left, new file on the right, both with line numbers), free from the chat window's width/height limits.
The conversation itself stays untouched: when a git diff shows up, a small "open in new tab" button appears on the block (a markdown diff code block, or the bash tool row that produced the diff — even while collapsed). One click opens the review page.
When the conversation mentions a local .diff / .patch file path, the path automatically becomes a view link button — a path inside backticks gets an inline pill right after it, a bare-text path gets a right-aligned button row under the message. One click opens that file's code review page (the browser cannot read local files, so the plugin host half reads them — absolute paths only, .diff/.patch suffix only, ≤ 5 MB).
Compatibility
- dsh ≥ 0.1.2-alpha.4 — fully supported since 0.1.5. The web UI restructure (chat snapshot moved from
useSession((s) => s.chat) to the session-standard useChat hook; markdown code-block DOM wrapped in a bannerWrap layer) is handled on both detection paths: markdown diff blocks and collapsed bash tool rows. Verified against dsh 0.1.3-alpha.2 since 0.2.1.
- dsh 0.1.0-rc.x — still supported via the legacy snapshot path.
Features
- ⬅️➡️ Two-column side-by-side view — the old file on the left, the new file on the right, each with its own line numbers, exactly like a GitHub split review.
- 🟥🟩 Add/remove coloring — deleted lines get a red background, added lines green; a consecutive delete-then-add block is paired row-by-row into a single "changed" row (the GitHub alignment).
- 🔎 Inline character-level highlight — when only a few characters change on a line, exactly those characters get a stronger red/green background (common prefix/suffix trimmed).
- 💡 Basic syntax highlighting — keywords, strings, comments, and numbers are colored using the official shiki theme variables (language inferred from the file extension; unknown languages just skip syntax coloring).
- 🧷 Hunk-aware — every
@@ hunk header is kept as a separator row with its original section name; multiple files in one diff are split into per-file sections with a status badge (modified / new file / deleted / renamed / binary).
- 🗂️ File navigation sidebar — every file is listed on the left; click to jump.
- 📊 Stats bar —
+N −M · F files in a sticky top bar, with a "copy raw diff" button and a collapsible raw-text section at the bottom.
- 🎨 Theme-matched — the page inherits the DSH theme colors at open time, so light/dark mode stays consistent.
- 📜 Full-page scrolling — no height caps; thousands of lines scroll naturally, and long lines wrap.
- 💬 Inline comments — hover any line number and click to add a comment right on that line (like GitHub reviews). Comments accumulate with a per-line count badge; when done, submit them all at once — as Looks Good To Me ✓ or plain comments — and they are written into the DSH composer as
file:line — comment lines, ready to send to the agent.
- 🔗 One-click local diff paths — absolute
…/xxx.diff / …/xxx.patch paths mentioned in the conversation become link buttons: backtick-wrapped paths get an inline pill right after them, bare-text paths get a right-aligned button row under the message, and paths listed in bash tool output join the tool row's button row. Clicking opens that file's code review page; on read failure (missing / not a diff / over 5 MB) the button briefly turns red.
- 🌐 Bilingual UI — follows the DSH interface language (official locale service): a Chinese UI gets Chinese buttons and review page, everything else gets English.
Screenshots
A diff code block in the conversation: collapsed to its title row with the "open in new tab" and "expand" buttons
The standalone review page: two-column side-by-side view with line numbers, add/remove coloring, syntax and character-level highlighting, file navigation sidebar and stats bar
Inline comments: a comment draft on a line, comment-count badge on the gutter, and the submit panel with Looks Good To Me / plain-comment choices
Where the button appears
- Markdown code blocks in assistant messages — a
```diff / ```patch fence, or any code block whose content looks like a git diff (e.g. pasted inside a text fence). The block is collapsed by default (just the title row + buttons), with an "expand/collapse" toggle if you want to read the raw text inline.
- Bash tool rows — when the agent runs
git diff in a bash tool, the button is added to the tool row (visible even while the row is collapsed; detected from the conversation data, since a collapsed row has no output in the DOM). The terminal card in the details panel gets one too.
- Local diff paths (since 0.2.0) — absolute
.diff/.patch paths in assistant or user messages:
- backtick-wrapped paths (e.g.
`/Users/me/proj/patch.diff`) get an inline pill button right after the path;
- bare-text paths get a right-aligned button row under the message (one pill per path); paths inside reasoning blocks and code blocks are not captured (collapsed by default / have their own button mechanism);
- paths listed in bash tool output join the tool row's button row.
Streaming output is handled gracefully: the button only appears once the content has been stable for ~1 second, so half-streamed diffs never produce a broken page.
When a path button is clicked, the plugin's host half (a route registered on webServer) reads the local file (absolute paths only, ~ expansion, .diff/.patch suffix, ≤ 5 MB, no CORS), and the browser half renders it into the standalone review page; on read failure the button briefly turns red. The route registration requires a dsh web restart (the host half registers it at startup); browser-half changes only need a hard refresh.
Install (30 seconds)
dsh plugin --profile web add @yangzhe1991/dsh-code-review
Restart the Web GUI (Ctrl+C the dsh web process and run it again) and refresh the browser tab. (dsh plugin runs pnpm add and auto-appends the bundle to dsh.profile.bundles.)
For local development, install from a path instead — the link: spec keeps a live symlink so edits take effect after a rebuild + restart:
dsh plugin --profile web add link:/path/to/@yangzhe1991/dsh-code-review
How it works
The plugin registers a root-scope shell.overlay seat that renders nothing and watches the whole document with a MutationObserver. For every .md-code-block and [data-terminal] element it runs a stability-debounced check; candidates get a small button appended to the block's banner/header (never replacing official nodes, so React's reconciliation stays safe). Collapsed bash tool rows are handled from the data layer: a session-scope seat subscribes to the conversation snapshot and records settled bash results that look like a diff.
Local path chips are split across the two layers: message rows (assistant/user) are probed by the same scanner — backtick-wrapped paths get an inline button inserted right after the code element, bare-text paths get a button row appended to the row; paths in bash tool results go through the data layer (same pipeline as the diff buttons). Text nodes are only read, never split (splitting React-managed text nodes would break streaming rendering), so bare-text paths get row-level buttons instead of in-place edits. On click, the browser half first opens a placeholder tab synchronously (beats popup blockers), then fetches GET /dsh-code-review/diff?path=… from the host route and navigates the tab to the standalone review page.
Clicking the button synchronously builds a self-contained HTML page (inline styles + inherited theme variables + the two-column content, all HTML-escaped) and opens it via a Blob URL — no server route needed, and the synchronous window.open inside the click gesture is not popup-blocked. The parser is a pure module (src/client/diff-parse.ts, unit-tested in test/parse.test.mjs) and supports: multi-file diffs, hunks with line-number tracking, delete/add pairing into changed rows (uneven counts degrade to single-side rows), \ No newline at end of file markers, new/deleted/renamed/binary files, bare patches without a diff --git header, and commit-message prefixes (git show output).
Development
npm install
npm run build # esbuild → lib/index.js (host half) + lib/client.js (browser half)
node test/parse.test.mjs # parser + standalone HTML unit tests
node test/path-detect.test.mjs # local path-detection regex unit tests
node test/node-route.test.mjs # host-half route (read/validation/error codes) tests
node test/client-smoke.test.mjs # bundle smoke test (mock __ModuleLoader__)
Browser-side changes only need a rebuild; the host serves lib/ live, so a hard refresh (⌘+Shift+R) picks them up. Profile-level changes (package name/bundles) require restarting dsh web.