# Gitapp Ui API reference

> Exports of @dev-bench/gitapp-ui.

Source: https://gitapp.n.yatsyk.com/docs/gitapp-ui/jsdoc

## Functions

### mergeTheme

```ts
mergeTheme(base: Theme, override: PartialTheme): Theme
```

### ThemeProvider

```ts
ThemeProvider({ theme, themeOverride, children }: Props): ReactNode
```

### useTheme

```ts
useTheme(): Theme
```

### Icon

```ts
Icon({ name, size, color, strokeWidth }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Spinner

```ts
Spinner({ size, thickness, tone, trackOpacity, }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Chip

```ts
Chip({ label, tone, mono, leading, trailing, }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Dot

```ts
Dot({ tone, size }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Banner

```ts
Banner({ kind, title, children, icon, trailing }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Button

```ts
Button({ label, onPress, variant, size, disabled, leading, trailing, block, accessibilityLabel, }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Card

```ts
Card({ children, elevation, padding, borderColor, style, accent, }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### Skeleton

```ts
Skeleton({ width, height, radius, style, }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ProgressBar

```ts
ProgressBar({ ratio, tone, height }: Props): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ConnectionPanel

ConnectionPanel renders the full connection flow:
  1. Provider radio (GitHub / Gitea / GitLab)
  2. Auth sub-form (OAuth button or PAT input, per provider)
  3. Custom host input (Gitea / GitLab)
  4. Full Resource<T> shell on the auth attempt

```ts
ConnectionPanel({ model }: ConnectionPanelProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### RepoPickerSheet

RepoPickerSheet is a bottom-sheet style modal.

List surface is derived from a single pickListSurface() helper that takes
repos + filteredRepos + picked and returns one discriminated ListSurface.
The RepoRow sub-component is extracted so consumers can wrap it with
observer() to get per-row MobX reactivity.

```ts
RepoPickerSheet({ model }: RepoPickerSheetProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### RepoRow

```ts
RepoRow({ repo, isPicking, onSelect, theme, }: { readonly repo: RepoInfo; readonly isPicking: boolean; readonly onSelect: () => void; readonly theme: Theme; }): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### CreateRepoForm

CreateRepoForm renders the "New repository" modal form.

The form drives its submit button entirely through match(model.create).exhaustive()
so all four Resource states have a distinct, visible affordance.

```ts
CreateRepoForm({ model }: CreateRepoFormProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### InitializationSequence

```ts
InitializationSequence({ model, repoLabel }: InitializationSequenceProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### InitStepRow

```ts
InitStepRow({ step, theme }: InitStepRowProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### SyncStatusBar

The SyncStatusBar derives its display surface from FOUR independent
state inputs:
  - the sync Resource<T> (idle / loading / ready / error)
  - the connectivity union (online / offline / uncertain)
  - the categorised errorKind (when sync errored)
  - the expanded flag (whether the recovery panel is open)

Offline overrides everything else — even a successful sync is misleading
if the user has been disconnected since (their next mutation will queue).
That's why the surface decision is a single `pickSurface()` function
that takes all four inputs and returns one discriminated `Surface`.

```ts
SyncStatusBar({ model, nowISO }: SyncStatusBarProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ConflictShell

```ts
ConflictShell({ vm, getSides, renderPreview, viewport, drawerOpen: drawerOpenProp, }: ConflictShellProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### FileTreeRow

```ts
FileTreeRow({ file, isSelected, onSelect, onKeepMine, onKeepTheirs, theme, }: FileTreeRowProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### BranchGraph

```ts
BranchGraph({ size, mine, theirs, mergeBase, maxDots }: BranchGraphProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### DivergenceExplainer

```ts
DivergenceExplainer({ model }: DivergenceExplainerProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ConflictResolverSheet

```ts
ConflictResolverSheet({ model, renderPreview, viewport, drawerOpen, }: ConflictResolverSheetProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### TextPreview

textPreviewForSides — the default preview for §4.6 text/markdown files.
Shows side-by-side monospaced text panes (mine left, theirs right), with
the active side highlighted. One column on phone (side switcher controls
which side is visible).

RN v1 fallback: plain monospace <Text> blocks, no syntax highlighting.
On web, the consuming app can replace this with

```ts
TextPreview({ file, side, mine, theirs }: ConflictPreviewProps): ReactNode
```

### RepoStateRecoveryPanel

```ts
RepoStateRecoveryPanel({ model }: RepoStateRecoveryPanelProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### FindingItem

```ts
FindingItem({ finding, theme }: FindingItemProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ValidationFailureBanner

```ts
ValidationFailureBanner({ model }: ValidationFailureBannerProps): any
```

### FailureRow

```ts
FailureRow({ failure, model }: FailureRowProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### RepoStatusRow

```ts
RepoStatusRow({ model }: RepoStatusRowProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ResourceButton

```ts
<T>({ state, label, onPress, variant, size, renderReady, }: IResourceButtonProps<T>): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### DiagnosticsSheet

```ts
DiagnosticsSheet({ model }: DiagnosticsSheetProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### DiagnosticsRow

```ts
DiagnosticsRow({ entry, isExpanded, onToggle, theme }: DiagnosticsRowProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### ProviderSelector

```ts
ProviderSelector({ model, }: ProviderSelectorProps): import("/home/runner/work/gitapp/gitapp/node_modules/.pnpm/@types+react@18.3.28/node_modules/@types/react/jsx-runtime").JSX.Element
```

### SyncStatusBadge

SyncStatusBadge — pure presentational indicator of git sync state.

The `compact` variant renders a small inline badge suitable for the app
chrome (top status row, header). The `expanded` variant renders a multi-
line info panel suitable for a sheet or settings screen.

Always pass `onPress` — dead UI is banned by CLAUDE.md. If the action
isn't implemented yet, use warnNotImplemented in the caller.

```ts
SyncStatusBadge({ state, onPress, variant, }: SyncStatusBadgeProps): JSX.Element
```

### EditorialBadge

EditorialBadge — pure presentational chip for the editorial state of a record.

Not interactive (no onPress). If you need a tappable chip that drills into
the review sheet, wrap this in a Pressable in the consuming component.

```ts
EditorialBadge({ state, size, tokens, }: EditorialBadgeProps): JSX.Element
```

### ForkRepoButton

ForkRepoButton — triggers a fork of `vm.source` to the user's account.

Renders all four Resource states distinctly. The `ready` display auto-fades
to idle after 2 s (without mutating the parent's Resource) so the user can
fork again if needed.

```ts
ForkRepoButton({ vm }: ForkRepoButtonProps): JSX.Element
```

### PullRequestComposer

PullRequestComposer — sheet/modal for opening a PR.

Supports two modes:
  - "editorial-submit": submitting a record for review (with optional
    scheduled-publish datetime).
  - "fork-back": proposing changes back to an upstream repo.

All four Resource states on `vm.state` are rendered distinctly. The `ready`
state auto-closes (calls onCancel) after 1 s so the user sees the success
before the sheet dismisses.

```ts
PullRequestComposer({ vm }: PullRequestComposerProps): JSX.Element
```

### EditorialQueueScreen

EditorialQueueScreen — queue of content records by editorial state.

Per-row observer pattern: EditorialQueueRowCard is extracted so MobX
consumers can wrap individual rows with observer() without touching this file.

```ts
EditorialQueueScreen({ vm }: EditorialQueueScreenProps): JSX.Element
```

### EditorialQueueRowCard

```ts
EditorialQueueRowCard({ row, onPress, }: EditorialQueueRowCardProps): JSX.Element
```

### RecordReviewSheet

RecordReviewSheet — sheet for reviewing a single content record PR.

Three footer actions:
  - Approve: merges/approves, each with its own Resource<void> state.
  - Request Changes: expands a comment input, then submits.
  - Close PR: closes without merging, with its own Resource<void> state.

The renderPreview slot allows consuming apps to render domain-specific
content (PPA: TaskCard, CMS: MarkdownArticle, etc.).

```ts
RecordReviewSheet({ vm, renderPreview, }: RecordReviewSheetProps): JSX.Element
```

### PreviewPpaTask

```ts
PreviewPpaTask({ file, side, mine, theirs, mineTask, theirsTask, }: PreviewPpaTaskProps): ReactNode
```

### PreviewCmsArticle

```ts
PreviewCmsArticle({ file, side, mine, theirs, mineArticle, theirsArticle, frontmatterDiff, }: PreviewCmsArticleProps): ReactNode
```

### PreviewBookmark

```ts
PreviewBookmark({ file, side, mine, theirs, mineBookmark, theirsBookmark, }: PreviewBookmarkProps): ReactNode
```

### PreviewPasswordEntry

```ts
PreviewPasswordEntry({ file, side, mine, theirs, mineEntry, theirsEntry, }: PreviewPasswordEntryProps): ReactNode
```

### isWritable

Returns true when the mode permits writes.

```ts
isWritable(mode: RepoMode): boolean
```

### describeMode

Human-readable description of the mode, exhaustive over all variants.

```ts
describeMode(mode: RepoMode): string
```

### effectiveMode

```ts
effectiveMode(selection: Pick<RepoSelection, "owner" | "name">, mode: RepoMode, demo: DemoRepoRef): RepoMode
```

### requireWrite

```ts
requireWrite(mode: RepoMode, attemptedAction: string): WriteAccess
```

### normalizeHost

```ts
normalizeHost(provider: RepoProviderId, host: string): string
```

### describeEditorialState

Human-readable description of a record's editorial state, exhaustive.

```ts
describeEditorialState(state: RecordEditorialState): string
```

### readEditorialConfig

Reads and validates the repo editorial config from `.gitapp/editorial.json`.

- Returns `{ kind: "off" }` when the file is absent.
- Returns `{ kind: "off" }` and calls `warnDegraded` on parse failure
  (forward-compat: unknown future variants fall through gracefully).

```ts
readEditorialConfig(source: EditorialConfigSource): Promise<RepoEditorialMode>
```

## Types

### Resource

```ts
type Resource = Resource<T>
```

### ResourceT

```ts
type ResourceT = Resource<T>
```

### Err

```ts
type Err = { readonly message: string; readonly cause?: unknown }
```

### StatusTone

```ts
type StatusTone = | "neutral"
  | "brand"
  | "success"
  | "warn"
  | "danger"
  | "info"
  | "mine"
  | "theirs"
  | "merged"
```

### Connectivity

```ts
type Connectivity = | { kind: "online" }
  | { kind: "offline" }
  | { kind: "uncertain" }
```

### ProviderId

```ts
type ProviderId = "github" | "gitea" | "gitlab"
```

### IconName

```ts
type IconName = | "check" | "x" | "alert" | "alertTri" | "info"
  | "chevronRight" | "chevronDown" | "chevronLeft" | "chevronUp"
  | "arrowRight" | "arrowLeft"
  | "refresh" | "rotate" | "search" | "plus" | "more" | "menu"
  | "lock" | "unlock" | "key"
  | "cloud" | "cloudOff" | "git" | "branch"
  | "folder" | "file" | "fileText" | "image"
  | "user" | "users" | "eye" | "eyeOff"
  | "download" | "upload" | "bookmark" | "bolt" | "trash"
  | "settings" | "activity" | "shield" | "wrench"
  | "circle" | "dot"
```

### ConnectionErrorKind

Categorised connection error so recovery copy can be specific.

```ts
type ConnectionErrorKind = | { kind: "auth"; reason: string }   // bad token / OAuth failure
  | { kind: "network" }                // unreachable
  | { kind: "not-found" }
```

### RepoAccess

Access state for a single repo — checked against current auth token.

```ts
type RepoAccess = | { kind: "found" }
  | { kind: "forbidden"; currentLogin: string }
  | { kind: "missing" }
  | { kind: "checking" }
```

### CreateErrorKind

Categorised create error — drives specific copy.

```ts
type CreateErrorKind = | { kind: "auth" }
  | { kind: "duplicate"; name: string }
  | { kind: "network" }
  | { kind: "unknown"; detail: string }
```

### InitStepState

```ts
type InitStepState = | { kind: "pending" }
  | { kind: "running";  progress: InitStepProgress }
  | { kind: "ok";       durationMs: number }
  | { kind: "error";    message: string }
  | { kind: "blocked";  reason: string }
```

### InitStepProgress

```ts
type InitStepProgress = | { kind: "indeterminate" }
  | { kind: "determinate"; ratio: number; label: string }
```

### InitOverall

```ts
type InitOverall = | { kind: "running";  currentIndex: number }
  | { kind: "ok" }
  | { kind: "blocked";  failedIndex: number; error: string }
```

### SyncErrorKind

Categorised error so the recovery affordance can route appropriately.

```ts
type SyncErrorKind = | { kind: "auth" }                     // 401 / 403 → reconnect
  | { kind: "network" }                  // network unreachable
  | { kind: "conflict" }                 // non-fast-forward → opens Divergence
  | { kind: "rejected" }                 // server-side hook rejected
  | { kind: "unknown"; detail: string }
```

### ConflictSideId

```ts
type ConflictSideId = "mine" | "theirs" | "merged"
```

### ConflictFileStatus

```ts
type ConflictFileStatus = | { kind: "modifiedBoth" }
  | { kind: "modifiedMine" }
  | { kind: "modifiedTheirs" }
  | { kind: "deletedMine" }
  | { kind: "deletedTheirs" }
  | { kind: "addedMine" }
  | { kind: "addedTheirs" }
  | { kind: "renamed"; fromPath: string; toPath: string }
  | { kind: "binary" }
```

### ConflictFile

```ts
type ConflictFile = {
  readonly id: string;
  readonly path: string;
  readonly displayName: string;
  readonly status: ConflictFileStatus;
  readonly resolved: boolean;
}
```

### ConflictSide

```ts
type ConflictSide = | { kind: "text";    title: string; content: string }
  | { kind: "binary";  title: string; mimeType: string; sizeBytes: number }
  | { kind: "deleted"; title: string }
  | { kind: "missing"; title: string; reason: string }
```

### CommitSummary

```ts
type CommitSummary = {
  readonly id: string;
  readonly side: "mine" | "theirs";
  readonly shortHash: string;
  readonly title: string;
  readonly author: string;
  readonly whenISO: string;
}
```

### DivergedCommit

```ts
type DivergedCommit = {
  readonly commitId: string;        // shown ONLY in "technical details" expand — never here
  readonly summary: string;         // plain-language label from summarizeCommit()
  readonly author: string;          // device name
  readonly whenISO: string;
}
```

### DivergedFileChange

```ts
type DivergedFileChange = {
  readonly path: string;
  readonly mineEdited: boolean;
  readonly theirsEdited: boolean;
  readonly bothEditedSameContent: boolean;
}
```

### ConflictDecision

```ts
type ConflictDecision = | { kind: "pending" }
  | { kind: "keepMine" }
  | { kind: "keepTheirs" }
  | { kind: "merged"; content: string }
  | { kind: "deferred" }
```

### ResolvableFile

A resolvable file as the resolver sees it — richer than the shell's
ConflictFile (includes raw content + commit metadata + decision).
The resolver maps this to the shell's ConflictFile via shellVm.files.

```ts
type ResolvableFile = {
  readonly path: string;
  readonly mine: {
    readonly content: string;
    readonly commit: string;
    readonly author: string;
    readonly whenISO: string;
  };
  readonly theirs: {
    readonly content: string;
    readonly commit: string;
    readonly author: string;
    readonly whenISO: string;
  };
  readonly decision: ConflictDecision;
}
```

### FindingPath

```ts
type FindingPath = | { kind: "none" }
  | { kind: "some"; value: string }
```

### DiagnosisState

```ts
type DiagnosisState = | { kind: "loading" }
  | { kind: "ready"; findings: Finding[] }
```

### LastGoodCommit

```ts
type LastGoodCommit = | { kind: "none" }
  | { kind: "some"; sha: string; whenISO: string }
```

### RepoStatusState

```ts
type RepoStatusState = | { kind: "disconnected" }
  | { kind: "connectedNoRepo"; login: string }
  | { kind: "connected";       login: string; repo: { owner: string; name: string; branch: string } }
  | { kind: "connectedMismatch"; login: string; repo: { owner: string; name: string; branch: string } }
  | { kind: "checking";        login: string }
```

### DiagnosticsOp

```ts
type DiagnosticsOp = "push" | "pull" | "commit" | "clone"
```

### DiagnosticsResult

```ts
type DiagnosticsResult = | { kind: "ok";                detail: string }
  | { kind: "non-fast-forward";  detail: string }
  | { kind: "auth-error";        message: string }
  | { kind: "network";           message: string }
  | { kind: "rejected";          message: string }
  | { kind: "unknown";           message: string }
```

### DiagnosticsResultKind

```ts
type DiagnosticsResultKind = | "ok"
  | "non-fast-forward"
  | "auth-error"
  | "network"
  | "rejected"
  | "unknown"
```

### DiagnosticsFilter

```ts
type DiagnosticsFilter = "all" | "errors" | "last-hour"
```

### SyncState

```ts
type SyncState = | { kind: "synced"; lastSyncedAt: number; sha: string }
  | { kind: "syncing"; phase: "push" | "pull" | "rebase"; sinceMs: number }
  | { kind: "ahead"; commits: number; sha: string }      // local has unpushed commits
  | { kind: "behind"; commits: number; sha: string }     // remote has unpulled commits
  | { kind: "diverged"; localCommits: number; remoteCommits: number }
  | { kind: "conflict"; files: ReadonlyArray<string> }
  | { kind: "offline"; lastSyncedAt: number }
  | { kind: "error"; error: { message: string }; tag: string }
  | { kind: "idle" }
```

### SyncStatusBadgeVariant

Controls how much space the badge occupies.

```ts
type SyncStatusBadgeVariant = "compact" | "expanded"
```

### PullRequestComposerMode

```ts
type PullRequestComposerMode = | { kind: "editorial-submit"; recordTitle: string }
  | { kind: "fork-back"; sourceUpstream: { owner: string; name: string } }
```

### ScheduledPublish

```ts
type ScheduledPublish = | { kind: "none" }
  | { kind: "at"; iso: string }
```

### FrontmatterDiffLine

```ts
type FrontmatterDiffLine = | { kind: "removed"; text: string }
  | { kind: "added";   text: string }
  | { kind: "context"; text: string }
```

### RepoMode

RepoMode — discriminated union expressing the write-access level of a repo.

- rw: user has full read-write access.
- ro: user can only read. The `reason` field distinguishes the cause.

```ts
type RepoMode = | { kind: "rw" }
  | { kind: "ro"; reason: "no-access" | "demo" | "user-locked" }
```

### RepoProviderId

```ts
type RepoProviderId = "github" | "gitea" | "gitlab"
```

### RoReason

```ts
type RoReason = | { readonly kind: "demo" }
  | { readonly kind: "no-access" }
  | { readonly kind: "user-locked" }
```

### WriteAccess

```ts
type WriteAccess = | { readonly kind: "granted" }
  | { readonly kind: "denied"; readonly reason: RoReason; readonly attemptedAction: string }
```

### RecordEditorialState

RecordEditorialState — the editorial lifecycle of a single content record.

All variants are exhaustive — no null/undefined in the union.

```ts
type RecordEditorialState = | { kind: "published"; sha: string }
  | { kind: "draft"; branch: string; lastEdit: number; author: string }
  | {
      kind: "in-review";
      branch: string;
      pr: PullRequestRef;
      reviewers: ReadonlyArray<string>;
    }
  | { kind: "approved"; pr: PullRequestRef; readyToMerge: true }
  | {
      kind: "changes-requested";
      pr: PullRequestRef;
      reviewer: string;
      comment: string;
    }
```

### RepoEditorialMode

RepoEditorialMode — the editorial workflow configured for the repo.

- off: no editorial workflow; records are pushed to main directly.
- branch-per-record: each record gets its own branch and PR.
- single-working-branch: all drafts share one named branch.
- open-authoring: forks from a specified upstream repo.

```ts
type RepoEditorialMode = | { kind: "off" }
  | { kind: "branch-per-record" }
  | { kind: "single-working-branch"; branch: string }
  | { kind: "open-authoring"; upstream: { owner: string; name: string } }
```

## Interfaces

### Theme

```ts
interface Theme
```

- `name: string`
- `colors: ThemeColors`
- `fonts: ThemeFonts`
- `radii: ThemeRadii`
- `spacing: ThemeSpacing`
- `shadows: ThemeShadows`
- `typography: ThemeTypography`

### ThemeColors

```ts
interface ThemeColors
```

- `bg: string`
- `surface: string`
- `surface2: string`
- `surface3: string`
- `border: string`
- `border2: string`
- `text: string`
- `text2: string`
- `text3: string`
- `text4: string`
- `brand: string`
- `brand2: string`
- `brandFg: string`
- `brandBg: string`
- `success: string`
- `successBg: string`
- `successBorder: string`
- `warn: string`
- `warnBg: string`
- `warnBorder: string`
- `danger: string`
- `dangerBg: string`
- `dangerBorder: string`
- `info: string`
- `infoBg: string`
- `infoBorder: string`
- `mine: string`
- `mineBg: string`
- `mineBorder: string`
- `theirs: string`
- `theirsBg: string`
- `theirsBorder: string`
- `merged: string`
- `mergedBg: string`
- `mergedBorder: string`

### ThemeFonts

```ts
interface ThemeFonts
```

- `ui: string`
- `mono: string`

### ThemeRadii

```ts
interface ThemeRadii
```

- `sm: number`
- `md: number`
- `lg: number`

### ThemeSpacing

```ts
interface ThemeSpacing
```

- `xs: number`
- `sm: number`
- `md: number`
- `lg: number`
- `xl: number`

### ThemeShadow

```ts
interface ThemeShadow
```

- `color: string`
- `opacity: number`
- `offsetX: number`
- `offsetY: number`
- `radius: number`
- `elevation: number`

### ThemeShadows

```ts
interface ThemeShadows
```

- `s1: ThemeShadow`
- `s2: ThemeShadow`
- `modal: ThemeShadow`

### ThemeTypography

```ts
interface ThemeTypography
```

- `t12: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t13: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t14: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t15: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t16: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t18: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`
- `t22: { size: number; lineHeight: number; weight: "400" | "500" | "600" | "700"; }`

### ConnectionPanelProps

```ts
interface ConnectionPanelProps
```

- `model: IConnectionModel`

### IConnectionModel

```ts
interface IConnectionModel
```

- `provider: ProviderId` — Current selected provider.
- `authMethod: { kind: "oauth"; } | { kind: "pat"; token: string; }` — OAuth or PAT — GitHub supports both; Gitea/GitLab require PAT.
- `host: { kind: "default"; } | { kind: "custom"; url: string; }` — Default = the provider's public cloud; custom = self-hosted URL.
- `connection: Resource<ConnectionSuccess>` — Four-state Resource for the current authentication attempt.
- `errorKind: { kind: "none"; } | { kind: "some"; value: ConnectionErrorKind; }` — Categorised error when connection.kind === "error".
- `onProviderChange(p: ProviderId): void`
- `onAuthMethodChange(m: { kind: "oauth"; } | { kind: "pat"; token: string; }): void`
- `onHostChange(h: { kind: "default"; } | { kind: "custom"; url: string; }): void`
- `onConnect(): Promise<void>`
- `onDisconnect(): Promise<void>`

### ConnectionSuccess

Connection success payload.

```ts
interface ConnectionSuccess
```

- `login: string`
- `provider: ProviderId`

### RepoPickerSheetProps

```ts
interface RepoPickerSheetProps
```

- `model: IRepoPickerModel`

### IRepoPickerModel

```ts
interface IRepoPickerModel
```

- `repos: Resource<readonly RepoInfo[]>` — Full repo list — all 4 Resource states.
- `filter: string` — Current filter text (controlled).
- `picked: { kind: "none"; } | { kind: "picking"; repo: RepoInfo; } | { kind: "picked"; repo: RepoInfo; }` — Selection state — discriminated union, no nullable.
- `currentLogin: { kind: "anonymous"; } | { kind: "loggedIn"; login: string; }` — Who we are authed as (shown in forbidden chip copy).
- `filteredRepos: readonly RepoInfo[]` — Filtered + sorted subset — computed by the model, not this component.
- `canCreate: boolean` — Whether the current auth token has repo-create scope.
- `onFilterChange(v: string): void`
- `onSelect(repo: RepoInfo): Promise<void>`
- `onRefresh(): Promise<void>`
- `onOpenCreate(): void`
- `onDismiss(): void`

### RepoInfo

Repo metadata returned from the provider's list-repos API.

```ts
interface RepoInfo
```

- `owner: string`
- `name: string`
- `isPrivate: boolean`
- `defaultBranch: string`
- `pushedAt: string`
- `access: RepoAccess`

### CreateRepoFormProps

```ts
interface CreateRepoFormProps
```

- `model: ICreateRepoModel`

### ICreateRepoModel

```ts
interface ICreateRepoModel
```

- `name: string` — Controlled repo name (no spaces; dashes + alphanumeric only).
- `visibility: "private" | "public"` — Visibility toggle — private is the default and recommended.
- `initWithReadme: boolean` — Whether to create an initial README commit (required for push).
- `create: Resource<CreatedRepo>` — Four-state Resource for the create attempt.
- `nameValidation: { kind: "ok"; } | { kind: "invalid"; reason: string; }` — Inline name validation — shown under the input.
- `createErrorKind: { kind: "none"; } | { kind: "some"; value: CreateErrorKind; }` — Categorised error when create.kind === "error".
- `onNameChange(v: string): void`
- `onVisibilityChange(v: "private" | "public"): void`
- `onInitWithReadmeChange(v: boolean): void`
- `onSubmit(): Promise<void>`
- `onCancel(): void`

### CreatedRepo

Success payload — the created repo coordinates.

```ts
interface CreatedRepo
```

- `owner: string`
- `name: string`

### InitializationSequenceProps

```ts
interface InitializationSequenceProps
```

- `model: IInitModel`
- `repoLabel: string` — Shown in the heading, e.g. "byoklab/ppa-data-andrew"

### IInitModel

```ts
interface IInitModel
```

- `steps: readonly InitStep[]`
- `overall: InitOverall`
- `onRetry(stepId: string): Promise<void>`
- `onReset(): Promise<void>`
- `onCancel(): void`

### InitStep

```ts
interface InitStep
```

- `id: string`
- `label: string`
- `state: InitStepState`

### InitStepRowProps

```ts
interface InitStepRowProps
```

- `step: InitStep`
- `theme: Theme`

### SyncStatusBarProps

```ts
interface SyncStatusBarProps
```

- `model: ISyncStatusModel`
- `nowISO: string` — Called every minute or so by the parent so "3m ago" updates.

### ISyncStatusModel

```ts
interface ISyncStatusModel
```

- `sync: Resource<SyncSummary>` — The four-state Resource for the latest sync attempt.
- `connectivity: Connectivity` — Network reachability — orthogonal to sync.
- `errorKind: { kind: "none"; } | { kind: "some"; value: SyncErrorKind; }` — Categorised error when sync.kind === "error".
- `pendingCount: number` — Pending writes count (used in offline label).
- `expanded: boolean` — Whether the strip's expanded recovery panel is open.
- `onToggleExpanded(): void`
- `onRetry(): Promise<void>`
- `onOpenDivergence(): void`
- `onOpenDiagnostics(): void`
- `onReconnect(): void`

### SyncSummary

Successful-state payload — what the strip shows in `ready`.

```ts
interface SyncSummary
```

- `lastPushedISO: string`
- `pendingCount: number`

### ConflictShellProps

```ts
interface ConflictShellProps
```

- `vm: IConflictShellViewModel`
- `getSides: (file: ConflictFile) => { mine: ConflictSide; theirs: ConflictSide; merged: ConflictSide; }`
- `renderPreview: (props: ConflictPreviewProps) => ReactNode` — Render-prop slot — the consuming app provides domain-specific preview.
Called with the currently-selected file + active side + all three sides.
- `viewport: "phone" | "desktop"` — Viewport hint. Pass "phone" to force the mobile drawer layout,
"desktop" for the persistent two-pane layout.
Default: auto-detect from window width when undefined.
- `drawerOpen: boolean` — Force the mobile drawer open (useful for Storybook / showcase).
Only meaningful when viewport="phone".

### IConflictShellViewModel

```ts
interface IConflictShellViewModel
```

- `files: readonly ConflictFile[]`
- `commits: readonly CommitSummary[]`
- `selectedFile: { kind: "selected"; fileId: string; } | { kind: "none"; }`
- `selectedSide: ConflictSideId`
- `railMode: { kind: "files"; } | { kind: "commits"; }` — Mobile only: toggle between file list and commit list.
- `resolvedCount: number`
- `pendingCount: number`
- `canApply: boolean`
- `selectFile(fileId: string): void`
- `selectSide(side: ConflictSideId): void`
- `setRailMode(mode: IConflictShellViewModel["railMode"]): void`
- `keepMine(fileId: string): void` — Whole-file shortcut — no preview needed, resolves inline.
- `keepTheirs(fileId: string): void` — Whole-file shortcut — no preview needed, resolves inline.
- `skip(fileId: string): void` — Mark deferred — skip this record for now.
- `apply(): Promise<void>`

### ConflictPreviewProps

```ts
interface ConflictPreviewProps
```

- `file: ConflictFile`
- `side: ConflictSideId`
- `mine: ConflictSide`
- `theirs: ConflictSide`
- `merged: ConflictSide`

### FileTreeRowProps

```ts
interface FileTreeRowProps
```

- `file: ConflictFile`
- `isSelected: boolean`
- `onSelect: () => void`
- `onKeepMine: () => void`
- `onKeepTheirs: () => void`
- `theme: Theme`

### BranchGraphProps

```ts
interface BranchGraphProps
```

- `size: "phone" | "desktop" | "inline"`
- `mine: readonly DivergedCommit[]`
- `theirs: readonly DivergedCommit[]`
- `mergeBase: { commitId: string; whenISO: string; }`
- `maxDots: number` — Override the per-size default maximum dots per lane.

### DivergenceExplainerProps

```ts
interface DivergenceExplainerProps
```

- `model: IDivergenceModel`

### IDivergenceModel

```ts
interface IDivergenceModel
```

- `mine: readonly DivergedCommit[]`
- `theirs: readonly DivergedCommit[]`
- `mergeBase: { commitId: string; whenISO: string; }`
- `fileChanges: readonly DivergedFileChange[]`
- `strategy: { kind: "choosing"; } | { kind: "tryingAutomatic"; } | { kind: "automaticSucceeded"; } | { kind: "automaticFailed"; } | { kind: "confirmingDiscard"; side: "mine" | "theirs"; } | { kind: "discarding"; } | { kind: "discarded"; } | { kind: "applyError"; reason: string; }`
- `canShowAutomatic: boolean`
- `onTryAutomatic(): Promise<void>`
- `onResolveManually(): void`
- `onDiscard(side: "mine" | "theirs"): void`
- `onConfirmDiscard(typed: string): Promise<void>`
- `onCancel(): void`
- `onShowTechnicalDetails(): void`

### ConflictResolverSheetProps

```ts
interface ConflictResolverSheetProps
```

- `model: IConflictResolverModel`
- `renderPreview: (props: ConflictPreviewProps) => ReactNode` — Consuming-app override for the preview slot.
If omitted, the default text-v1 side-by-side panes are used.
- `viewport: "phone" | "desktop"`
- `drawerOpen: boolean`

### IConflictResolverModel

```ts
interface IConflictResolverModel
```

- `files: readonly ResolvableFile[]`
- `tab: "all" | "pending" | "resolved"`
- `overall: { kind: "browsing"; } | { kind: "resolvingFile"; path: string; } | { kind: "applying"; } | { kind: "applied"; } | { kind: "applyError"; reason: string; }`
- `pendingCount: number`
- `resolvedCount: number`
- `canApply: boolean`
- `shellVm: IConflictShellViewModel` — The adapter the shell consumes.
- `renderPreview(props: ConflictPreviewProps): ReactNode` — Default renderPreview — text-v1 side-by-side panes.
- `onTabChange(tab: "all" | "pending" | "resolved"): void`
- `onOpenFile(path: string): void`
- `onCloseFile(): void`
- `onDecide(path: string, decision: ConflictDecision): void`
- `onKeepAllMine(): void`
- `onKeepAllTheirs(): void`
- `onApply(): Promise<void>`
- `onDismiss(): void`

### RepoStateRecoveryPanelProps

```ts
interface RepoStateRecoveryPanelProps
```

- `model: IRecoveryModel`

### IRecoveryModel

```ts
interface IRecoveryModel
```

- `repoOwner: string`
- `repoName: string`
- `lastWorkingISO: { kind: "none"; } | { kind: "some"; value: string; }`
- `localChangesCount: number`
- `diagnosis: DiagnosisState`
- `recovery: Resource<RecoveryOutcome>`
- `onSoftRefresh(): void`
- `onPull(): Promise<void>`
- `onReset(typedConfirmation: string): Promise<void>`
- `onOpenDiagnostics(): void`
- `onSwitchRepo(): void`

### Finding

```ts
interface Finding
```

- `severity: "warn" | "error"`
- `message: string`
- `path: FindingPath`

### RecoveryOutcome

Outcome returned by successful recovery operations.

```ts
interface RecoveryOutcome
```

- `message: string`

### ValidationFailureBannerProps

```ts
interface ValidationFailureBannerProps
```

- `model: IValidationModel`

### IValidationModel

```ts
interface IValidationModel
```

- `failures: Resource<ValidationFailure[]>`
- `onShowDetails(file: string): void`
- `onOpenRawEditor(file: string): void`
- `onReset(file: string): void`
- `onDismiss(file: string): void`

### ValidationFailure

```ts
interface ValidationFailure
```

- `path: string`
- `oneLineError: string`
- `fullErrors: readonly string[]`
- `lastGoodCommit: LastGoodCommit`

### FailureRowProps

```ts
interface FailureRowProps
```

- `failure: ValidationFailure`
- `model: IValidationModel`

### RepoStatusRowProps

```ts
interface RepoStatusRowProps
```

- `model: IRepoStatusModel`

### IRepoStatusModel

```ts
interface IRepoStatusModel
```

- `state: RepoStatusState`
- `onPress(): void`

### IResourceButtonProps

```ts
interface IResourceButtonProps
```

- `state: Resource<T>`
- `label: string`
- `onPress: (() => void) | (() => Promise<void>)`
- `variant: "danger" | "primary" | "secondary"`
- `size: "sm" | "md" | "lg"`
- `renderReady: (value: T) => ReactNode` — Override the success content. Receives the unwrapped ready value.

### DiagnosticsSheetProps

```ts
interface DiagnosticsSheetProps
```

- `model: IDiagnosticsModel`

### IDiagnosticsModel

```ts
interface IDiagnosticsModel
```

- `entries: Resource<DiagnosticsEntry[]>`
- `filter: DiagnosticsFilter`
- `expanded: { kind: "none"; } | { kind: "some"; entryId: string; }`
- `onFilterChange(filter: DiagnosticsFilter): void`
- `onToggleExpanded(id: string): void`
- `onClear(): void`
- `onExport(): void`

### DiagnosticsEntry

```ts
interface DiagnosticsEntry
```

- `id: string`
- `whenISO: string`
- `op: DiagnosticsOp`
- `result: DiagnosticsResult`
- `durationMs: number`
- `args: Record<string, unknown>`

### DiagnosticsRowProps

```ts
interface DiagnosticsRowProps
```

- `entry: DiagnosticsEntry`
- `isExpanded: boolean`
- `onToggle: () => void`
- `theme: Theme`

### ProviderSelectorProps

```ts
interface ProviderSelectorProps
```

- `model: IProviderSelectorModel`

### IProviderSelectorModel

```ts
interface IProviderSelectorModel
```

- `current: { provider: ProviderId; host: string; }` — Current provider and host (read-only display).
- `state: Resource<void>` — Selection lifecycle state.
- `isOpen: boolean` — Whether the selector is open (controls sheet visibility).
- `onSelectProvider(provider: ProviderId): void`
- `onCustomHostChange(host: string): void`
- `onConfirm(): Promise<void>`
- `onDismiss(): void`

### SyncStatusBadgeProps

```ts
interface SyncStatusBadgeProps
```

- `state: SyncState` — The current sync state to render.
- `onPress: (state: SyncState) => void` — Called when the user taps the badge. Receives the current state so the
parent can route to the correct sheet (e.g. conflict resolver for "conflict",
retry for "error", manual sync sheet for "idle").
- `variant: SyncStatusBadgeVariant` — Display density.
  compact  — single-line pill / badge (~24px tall) for chrome.
  expanded — multi-line panel with sha, counts, retry button.
- `tokens: Partial<{ accent: string; accentFg: string; success: string; warn: string; danger: string; text: string; text2: string; text3: string; surface: string; border: string; warnBg: string; dangerBg: string; successBg: string; }>` — Optional theme token overrides. When omitted, the badge reads from the
nearest <ThemeProvider>. Useful in Storybook / dev-hub where the
ThemeProvider may not wrap the story.

### EditorialBadgeProps

```ts
interface EditorialBadgeProps
```

- `state: RecordEditorialState`
- `size: "sm" | "md"`
- `tokens: EditorialBadgeTokens` — Optional per-state color overrides. If omitted, theme defaults apply.

### EditorialBadgeTokens

```ts
interface EditorialBadgeTokens
```

- `draft: string` — Override the background color for a specific state.
- `inReview: string`
- `approved: string`
- `changesRequested: string`
- `published: string`

### ForkRepoButtonProps

```ts
interface ForkRepoButtonProps
```

- `vm: ForkRepoViewModel`

### ForkRepoViewModel

```ts
interface ForkRepoViewModel
```

- `source: { owner: string; name: string; }` — The upstream repo being forked.
- `state: Resource<{ owner: string; name: string; }>` — Resource tracking the fork operation. ready.value is the new forked repo.
- `onPress(): void`

### PullRequestComposerProps

```ts
interface PullRequestComposerProps
```

- `vm: PullRequestComposerViewModel`

### PullRequestComposerViewModel

```ts
interface PullRequestComposerViewModel
```

- `mode: PullRequestComposerMode`
- `title: string`
- `body: string`
- `baseBranch: string`
- `scheduledPublish: ScheduledPublish`
- `state: Resource<PullRequestRef>`
- `onChangeTitle(s: string): void`
- `onChangeBody(s: string): void`
- `onChangeBaseBranch(s: string): void`
- `onChangeScheduled(s: ScheduledPublish): void`
- `onSubmit(): void`
- `onCancel(): void`

### EditorialQueueScreenProps

```ts
interface EditorialQueueScreenProps
```

- `vm: EditorialQueueViewModel`

### EditorialQueueViewModel

```ts
interface EditorialQueueViewModel
```

- `rows: readonly EditorialQueueRow[]`
- `filter: EditorialQueueFilter`
- `layout: "kanban" | "list"`
- `state: Resource<void>` — Load state for the queue itself.
- `onSelectRow(id: string): void`
- `onChangeFilter(filter: EditorialQueueFilter): void`
- `onToggleLayout(): void`

### EditorialQueueRow

```ts
interface EditorialQueueRow
```

- `id: string`
- `title: string`
- `state: RecordEditorialState`
- `author: string`
- `updatedAt: number`

### EditorialQueueFilter

```ts
interface EditorialQueueFilter
```

- `state: readonly ("published" | "draft" | "in-review" | "approved" | "changes-requested")[]`
- `author: string`

### EditorialQueueRowCardProps

```ts
interface EditorialQueueRowCardProps
```

- `row: EditorialQueueRow`
- `onPress: (id: string) => void`

### RecordReviewSheetProps

```ts
interface RecordReviewSheetProps
```

- `vm: RecordReviewSheetViewModel`
- `renderPreview: (record: { rawContent: string; meta: unknown; }) => ReactNode` — Render-prop slot — the consuming app provides domain-specific record preview.
PPA passes a task card renderer; a CMS app passes a markdown article renderer.

### RecordReviewSheetViewModel

```ts
interface RecordReviewSheetViewModel
```

- `record: { readonly id: string; readonly title: string; readonly rawContent: string; readonly meta: unknown; }`
- `pr: PullRequestRef`
- `approveState: Resource<void>`
- `requestChangesState: Resource<{ comment: string; }>`
- `closeState: Resource<void>`
- `onApprove(): void`
- `onRequestChanges(comment: string): void`
- `onClose(): void`
- `onCancel(): void`

### PreviewPpaTaskProps

```ts
interface PreviewPpaTaskProps
```

- `mineTask: PpaTaskRecord` — Parsed mine task — if absent, raw text from ConflictSide is shown.
- `theirsTask: PpaTaskRecord` — Parsed theirs task — if absent, raw text from ConflictSide is shown.

### PpaTaskRecord

```ts
interface PpaTaskRecord
```

- `title: string` — Markdown YAML frontmatter fields
- `status: string`
- `context: string`
- `project: string`
- `due: string`
- `body: string` — Markdown body below the frontmatter
- `author: string` — Commit author label
- `whenLabel: string` — Relative time label, e.g. "4m ago"

### PreviewCmsArticleProps

```ts
interface PreviewCmsArticleProps
```

- `mineArticle: CmsArticleRecord`
- `theirsArticle: CmsArticleRecord`
- `frontmatterDiff: readonly FrontmatterDiffLine[]` — Pre-computed frontmatter diff lines for the expander.

### CmsArticleRecord

```ts
interface CmsArticleRecord
```

- `title: string` — Frontmatter fields
- `slug: string`
- `publishedAt: string`
- `authors: readonly string[]`
- `tags: readonly string[]`
- `draft: boolean`
- `body: string` — Raw markdown body
- `author: string` — Contributor label
- `whenLabel: string` — Relative time, e.g. "2h ago"

### PreviewBookmarkProps

```ts
interface PreviewBookmarkProps
```

- `mineBookmark: BookmarkRecord`
- `theirsBookmark: BookmarkRecord`

### BookmarkRecord

```ts
interface BookmarkRecord
```

- `id: string`
- `url: string`
- `title: string`
- `tags: readonly string[]`
- `notes: string`
- `addedAt: string`
- `whenLabel: string` — Relative time label

### PreviewPasswordEntryProps

```ts
interface PreviewPasswordEntryProps
```

- `mineEntry: PasswordRecord`
- `theirsEntry: PasswordRecord`

### PasswordRecord

```ts
interface PasswordRecord
```

- `accountName: string` — Human-readable account label, e.g. "GitHub (work)"
- `username: string`
- `passwordMasked: true` — Always masked here — "tap to reveal" stub.
- `has2FaSeed: boolean`
- `url: string`
- `notes: string`
- `whenLabel: string` — Relative time label
- `deviceLabel: string` — Device label

### RepoSelection

```ts
interface RepoSelection
```

- `providerId: RepoProviderId`
- `owner: string`
- `name: string`
- `provider: RepoProviderId`
- `host: string`

### DemoRepoRef

```ts
interface DemoRepoRef
```

- `owner: string`
- `name: string`

### PullRequestRef

PullRequestRef — minimal PR reference mirroring the shape Track 3 uses
in

```ts
interface PullRequestRef
```

- `number: number`
- `state: "merged" | "open" | "closed"`
- `title: string`
- `htmlUrl: string`

### EditorialConfigSource

Provider-agnostic file-reading interface.
PPA wires the iso-git read in later; tests inject a fake.

```ts
interface EditorialConfigSource
```

- `readFile(path: string): Promise<string | null>`

## Constants

### defaultLightTheme

```ts
const defaultLightTheme: Theme
```

### RepoModeSchema

Zod schema for RepoMode.

Uses .passthrough() for forward-compatibility — unknown fields are
preserved rather than rejected.
Never uses .optional() — missing fields signal a parse failure.

```ts
const RepoModeSchema: z.ZodType<RepoMode, z.ZodTypeDef, RepoMode>
```

### ProviderIdSchema

```ts
const ProviderIdSchema: z.ZodType<RepoProviderId, z.ZodTypeDef, RepoProviderId>
```

### RepoSelectionSchema

```ts
const RepoSelectionSchema: z.ZodType<RepoSelection, z.ZodTypeDef, RepoSelection>
```

### RecordEditorialStateSchema

Zod schema for RecordEditorialState.
Uses .passthrough() on each variant for forward-compatibility.

```ts
const RecordEditorialStateSchema: z.ZodType<RecordEditorialState, z.ZodTypeDef, RecordEditorialState>
```

### RepoEditorialModeSchema

Zod schema for RepoEditorialMode.
Uses .passthrough() on each variant for forward-compatibility.

```ts
const RepoEditorialModeSchema: z.ZodType<RepoEditorialMode, z.ZodTypeDef, RepoEditorialMode>
```

### PullRequestRefSchema

Zod schema for PullRequestRef.

```ts
const PullRequestRefSchema: z.ZodType<PullRequestRef, z.ZodTypeDef, PullRequestRef>
```
