# Diff

> A change to read and review: one card per file (status, path, counts, collapse), hunks drawn unified or split in one table whose rows sit in chunks the browser skips until they near the screen, word-level marks, review threads between rows, and one island for the whole page.

```ts
import { Diff } from "@sdxc/ui/app";
```

Without script each `+` links to `commentHref`; mapping `@sdxc/ui/app/diff` runs one
DiffInteractions for every file, turning `+`, drags and copy into `DIFF_COMMENT_EVENT`.
Set `--diff-font-size`, `--diff-line-height` and the `--diff-add-*`/`--diff-del-*` tints.

## Examples

```tsx
<Diff labels={{ copied: t("copied") }}>
	<Diff.Toolbar view={view} unifiedHref="?diff=unified" splitHref="?diff=split" viewed={2} total={files.length} labels={toolbarLabels} />
	{files.map((file) => <Diff.File path={file.newPath} oldPath={file.oldPath} status={file.status} additions={file.additions} deletions={file.deletions} hunks={parseHunks(text, file)} language="ts" view={view} labels={fileLabels} />)}
</Diff>
```

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `labels` | `Labels` | The strings the page's island announces, so it reads in any language. |
| `children?` | `RemixNode` | A Diff.Toolbar and the page's Diff.File cards. |

Also accepts everything in `TagProps<"div">`.


### Diff.Toolbar

Renders the bar above the files: the unified/split switch as two GET links carried in the address, the app's own controls, then "n / N files viewed" with a bar that fills as the review burns down.

| Prop | Type | Description |
| --- | --- | --- |
| `view` | `"unified" \| "split"` | The view the page renders, which the switch marks current. |
| `unifiedHref` | `string` | This page's address with `?diff=unified`. |
| `splitHref` | `string` | This page's address with `?diff=split`. |
| `target?` | `string` | The frame the view links swap, such as `"main"`. |
| `viewed?` | `number` | Files marked viewed; with `total`, draws "n / N files viewed" and its bar. |
| `total?` | `number` | The files on the page; with `viewed`, draws the progress. |
| `labels` | `ToolbarLabels` | The toolbar's strings. |
| `children?` | `RemixNode` | Controls between the switch and the progress: "Jump to", a file filter. |

Also accepts everything in `TagProps<"div">`.


### Diff.File

Renders one file's card: a `<details>` whose summary carries the chevron, status, path, counts and five-block bar, with copy, `⋯` and "Viewed" beside it, then the lines or the note standing in for them.

| Prop | Type | Description |
| --- | --- | --- |
| `oldPath?` | `string` | The path on the old side, drawn as `old → new` when it differs from `path`. |
| `status` | `FileStatus` | Drawn as a toned letter (A, M, D, R, C) and read out through `labels.statuses`. |
| `additions` | `number` | Lines added, counted in the header and its five-block bar. |
| `deletions` | `number` | Lines deleted, counted in the header and its five-block bar. |
| `hunks?` | `readonly Hunk[]` | The file's hunks; omitted for a binary file, a dropped patch or a lazy one. |
| `binary?` | `boolean` | Git declared the file binary. |
| `tooLarge?` | `boolean` | The patch was dropped for size, so the file draws "Large diff not shown". |
| `collapsed?` | `boolean` | Renders closed, as a reader's collapse would. |
| `viewed?` | `boolean` | The reader marked the file viewed, which renders it closed. |
| `viewedControl?` | `RemixNode` | The "Viewed" checkbox, as a form that posts the mark (an `OptimisticToggle`). |
| `menu?` | `RemixNode` | A `⋯` menu trigger at the header's end. |
| `load?` | `RemixNode` | What a large file shows in place of its lines, such as a lazy frame's "Load diff"; given, a file past `largeThreshold` renders collapsed and draws no row. |
| `largeThreshold?` | `number` | Changed lines past which the file counts as large. @default DEFAULT_LARGE_THRESHOLD |
| `labels` | `FileLabels` | The card's strings, so it reads in any language. |

Also accepts everything in `Omit<TagProps<"section">, "children">`.

Also accepts everything in `Omit<HunksProps, keyof TagProps<"div"> | "hunks" | "labels" | "fileId" | "anchored">`.


### Diff.Hunks

Renders hunks as one table without a card, for Diff.File and for a thread's excerpt or suggestion.

| Prop | Type | Description |
| --- | --- | --- |
| `path` | `string` | The file's path, which names the table and keys the comment links. |
| `hunks` | `readonly Hunk[]` | The parsed hunks to draw, with their word-level ranges. |
| `language?` | `string` | Language the code highlights as (`ts`, `css`); omitted, lines draw plain. |
| `view?` | `"unified" \| "split"` | One column, or old and new side by side above 48rem. @default "unified" |
| `fileId?` | `string` | Prefix of every line anchor, `<fileId>R12`. @default "diff-" plus a hash of the path |
| `threads?` | `Readonly<Record<string, RemixNode>>` | Review threads drawn as a full-width row after their line, keyed `R12` (new side) or `L12` (old side). |
| `lineHref?` | `(side: DiffSide, line: number) => string` | The address a line's number links to. @default `#<fileId><side><line>` |
| `commentHref?` | `(path: string, side: DiffSide, line: number) => string` | Where a line's `+` goes without script; set, every line draws a `+`. |
| `expandHref?` | `(hunk: Hunk, index: number) => string` | Where a hunk's expand link goes, which the app can answer as a frame. |
| `chunkSize?` | `number` | Lines per chunk. @default DEFAULT_CHUNK_SIZE |
| `anchored?` | `boolean` | Gives each number an `id` and a link. Turn it off for a copy of lines shown beside the file itself (a thread's excerpt or suggestion), so anchors stay unique. @default true |
| `labels` | `HunksLabels` | The table's strings. |

Also accepts everything in `TagProps<"div">`.


## Types

| Name | Values | What it decides |
| --- | --- | --- |
| `View` | `"unified" \| "split"` | How hunks draw: one column of lines, or the old and new sides beside each other. |
