feat: inline permission prompt and improve TUI layout

- Render permission modal inline below log panel instead of floating CenteredModal overlay
- Hide input area when permission prompt is active
- Add dev:debug and dev:debug-brk scripts for Bun inspector debugging
- Add background color to header to prevent content bleeding through
- Add margin spacing between header and log panel
- Change permission modal border to top-only for cleaner inline appearance
This commit is contained in:
2026-02-06 09:09:21 -05:00
parent f2641d6ab0
commit 8adf48abd3
9 changed files with 152 additions and 20 deletions

View File

@@ -16,6 +16,7 @@ import {
advanceStep,
getExecutionState,
} from "@services/chat-tui-service";
import { DISABLE_MOUSE_TRACKING } from "@constants/terminal";
import versionData from "@/version.json";
import { ExitProvider, useExit } from "@tui-solid/context/exit";
import { RouteProvider, useRoute } from "@tui-solid/context/route";
@@ -179,7 +180,11 @@ function AppContent(props: AppProps) {
const toggled = togglePauseResume();
if (toggled) {
const state = getExecutionState();
toast.info(state.state === "paused" ? "⏸ Execution paused" : "▶ Execution resumed");
toast.info(
state.state === "paused"
? "⏸ Execution paused"
: "▶ Execution resumed",
);
evt.preventDefault();
return;
}
@@ -191,7 +196,9 @@ function AppContent(props: AppProps) {
if (state.state !== "idle") {
abortCurrentOperation(true).then((aborted) => {
if (aborted) {
toast.info(`Aborted with rollback of ${state.rollbackCount} action(s)`);
toast.info(
`Aborted with rollback of ${state.rollbackCount} action(s)`,
);
}
});
evt.preventDefault();
@@ -205,7 +212,9 @@ function AppContent(props: AppProps) {
if (state.state !== "idle") {
const isStepMode = state.state === "stepping";
setStepMode(!isStepMode);
toast.info(isStepMode ? "🏃 Step mode disabled" : "🚶 Step mode enabled");
toast.info(
isStepMode ? "🏃 Step mode disabled" : "🚶 Step mode enabled",
);
evt.preventDefault();
return;
}
@@ -511,11 +520,16 @@ export interface TuiRenderOptions extends TuiInput {
export function tui(options: TuiRenderOptions): Promise<TuiOutput> {
return new Promise<TuiOutput>((resolve) => {
render(() => <App {...options} onExit={resolve} />, {
const handleExit = (output: TuiOutput): void => {
process.stdout.write(DISABLE_MOUSE_TRACKING);
resolve(output);
};
render(() => <App {...options} onExit={handleExit} />, {
targetFps: 60,
exitOnCtrlC: false,
useKittyKeyboard: {},
useMouse: false,
useMouse: true,
});
});
}

View File

@@ -139,8 +139,10 @@ export function Header(props: HeaderProps) {
justifyContent="space-between"
paddingLeft={1}
paddingRight={1}
marginBottom={1}
borderColor={theme.colors.border}
border={["bottom"]}
backgroundColor={theme.colors.background}
>
<box flexDirection="row" gap={1}>
<Show when={showBanner()}>

View File

@@ -100,12 +100,15 @@ export function PermissionModal(props: PermissionModalProps) {
<box
flexDirection="column"
borderColor={theme.colors.borderWarning}
border={["top", "bottom", "left", "right"]}
border={["top"]}
backgroundColor={theme.colors.background}
paddingLeft={2}
paddingRight={2}
paddingTop={1}
paddingBottom={1}
width="100%"
height="auto"
flexShrink={0}
>
<box marginBottom={1}>
<text fg={theme.colors.warning} attributes={TextAttributes.BOLD}>

View File

@@ -287,8 +287,20 @@ export function Session(props: SessionProps) {
</Show>
</box>
<Show
when={app.mode() === "permission_prompt" && app.permissionRequest()}
>
<PermissionModal
request={app.permissionRequest()!}
onRespond={props.onPermissionResponse}
isActive={app.mode() === "permission_prompt"}
/>
</Show>
<StatusBar />
<InputArea onSubmit={props.onSubmit} />
<Show when={app.mode() !== "permission_prompt"}>
<InputArea onSubmit={props.onSubmit} />
</Show>
<Switch>
<Match when={app.mode() === "command_menu"}>
@@ -393,18 +405,6 @@ export function Session(props: SessionProps) {
</CenteredModal>
</Match>
<Match
when={app.mode() === "permission_prompt" && app.permissionRequest()}
>
<CenteredModal>
<PermissionModal
request={app.permissionRequest()!}
onRespond={props.onPermissionResponse}
isActive={app.mode() === "permission_prompt"}
/>
</CenteredModal>
</Match>
<Match when={app.mode() === "learning_prompt" && app.learningPrompt()}>
<CenteredModal>
<LearningModal