diff --git a/README.md b/README.md
index 1bb6c21c..d3559803 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](./LICENSE)
[](https://www.npmjs.com/package/@maria_rcks/t1code)
-[](https://github.com/maria-rcks/t1code)
+[](https://github.com/ahzs645/t1chat)
@@ -12,6 +12,8 @@ _T3Code, but in your terminal._
+## t1code (code mode)
+
Run instantly:
```bash
@@ -27,10 +29,51 @@ bun add -g @maria_rcks/t1code
Develop from source:
```bash
-git clone https://github.com/maria-rcks/t1code.git
+git clone https://github.com/ahzs645/t1chat.git
cd t1code
bun install
bun dev:tui
```
+## t1chat (chat mode)
+
+
+

+
+
+t1chat is a chat-focused mode that transforms the TUI into a conversational interface inspired by [T3 Chat](https://t3.chat). It features a pink/magenta/lavender theme, a flat thread list grouped by time, and a streamlined UI without code-specific tools.
+
+### What changes in chat mode
+
+- Sidebar shows a flat thread list grouped by time (Today, Yesterday, Last 7 Days, etc.) instead of nested projects
+- "New Chat" button and thread search in the sidebar
+- Title shows "T1 Chat" instead of "T1 Code"
+- Git tools, diff viewer, Chat/Plan toggle, and Full access button are hidden
+- Settings and temp chat toggle in the top-right corner
+- Composer placeholder says "Type your message here..."
+- Pink/magenta/lavender color scheme matching T3 Chat
+
+### Run chat mode
+
+If installed globally:
+
+```bash
+t1chat
+```
+
+Run instantly:
+
+```bash
+bunx @maria_rcks/t1code t1chat
+```
+
+Develop from source:
+
+```bash
+git clone https://github.com/ahzs645/t1chat.git
+cd t1code
+bun install
+T1CODE_CHAT_MODE=1 bun dev:tui
+```
+
Based on T3 Code by [@t3dotgg](https://github.com/t3dotgg) and [@juliusmarminge](https://github.com/juliusmarminge).
diff --git a/apps/tui/bin/t1chat.js b/apps/tui/bin/t1chat.js
new file mode 100755
index 00000000..0b76b92a
--- /dev/null
+++ b/apps/tui/bin/t1chat.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env bun
+
+import { spawn } from "node:child_process";
+import { fileURLToPath } from "node:url";
+
+const entryPath = fileURLToPath(new URL("../dist/index.mjs", import.meta.url));
+
+function printError(error) {
+ process.stderr.write(
+ `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
+ );
+}
+
+process.env.T1CODE_CHAT_MODE = "1";
+
+if (process.versions.bun === undefined) {
+ const bunBin = process.env.T1CODE_BUN_BIN?.trim() || "bun";
+ const child = spawn(bunBin, [entryPath, ...process.argv.slice(2)], {
+ stdio: "inherit",
+ env: process.env,
+ });
+
+ child.once("exit", (code, signal) => {
+ if (signal) {
+ process.kill(process.pid, signal);
+ return;
+ }
+ process.exit(code ?? 1);
+ });
+
+ child.once("error", (error) => {
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
+ printError("t1code requires Bun on your PATH to launch the TUI runtime.");
+ process.exit(1);
+ return;
+ }
+ printError(error);
+ process.exit(1);
+ });
+} else {
+ import("../dist/index.mjs").catch((error) => {
+ printError(error);
+ process.exit(1);
+ });
+}
diff --git a/apps/tui/package.json b/apps/tui/package.json
index f18b9008..c7ac48a9 100644
--- a/apps/tui/package.json
+++ b/apps/tui/package.json
@@ -14,7 +14,8 @@
},
"bin": {
"t1": "./bin/t1code.js",
- "t1code": "./bin/t1code.js"
+ "t1code": "./bin/t1code.js",
+ "t1chat": "./bin/t1chat.js"
},
"files": [
"README.md",
diff --git a/apps/tui/src/profiles.ts b/apps/tui/src/profiles.ts
new file mode 100644
index 00000000..1fbe56d8
--- /dev/null
+++ b/apps/tui/src/profiles.ts
@@ -0,0 +1,72 @@
+/**
+ * Profile management for t1chat mode.
+ *
+ * Profiles let users organize conversations under different personas.
+ * Each profile has a name, icon, and unique ID. Threads can be
+ * associated with a profile so switching profiles filters the sidebar.
+ */
+
+export interface Profile {
+ id: string;
+ name: string;
+ icon: string;
+}
+
+/** Nerd Font icons available for profile selection. */
+export const PROFILE_ICON_OPTIONS: { icon: string; label: string }[] = [
+ { icon: "", label: "Chat" },
+ { icon: "", label: "Star" },
+ { icon: "", label: "Bookmark" },
+ { icon: "", label: "Heart" },
+ { icon: "", label: "Flag" },
+ { icon: "", label: "Lightning" },
+ { icon: "", label: "Play" },
+ { icon: "", label: "Sparkles" },
+ { icon: "", label: "Bell" },
+ { icon: "", label: "Bulb" },
+ { icon: "", label: "Home" },
+ { icon: "", label: "Folder" },
+ { icon: "", label: "Calendar" },
+ { icon: "", label: "Mail" },
+ { icon: "", label: "File" },
+ { icon: "", label: "Book" },
+ { icon: "", label: "Briefcase" },
+ { icon: "", label: "Database" },
+ { icon: "", label: "Cube" },
+ { icon: "", label: "Music" },
+ { icon: "", label: "Camera" },
+ { icon: "", label: "Eye" },
+ { icon: "", label: "Globe" },
+ { icon: "", label: "Graduate" },
+];
+
+export const DEFAULT_PROFILE: Profile = {
+ id: "default",
+ name: "Default",
+ icon: "",
+};
+
+export function createProfile(name: string, icon: string): Profile {
+ const slug = name
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-|-$/g, "");
+ return {
+ id: `${slug}-${Date.now()}`,
+ name,
+ icon,
+ };
+}
+
+export function reorderProfiles(
+ profiles: Profile[],
+ fromIndex: number,
+ toIndex: number,
+): Profile[] {
+ const result = [...profiles];
+ const [moved] = result.splice(fromIndex, 1);
+ if (moved) {
+ result.splice(toIndex, 0, moved);
+ }
+ return result;
+}
diff --git a/apps/tui/src/responsiveLayout.ts b/apps/tui/src/responsiveLayout.ts
index 7d6ad5b6..59d377cd 100644
--- a/apps/tui/src/responsiveLayout.ts
+++ b/apps/tui/src/responsiveLayout.ts
@@ -27,6 +27,7 @@ export type TuiResponsiveLayout = Readonly<{
export function resolveTuiResponsiveLayout(input: {
viewportColumns: number;
sidebarCollapsedPreference: boolean;
+ isChatMode?: boolean;
}): TuiResponsiveLayout {
const openSidebarMainPanelColumns = input.viewportColumns - TUI_SIDEBAR_WIDTH - 1;
const showSidebarToggle =
@@ -53,7 +54,7 @@ export function resolveTuiResponsiveLayout(input: {
// should track sidebar visibility rather than the overall terminal width.
showWindowDots: showSidebar,
showSidebarAlphaBadge: showSidebar,
- sidebarTitle: showSidebar ? "T1 Code" : "T1",
+ sidebarTitle: showSidebar ? (input.isChatMode ? "T1 Chat" : "T1 Code") : "T1",
showHeaderProjectBadge: input.viewportColumns >= 144,
showComposerModeLabels,
showComposerModelLabel,
diff --git a/apps/tui/src/theme.ts b/apps/tui/src/theme.ts
index aa7676bc..43971d07 100644
--- a/apps/tui/src/theme.ts
+++ b/apps/tui/src/theme.ts
@@ -16,12 +16,14 @@ export interface TerminalColors {
export type TuiColor = string;
export const TERMINAL_MATCH_THEME_ID = "terminal-match" as const;
-export const TUI_THEME_IDS = ["default", TERMINAL_MATCH_THEME_ID] as const;
+export const BORING_THEME_ID = "boring" as const;
+export const TUI_THEME_IDS = ["default", TERMINAL_MATCH_THEME_ID, BORING_THEME_ID] as const;
export type TuiThemeId = (typeof TUI_THEME_IDS)[number];
export const DEFAULT_TUI_THEME_ID = "default" as const;
export const TUI_THEME_LABELS: Record = {
default: "Default",
[TERMINAL_MATCH_THEME_ID]: "Terminal Match",
+ [BORING_THEME_ID]: "Boring",
};
export type TuiThemeMode = "light" | "dark";
@@ -55,44 +57,44 @@ export interface ResolveTuiThemeOptions {
}
const DEFAULT_DARK_PALETTE = {
- canvas: "#171717",
- sidebar: "#151515",
- main: "#171717",
- surface: "#1b1b1b",
- surfaceAlt: "#1f1f1f",
- input: "#111111",
- surfaceUser: "#202020",
+ canvas: "#21141e",
+ sidebar: "#1a0f18",
+ main: "#21141e",
+ surface: "#2a1825",
+ surfaceAlt: "#311e2c",
+ input: "#1a0f18",
+ surfaceUser: "#2a1825",
surfacePlan: "#1f221c",
surfaceWarn: "#262016",
- surfaceInfo: "#1d2026",
- footer: "#171717",
- diff: "#1b1b1b",
- popup: "#1c1c1c",
+ surfaceInfo: "#261a2e",
+ footer: "#21141e",
+ diff: "#2a1825",
+ popup: "#2a1825",
scrim: "#00000099",
- border: "#252525",
- divider: "#2d2d2d",
+ border: "#3d2438",
+ divider: "#3d2438",
control: "transparent",
- controlHover: "#202020",
- controlActive: "#292929",
- controlActiveStrong: "#1e1e1e",
- controlInset: "#141414",
- controlInsetHover: "#1a1a1a",
- composerPanel: "#1a1a1a",
- composerBorder: "#2a3f95",
- composerBorderMuted: "#313131",
- composerSend: "#2f438e",
- composerSendHover: "#3c57ba",
+ controlHover: "#311e2c",
+ controlActive: "#3d2438",
+ controlActiveStrong: "#2a1825",
+ controlInset: "#1a0f18",
+ controlInsetHover: "#21141e",
+ composerPanel: "#2a1825",
+ composerBorder: "#a3004c",
+ composerBorderMuted: "#3d2438",
+ composerSend: "#a3004c",
+ composerSendHover: "#e33f86",
composerStop: "#dc2626",
composerStopHover: "#ef4444",
- accent: "#7c87ff",
+ accent: "#e33f86",
cursor: "#d4d4d4",
- selection: "#1f4f95",
- selectionActive: "#2b61b0",
- text: "#f5f5f5",
- muted: "#a3a3a3",
- subtle: "#737373",
+ selection: "#5c1a3e",
+ selectionActive: "#7a2450",
+ text: "#f9f8fb",
+ muted: "#b89eb5",
+ subtle: "#8a6b87",
success: "#10b981",
- info: "#3b82f6",
+ info: "#c074b2",
warning: "#f59e0b",
claude: "#d97757",
macRed: "#ff5f57",
@@ -105,23 +107,23 @@ export type TuiPalette = { [Key in keyof TuiPaletteShape]: TuiColor };
const DEFAULT_THEME_DETAILS = {
attachmentPillTones: [
- { backgroundColor: "#1d2026", textColor: "#3b82f6" },
- { backgroundColor: "#241b2f", textColor: "#a78bfa" },
+ { backgroundColor: "#2e1528", textColor: "#e33f86" },
+ { backgroundColor: "#241b2f", textColor: "#c074b2" },
{ backgroundColor: "#2a2417", textColor: "#facc15" },
{ backgroundColor: "#2a1b1b", textColor: "#f87171" },
{ backgroundColor: "#1c2721", textColor: "#34d399" },
{ backgroundColor: "#272019", textColor: "#fb923c" },
],
codeBlock: {
- background: "#101010",
- language: "#8a8a8a",
- copyIcon: "#9a9a9a",
+ background: "#1a0f18",
+ language: "#8a6b87",
+ copyIcon: "#b89eb5",
},
status: {
- awaitingInput: "#818cf8",
- working: "#7dd3fc",
+ awaitingInput: "#e33f86",
+ working: "#c074b2",
planReady: "#a78bfa",
- pulse: "#3b82f6",
+ pulse: "#e33f86",
},
diffViewer: {
addedBg: "#173124",
@@ -157,41 +159,41 @@ const DEFAULT_DARK_THEME: TuiTheme = {
const DEFAULT_LIGHT_PALETTE: TuiPalette = {
...DEFAULT_DARK_PALETTE,
- canvas: "#f5f5f5",
- sidebar: "#eeeeee",
- main: "#f7f7f7",
+ canvas: "#f2e1f4",
+ sidebar: "#ead0ef",
+ main: "#fdf7fd",
surface: "#ffffff",
- surfaceAlt: "#f1f1f1",
+ surfaceAlt: "#f5eaf6",
input: "#ffffff",
- surfaceUser: "#ececec",
+ surfaceUser: "#f0ddf2",
surfacePlan: "#eef6ec",
surfaceWarn: "#fff5e6",
- surfaceInfo: "#eef4ff",
- footer: "#f7f7f7",
+ surfaceInfo: "#f5eaff",
+ footer: "#fdf7fd",
diff: "#fafafa",
popup: "#ffffff",
scrim: "#00000022",
- border: "#dddddd",
- divider: "#d8d8d8",
- controlHover: "#ebebeb",
- controlActive: "#e2e2e2",
- controlActiveStrong: "#cdcdcd",
- controlInset: "#e7e7e7",
- controlInsetHover: "#dddddd",
+ border: "#efbdeb",
+ divider: "#e0b8dc",
+ controlHover: "#f0ddf2",
+ controlActive: "#e6cce9",
+ controlActiveStrong: "#d9b8dd",
+ controlInset: "#ead0ef",
+ controlInsetHover: "#e0c2e4",
composerPanel: "#ffffff",
- composerBorder: "#0891b2",
- composerBorderMuted: "#d0d0d0",
- composerSend: "#60a5fa",
- composerSendHover: "#3b82f6",
- accent: "#0891b2",
+ composerBorder: "#e33f86",
+ composerBorderMuted: "#e0b8dc",
+ composerSend: "#e33f86",
+ composerSendHover: "#ca0277",
+ accent: "#ca0277",
cursor: "#a3a3a3",
- selection: "#dbeafe",
- selectionActive: "#bfdbfe",
- text: "#171717",
- muted: "#666666",
- subtle: "#8a8a8a",
+ selection: "#f5d0e8",
+ selectionActive: "#f0b8dd",
+ text: "#501854",
+ muted: "#7a3f7e",
+ subtle: "#9a6b9e",
success: "#059669",
- info: "#2563eb",
+ info: "#8b3fa0",
warning: "#d97706",
claude: "#c96d4d",
};
@@ -207,6 +209,84 @@ const DEFAULT_LIGHT_THEME: TuiTheme = {
},
};
+const BORING_DARK_PALETTE: TuiPalette = {
+ ...DEFAULT_DARK_PALETTE,
+ canvas: "#151515",
+ sidebar: "#1a1a1a",
+ main: "#151515",
+ surface: "#1e1e1e",
+ surfaceAlt: "#222222",
+ input: "#1a1a1a",
+ surfaceUser: "#1e1e1e",
+ footer: "#151515",
+ popup: "#1e1e1e",
+ border: "#282828",
+ divider: "#282828",
+ controlHover: "#252525",
+ controlActive: "#2a2a2a",
+ controlActiveStrong: "#222222",
+ controlInset: "#1a1a1a",
+ controlInsetHover: "#202020",
+ composerPanel: "#1e1e1e",
+ composerBorder: "#763750",
+ composerBorderMuted: "#333333",
+ composerSend: "#763750",
+ composerSendHover: "#ad5273",
+ accent: "#ad5273",
+ selection: "#3a2030",
+ selectionActive: "#4a2840",
+ text: "#e6e6e6",
+ muted: "#b0b0b0",
+ subtle: "#707070",
+ info: "#888888",
+};
+
+const BORING_LIGHT_PALETTE: TuiPalette = {
+ ...DEFAULT_LIGHT_PALETTE,
+ canvas: "#ebebeb",
+ sidebar: "#e0e0e0",
+ main: "#f0f0f0",
+ surface: "#ffffff",
+ surfaceAlt: "#e8e8e8",
+ input: "#ffffff",
+ surfaceUser: "#e0e0e0",
+ footer: "#f0f0f0",
+ popup: "#ffffff",
+ border: "#d4d4d4",
+ divider: "#d0d0d0",
+ controlHover: "#e0e0e0",
+ controlActive: "#d4d4d4",
+ controlActiveStrong: "#c8c8c8",
+ controlInset: "#e0e0e0",
+ controlInsetHover: "#d8d8d8",
+ composerPanel: "#ffffff",
+ composerBorder: "#ad5273",
+ composerBorderMuted: "#c9c9c9",
+ composerSend: "#ad5273",
+ composerSendHover: "#8a3f5c",
+ accent: "#ad5273",
+ selection: "#e8d0dd",
+ selectionActive: "#ddbece",
+ text: "#171717",
+ muted: "#616161",
+ subtle: "#8a8a8a",
+ info: "#666666",
+};
+
+const BORING_DARK_THEME: TuiTheme = {
+ ...DEFAULT_DARK_THEME,
+ id: BORING_THEME_ID,
+ mode: "dark",
+ palette: BORING_DARK_PALETTE,
+};
+
+const BORING_LIGHT_THEME: TuiTheme = {
+ ...DEFAULT_LIGHT_THEME,
+ id: BORING_THEME_ID,
+ mode: "light",
+ palette: BORING_LIGHT_PALETTE,
+};
+
export const DEFAULT_TUI_THEME = DEFAULT_DARK_THEME;
function defaultThemeForMode(mode: TuiThemeMode): TuiTheme {
@@ -714,6 +794,8 @@ export function resolveTuiThemeMode(
const THEME_CACHE = new Map([
[`${DEFAULT_TUI_THEME_ID}:dark`, DEFAULT_DARK_THEME],
[`${DEFAULT_TUI_THEME_ID}:light`, DEFAULT_LIGHT_THEME],
+ [`${BORING_THEME_ID}:dark`, BORING_DARK_THEME],
+ [`${BORING_THEME_ID}:light`, BORING_LIGHT_THEME],
]);
export function resolveTuiTheme(
diff --git a/apps/tui/src/ui.tsx b/apps/tui/src/ui.tsx
index bc516814..619334f7 100644
--- a/apps/tui/src/ui.tsx
+++ b/apps/tui/src/ui.tsx
@@ -194,6 +194,8 @@ import {
shouldClearPendingCreatedThread,
} from "./threadSelection";
import { resolveWorkEntryIcon } from "./workEntryIcons";
+import { createProfile, PROFILE_ICON_OPTIONS } from "./profiles";
+import { BORING_THEME_ID } from "./theme";
type FocusArea =
| "projects"
@@ -213,7 +215,8 @@ type OverlayMenu =
| "sidebar-sort"
| "git-actions"
| "composer-env"
- | "composer-branch";
+ | "composer-branch"
+ | "chat-settings";
type SettingsSelectKind =
| "theme"
| "theme-preset"
@@ -1315,6 +1318,41 @@ function AttachmentPill({
);
}
+function ChatCategoryButton(props: {
+ icon: string;
+ label: string;
+ onPress: () => void;
+}) {
+ const [hoveredCategory, setHoveredCategory] = useState(false);
+ return (
+ setHoveredCategory(true)}
+ onMouseOut={() => setHoveredCategory(false)}
+ onMouseDown={props.onPress}
+ style={{
+ backgroundColor: hoveredCategory ? RGBA.fromHex("#a23b67") : PALETTE.surfaceAlt,
+ paddingLeft: 2,
+ paddingRight: 2,
+ paddingTop: 0,
+ paddingBottom: 0,
+ marginRight: 1,
+ marginBottom: 1,
+ flexDirection: "row",
+ alignItems: "center",
+ border: true,
+ borderStyle: "rounded",
+ borderColor: hoveredCategory ? RGBA.fromHex("#a23b67") : PALETTE.border,
+ }}
+ >
+
+
+
+ );
+}
+
function PathSuggestionRow(props: {
entry: ProjectEntry;
active?: boolean;
@@ -2800,6 +2838,20 @@ export function App({
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const previewAttachmentCacheRef = useRef