# zidane-ui/app

Import: `import { … } from 'zidane-ui/app'`. Source: [ui/src/app/index.ts](https://github.com/Tahul/zidane/blob/main/ui/src/app/index.ts).

## Components

### `AppHostProvider`

Component.

Provides an [`AppHost`](https://zidane.si/docs/reference/ui-app-headless.md#AppHost) to the components below it, `host.chat` through a `ChatProvider` and `host.palette` through a `PaletteProvider` when present.

```ts
function AppHostProvider(props: AppHostProviderProps): Element
```

**Parameters**

- `props` (`AppHostProviderProps`): Provider props: the `host`, optional `chatLabels`, and the `children` to render.

**Returns** `Element`

**Props**

- `chatLabels?` (`Partial<{ addDiagnosticToChat: (message: string) => string; addFavorite: string; additionalInstructions: string; additionalInstructionsHint: string; additionalInstructionsPlaceholder: string; address: string; addressPlaceholder: string; addToChat: string; agent: string; agentDraftNote: string; agentProfiles: string; archive: string; archived: string; attachFile: string; attachmentEmpty: (name: string) => string; attachmentExportPdf: (name: string) => string; attachmentNotices: string; attachmentReadFailed: (name: string) => string; attachments: string; attachmentTitle: (name: string, detail: string) => string; attachmentTooLarge: (name: string, size: string, limit: string) => string; attachmentUnreadableImage: (name: string) => string; attachmentUnsupported: (name: string) => string; authCancel: string; authCard: string; authCopied: string; authCopyDetails: string; authEnterCode: (code: string) => string; authFailed: (message: string) => string; authFixed: string; authHeadline: (auth: AuthFailure, label: string) => string; authOpeningBrowser: string; authOpenPage: string; authPaste: string; authPasteSend: string; authSettings: string; authSettingsHint: string; authSignedIn: string; authSignedInRetrying: string; authSignInAgain: string; authSwitching: (account: string) => string; authTryAgain: string; authUseAccount: (account: string) => string; authWaiting: string; autoDenied: string; back: string; badgeCopied: string; badgeNoComposer: string; badgeOpenIn: (app: string) => string; badgeOpenInBrowserPane: string; badgePathUnavailable: string; blockRunAgain: string; blockRunAttach: string; blockRunAttachmentName: string; blockRunCopy: string; blockRunFailed: (reason: string) => string; blockRunHere: string; blockRunInTerminal: string; blockRunMenu: string; blockRunNeedsTerminal: string; blockRunning: string; blockRunOutput: string; blockRunRemove: string; blockRunStop: string; blockRunStopped: string; blockRunTimedOut: string; blockRunTruncated: string; cancel: string; chats: string; chipBranch: (name: string) => string; chipChat: (name: string) => string; chipCommand: (name: string) => string; chipCommit: (sha: string) => string; chipFactAliases: string; chipFactBranch: string; chipFactChanges: string; chipFactContext: string; chipFactDate: string; chipFactModel: string; chipFactPrompts: string; chipFactSize: string; chipFactTurns: string; chipFactUpdated: string; chipLoading: string; chipNotAmongChats: string; chipNotFound: (title: string) => string; chipRemoteFile: (path: string, host: string) => string; chipSessionError: string; chipSessionRunning: string; chipSkill: (name: string) => string; chipTokens: (count: string) => string; chipUntitledChat: string; chooseModel: string; clickToAnswer: string; close: string; closeTab: (name: string) => string; code: string; collapseGroup: (label: string) => string; commandDetail: string; commandSuggestions: string; completionSuggestions: string; composerBadges: string; composerLabel: string; composerPlaceholder: string; composerPlaceholderQueue: string; composerPlaceholderSteer: string; confirm: string; confirmNo: string; confirmYes: string; contextActiveSkills: string; contextAutoCompact: (pct: number) => string; contextCacheRead: string; contextCacheWrite: string; contextClickToCopy: (hasDescription: boolean) => string; contextCopied: string; contextDeferred: string; contextDetail: string; contextEstimated: string; contextFreshInput: string; contextLastTurn: string; contextNoBreakdown: string; contextOutput: string; contextRunCount: (runs: number) => string; contextSessionCost: string; contextSummary: (used: string, window: string, pct: number) => string; contextSummaryCost: (summary: string, cost: string) => string; contextWindow: string; continue: string; continueHint: string; continueRun: string; copied: string; copy: string; copyCode: string; copyDiagramSource: string; copyImage: string; copyImageBase64: string; copyImageDataUrl: string; current: string; declineSecret: string; declineSecretHint: string; defaultAgent: string; delete: string; diagnosticsCheckedBy: (source: string) => string; diagnosticsChecking: string; diagnosticsNone: string; diagram: (type: string | null) => string; diagramError: (message: string, at: string) => string; diagramErrorAt: (line: number, column: number) => string; dismiss: string; dismissHint: string; dotAttached: string; dotAttention: string; dotCompleted: string; dotError: string; dotIdle: string; dotPending: string; dotRunning: string; earlierHistory: string; earlierLines: (count: number) => string; earlierMessages: (count: number) => string; editorConflict: string; editorConflictDetail: string; editorEmpty: string; editorOverwrite: string; editorReadFailed: (message: string) => string; editorReload: string; effortLevel: (level: string) => string; errorFailed: (region: string) => string; errorStack: string; errorTryAgain: string; estimateDue: string; estimateDueTitle: string; estimateOver: string; estimateOverTitle: string; estimatePausedTitle: (left: string) => string; estimateProblemTitle: (reason: string, message?: string) => string; estimateTitle: (left: string) => string; expandGroup: (label: string) => string; favorites: string; filePath: (path: string) => string; fileReadOnly: (path: string) => string; fileTree: string; fileUnsaved: string; fileUnsavedTitle: (path: string) => string; filterActivities: { '1d': string; '30d': string; '3d': string; '7d': string; all: string }; filterActivity: string; filterAgent: string; filterAllAgents: string; filterAllAgentsOption: string; filterAllProjects: string; filterAllProjectsOption: string; filterAny: string; filterNoAgents: string; filterNoProjects: string; filterOrigin: string; filterOrigins: { all: string; scheduled: string; unscheduled: string }; filterPicked: (count: number) => string; filterProject: string; filterSaveView: string; filterSessions: string; filterSort: string; filterSorts: { alphabetical: string; createdTime: string; recency: string }; filterSpaceDefault: string; filterStatus: string; filterStatuses: { active: string; all: string; archived: string }; filterView: string; filterViews: { agent: string; date: string; host: string; none: string; project: string; state: string }; findClose: string; findCloseHint: string; findInConversation: string; findInConversationClose: string; findInFile: string; findMatchCase: string; findNext: string; findNextHint: string; findNoResults: string; findPrevious: string; findPreviousHint: string; findReferences: string; fitDiagram: string; foldOutcomes: (ok: number, failed: number) => string; foldSteps: (count: number) => string; generatedImage: string; generatingImage: (model: string | null) => string; generatingImages: (count: number, model: string | null) => string; goToDefinition: string; goToStep: (step: string) => string; hostBadgeTitle: (host: string, status: string) => string; hostCollapse: (name: string) => string; hostExpand: (name: string) => string; hostOpenSettings: (host: string) => string; hostPicker: string; hostRunsOn: (name: string) => string; hostRunsOnSection: string; hostStatusError: (status: string, error: string) => string; hostStatuses: { 'auth-refused': string; connected: string; connecting: string; identity: string; local: string; offline: string; reconnecting: string; 'version-mismatch': string }; hostThisMachine: string; imageModelReferences: string; imageModelReferencesHint: string; imageModelsNone: string; imageModelsPill: string; imageModelTransparency: string; imageModelTransparencyHint: string; inboxEmpty: string; inboxInProgress: string; inboxNeedsYou: string; inboxPreviousSession: string; inboxRecent: string; inputCaption: string; interactionAnsweredBy: (who: string) => string; interactionApproved: string; interactionDismissed: string; interactionPlan: string; interactionQuestion: string; interactionRejected: string; interactionRevise: string; interactionsLabel: string; interactionWaitingAnswers: string; interactionWaitingDecision: string; justNow: string; loadEarlierHistory: string; loadFullOutput: string; loading: string; loadingEarlierHistory: string; loadingEarlierMessages: string; loadingFullOutput: string; mentionSuggestions: string; modalBack: string; modalFailed: string; modelAccountBlocked: (provider: string) => string; modelChain: string; modelChainNoModel: string; modelChainOrderHint: string; modelFallback: string; modelFallbackHint: string; modelFallbackPosition: (n: number) => string; modelFeatures: string; modelForNextChat: string; modelInUse: string; modelKinds: string; modelLegacy: string; modelLegacyHint: string; modelNotInCatalog: string; modelNotInCatalogHint: string; modelOptions: string; modelOptionsEmpty: string; modelOptionsHint: string; modelPrimary: string; modelPrimaryHint: string; modelReasoning: string; modelReasoningHint: string; modelRemoveFallback: (n: number) => string; modelRemoveFromChain: string; modelRemovePrimary: string; modelsChatTab: string; modelsCustomId: string; modelServing: (picked: string, serving: string) => string; modelServingFallback: string; modelsImageTab: string; modelsInUse: string; modelsJumpHint: (provider: string, digit: number | null) => string; modelsJumpToProvider: string; modelsLegacy: string; modelsLegacyHidden: (count: number) => string; modelsLoadFailed: (error: string) => string; modelsLoading: string; modelsNoMatch: (query: string) => string; modelsNoProviders: string; modelsSelected: (count: number) => string; modelsShowLegacy: string; modelsShowLegacyHint: string; modelsShownOf: (shown: number, total: number) => string; modelsTypeAnId: string; modelUnavailable: string; modelVerdictTitle: (approved: boolean, model: string, confidence: string | null) => string; modelVia: string; modelViaHint: (modelId: string, provider: string) => string; months: readonly string[]; moreActions: string; moreAttachments: (count: number) => string; newSession: string; newSessionIn: (label: string) => string; next: string; noAnswer: string; noMatches: (query: string) => string; noReferences: string; noSessions: string; noSuggestions: string; of: (index: number, count: number) => string; openFiles: string; openGroup: (label: string) => string; openImageFile: (path: string) => string; openInNewTab: string; openNamedSession: (title: string) => string; openSecrets: string; openSession: string; outputCaption: string; paletteBack: string; paletteBackHint: string; paletteChoose: string; paletteEmpty: string; paletteFilter: string; paletteLabel: string; paletteLoading: string; paletteNoMatch: (query: string) => string; paletteOpen: string; paletteRun: string; paletteScopeHint: string; paletteTurnOff: string; paletteTurnOn: string; pickModel: string; pickOneOrMore: string; planApprove: string; planReject: string; planRejectAndStop: string; planRejectNote: string; planRejectPlaceholder: string; planRevise: string; planReviseNote: string; planRevisePlaceholder: string; planSendRevision: string; previewFailed: (name: string) => string; previewTooLarge: (name: string, size: string) => string; previous: string; questionCount: (count: number) => string; queueHint: string; queueMessage: string; reasoningEffort: string; receiptAgent: string; receiptAgentSwitched: string; receiptCost: string; receiptDuration: string; receiptModel: string; receiptModelOthers: (others: number) => string; receiptSpeed: string; receiptTokens: (sent: string, received: string) => string; receiptTurns: (turns: number) => string; reload: string; removeAttachment: (name: string) => string; removeFavorite: string; rename: string; requiredHint: string; requiredMark: string; resetsAt: (time: string) => string; retry: string; retryHint: string; runningFor: (elapsed: string) => string; runReceipt: string; runRetrying: (attempt: number, max: number, source?: string) => string; runWaiting: string; saveSecurely: string; screenBack: (screen: string) => string; screenBackKey: string; scriptCallsDone: (done: number) => string; scriptCallsFailed: (failed: number) => string; scriptCallsOf: (done: number, total: number) => string; scriptCallsRunning: (running: number) => string; scriptLineCalls: (calls: number) => string; scriptLiveOutput: string; scriptLoop: string; scriptLoopRound: (round: number) => string; scriptLoopRounds: (rounds: number) => string; scriptLoopRuns: (runs: number) => string; scriptOptionOutput: string; scriptOptions: string; scriptOptionTimeout: string; scriptOptionTokens: (tokens: number) => string; scriptPercent: (percent: number) => string; scriptProgress: string; scriptWaitLeft: (seconds: number) => string; searchModels: string; searchModelsPlaceholder: string; searchSessions: string; secretDeclined: (name: string) => string; secretNote: (ttl: SecretTtl) => string; secretReused: (name: string) => string; secretSaved: (name: string, left: SecretTimeLeft) => string; secretSent: (name: string) => string; secretTtlLabel: string; secretTtlName: (ttl: SecretTtl) => string; secretValueLabel: string; secretValuePlaceholder: string; selectTurnCopied: string; selectTurnCopy: string; selectTurnDetails: string; selectTurnExit: string; selectTurnFork: string; selectTurnForkFailed: string; selectTurnMove: string; selectTurnNothingToCopy: string; selectTurnSelecting: string; sendAnswers: (count: number) => string; sendHint: string; sendMessage: string; sessionActions: string; sessionEventLabel: (title: string, verb: string, needsAttention: boolean) => string; sessions: string; setModelOptions: string; shellCancelled: string; shellExit: (code: number) => string; shellKilled: string; showDiagram: string; showDiagramSource: string; showMore: string; showMoreAttachments: (count: number) => string; showOutput: string; showSystemPrompt: string; signInCopyCode: string; signInDone: (account?: string) => string; signInEnterCode: (host: string) => string; signInFailed: string; signInInBrowser: (service: string) => string; signInNotOpened: string; signInOpenPage: string; signInPaste: string; signInStarting: string; signInSubmit: string; skip: string; spawnResumes: (childId: string) => string; spawnTask: string; stateCompleted: string; stateError: string; stateIdle: string; stateRunning: string; steerHint: string; steerMessage: string; stepDone: string; stepFailed: string; stepOf: (index: number, count: number) => string; stepResuming: string; stepRunning: string; steps: string; stepWaiting: string; stopCommand: string; stopHint: string; stopped: string; stoppedByYou: string; stopRun: string; switchAgent: string; switchingAgent: string; switchModel: string; terminal: string; terminalExited: (code: number) => string; terminalLoadFailed: (message: string) => string; thinkingLive: string; thoughtFor: (seconds: number) => string; thoughts: string; thoughtTimes: (count: number) => string; today: string; toolAttachments: string; transcriptLoading: string; turnMenuCopy: string; turnMenuCopySelection: string; turnMenuCopySelectionFailed: string; turnMenuDelete: string; turnMenuEdit: string; turnMenuEditNeedsComposer: string; turnMenuFork: string; turnMenuRewind: string; turnMenuSent: (age: string) => string; turnMenuStopFirst: (label: string) => string; turnsCompacted: string; unarchive: string; unsupportedInteraction: (tool: string) => string; viewAttachment: (name: string) => string; waitingForSlot: (max: number) => string; workerContinues: string; workerModel: string; workerSystemPrompt: string; workerSystemReplaced: string; workerType: string; working: string; wrapLines: string; yesterday: string; zoomFit: string; zoomInDiagram: string; zoomOutDiagram: string; zoomPercent: (percent: number) => string }>`): Overrides for the chat components' strings, passed to `ChatProvider`. Keys you leave out keep the default English label.
- `children` (`ReactNode`): The app's components.
- `host` (`AppHost`): Every area's host the app offers.
- `loadExtensionModule?` (`ExtensionModuleLoader`): Loads an extension's `ui.tsx` module for the extension components (`ExtensionsProvider`'s `loadModule`). Absent: extension items draw their data.

Source: [ui/src/app/AppHostProvider.tsx:62](https://github.com/Tahul/zidane/blob/main/ui/src/app/AppHostProvider.tsx#L62)

### `ModalProvider`

Component.

Holds the modal slot and its stacked layer, and renders them above `children` (see the top of this file).

```ts
function ModalProvider(props: ModalProviderProps): Element
```

**Parameters**

- `props` (`ModalProviderProps`): The `children` and an optional `chromeInset`.

**Returns** `Element`

**Props**

- `children` (`ReactNode`): The app: inert while a modal is open.
- `chromeInset?` (`ModalChromeInset`): Native window chrome over the page; absent: a 44px band with nothing to keep clear.

Source: [ui/src/app/ModalProvider.tsx:149](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L149)

### `ModalShell`

Component.

Standard modal shell — title row + body + optional footer. Every modal in
the app uses this so spacing, borders, and the close-X stay consistent.

```ts
function ModalShell(props: ModalShellProps): Element
```

**Parameters**

- `props` (`ModalShellProps`): The title, body, footer, size and close handlers.

**Returns** `Element`

**Props**

- `children` (`ReactNode`): The body.
- `className?` (`string`): Extra classes on the panel.
- `description?` (`ReactNode`): A line under the title.
- `footer?` (`ReactNode`): Optional element rendered in the bottom-right corner (e.g. primary action).
- `onBack?` (`() => void`): Optional back-navigation handler. When set, the header renders a
  chevron-left button to the left of the title. Used by the surfaces
  Settings opens (Safelist, the Claude Code import) so the sub-modal can
  return to Settings instead of dead-ending on close. `onClose` is still
  called for the X button and Esc.
- `onClose` (`() => void`): The close button and Esc.
- `scrollBody?` (`boolean`): When false, the shell body clips instead of scrolling and the child
  owns its scroll containers. For modals with an internal fixed rail +
  scrolling panel (Settings): a scrolling shell body would drag the rail
  along whenever the content outgrows the viewport. Default true.
- `size?` (`'medium' | 'large' | 'xlarge' | 'xxlarge'`): Size preset, by panel width: `medium` 560px for forms / pickers;
  `large` 760px for content-rich surfaces (the model picker, the
  safelist); `xlarge` 920px for diff-heavy modals (the file-edit review, `chat/composer/ApprovalDock.tsx`);
  `xxlarge` 1040px for workspace-style surfaces with their own internal
  navigation. It also raises the height cap from 85vh to 88vh. Every
  preset is clamped by `max-w-[92vw]` on narrower windows.
- `style?` (`CSSProperties`): Inline styles on the panel.
- `title` (`ReactNode`): Plain string for most modals; a ReactNode lets a modal animate its own
  heading content (Settings crossfades the active tab's label in place).

Source: [ui/src/app/ModalProvider.tsx:568](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L568)

### `ScreenShell`

Component.

Two-column full-window shell mirroring the chat layout: a sidebar-width
left column on the tray tone (traffic-light spacer, back button,
then whatever navigation the surface provides) and a main column with a
heading row over the content. Escape and the back button both call
`onClose`.

```ts
function ScreenShell(props: ScreenShellProps): Element
```

**Parameters**

- `props` (`ScreenShellProps`): The heading, the left column, the body and `onClose`.

**Returns** `Element`

**Props**

- `aside` (`{ nav: ReactNode; search?: ReactNode }`): Left column: `search` shares the title row; `nav` fills the rest.
- `asideFooter?` (`ReactNode`): Pinned to the bottom of the left column.
- `children` (`ReactNode`): The main column's body.
- `className?` (`string`): Extra classes on the root.
- `description?` (`ReactNode`): A line under the heading.
- `headerActions?` (`ReactNode`): Right-aligned controls on the main header row.
- `lead?` (`ReactNode`): Before the heading and its line (a mark).
- `onClose` (`() => void`): The back button and Esc.
- `screenLabel` (`string`): Label for the back affordance in the left column, e.g. "Settings".
- `style?` (`CSSProperties`): Inline styles on the root.
- `title` (`ReactNode`): Heading for the main column (stamped with MODAL_TITLE_ID).

Source: [ui/src/app/ModalProvider.tsx:654](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L654)

## Functions

### `useAppHost`

Function.

Returns the [`AppHost`](https://zidane.si/docs/reference/ui-app-headless.md#AppHost) from the nearest [`AppHostProvider`](https://zidane.si/docs/reference/ui-app.md#AppHostProvider). Throws when called outside one.

```ts
function useAppHost(): AppHost
```

**Returns** `AppHost`

Source: [ui/src/app/AppHostProvider.tsx:131](https://github.com/Tahul/zidane/blob/main/ui/src/app/AppHostProvider.tsx#L131)

### `useConfirm`

Function.

A function that opens a ConfirmDialog above whatever is open and resolves with the choice. Needs a `ModalProvider`.

```ts
function useConfirm(): (options: ConfirmOptions) => Promise<boolean>
```

**Returns** `(options: ConfirmOptions) => Promise<boolean>`

Source: [ui/src/app/useConfirm.tsx:46](https://github.com/Tahul/zidane/blob/main/ui/src/app/useConfirm.tsx#L46)

### `useModal`

Function.

The modal layer of the nearest [`ModalProvider`](https://zidane.si/docs/reference/ui-app.md#ModalProvider); throws outside one.

```ts
function useModal(): ModalContextValue
```

**Returns** `ModalContextValue`

Source: [ui/src/app/ModalProvider.tsx:392](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L392)

### `useOptionalConfirm`

Function.

[`useConfirm`](https://zidane.si/docs/reference/ui-app.md#useConfirm) where a `ModalProvider` may be missing (a component mounted on its own, a gallery):
`null` there, so the caller leaves out what must ask first.

```ts
function useOptionalConfirm(): ((options: ConfirmOptions) => Promise<boolean>) | null
```

**Returns** `((options: ConfirmOptions) => Promise<boolean>) | null`

Source: [ui/src/app/useConfirm.tsx:55](https://github.com/Tahul/zidane/blob/main/ui/src/app/useConfirm.tsx#L55)

### `useOptionalModal`

Function.

The modal layer, or `null` outside a [`ModalProvider`](https://zidane.si/docs/reference/ui-app.md#ModalProvider) (a component that can do without it).

```ts
function useOptionalModal(): ModalContextValue | null
```

**Returns** `ModalContextValue | null`

Source: [ui/src/app/ModalProvider.tsx:387](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L387)

### `useResource`

Function.

The resource `spec` describes, kept current (`null`: nothing). Re-renders only when its snapshot changes.

```ts
function useResource<T, Topic extends string = string>(
  store: ResourceStore<Topic>,
  spec: ResourceSpec<T, Topic> | null,
): ResourceSnapshot<T>
```

**Type parameters**

- `T`
- `Topic` (`extends string`, default `string`)

**Parameters**

- `store` (`ResourceStore<Topic>`): The store holding it.
- `spec` (`ResourceSpec<T, Topic> | null`): The resource; its key names it, so a new spec object with the same key reads the same entry.

**Returns** `ResourceSnapshot<T>`

Source: [ui/src/app/useResource.ts:17](https://github.com/Tahul/zidane/blob/main/ui/src/app/useResource.ts#L17)

## Constants

### `MODAL_TITLE_ID`

Constant.

Stable id contract — every `<ModalShell>` stamps this id onto its
title `<h2>` so the scrim can reference it via `aria-labelledby`.
Only one modal is open at a time, so a static id is collision-free.

```ts
const MODAL_TITLE_ID = 'zg-modal-title'
```

Source: [ui/src/app/ModalProvider.tsx:57](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L57)

## Interfaces

### `AppHostProviderProps`

Interface.

Props for [`AppHostProvider`](https://zidane.si/docs/reference/ui-app.md#AppHostProvider).

```ts
interface AppHostProviderProps
```

**Properties**

- `chatLabels?` (`Partial<{ addDiagnosticToChat: (message: string) => string; addFavorite: string; additionalInstructions: string; additionalInstructionsHint: string; additionalInstructionsPlaceholder: string; address: string; addressPlaceholder: string; addToChat: string; agent: string; agentDraftNote: string; agentProfiles: string; archive: string; archived: string; attachFile: string; attachmentEmpty: (name: string) => string; attachmentExportPdf: (name: string) => string; attachmentNotices: string; attachmentReadFailed: (name: string) => string; attachments: string; attachmentTitle: (name: string, detail: string) => string; attachmentTooLarge: (name: string, size: string, limit: string) => string; attachmentUnreadableImage: (name: string) => string; attachmentUnsupported: (name: string) => string; authCancel: string; authCard: string; authCopied: string; authCopyDetails: string; authEnterCode: (code: string) => string; authFailed: (message: string) => string; authFixed: string; authHeadline: (auth: AuthFailure, label: string) => string; authOpeningBrowser: string; authOpenPage: string; authPaste: string; authPasteSend: string; authSettings: string; authSettingsHint: string; authSignedIn: string; authSignedInRetrying: string; authSignInAgain: string; authSwitching: (account: string) => string; authTryAgain: string; authUseAccount: (account: string) => string; authWaiting: string; autoDenied: string; back: string; badgeCopied: string; badgeNoComposer: string; badgeOpenIn: (app: string) => string; badgeOpenInBrowserPane: string; badgePathUnavailable: string; blockRunAgain: string; blockRunAttach: string; blockRunAttachmentName: string; blockRunCopy: string; blockRunFailed: (reason: string) => string; blockRunHere: string; blockRunInTerminal: string; blockRunMenu: string; blockRunNeedsTerminal: string; blockRunning: string; blockRunOutput: string; blockRunRemove: string; blockRunStop: string; blockRunStopped: string; blockRunTimedOut: string; blockRunTruncated: string; cancel: string; chats: string; chipBranch: (name: string) => string; chipChat: (name: string) => string; chipCommand: (name: string) => string; chipCommit: (sha: string) => string; chipFactAliases: string; chipFactBranch: string; chipFactChanges: string; chipFactContext: string; chipFactDate: string; chipFactModel: string; chipFactPrompts: string; chipFactSize: string; chipFactTurns: string; chipFactUpdated: string; chipLoading: string; chipNotAmongChats: string; chipNotFound: (title: string) => string; chipRemoteFile: (path: string, host: string) => string; chipSessionError: string; chipSessionRunning: string; chipSkill: (name: string) => string; chipTokens: (count: string) => string; chipUntitledChat: string; chooseModel: string; clickToAnswer: string; close: string; closeTab: (name: string) => string; code: string; collapseGroup: (label: string) => string; commandDetail: string; commandSuggestions: string; completionSuggestions: string; composerBadges: string; composerLabel: string; composerPlaceholder: string; composerPlaceholderQueue: string; composerPlaceholderSteer: string; confirm: string; confirmNo: string; confirmYes: string; contextActiveSkills: string; contextAutoCompact: (pct: number) => string; contextCacheRead: string; contextCacheWrite: string; contextClickToCopy: (hasDescription: boolean) => string; contextCopied: string; contextDeferred: string; contextDetail: string; contextEstimated: string; contextFreshInput: string; contextLastTurn: string; contextNoBreakdown: string; contextOutput: string; contextRunCount: (runs: number) => string; contextSessionCost: string; contextSummary: (used: string, window: string, pct: number) => string; contextSummaryCost: (summary: string, cost: string) => string; contextWindow: string; continue: string; continueHint: string; continueRun: string; copied: string; copy: string; copyCode: string; copyDiagramSource: string; copyImage: string; copyImageBase64: string; copyImageDataUrl: string; current: string; declineSecret: string; declineSecretHint: string; defaultAgent: string; delete: string; diagnosticsCheckedBy: (source: string) => string; diagnosticsChecking: string; diagnosticsNone: string; diagram: (type: string | null) => string; diagramError: (message: string, at: string) => string; diagramErrorAt: (line: number, column: number) => string; dismiss: string; dismissHint: string; dotAttached: string; dotAttention: string; dotCompleted: string; dotError: string; dotIdle: string; dotPending: string; dotRunning: string; earlierHistory: string; earlierLines: (count: number) => string; earlierMessages: (count: number) => string; editorConflict: string; editorConflictDetail: string; editorEmpty: string; editorOverwrite: string; editorReadFailed: (message: string) => string; editorReload: string; effortLevel: (level: string) => string; errorFailed: (region: string) => string; errorStack: string; errorTryAgain: string; estimateDue: string; estimateDueTitle: string; estimateOver: string; estimateOverTitle: string; estimatePausedTitle: (left: string) => string; estimateProblemTitle: (reason: string, message?: string) => string; estimateTitle: (left: string) => string; expandGroup: (label: string) => string; favorites: string; filePath: (path: string) => string; fileReadOnly: (path: string) => string; fileTree: string; fileUnsaved: string; fileUnsavedTitle: (path: string) => string; filterActivities: { '1d': string; '30d': string; '3d': string; '7d': string; all: string }; filterActivity: string; filterAgent: string; filterAllAgents: string; filterAllAgentsOption: string; filterAllProjects: string; filterAllProjectsOption: string; filterAny: string; filterNoAgents: string; filterNoProjects: string; filterOrigin: string; filterOrigins: { all: string; scheduled: string; unscheduled: string }; filterPicked: (count: number) => string; filterProject: string; filterSaveView: string; filterSessions: string; filterSort: string; filterSorts: { alphabetical: string; createdTime: string; recency: string }; filterSpaceDefault: string; filterStatus: string; filterStatuses: { active: string; all: string; archived: string }; filterView: string; filterViews: { agent: string; date: string; host: string; none: string; project: string; state: string }; findClose: string; findCloseHint: string; findInConversation: string; findInConversationClose: string; findInFile: string; findMatchCase: string; findNext: string; findNextHint: string; findNoResults: string; findPrevious: string; findPreviousHint: string; findReferences: string; fitDiagram: string; foldOutcomes: (ok: number, failed: number) => string; foldSteps: (count: number) => string; generatedImage: string; generatingImage: (model: string | null) => string; generatingImages: (count: number, model: string | null) => string; goToDefinition: string; goToStep: (step: string) => string; hostBadgeTitle: (host: string, status: string) => string; hostCollapse: (name: string) => string; hostExpand: (name: string) => string; hostOpenSettings: (host: string) => string; hostPicker: string; hostRunsOn: (name: string) => string; hostRunsOnSection: string; hostStatusError: (status: string, error: string) => string; hostStatuses: { 'auth-refused': string; connected: string; connecting: string; identity: string; local: string; offline: string; reconnecting: string; 'version-mismatch': string }; hostThisMachine: string; imageModelReferences: string; imageModelReferencesHint: string; imageModelsNone: string; imageModelsPill: string; imageModelTransparency: string; imageModelTransparencyHint: string; inboxEmpty: string; inboxInProgress: string; inboxNeedsYou: string; inboxPreviousSession: string; inboxRecent: string; inputCaption: string; interactionAnsweredBy: (who: string) => string; interactionApproved: string; interactionDismissed: string; interactionPlan: string; interactionQuestion: string; interactionRejected: string; interactionRevise: string; interactionsLabel: string; interactionWaitingAnswers: string; interactionWaitingDecision: string; justNow: string; loadEarlierHistory: string; loadFullOutput: string; loading: string; loadingEarlierHistory: string; loadingEarlierMessages: string; loadingFullOutput: string; mentionSuggestions: string; modalBack: string; modalFailed: string; modelAccountBlocked: (provider: string) => string; modelChain: string; modelChainNoModel: string; modelChainOrderHint: string; modelFallback: string; modelFallbackHint: string; modelFallbackPosition: (n: number) => string; modelFeatures: string; modelForNextChat: string; modelInUse: string; modelKinds: string; modelLegacy: string; modelLegacyHint: string; modelNotInCatalog: string; modelNotInCatalogHint: string; modelOptions: string; modelOptionsEmpty: string; modelOptionsHint: string; modelPrimary: string; modelPrimaryHint: string; modelReasoning: string; modelReasoningHint: string; modelRemoveFallback: (n: number) => string; modelRemoveFromChain: string; modelRemovePrimary: string; modelsChatTab: string; modelsCustomId: string; modelServing: (picked: string, serving: string) => string; modelServingFallback: string; modelsImageTab: string; modelsInUse: string; modelsJumpHint: (provider: string, digit: number | null) => string; modelsJumpToProvider: string; modelsLegacy: string; modelsLegacyHidden: (count: number) => string; modelsLoadFailed: (error: string) => string; modelsLoading: string; modelsNoMatch: (query: string) => string; modelsNoProviders: string; modelsSelected: (count: number) => string; modelsShowLegacy: string; modelsShowLegacyHint: string; modelsShownOf: (shown: number, total: number) => string; modelsTypeAnId: string; modelUnavailable: string; modelVerdictTitle: (approved: boolean, model: string, confidence: string | null) => string; modelVia: string; modelViaHint: (modelId: string, provider: string) => string; months: readonly string[]; moreActions: string; moreAttachments: (count: number) => string; newSession: string; newSessionIn: (label: string) => string; next: string; noAnswer: string; noMatches: (query: string) => string; noReferences: string; noSessions: string; noSuggestions: string; of: (index: number, count: number) => string; openFiles: string; openGroup: (label: string) => string; openImageFile: (path: string) => string; openInNewTab: string; openNamedSession: (title: string) => string; openSecrets: string; openSession: string; outputCaption: string; paletteBack: string; paletteBackHint: string; paletteChoose: string; paletteEmpty: string; paletteFilter: string; paletteLabel: string; paletteLoading: string; paletteNoMatch: (query: string) => string; paletteOpen: string; paletteRun: string; paletteScopeHint: string; paletteTurnOff: string; paletteTurnOn: string; pickModel: string; pickOneOrMore: string; planApprove: string; planReject: string; planRejectAndStop: string; planRejectNote: string; planRejectPlaceholder: string; planRevise: string; planReviseNote: string; planRevisePlaceholder: string; planSendRevision: string; previewFailed: (name: string) => string; previewTooLarge: (name: string, size: string) => string; previous: string; questionCount: (count: number) => string; queueHint: string; queueMessage: string; reasoningEffort: string; receiptAgent: string; receiptAgentSwitched: string; receiptCost: string; receiptDuration: string; receiptModel: string; receiptModelOthers: (others: number) => string; receiptSpeed: string; receiptTokens: (sent: string, received: string) => string; receiptTurns: (turns: number) => string; reload: string; removeAttachment: (name: string) => string; removeFavorite: string; rename: string; requiredHint: string; requiredMark: string; resetsAt: (time: string) => string; retry: string; retryHint: string; runningFor: (elapsed: string) => string; runReceipt: string; runRetrying: (attempt: number, max: number, source?: string) => string; runWaiting: string; saveSecurely: string; screenBack: (screen: string) => string; screenBackKey: string; scriptCallsDone: (done: number) => string; scriptCallsFailed: (failed: number) => string; scriptCallsOf: (done: number, total: number) => string; scriptCallsRunning: (running: number) => string; scriptLineCalls: (calls: number) => string; scriptLiveOutput: string; scriptLoop: string; scriptLoopRound: (round: number) => string; scriptLoopRounds: (rounds: number) => string; scriptLoopRuns: (runs: number) => string; scriptOptionOutput: string; scriptOptions: string; scriptOptionTimeout: string; scriptOptionTokens: (tokens: number) => string; scriptPercent: (percent: number) => string; scriptProgress: string; scriptWaitLeft: (seconds: number) => string; searchModels: string; searchModelsPlaceholder: string; searchSessions: string; secretDeclined: (name: string) => string; secretNote: (ttl: SecretTtl) => string; secretReused: (name: string) => string; secretSaved: (name: string, left: SecretTimeLeft) => string; secretSent: (name: string) => string; secretTtlLabel: string; secretTtlName: (ttl: SecretTtl) => string; secretValueLabel: string; secretValuePlaceholder: string; selectTurnCopied: string; selectTurnCopy: string; selectTurnDetails: string; selectTurnExit: string; selectTurnFork: string; selectTurnForkFailed: string; selectTurnMove: string; selectTurnNothingToCopy: string; selectTurnSelecting: string; sendAnswers: (count: number) => string; sendHint: string; sendMessage: string; sessionActions: string; sessionEventLabel: (title: string, verb: string, needsAttention: boolean) => string; sessions: string; setModelOptions: string; shellCancelled: string; shellExit: (code: number) => string; shellKilled: string; showDiagram: string; showDiagramSource: string; showMore: string; showMoreAttachments: (count: number) => string; showOutput: string; showSystemPrompt: string; signInCopyCode: string; signInDone: (account?: string) => string; signInEnterCode: (host: string) => string; signInFailed: string; signInInBrowser: (service: string) => string; signInNotOpened: string; signInOpenPage: string; signInPaste: string; signInStarting: string; signInSubmit: string; skip: string; spawnResumes: (childId: string) => string; spawnTask: string; stateCompleted: string; stateError: string; stateIdle: string; stateRunning: string; steerHint: string; steerMessage: string; stepDone: string; stepFailed: string; stepOf: (index: number, count: number) => string; stepResuming: string; stepRunning: string; steps: string; stepWaiting: string; stopCommand: string; stopHint: string; stopped: string; stoppedByYou: string; stopRun: string; switchAgent: string; switchingAgent: string; switchModel: string; terminal: string; terminalExited: (code: number) => string; terminalLoadFailed: (message: string) => string; thinkingLive: string; thoughtFor: (seconds: number) => string; thoughts: string; thoughtTimes: (count: number) => string; today: string; toolAttachments: string; transcriptLoading: string; turnMenuCopy: string; turnMenuCopySelection: string; turnMenuCopySelectionFailed: string; turnMenuDelete: string; turnMenuEdit: string; turnMenuEditNeedsComposer: string; turnMenuFork: string; turnMenuRewind: string; turnMenuSent: (age: string) => string; turnMenuStopFirst: (label: string) => string; turnsCompacted: string; unarchive: string; unsupportedInteraction: (tool: string) => string; viewAttachment: (name: string) => string; waitingForSlot: (max: number) => string; workerContinues: string; workerModel: string; workerSystemPrompt: string; workerSystemReplaced: string; workerType: string; working: string; wrapLines: string; yesterday: string; zoomFit: string; zoomInDiagram: string; zoomOutDiagram: string; zoomPercent: (percent: number) => string }>`): Overrides for the chat components' strings, passed to `ChatProvider`. Keys you leave out keep the default English label.
- `children` (`ReactNode`): The app's components.
- `host` (`AppHost`): Every area's host the app offers.
- `loadExtensionModule?` (`ExtensionModuleLoader`): Loads an extension's `ui.tsx` module for the extension components (`ExtensionsProvider`'s `loadModule`). Absent: extension items draw their data.

Source: [ui/src/app/AppHostProvider.tsx:46](https://github.com/Tahul/zidane/blob/main/ui/src/app/AppHostProvider.tsx#L46)

### `LayerOptions`

Interface.

Options of `ModalContextValue.openLayer`.

```ts
interface LayerOptions
```

**Properties**

- `labelledBy` (`string`): Id of the layer's own title element.
- `onDismiss` (`() => void`): Called when the layer is dismissed from outside (Esc, scrim, slot closed, replaced).

Source: [ui/src/app/ModalProvider.tsx:63](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L63)

### `ModalChromeInset`

Interface.

The band native window chrome covers, in device-independent pixels.

```ts
interface ModalChromeInset
```

**Properties**

- `leading` (`number`): Width kept clear at the start of that band; 0 for none.
- `top` (`number`): Height of the top band (the traffic lights' row).

Source: [ui/src/app/ModalProvider.tsx:123](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L123)

### `ModalContextValue`

Interface.

What [`useModal`](https://zidane.si/docs/reference/ui-app.md#useModal) returns.

```ts
interface ModalContextValue
```

**Properties**

- `activeId` (`string | null`): Whose surface fills the slot. Callers that pass `id` on `open` can tell
  "still mine" from "replaced by another modal" without relying on an
  unmount, which StrictMode simulates in development.
- `close` (`(id?: string) => void`): Close the slot; with `id`, only while that surface still owns it (a self-close).
- `closeLayer` (`() => void`): Removes the layer without calling its `onDismiss`.
- `isOpen` (`boolean`): A slot surface or a stacked layer is up.
- `open` (`(node: ReactNode, options?: ModalOpenOptions) => void`): Show `node` in the slot, replacing what is there.
- `openLayer` (`(node: ReactNode, options: LayerOptions) => void`): Shows `node` above the current slot without replacing it.
- `setDismissHandler` (`(handler: (() => void) | null, owner?: () => void) => void`): What Esc and a scrim click call instead of `close` (a shell registers its `onClose`); `null` clears it.
  With `owner`, `null` clears only while `owner` is still the handler: a surface that leaves after the
  next one registered never clears that one's.

Source: [ui/src/app/ModalProvider.tsx:95](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L95)

### `ModalOpenOptions`

Interface.

Options of `ModalContextValue.open`.

```ts
interface ModalOpenOptions
```

**Properties**

- `id?` (`string`): Names the surface, so it can tell "still mine" (`ModalContextValue.activeId`) and close only itself.
- `scrim?` (`ModalScrimKind`): The dialog scrim: `blur` (default) frosts the window behind a dialog you
  stay in; `dim` only darkens it, for a quick surface (the palette) that
  comes and goes with a key and must never cost a frame.
- `variant?` (`ModalVariant`): `dialog` (default) or `screen`.

Source: [ui/src/app/ModalProvider.tsx:71](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L71)

### `ModalProviderProps`

Interface.

Props for [`ModalProvider`](https://zidane.si/docs/reference/ui-app.md#ModalProvider).

```ts
interface ModalProviderProps
```

**Properties**

- `children` (`ReactNode`): The app: inert while a modal is open.
- `chromeInset?` (`ModalChromeInset`): Native window chrome over the page; absent: a 44px band with nothing to keep clear.

Source: [ui/src/app/ModalProvider.tsx:137](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L137)

### `ModalShellProps`

Interface.

Props for [`ModalShell`](https://zidane.si/docs/reference/ui-app.md#ModalShell).

```ts
interface ModalShellProps
```

**Properties**

- `children` (`ReactNode`): The body.
- `className?` (`string`): Extra classes on the panel.
- `description?` (`ReactNode`): A line under the title.
- `footer?` (`ReactNode`): Optional element rendered in the bottom-right corner (e.g. primary action).
- `onBack?` (`() => void`): Optional back-navigation handler. When set, the header renders a
  chevron-left button to the left of the title. Used by the surfaces
  Settings opens (Safelist, the Claude Code import) so the sub-modal can
  return to Settings instead of dead-ending on close. `onClose` is still
  called for the X button and Esc.
- `onClose` (`() => void`): The close button and Esc.
- `scrollBody?` (`boolean`): When false, the shell body clips instead of scrolling and the child
  owns its scroll containers. For modals with an internal fixed rail +
  scrolling panel (Settings): a scrolling shell body would drag the rail
  along whenever the content outgrows the viewport. Default true.
- `size?` (`'medium' | 'large' | 'xlarge' | 'xxlarge'`): Size preset, by panel width: `medium` 560px for forms / pickers;
  `large` 760px for content-rich surfaces (the model picker, the
  safelist); `xlarge` 920px for diff-heavy modals (the file-edit review, `chat/composer/ApprovalDock.tsx`);
  `xxlarge` 1040px for workspace-style surfaces with their own internal
  navigation. It also raises the height cap from 85vh to 88vh. Every
  preset is clamped by `max-w-[92vw]` on narrower windows.
- `style?` (`CSSProperties`): Inline styles on the panel.
- `title` (`ReactNode`): Plain string for most modals; a ReactNode lets a modal animate its own
  heading content (Settings crossfades the active tab's label in place).

Source: [ui/src/app/ModalProvider.tsx:485](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L485)

### `ScreenShellProps`

Interface.

Props for [`ScreenShell`](https://zidane.si/docs/reference/ui-app.md#ScreenShell).

```ts
interface ScreenShellProps
```

**Properties**

- `aside` (`{ nav: ReactNode; search?: ReactNode }`): Left column: `search` shares the title row; `nav` fills the rest.
- `asideFooter?` (`ReactNode`): Pinned to the bottom of the left column.
- `children` (`ReactNode`): The main column's body.
- `className?` (`string`): Extra classes on the root.
- `description?` (`ReactNode`): A line under the heading.
- `headerActions?` (`ReactNode`): Right-aligned controls on the main header row.
- `lead?` (`ReactNode`): Before the heading and its line (a mark).
- `onClose` (`() => void`): The back button and Esc.
- `screenLabel` (`string`): Label for the back affordance in the left column, e.g. "Settings".
- `style?` (`CSSProperties`): Inline styles on the root.
- `title` (`ReactNode`): Heading for the main column (stamped with MODAL_TITLE_ID).

Source: [ui/src/app/ModalProvider.tsx:620](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L620)

## Types

### `ModalScrimKind`

Type.

The dialog scrim: `blur` frosts the window behind, `dim` only darkens it.

```ts
type ModalScrimKind = 'blur' | 'dim'
```

Source: [ui/src/app/ModalProvider.tsx:85](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L85)

### `ModalVariant`

Type.

How the slot presents its surface: a centred dialog over a scrim, or the whole window.

```ts
type ModalVariant = 'dialog' | 'screen'
```

Source: [ui/src/app/ModalProvider.tsx:60](https://github.com/Tahul/zidane/blob/main/ui/src/app/ModalProvider.tsx#L60)
