Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/smooth-lights-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
20 changes: 10 additions & 10 deletions docs/extension-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ commands never pay OpenTUI's native-library extraction).

## Four-edge pane system

`src/ui/lib/extensionPanes.ts` owns open state, availability, and one rectangle
`src/ui/ext/extensionPanes.ts` owns open state, availability, and one rectangle
plan for panes, dividers, and review bounds. Left/right panes consume columns;
top/bottom panes consume rows from the central review column, outside review
stream coordinates.
Expand Down Expand Up @@ -113,7 +113,7 @@ inputs are unchanged so row memoization can hold. The epoch is owned by
`ctx.highlights.refresh`, using the shared scoped-epoch policy in
`src/ui/lib/scopedEpochs.ts` — the same module `src/ui/fileViews/state.ts`
delegates to — and the shared bounded `readDocument` capability lives in
`src/ui/lib/extensionDocumentReader.ts`.
`src/ui/ext/extensionDocumentReader.ts`.

Application is paint-time by construction. `src/ui/diff/lineHighlightPaint.ts`
owns the one mapping from source coordinates (raw code-unit offsets) to
Expand Down Expand Up @@ -156,10 +156,10 @@ status, menu, reload, and unmount. Mode controls are activation-scoped;
`onEnter` and `onExit` cannot change ownership, while `onKey` may deliberately
replace its activation without letting the outgoing callback defeat recovery or
manipulate the replacement.
`src/ui/lib/extensionKeyEvent.ts` freezes the method-free public key snapshot
`src/ui/ext/extensionKeyEvent.ts` freezes the method-free public key snapshot
used by both session and file-view mode delivery, so OpenTUI events and their
consumption methods never cross the extension boundary. Their shared
`src/ui/lib/synchronousExtensionCallback.ts` path contains lifecycle failures,
`src/ui/ext/synchronousExtensionCallback.ts` path contains lifecycle failures,
rejects thenables without leaving unhandled rejections, and normalizes key
results; each mode module supplies only its context and attributed warnings. A
focused file-view mode may overlap and temporarily outrank a session mode;
Expand All @@ -174,13 +174,13 @@ and extension-owned ids in disjoint spaces however either grows; modal surfaces
(dialogs, menus, focused inputs) own their keys first and are deliberately not
commands. Extension
`registerCommand` entries join the same table via
`src/ui/lib/extensionCommands.ts` — built-ins win key conflicts, refused one
`src/ui/ext/extensionCommands.ts` — built-ins win key conflicts, refused one
chord at a time and detected by probing matchers with a synthesized event
(`src/lib/commandKeys.ts`). Command handlers receive pane controls and a selection snapshot from
`src/ui/lib/extensionSelection.ts`, derived from the same frozen file views the
`src/ui/ext/extensionSelection.ts`, derived from the same frozen file views the
panes render. App reads it through a ref so the dispatch table stays stable.

`src/ui/lib/extensionNavigation.ts` mints the guarded navigation behind both
`src/ui/ext/extensionNavigation.ts` mints the guarded navigation behind both
`ctx.navigation` and a pane's `actions`, so a jump from either surface is
validated, attributed, and reported the same way. It owns argument policy only
— visible-file validation, hunk clamping, `revealLine`'s side and line-number
Expand All @@ -193,7 +193,7 @@ position.

`ctx.dialogs` is the one place extension code can interrupt the user, so its
ordering and settlement live outside React in
`src/ui/lib/extensionDialogs.ts` — one FIFO queue per App instance, minting a
`src/ui/ext/extensionDialogs.ts` — one FIFO queue per App instance, minting a
per-extension `dialogs` object, normalizing (and sanitizing) extension-authored
text into a request the host draws, and answering by request id so a duplicated
Enter cannot spill onto whatever was queued behind. App subscribes with
Expand All @@ -210,7 +210,7 @@ frame always carries an `ext <id>` attribution row — the toast marker — beca
the title is extension-authored and a prompt must not be able to impersonate
Hunk.

`src/ui/lib/extensionWorkspace.ts` owns the policy for `ctx.workspace`. Reads
`src/extensions/workspace/extensionWorkspace.ts` owns the policy for `ctx.workspace`. Reads
resolve reviewed file ids through the existing source fetcher, which retains
ownership of caching and size limits. Missing or unreadable sources become
`null`.
Expand Down Expand Up @@ -241,7 +241,7 @@ their key text from resolved `keyLabels` and run entries through
exist for a menu item; they never match a key but remain bindable by id.

Command handlers receive guarded `ctx.commands` controls built by
`src/ui/lib/extensionCommandControls.ts`. They resolve the live App command table on every call,
`src/ui/ext/extensionCommandControls.ts`. They resolve the live App command table on every call,
then expose only built-ins carrying explicit public metadata. Counted movement reaches the same
command callback once with a normalized delta; it is never implemented as repeated synchronous
dispatch. Current-line alignment is also semantic: App raises an alignment request and `DiffPane`
Expand Down
2 changes: 2 additions & 0 deletions docs/source-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ src/core/theme/ bundled theme metadata, custom-theme rules, and terminal th
src/core/watch/ input signatures, observation plans/backends, and refresh coordination
src/core/vcs/ provider-neutral VCS catalog, contracts, operation dispatch, and host support
src/extensions/ extension host, registry, trust, lifecycle, and bundled extensions
src/extensions/workspace/ renderer-free workspace capability policy and filesystem confinement
src/session/ shared session protocol, schemas, types, agent surface, app bridge, and broker transport
src/session/client/ shared session-daemon HTTP and compatibility client support
src/session/agent/ agent-facing session CLI, command manifest, errors, and formatting
src/session/app/ mounted-review registration, bridge, and reload authorization
src/session/broker/ local daemon transport, launcher, Hunk broker state, wire parsing, projections
src/ui/ interactive review application, rendering, interaction, and chrome
src/ui/ext/ terminal UI integration for extension commands, panes, and interaction
src/extension-api/ public `hunkdiff/extension` declaration and runtime boundary
src/opentui/ public `hunkdiff/opentui` component boundary
src/lib/ small product-wide utilities with no feature ownership
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { normalizeDiffPath } from "../../core/diffPaths";
import type { FileSourceSide } from "../../core/fileSource";
import { canReloadInput } from "../../core/inputReload";
import type { CliInput } from "../../core/types";
import { readMetadataChangeType } from "../../extensions/events";
import { readMetadataChangeType } from "../events";

/**
* The slice of one reviewed file the workspace policy inspects.
Expand Down
18 changes: 9 additions & 9 deletions src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,18 @@ import {
type AppCommand,
} from "./lib/appCommands";
import { buildAppMenus } from "./lib/appMenus";
import { buildExtensionAppCommands, extensionCommandKeyDefaults } from "./lib/extensionCommands";
import { createExtensionCommandControls } from "./lib/extensionCommandControls";
import { buildExtensionAppCommands, extensionCommandKeyDefaults } from "./ext/extensionCommands";
import { createExtensionCommandControls } from "./ext/extensionCommandControls";
import {
applyExtensionCurrentLinePaintUpdate,
extensionCurrentLinePaintMatchesCursor,
type ExtensionCurrentLinePaintState,
type ExtensionCurrentLinePaintUpdate,
} from "./lib/extensionCurrentLine";
import { createGuardedReviewNavigation } from "./lib/extensionNavigation";
} from "./ext/extensionCurrentLine";
import { createGuardedReviewNavigation } from "./ext/extensionNavigation";
import type { CurrentLineAlignment } from "./lib/hunkScroll";
import type { LineCursor } from "./lib/lineCursors";
import { buildExtensionReviewSelection } from "./lib/extensionSelection";
import { buildExtensionReviewSelection } from "./ext/extensionSelection";
import { useFilePresentationController } from "./fileViews/useFilePresentationController";
import { useFilePresentationRendering } from "./fileViews/useFilePresentationRendering";
import { mergeLineHighlightMaps } from "./highlights/merge";
Expand All @@ -126,18 +126,18 @@ import {
reconcilePaneOpenState,
resolvePaneKey,
type PlannedPane,
} from "./lib/extensionPanes";
} from "./ext/extensionPanes";
import type { ExtensionPanePlacement } from "../extension-api/types";
import { HUNK_FILES_PANE_KEY } from "../extensions/extensionIds";
import { extensionPaneSize } from "../extensions/panes";
import { nextExtensionTrustPromptRoot } from "./lib/extensionTrustPrompt";
import { nextExtensionTrustPromptRoot } from "./ext/extensionTrustPrompt";
import {
normalizeWorkspaceWriteRequest,
resolveExtensionWorkspaceRead,
resolveExtensionWorkspaceWriteTarget,
} from "./lib/extensionWorkspace";
} from "../extensions/workspace/extensionWorkspace";
import { maxFileHeaderStatsWidth } from "./lib/fileHeader";
import { verifyWorkspaceWriteTarget } from "./lib/workspaceWriteGuard";
import { verifyWorkspaceWriteTarget } from "../extensions/workspace/workspaceWriteGuard";
import { openSelectedFileInEditor } from "./lib/openInEditor";
import { resolveResponsiveLayout } from "./lib/responsive";
import { resizeSidebarWidth } from "./lib/sidebar";
Expand Down
2 changes: 1 addition & 1 deletion src/ui/AppHost.extension-dialogs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function loadAppBootstrap(...args: Parameters<typeof loadCoreAppBootstrap>): Pro
* question from a command handler, the modal renders inside the mounted review,
* and the keys the user presses are the ones the handler's promise resolves on.
* Only the whole stack can show that — the queue's own semantics are unit-tested
* in `lib/extensionDialogs.test.ts`.
* in `ext/extensionDialogs.test.ts`.
*/

const tempDirs: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/ui/AppHost.extension-navigation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function loadAppBootstrap(...args: Parameters<typeof loadCoreAppBootstrap>): Pro
* `ctx.navigation`, driven through the real app: a fixture extension's command
* jumps the review stream, and the `selection_changed` event that comes back is
* the proof the navigation ran through the same controller as a sidebar click.
* The guard semantics themselves are unit-tested in `lib/extensionNavigation.test.ts`.
* The guard semantics themselves are unit-tested in `ext/extensionNavigation.test.ts`.
*/

const tempDirs: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/ui/AppHost.workspace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function loadAppBootstrap(...args: Parameters<typeof loadCoreAppBootstrap>): Pro
* `ctx.workspace`, driven through the real app: a fixture extension reads a
* reviewed file's document and asks to replace it, Hunk raises the confirm the
* user actually answers, and the bytes on disk are what the answer decided. The
* policy behind the refusals is unit-tested in `lib/extensionWorkspace.test.ts`;
* policy behind the refusals is unit-tested in `src/extensions/workspace/extensionWorkspace.test.ts`;
* only the whole stack can show the real loader-attached source behind a read,
* the prompt, the write, and the reload.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/chrome/ExtensionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type {
ExtensionDialogRequest,
ExtensionInputDialogRequest,
ExtensionSelectDialogRequest,
} from "../../lib/extensionDialogs";
import { extensionToastPrefix } from "../../lib/extensionNotifications";
} from "../../ext/extensionDialogs";
import { extensionToastPrefix } from "../../ext/extensionNotifications";
import { listWindowStart } from "../../lib/listWindow";
import { fitText, padText } from "../../lib/text";
import type { AppTheme } from "../../themes";
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/chrome/ExtensionToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
extensionToastColor,
extensionToastMessage,
extensionToastPrefix,
} from "../../lib/extensionNotifications";
} from "../../ext/extensionNotifications";
import type { AppTheme } from "../../themes";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/panes/DiffPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { DiffFileHeaderRow } from "./DiffFileHeaderRow";
import {
createExtensionCurrentLinePaint,
type ExtensionCurrentLinePaintUpdate,
} from "../../lib/extensionCurrentLine";
} from "../../ext/extensionCurrentLine";
import { VerticalScrollbar, type VerticalScrollbarHandle } from "../scrollbar/VerticalScrollbar";
import type { VisibleBodyBounds } from "../../diff/rowWindowing";
import type { ResolvedFileViewLayout } from "../../fileViews/useFileViews";
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/panes/ExtensionPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { paneKey } from "../../../extensions/apply";
import { BuiltInSidebarView } from "../../../extensions/default/ui/sidebar";
import { HUNK_FILES_PANE_KEY } from "../../../extensions/extensionIds";
import type { ExtensionNotifySink, RegisteredPane } from "../../../extensions/types";
import { createGuardedReviewNavigation } from "../../lib/extensionNavigation";
import { toExtensionPaintTheme } from "../../lib/extensionPaintTheme";
import { createGuardedReviewNavigation } from "../../ext/extensionNavigation";
import { toExtensionPaintTheme } from "../../ext/extensionPaintTheme";
import type { AppTheme } from "../../themes";

function describeError(error: unknown) {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/panes/FileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { plannedRowMatchesCursor, type CursorHighlight } from "../../diff/render
import { cursorLineHighlightBg } from "../../diff/rowStyle";
import { resolveVisibleRowIndexWindow, type VisibleBodyBounds } from "../../diff/rowWindowing";
import { reviewRowId } from "../../lib/ids";
import { toExtensionPaintTheme } from "../../lib/extensionPaintTheme";
import { toExtensionPaintTheme } from "../../ext/extensionPaintTheme";
import type { PlannedFileViewRow } from "../../fileViews/renderPlan";
import type { FileViewRowFailure } from "../../fileViews/types";
import type { ResolvedFileViewLayout } from "../../fileViews/useFileViews";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import type { AppCommand } from "./appCommands";
import { MAX_APP_COMMAND_COUNT } from "./appCommands";
import type { AppCommand } from "../lib/appCommands";
import { MAX_APP_COMMAND_COUNT } from "../lib/appCommands";
import { createExtensionCommandControls } from "./extensionCommandControls";

/** Build one host command without coupling these capability tests to App callbacks. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
isCommandEnabled,
normalizeAppCommandCount,
type AppCommand,
} from "./appCommands";
} from "../lib/appCommands";

/** Validate an execution id passed by JavaScript despite the TypeScript contract. */
function requireCommandId(commandId: unknown): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import type { RegisteredCommand } from "../../extensions/types";
import { synthesizeKeyEvent, parseKeyChord } from "../../lib/commandKeys";
import { builtinCommandMatchProbes, dispatchAppCommand } from "./appCommands";
import { builtinCommandMatchProbes, dispatchAppCommand } from "../lib/appCommands";
import { buildExtensionAppCommands } from "./extensionCommands";

function registeredCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
synthesizeKeyEvent,
toKeyChordList,
} from "../../lib/commandKeys";
import type { AppCommand, ResolvedCommandKeys } from "./appCommands";
import { formatKeyChord } from "./keymap";
import type { AppCommand, ResolvedCommandKeys } from "../lib/appCommands";
import { formatKeyChord } from "../lib/keymap";

/** One extension binding refused because its chord is already taken. */
export interface ExtensionCommandConflict {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
createExtensionCurrentLinePaint,
extensionCurrentLinePaintMatchesCursor,
} from "./extensionCurrentLine";
import type { LineCursor } from "./lineCursors";
import type { LineCursor } from "../lib/lineCursors";

/** Build one accepted split row plan and a cursor that resolves inside it. */
function splitPlanFixture() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ExtensionCurrentLinePaint } from "../../extension-api/types";
import type { DiffRow, SplitLineCell, StackLineCell } from "../diff/pierre";
import { DiffRowView } from "../diff/renderRows";
import type { DiffSectionRowPlan } from "../diff/diffSectionRowPlan";
import type { LineCursor } from "./lineCursors";
import type { LineCursor } from "../lib/lineCursors";
import type { AppTheme } from "../themes";

type SplitLineRow = Extract<DiffRow, { type: "split-line" }>;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/ui/fileViews/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
ExtensionFileViewInput,
} from "../../extension-api/types";
import { readMetadataHunkSummaries, toReadOnlyFileViews } from "../../extensions/events";
import { createExtensionDocumentReader } from "../lib/extensionDocumentReader";
import { createExtensionDocumentReader } from "../ext/extensionDocumentReader";

/** Build public added/removed ranges from parsed hunks, without leaking Pierre types. */
export function fileViewChanges(file: DiffFile): readonly ExtensionFileChangeRange[] {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/fileViews/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { RegisteredFileView } from "../../extensions/types";
import {
deliverSynchronousExtensionModeKey,
runSynchronousExtensionModeLifecycle,
} from "../lib/synchronousExtensionCallback";
} from "../ext/synchronousExtensionCallback";
import { registeredFileViewKey, resolveFileViewSelectionTarget } from "./state";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/ui/fileViews/useFilePresentationController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { DiffFile } from "../../core/types";
import { toReadOnlyFileViews } from "../../extensions/events";
import type { RegisteredFileView } from "../../extensions/types";
import { createTestDiffFile } from "../../../test/helpers/diff-helpers";
import { buildExtensionReviewSelection } from "../lib/extensionSelection";
import { buildExtensionReviewSelection } from "../ext/extensionSelection";
import { registeredFileViewKey } from "./state";
import { useFilePresentationController } from "./useFilePresentationController";

Expand Down
2 changes: 1 addition & 1 deletion src/ui/highlights/useLineHighlights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { DiffFile } from "../../core/types";
import type { ExtensionLineHighlightInput } from "../../extension-api/types";
import { toReadOnlyFileViews } from "../../extensions/events";
import type { RegisteredLineHighlighter } from "../../extensions/types";
import { createExtensionDocumentReader } from "../lib/extensionDocumentReader";
import { createExtensionDocumentReader } from "../ext/extensionDocumentReader";
import { scopedEpoch } from "../lib/scopedEpochs";
import { registeredLineHighlighterKey, type LineHighlightEpochState } from "./state";
import {
Expand Down
4 changes: 2 additions & 2 deletions src/ui/hooks/useAppKeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type {
} from "../../extensions/types";
import type { MenuId } from "../components/chrome/menu";
import { dispatchAppCommand, type AppCommand } from "../lib/appCommands";
import type { ExtensionDialogRequest } from "../lib/extensionDialogs";
import { toExtensionKeyEvent } from "../lib/extensionKeyEvent";
import type { ExtensionDialogRequest } from "../ext/extensionDialogs";
import { toExtensionKeyEvent } from "../ext/extensionKeyEvent";
import { isEscapeKey, isSaveDraftNoteKey } from "../lib/keyboard";
import { routeKeyOwnership, type KeyOwner } from "../lib/keyRouting";

Expand Down
2 changes: 1 addition & 1 deletion src/ui/hooks/useExtensionDialogController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
createExtensionDialogQueue,
type ExtensionDialogQueue,
type ExtensionDialogRequest,
} from "../lib/extensionDialogs";
} from "../ext/extensionDialogs";

export interface ExtensionDialogController {
/** Build the dialog capability one extension command receives. */
Expand Down
2 changes: 1 addition & 1 deletion src/ui/hooks/useExtensionNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
import {
enqueueExtensionNotification,
EXTENSION_TOAST_DURATION_MS,
} from "../lib/extensionNotifications";
} from "../ext/extensionNotifications";

/**
* Subscribe the app to extension notifications and surface them one at a time.
Expand Down
2 changes: 1 addition & 1 deletion src/ui/keyboardModes/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { sanitizeTerminalLine } from "../../lib/terminalText";
import {
deliverSynchronousExtensionModeKey,
runSynchronousExtensionModeLifecycle,
} from "../lib/synchronousExtensionCallback";
} from "../ext/synchronousExtensionCallback";

/** Everything the host retains while one session keyboard mode is active. */
export interface ActiveSessionKeyboardMode {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/lib/appMenus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type ResolvedCommandKeys,
} from "./appCommands";
import { buildAppMenus, type BuildAppMenusOptions } from "./appMenus";
import { buildExtensionAppCommands } from "./extensionCommands";
import { buildExtensionAppCommands } from "../ext/extensionCommands";
import { resolveCommandKeys } from "./keymap";

/** The app-state half of the menu options, so tests only state what they exercise. */
Expand Down
Loading