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
3 changes: 2 additions & 1 deletion apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@tiptap/starter-kit": "^3.26.0",
"@tiptap/suggestion": "^3.26.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-unicode11": "^0.9.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"ansi-to-html": "^0.7.2",
Expand Down Expand Up @@ -127,10 +128,10 @@
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"bb-plugin-automations": "workspace:*",
"@typescript-eslint/parser": "^8.63.0",
"@vitejs/plugin-react": "^6.0.1",
"babel-plugin-react-compiler": "^1.0.0",
"bb-plugin-automations": "workspace:*",
"eslint": "^9.39.3",
"eslint-plugin-react-hooks": "^7.0.1",
"mdast-util-to-hast": "^13.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export function ThreadTerminalContent({
isPanelOpen={controller.isPanelOpen}
onOpenLink={onOpenLink}
onSelectionAddToChat={onSelectionAddToChat}
onSessionChange={controller.handleActiveTerminalSessionChange}
onTitleChange={controller.handleActiveTerminalTitleChange}
onUserInput={controller.handleActiveTerminalUserInput}
session={controller.activeSession}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ function noopAction(): void {}

function noopTerminalIdAction(_terminalId: string): void {}

function noopSessionChange(_session: TerminalSession): void {}

function noopTitleChange(_title: string): void {}

function makeController({
Expand All @@ -98,6 +100,7 @@ function makeController({
canCreateTerminal,
closingTerminalId,
emptyTerminalMessage,
handleActiveTerminalSessionChange: noopSessionChange,
handleActiveTerminalTitleChange: noopTitleChange,
handleActiveTerminalUserInput: noopAction,
handleClosePanel: noopAction,
Expand Down Expand Up @@ -237,10 +240,7 @@ function RunningTerminalPreview() {
export function Overview() {
return (
<StoryCard labelWidth="190px">
<StoryRow
label="disconnected"
hint="Replacement available."
>
<StoryRow label="disconnected" hint="Replacement available.">
<TerminalContentStage controller={disconnectedController} />
</StoryRow>
<StoryRow
Expand All @@ -252,7 +252,10 @@ export function Overview() {
<StoryRow label="starting" hint="Session exists but is not running yet.">
<TerminalContentStage controller={startingController} />
</StoryRow>
<StoryRow label="exited" hint="Terminal has ended and cannot accept input.">
<StoryRow
label="exited"
hint="Terminal has ended and cannot accept input."
>
<TerminalContentStage controller={exitedController} />
</StoryRow>
<StoryRow label="empty" hint="Right panel tab with no visible sessions.">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,57 @@
import { describe, expect, it, vi } from "vitest";
import { buildTerminalThemeFromCssColors } from "./ThreadTerminalView";
import { TERMINAL_DATA_MAX_BYTES } from "@bb/domain";
import {
buildTerminalThemeFromCssColors,
decodeTerminalOutputBytes,
encodeTerminalInputChunks,
TERMINAL_ALLOW_PROPOSED_API,
TERMINAL_FONT_FAMILY,
TERMINAL_UNICODE_VERSION,
} from "./ThreadTerminalView";

describe("terminal output encoding", () => {
it("splits large paste input at the wire limit without losing UTF-8 bytes", () => {
const input = `${"a".repeat(TERMINAL_DATA_MAX_BYTES - 1)}🙂tail`;
const chunks = encodeTerminalInputChunks(input);
const decoded = Buffer.concat(
chunks.map((chunk) => Buffer.from(chunk, "base64")),
);

expect(chunks).toHaveLength(2);
expect(
chunks.every(
(chunk) =>
Buffer.from(chunk, "base64").byteLength <= TERMINAL_DATA_MAX_BYTES,
),
).toBe(true);
expect(decoded.toString("utf8")).toBe(input);
});

it("keeps UTF-8 bytes intact when a glyph spans output chunks", () => {
const encoded = new TextEncoder().encode("🙂");
const first = decodeTerminalOutputBytes(
Buffer.from(encoded.subarray(0, 2)).toString("base64"),
);
const second = decodeTerminalOutputBytes(
Buffer.from(encoded.subarray(2)).toString("base64"),
);
const decoder = new TextDecoder();

expect(
decoder.decode(first, { stream: true }) + decoder.decode(second),
).toBe("🙂");
});

it("enables the proposed xterm API required by the Unicode addon", () => {
expect(TERMINAL_ALLOW_PROPOSED_API).toBe(true);
expect(TERMINAL_UNICODE_VERSION).toBe("11");
});

it("prefers installed Nerd Font families before system monospace fallbacks", () => {
expect(TERMINAL_FONT_FAMILY).toContain("Nerd Font");
expect(TERMINAL_FONT_FAMILY).toContain("ui-monospace");
});
});

describe("buildTerminalThemeFromCssColors", () => {
it("paints the terminal canvas and cursor cutout with the sidebar surface", () => {
Expand Down
Loading