diff --git a/README.md b/README.md index 04418161..661a6ac4 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,124 @@ -# Git Diff Review - -Git Diff Review is a native macOS app for reviewing local Git changes before -they are committed. It gives a GitHub-style review surface for a working tree: -file navigation, staged/unstaged/untracked diffs, split or unified views, -viewed-file tracking, review notes, and inline comments stored locally. +
-The desktop app is built with Electron, Vue, and TypeScript. A Go backend reads -repository state through Git, serves a local HTTP API over a Unix socket, and -persists review sessions in SQLite. +Git Diff Review -## What It Does +# Git Diff Review -- opens any local Git repository; -- shows changed files with additions, deletions, status, and rename metadata; -- renders staged, unstaged, and untracked patches; -- supports split and unified diff modes; -- can hide whitespace-only diff noise; -- tracks viewed files per repository in local storage; -- creates local review sessions with rich-text summaries; -- stores inline review comments and timeline events in SQLite; -- runs as a packaged macOS app or as a local development stack. +**Review your local Git changes like a pull request — before you commit.** +**On your machine. Nothing leaves it.** -This is a local review tool. It does not publish comments to GitHub, create pull -requests, or modify the repository contents. +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +![Platform: macOS (Apple Silicon)](https://img.shields.io/badge/platform-macOS%20(Apple%20Silicon)-black) +[![Latest release](https://img.shields.io/github/v/release/oullin/git-diff?label=download)](https://github.com/oullin/git-diff/releases) -## Workspace Layout +![Git Diff Review reviewing a working tree](docs/images/hero.png) -| Path | Role | -| --------------------- | ------------------------------------------------------------ | -| `packages/ui` | Electron/Vue desktop app and renderer UI. | -| `packages/bridge` | TypeScript HTTP client used by Electron. | -| `packages/domain` | Shared DTOs + framework-agnostic diff logic, consumed by `ui` and `bridge`. | -| `packages/api` | Go backend that reads Git diffs and stores reviews. | -| `packages/tools` | Turbo cache wrapper and unsigned macOS release helper. | +
-## Requirements +Git Diff Review is a native macOS app that gives you a full GitHub-style review +surface for your **local working tree**. Open any repository, walk every changed +file, read the diff in split or unified view, mark files as you review them, leave +yourself notes and inline comments — then commit with confidence. It never pushes +your code anywhere. -- macOS -- Git -- Go -- Node.js with Corepack -- pnpm `10.33.0` +## Why you need it -Enable pnpm through Corepack if needed: +You commit changes you never actually re-read. `git diff` in the terminal is +cramped and easy to skim past. Opening a real pull request just to review your own +work is heavyweight — and it forces your code onto a server before it's ready. -```sh -corepack enable -corepack prepare pnpm@10.33.0 --activate -``` +Git Diff Review gives you the **pull-request review experience, locally**: -## Quick Start +- Catch the debug log, the stray `TODO`, the half-finished refactor *before* it + lands in history. +- Review with the same muscle memory you already have from GitHub — file list, + viewed checkboxes, split diffs, inline comments. +- Keep everything on your machine. No cloud, no sign-up, no telemetry. -From the repository root: +## What you get -```sh -pnpm install -pnpm build -pnpm --filter ui start -``` +- **A real review surface.** Navigate changed files with status badges and + addition/deletion counts, jump between hunks, and search files across all your + repos at once. +- **Staged, unstaged, and untracked** — see every kind of change, with rename + detection and whitespace noise you can hide. +- **Split or unified diffs** with syntax highlighting, your choice per session. +- **Viewed-file tracking.** Tick files off as you go (`0/14 viewed`) so you never + lose your place in a large change. +- **Notes and inline comments.** Write a rich-text review summary and drop inline + comments on specific lines — stored locally and kept across sessions. +- **Branch, commit, and PR context.** Switch branches, pin a commit to review + against, and browse pull requests and history without leaving the app. +- **AI walkthroughs (optional).** Generate a guided walkthrough of a change to get + oriented fast. +- **Keyboard-driven.** `j`/`k` to navigate, `v` to mark viewed, `c` to comment, + `⌘↵` to submit — review without touching the mouse. +- **Native and local.** A fast Electron + Vue interface backed by a Go engine that + reads Git directly and stores your reviews in local SQLite. -For active development: +## See it in action -```sh -pnpm dev -``` +| | | +| --- | --- | +| ![File navigation and status badges](docs/images/file-navigation.png) | ![Split diff view](docs/images/split-view.png) | +| **Walk every changed file** with status, counts, and viewed tracking. | **Read split or unified diffs** with syntax highlighting. | +| ![Review notes and inline comments](docs/images/review-notes.png) | ![AI walkthrough](docs/images/walkthrough.png) | +| **Leave notes and inline comments** as you review. | **Generate an AI walkthrough** to get oriented fast. | -The dev command starts the Vite renderer, compiles Electron, starts the Go -backend, waits for the local socket API, and launches Electron. +> Screenshots live in [`docs/images/`](docs/images/). See that folder's README for +> what each capture should show. -## Backend CLI +## Install -The Electron app normally starts the backend automatically. To inspect the -backend directly: +> **macOS on Apple Silicon.** The app is currently in beta and distributed +> unsigned, so macOS Gatekeeper needs one extra click on first launch. -```sh -cd packages/api -go run ./cmd help -go run ./cmd serve-http --socket /tmp/git-diff.sock -``` +**Download the app** -To point the Electron app at an already-running backend, set -`API_BRIDGE_SOCKET` to the socket path before launching it. +1. Grab the latest `.dmg` from the + [Releases page](https://github.com/oullin/git-diff/releases). +2. Open it and drag **Git Diff Review** to Applications. +3. On first launch, **right-click the app → Open**, then confirm. (macOS blocks + unsigned apps on a normal double-click; right-click → Open only needs doing + once.) -## Development Commands +**Homebrew** _(planned)_ ```sh -pnpm build -pnpm test -pnpm lint -pnpm format:check -pnpm check +brew install --cask oullin/tap/git-diff ``` -Useful package-level commands: +## Your code stays yours -```sh -pnpm --filter ui build -pnpm --filter ui test -pnpm --filter ui dev -go test ./packages/api/... -``` - -## Review Data - -Review sessions, comments, events, and preferences are local. By default the Go -backend stores the review database under: +Git Diff Review reads your repository state through Git and serves a local API over +a Unix socket — it talks only to your machine. Review sessions, comments, events, +and preferences are stored in a local SQLite database at: ```text ~/Library/Application Support/git-diff/reviews.sqlite3 ``` -Viewed-file state is stored by the renderer per repository root. +It **does not** publish comments to GitHub, create pull requests, or modify your +repository contents. It's a review surface, nothing more. -## Release +## Build from source -Releases are built with [Electron Forge](https://www.electronforge.io/). Tag a -version to cut a release in CI: - -```sh -git tag v0.1.1 && git push origin v0.1.1 -``` - -The `Release` workflow builds the unsigned macOS arm64 `.dmg` + `.zip`, publishes -a GitHub Release, and opens a Homebrew cask bump PR against `oullin/homebrew-tap` -on final (non-prerelease) tags. See [docs/distribution.md](docs/distribution.md). - -To build the same artifacts locally: - -```sh -pnpm release:mac:unsigned # builds the Go API + renderer, then `electron-forge make` -``` +Git Diff Review is a pnpm + Turbo monorepo pairing an Electron/Vue/TypeScript +desktop app with a Go backend. -The Electron package is configured (in `packages/ui/forge.config.ts`) with: +| Path | Role | +| ----------------- | --------------------------------------------------------------------------- | +| `packages/ui` | Electron/Vue desktop app and renderer UI. | +| `packages/bridge` | TypeScript HTTP client used by Electron. | +| `packages/domain` | Shared DTOs + framework-agnostic diff logic, consumed by `ui` and `bridge`. | +| `packages/api` | Go backend that reads Git diffs and stores reviews. | +| `packages/tools` | Turbo cache wrapper and unsigned macOS release helper. | -- app id: `io.gocanto.git-diff` -- product name: `Git Diff Review` -- GitHub release target: `oullin/git-diff` +To set up the toolchain, run it locally, and learn the development commands, see +**[docs/development.md](docs/development.md)**. Releases are built with +[Electron Forge](https://www.electronforge.io/) — for tagging, packaging, signing, +and the release pipeline, see **[docs/distribution.md](docs/distribution.md)**. ## License -See [LICENSE](LICENSE). +[MIT](LICENSE) © Gus Canto diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..2e1eb780 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,123 @@ +# Development + +How to set up the toolchain, run Git Diff Review locally, and work in the +monorepo. For the product overview and install instructions, see the +[README](../README.md). For packaging and releases, see +[distribution.md](distribution.md). + +## Requirements + +- macOS +- Git +- Go +- Node.js with Corepack +- pnpm `10.33.0` + +Enable pnpm through Corepack if needed: + +```sh +corepack enable +corepack prepare pnpm@10.33.0 --activate +``` + +## Quick start + +From the repository root: + +```sh +pnpm install +pnpm build +pnpm --filter ui start +``` + +## Active development + +```sh +pnpm dev +``` + +The dev command starts the Vite renderer, compiles Electron, starts the Go +backend, waits for the local socket API, and launches Electron. + +## Backend CLI + +The Electron app normally starts the backend automatically. To inspect the +backend directly: + +```sh +cd packages/api +go run ./cmd help +go run ./cmd serve-http --socket /tmp/git-diff.sock +``` + +To point the Electron app at an already-running backend, set `API_BRIDGE_SOCKET` +to the socket path before launching it. + +## Development commands + +```sh +pnpm build +pnpm test +pnpm lint +pnpm format:check +pnpm check +``` + +Useful package-level commands: + +```sh +pnpm --filter ui build +pnpm --filter ui test +pnpm --filter ui dev +go test ./packages/api/... +``` + +## Formatting + +Format changed sources (Go + TS/Vue): + +```sh +pnpm format # or: make format +``` + +To format the entire repo, use `pnpm format-all` (`make format-all`). Run the +formatter after each change so commits stay clean. + +## Review data + +Review sessions, comments, events, and preferences are local. By default the Go +backend stores the review database under: + +```text +~/Library/Application Support/git-diff/reviews.sqlite3 +``` + +Viewed-file state is stored by the renderer per repository root. + +## Release + +Releases are built with [Electron Forge](https://www.electronforge.io/). Tag a +version to cut a release in CI: + +```sh +git tag v0.1.1 && git push origin v0.1.1 +``` + +The `Release` workflow builds the unsigned macOS arm64 `.dmg` + `.zip`, publishes +a GitHub Release, and opens a Homebrew cask bump PR against `oullin/homebrew-tap` +on final (non-prerelease) tags. + +To build the same artifacts locally: + +```sh +pnpm release:mac:unsigned # builds the Go API + renderer, then `electron-forge make` +``` + +The Electron package is configured (in `packages/ui/forge.config.ts`) with: + +- app id: `io.gocanto.git-diff` +- product name: `Git Diff Review` +- GitHub release target: `oullin/git-diff` + +See [distribution.md](distribution.md) for the full signing, notarization, and +Homebrew release flow. diff --git a/docs/images/README.md b/docs/images/README.md new file mode 100644 index 00000000..1311eaed --- /dev/null +++ b/docs/images/README.md @@ -0,0 +1,17 @@ +# Screenshots + +Images referenced by the [project README](../../README.md). Drop the real +captures here using the exact filenames below so the README renders them +automatically. Use PNG, retina resolution (capture at 2x), and a light UI theme +for consistency. + +| File | What it should show | +| --------------------- | ------------------------------------------------------------------------------ | +| `hero.png` | The main review window: file list, a split diff, branch/commit bar, toolbar. | +| `file-navigation.png` | The changed-files sidebar with status badges, +/- counts, and viewed checkboxes. | +| `split-view.png` | A diff in split (or unified) view with syntax highlighting. | +| `review-notes.png` | A review summary and/or an inline comment on a line. | +| `walkthrough.png` | The "Generate walkthrough" AI feature in use. | + +Until a file is added, its link in the README will show as a broken image — that +is expected and resolves the moment the PNG lands here. diff --git a/docs/images/file-navigation.png b/docs/images/file-navigation.png new file mode 100644 index 00000000..d3a618ac Binary files /dev/null and b/docs/images/file-navigation.png differ diff --git a/docs/images/hero.png b/docs/images/hero.png new file mode 100644 index 00000000..a7d40944 Binary files /dev/null and b/docs/images/hero.png differ diff --git a/docs/images/review-notes.png b/docs/images/review-notes.png new file mode 100644 index 00000000..a715ff2b Binary files /dev/null and b/docs/images/review-notes.png differ diff --git a/docs/images/split-view.png b/docs/images/split-view.png new file mode 100644 index 00000000..80961966 Binary files /dev/null and b/docs/images/split-view.png differ diff --git a/docs/images/walkthrough.png b/docs/images/walkthrough.png new file mode 100644 index 00000000..2ee7cf9c Binary files /dev/null and b/docs/images/walkthrough.png differ diff --git a/packages/ui/scripts/collect-release-artifacts.sh b/packages/ui/scripts/collect-release-artifacts.sh index 3a235e9c..b0cddf09 100755 --- a/packages/ui/scripts/collect-release-artifacts.sh +++ b/packages/ui/scripts/collect-release-artifacts.sh @@ -5,14 +5,15 @@ # # Run from the repo root. Usage: collect-release-artifacts.sh set -euo pipefail -shopt -s nullglob globstar version="${1:?usage: collect-release-artifacts.sh }" +# macOS GitHub runners ship bash 3.2, which lacks `globstar`. Use `find` instead +# of `**` so artifact discovery works regardless of the bash version. out="packages/ui/out/make" -dmgs=("$out"/**/*.dmg) -zips=("$out"/**/*.zip) -if [ "${#dmgs[@]}" -eq 0 ] || [ "${#zips[@]}" -eq 0 ]; then +src_dmg="$(set +o pipefail; find "$out" -type f -name '*.dmg' | head -n1)" +src_zip="$(set +o pipefail; find "$out" -type f -name '*.zip' | head -n1)" +if [ -z "$src_dmg" ] || [ -z "$src_zip" ]; then echo "Missing Forge artifacts under $out" find "$out" -type f || true exit 1 @@ -21,8 +22,8 @@ fi mkdir -p dist-release dmg="git-diff-review-${version}-arm64.dmg" zip="git-diff-review-${version}-arm64.zip" -cp "${dmgs[0]}" "dist-release/${dmg}" -cp "${zips[0]}" "dist-release/${zip}" +cp "$src_dmg" "dist-release/${dmg}" +cp "$src_zip" "dist-release/${zip}" cd dist-release shasum -a 256 "$dmg" "$zip" > SHASUMS256.txt diff --git a/packages/ui/src/components/diff/PierreDiffBody.vue b/packages/ui/src/components/diff/PierreDiffBody.vue index ef5cb10c..f0248c9f 100644 --- a/packages/ui/src/components/diff/PierreDiffBody.vue +++ b/packages/ui/src/components/diff/PierreDiffBody.vue @@ -100,7 +100,6 @@ function buildOptions(section: DiffSection): FileDiffOptions { // Pure-JS Shiki avoids wasm/worker bundling friction under Electron's // file:// origin; revisit for perf in a later pass. preferredHighlighter: 'shiki-js', - useCSSClasses: true, // Library renders its own "+" gutter affordance (single click or drag to // select a range); we only handle the resulting selection. enableGutterUtility: true, @@ -161,7 +160,12 @@ async function renderAll(): Promise { instances.set(section.id, fd); } - fd.render({ fileDiff: meta, fileContainer: el }); + // Pass the wrapper as `containerWrapper` (not `fileContainer`): the library + // then creates its own custom element inside `el`, whose + // constructor adopts the core stylesheet that resolves Shiki's per-token + // CSS variables to actual colors. Handing it a plain element as + // `fileContainer` skips that element, so tokens render uncoloured. + fd.render({ fileDiff: meta, containerWrapper: el }); } } diff --git a/packages/ui/src/style.css b/packages/ui/src/style.css index 4e2b2ccd..d3a571cd 100644 --- a/packages/ui/src/style.css +++ b/packages/ui/src/style.css @@ -631,171 +631,6 @@ color: var(--status-attention-fg); } - .diff-table { - font-family: var(--diffs-font-family); - font-size: var(--diffs-font-size); - line-height: var(--diffs-line-height); - background: var(--code-bg); - color: var(--foreground); - } - - .file-body { - overflow-x: auto; - } - - .diff-line { - display: grid; - width: 100%; - min-width: max-content; - grid-template-columns: 3.25rem 3.25rem minmax(0, 1fr) 2rem; - text-align: left; - border: 0; - } - - .diff-line code { - white-space: pre; - padding: 0 0.875rem; - color: var(--foreground); - background: transparent; - font-family: var(--font-code); - } - - .line-no { - color: var(--muted-foreground); - padding: 0 0.625rem; - text-align: right; - user-select: none; - font-variant-numeric: tabular-nums; - font-weight: 500; - } - - .diff-line:not(.diff-meta) .line-no:nth-child(1), - .diff-line:not(.diff-meta) .line-no:nth-child(2) { - border-right: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - } - - .diff-meta { - background: color-mix(in srgb, var(--muted-foreground) 8%, transparent); - color: var(--muted-foreground); - } - - .diff-meta code { - color: var(--muted-foreground); - } - - .diff-add { - background: var(--diff-added-bg); - } - - .diff-add .line-no { - background: var(--diff-added-bg-strong); - color: var(--diff-added); - } - - .diff-del { - background: var(--diff-removed-bg); - } - - .diff-del .line-no { - background: var(--diff-removed-bg-strong); - color: var(--diff-removed); - } - - .diff-line.diff-split-row { - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 2rem; - align-items: stretch; - min-width: 0; - } - - .diff-line.diff-split-row .split-side code { - white-space: pre-wrap; - overflow-wrap: anywhere; - } - - .diff-line.diff-split-meta { - grid-template-columns: minmax(0, 1fr); - min-width: 0; - } - - .split-side { - display: grid; - grid-template-columns: 3.25rem minmax(0, 1fr); - text-align: left; - border: 0; - background: transparent; - padding: 0; - color: inherit; - font: inherit; - cursor: pointer; - min-width: 0; - } - - .split-side:disabled { - cursor: default; - } - - .split-side + .split-side { - border-left: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - } - - .split-side .line-no { - border-right: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - } - - .split-side.side-del { - background: var(--diff-removed-bg); - } - - .split-side.side-del .line-no { - background: var(--diff-removed-bg-strong); - color: var(--diff-removed); - } - - .split-side.side-add { - background: var(--diff-added-bg); - } - - .split-side.side-add .line-no { - background: var(--diff-added-bg-strong); - color: var(--diff-added); - } - - .split-side.side-empty { - background: color-mix(in srgb, var(--muted-foreground) 6%, transparent); - } - - .split-side.side-empty .line-no { - background: transparent; - } - - .comment-icon { - align-self: center; - color: var(--muted-foreground); - opacity: 0; - transition: opacity 160ms ease; - } - - .diff-line:hover .comment-icon { - opacity: 1; - } - - .comment-row { - position: sticky; - left: 0; - width: min(100%, 720px); - margin: 0.5rem 1rem 0.75rem 5rem; - border: 1px solid var(--file-border); - border-left: 3px solid var(--accent-blue); - border-radius: 13px; - background: var(--file-bg); - padding: 0.75rem 0.875rem; - color: var(--foreground); - corner-shape: squircle; - -webkit-backdrop-filter: blur(24px) saturate(1.3); - backdrop-filter: blur(24px) saturate(1.3); - box-shadow: 0 16px 46px -34px rgb(0 0 0 / 0.32); - } - /* window chrome — squircle + glass treatment */ .window-frame { position: relative;