composed · Composed pattern
Diff
@devalok/shilp-sutra/composed/diffView in Storybook Preview
Changes
+3−31 change
1−function greet(name) {
2− console.log("Hi " + name)
3− return true
1+function greet(name: string) {
2+ console.log(`Namaste ${name}`)
3+ return { ok: true }
44 }
Variants
split (side-by-side)
mainbranch
+3−31 change
1−function greet(name) {
1+function greet(name: string) {
2− console.log("Hi " + name)
2+ console.log(`Namaste ${name}`)
3− return true
3+ return { ok: true }
4 }
4 }
word granularity (prose)
Changes
+4−14 changes
Craft is slower, becausebut it carries intention and care.
Reference
- Import: @devalok/shilp-sutra/composed/diff
- Server-safe: No
- Category: composed
Props
before: string (committed / old version)
after: string (pending / new version)
mode: "inline" | "split" | "fields" (unified, side-by-side, or structured JSON diff)
granularity: "line" | "word" (word = prose; ignored in fields mode)
collapseUnchanged: boolean (collapse long runs of unchanged lines behind an expander)
collapseThreshold: number (unchanged-line run length that triggers a collapse)
contextLines: number (unchanged lines kept visible each side of a collapsed run)
showSummary: boolean (+N / −N / change-count header)
beforeLabel: string (old-side column label, split mode)
afterLabel: string (new-side column label, split mode)
onAcceptHunk: (hunk) => void (adds per-change Accept control)
onRejectHunk: (hunk) => void (adds per-change Reject control)
Defaults
mode="inline", granularity="line", collapseUnchanged={true}, collapseThreshold={6}, contextLines={3}, showSummary={true}, beforeLabel="Committed", afterLabel="Pending"
Example
<Diff before={committed} after={pending} mode="split" />
<Diff before={a} after={b} granularity="word" /> // prose
<Diff before={jsonA} after={jsonB} mode="fields" /> // structured, diffs by key
<Diff before={a} after={b} onAcceptHunk={fn} onRejectHunk={fn} /> // review flow
// Composable — bring your own layout:
<Diff.Root before={a} after={b} mode="split">
<header><Diff.ColumnLabels /><Diff.Summary /></header>
<Diff.Body />
</Diff.Root>Composability
- Wraps the headless
diff(jsdiff) engine, owns the presentation — same pattern as charts (d3) and data-table (TanStack).diffis a runtime dependency. - Three modes.
inline(unified),split(side-by-side with intra-line word highlights),fields(parses JSON both sides, diffs by key — added/removed/changed — for structured content like YAML front-matter). - Compound parts:
Diff.Rootcomputes the diff once and provides it via context;Diff.Summary,Diff.Body,Diff.ColumnLabelsread from it. Use the parts to place the summary in a sticky header, scroll the body, or wrap hunks in custom chrome.useDiff()exposes the model. - Review surface, not just a viewer. Provide
onAcceptHunk/onRejectHunkand each contiguous change (hunk) gets Accept/Reject controls — pair with an approve/reject footer. - Collapse unchanged keeps long content readable (GitHub-style "expand N unchanged lines", animated open, reduced-motion aware).
- Semantic colour: additions use the
successscale, removals theerrorscale — flips correctly in dark.
Gotchas
fieldsmode requires valid JSON on both sides; it reports a clear message otherwise. For YAML, parse to an object andJSON.stringifybefore passing.granularity="word"is for prose; it renders a flowing word-level diff, not line gutters.- Accept/Reject controls only appear when the matching handler is provided — the Diff itself doesn't mutate content; the consumer applies the decision.
- Hunk = a contiguous run of added/removed lines;
onAcceptHunk/onRejectHunkreceive{ index, before, after }.