Merge Conflict
Both sides of a conflict with a resolution per hunk. "Keep both" is offered because it is frequently right — two people adding a different import conflict textually and agree semantically.
src/components/ui/merge-conflict.tsxAbout
From the component's own source, where it was written.
A merge conflict, with a resolution per hunk.
Both sides are shown in full, stacked, with the base labelled. Side-by-side looks tidier and is worse: conflict hunks are rarely the same height, so the columns desynchronise and you end up comparing line 4 against line 9.
"Both" is offered as a resolution because it is frequently the right answer — two people adding a different import to the same block conflict textually and agree semantically. A two-button ours/theirs forces a manual re-edit for the commonest case there is.
Nothing resolves itself. Unresolved hunks are counted in the header and the count does not go down until a choice is made, because a conflict UI that quietly defaults to one side is how code silently disappears in a merge.
Install
npx astralyx-ui add merge-conflict
Usage
import { MergeConflict } from '@/components/ui/merge-conflict'
<MergeConflict path={path} hunks={hunks} onResolve={resolve} />Composer
Change the props and the source below follows.
src/components/ui/status-badge.tsx2 unresolvedimport { cn } from '@/lib/utils'Ours
import { Badge } from '@/components/ui/badge'Base
Theirs
import { Button } from '@/components/ui/button'Ours
const label = status === 'active' ? 'Live' : 'Paused'
Base
const label = status
Theirs
const label = STATUS[status].label
<MergeConflict path={path} hunks={hunks} onResolve={resolve} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
MergeConflict
Extends Omit<ComponentProps<'div'>, 'children'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| path | ReactNode | — | — |
| hunks | ConflictHunk[] | required | { id, ours, theirs, base?, context? }. |
| resolutions | Record<string, ConflictSide> | — | Controlled or uncontrolled. Nothing resolves itself — a conflict UI that quietly defaults to one side is how code disappears in a merge. |
| onResolve | (id: string, side: ConflictSide) => void | — | — |
| oursLabel | ReactNode | 'Ours' | — |
| theirsLabel | ReactNode | 'Theirs' | — |
| baseLabel | ReactNode | 'Base' | — |
| bothLabel | ReactNode | 'Keep both' | — |
| unresolvedLabel | (count: number) => ReactNode | DEFAULT_UNRESOLVED_LABEL | — |
| resolvedLabel | ReactNode | 'All conflicts resolved' | — |
Types
ConflictHunk
| Field | Type | Description |
|---|---|---|
| id | string | — |
| ours | string | — |
| theirs | string | — |
| base? | string | Common ancestor, from a diff3-style conflict. |
| context? | string | Unchanged lines before the hunk, for orientation. |
| Type | Definition | Description |
|---|---|---|
ConflictSide | 'ours' | 'theirs' | 'both' | A merge conflict, with a resolution per hunk. |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| layout | stacked | Not side by side. Conflict hunks are rarely the same height, so columns desynchronise and you compare line 4 against line 9. |
| unresolved count | in the header | It does not go down until a choice is made. |