Skip to content
dsh.fish
Bundle

@yangzhe1991/dsh-code-review

DSH Web 插件:git diff 一键打开独立双列 Code Review 页,支持语法高亮、行内评论与中英双语;对话中的本地 .diff/.patch 路径自动变成一键查看按钮 | DSH web plugin: one-click standalone two-column code review page for git diff with syntax highlighting, inline comments & bilingual UI; local .diff/.patch paths become one-click view buttons

Source
yangzhe1991
stars
2 stars
License
MIT
Updated
Updated yesterday

Readme

# dsh-code-review

[English](README.md) | [中文](README.zh.md)

[![npm version](https://img.shields.io/npm/v/@yangzhe1991/dsh-code-review)](https://www.npmjs.com/package/@yangzhe1991/dsh-code-review)
[![npm downloads](https://img.shields.io/npm/dm/@yangzhe1991/dsh-code-review)](https://www.npmjs.com/package/@yangzhe1991/dsh-code-review)
[![license](https://img.shields.io/github/license/yangzhe1991/dsh-code-review)](LICENSE)
[![dsh-plugin](https://img.shields.io/badge/dsh-plugin-1e90ff)](https://github.com/topics/dsh-plugin)

**dsh-code-review** is a browser plugin for the [DSH (DeepSeek Harness)](https://github.com/deepseek-ai/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](https://raw.githubusercontent.com/yangzhe1991/dsh-code-review/main/1.jpg)

![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](https://raw.githubusercontent.com/yangzhe1991/dsh-code-review/main/2.jpg)

![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](https://raw.githubusercontent.com/yangzhe1991/dsh-code-review/main/3.jpg)

### Where the button appears

1. **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.
2. **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.
3. **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)

```sh
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:

```sh
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

```sh
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`.

Install

dsh plugin --profile web add github:yangzhe1991/dsh-code-review

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source