diff --git a/FEEDBACK.md b/FEEDBACK.md
index e03a291..31fa0fb 100644
--- a/FEEDBACK.md
+++ b/FEEDBACK.md
@@ -501,3 +501,56 @@
- Clicking the info button on messages does nothing — no panel appears
- Introduced in v1.15.0 (commit `b4813f0`)
- Fix the click handler / panel display logic
+
+## Item #47
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Themes — light mode, OLED black, custom theme support
+ - Add theme switcher (dark default, light mode, OLED black)
+ - Configurable accent color
+ - Persist choice in localStorage
+
+## Item #48
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Message search — Ctrl+F in conversation history
+ - Search bar that filters/highlights messages in the current session
+ - Navigate between results (up/down arrows)
+ - Keyboard shortcut Ctrl+F
+
+## Item #49
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Syntax highlight in the input textarea
+ - Color code blocks even while typing in the prompt input
+ - Highlight markdown syntax (bold, code, headers) in real-time
+
+## Item #50
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Multi-tab — split view for 2 sessions side by side
+ - Allow viewing 2 sessions simultaneously in a split pane layout
+ - Drag-to-resize divider between panes
+ - Toggle via button or keyboard shortcut
+
+## Item #51
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Typing preview — live markdown render while typing
+ - Show a preview pane below or beside the input with rendered markdown
+ - Toggle on/off
+ - Helps compose complex messages with formatting
+
+## Item #52
+- **Date:** 2026-02-12
+- **Priority:** medium
+- **Status:** pending
+- **Description:** Raw JSON viewer — toggle to see raw gateway messages
+ - Toggle button to switch between rendered view and raw JSON
+ - Show the full gateway message payload as formatted JSON
+ - Useful for debugging and understanding the protocol
diff --git a/src/App.tsx b/src/App.tsx
index 2ecd6a9..5107cc9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -14,7 +14,7 @@ export default function App() {
const {
status, messages, sessions, activeSession, isGenerating, isLoadingHistory,
sendMessage, abort, switchSession, deleteSession,
- authenticated, login, logout, connectError, isConnecting,
+ authenticated, login, logout, connectError, isConnecting, agentIdentity,
} = useGateway();
const [sidebarOpen, setSidebarOpen] = useState(false);
const [shortcutsOpen, setShortcutsOpen] = useState(false);
@@ -97,10 +97,10 @@ export default function App() {
onClose={() => setSidebarOpen(false)}
/>
-
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} soundEnabled={soundEnabled} onToggleSound={toggleSound} messages={messages} />
+ setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} soundEnabled={soundEnabled} onToggleSound={toggleSound} messages={messages} agentAvatarUrl={agentIdentity?.avatar} />
Loading…
}>
-
+
setShortcutsOpen(false)} />
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
index 2953c6a..da66522 100644
--- a/src/components/Chat.tsx
+++ b/src/components/Chat.tsx
@@ -16,6 +16,7 @@ interface Props {
sessionKey?: string;
onSend: (text: string, attachments?: Array<{ mimeType: string; fileName: string; content: string }>) => void;
onAbort: () => void;
+ agentAvatarUrl?: string;
}
function isNoReply(msg: ChatMessage): boolean {
@@ -66,7 +67,7 @@ function getDateKey(ts: number): string {
/** Threshold in pixels — if the user is within this distance of the bottom, auto-scroll */
const SCROLL_THRESHOLD = 150;
-export function Chat({ messages, isGenerating, isLoadingHistory, status, sessionKey, onSend, onAbort }: Props) {
+export function Chat({ messages, isGenerating, isLoadingHistory, status, sessionKey, onSend, onAbort, agentAvatarUrl }: Props) {
const t = useT();
const bottomRef = useRef(null);
const scrollContainerRef = useRef(null);
@@ -161,7 +162,7 @@ export function Chat({ messages, isGenerating, isLoadingHistory, status, session
)}
-
+
))}
{showTyping && }
diff --git a/src/components/ChatMessage.tsx b/src/components/ChatMessage.tsx
index 015dc33..4b507b7 100644
--- a/src/components/ChatMessage.tsx
+++ b/src/components/ChatMessage.tsx
@@ -320,7 +320,7 @@ function SystemEventMessage({ message }: { message: ChatMessageType }) {
);
}
-export function ChatMessageComponent({ message, onRetry }: { message: ChatMessageType; onRetry?: (text: string) => void }) {
+export function ChatMessageComponent({ message, onRetry, agentAvatarUrl }: { message: ChatMessageType; onRetry?: (text: string) => void; agentAvatarUrl?: string }) {
useLocale(); // re-render on locale change
const isUser = message.role === 'user';
@@ -342,10 +342,12 @@ export function ChatMessageComponent({ message, onRetry }: { message: ChatMessag
return (
{/* Avatar */}
-
+
{isUser
?
- :
+ : agentAvatarUrl
+ ?

+ :
}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index e71b6d7..04ed521 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -15,9 +15,10 @@ interface Props {
soundEnabled?: boolean;
onToggleSound?: () => void;
messages?: ChatMessage[];
+ agentAvatarUrl?: string;
}
-export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, onLogout, soundEnabled, onToggleSound, messages }: Props) {
+export function Header({ status, sessionKey, onToggleSidebar, activeSessionData, onLogout, soundEnabled, onToggleSound, messages, agentAvatarUrl }: Props) {
const t = useT();
const sessionLabel = activeSessionData ? sessionDisplayName(activeSessionData) : (sessionKey.split(':').pop() || sessionKey);
@@ -36,7 +37,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
-

+
{t('header.title')}
diff --git a/src/hooks/useGateway.ts b/src/hooks/useGateway.ts
index 9e8f42c..a02443f 100644
--- a/src/hooks/useGateway.ts
+++ b/src/hooks/useGateway.ts
@@ -3,7 +3,7 @@ import { GatewayClient, type JsonPayload } from '../lib/gateway';
import { genIdempotencyKey } from '../lib/utils';
import { getStoredCredentials, storeCredentials, clearCredentials } from '../lib/credentials';
import { isSystemEvent } from '../lib/systemEvent';
-import type { ChatMessage, MessageBlock, ConnectionStatus, Session } from '../types';
+import type { ChatMessage, MessageBlock, ConnectionStatus, Session, AgentIdentity } from '../types';
interface ChatPayloadMessage {
content?: string | Array<{ type: string; text?: string }>;
@@ -43,6 +43,7 @@ export function useGateway() {
const currentRunIdRef = useRef
(null);
const [activeSessions, setActiveSessions] = useState>(new Set());
const [unreadSessions, setUnreadSessions] = useState>(new Set());
+ const [agentIdentity, setAgentIdentity] = useState(null);
const handleAgentEvent = useCallback((payload: JsonPayload) => {
if (payload?.stream !== 'tool') return;
@@ -94,6 +95,22 @@ export function useGateway() {
localStorage.setItem('pinchchat-deleted-sessions', JSON.stringify([...deleted]));
}, [getDeletedSessions]);
+ const loadAgentIdentity = useCallback(async () => {
+ try {
+ const res = await clientRef.current?.send('agent.identity.get', {});
+ if (res) {
+ setAgentIdentity({
+ name: res.name as string | undefined,
+ emoji: res.emoji as string | undefined,
+ avatar: res.avatar as string | undefined,
+ agentId: res.agentId as string | undefined,
+ });
+ }
+ } catch {
+ // Silently ignore — identity is optional
+ }
+ }, []);
+
const loadSessions = useCallback(async () => {
try {
const res = await clientRef.current?.send('sessions.list', {});
@@ -228,6 +245,7 @@ export function useGateway() {
isConnectingRef.current = false;
storeCredentials(wsUrl, token);
loadSessions();
+ loadAgentIdentity();
loadHistory(activeSessionRef.current);
} else if (s === 'disconnected' && !client.isConnected) {
// If we never connected successfully, this is an auth/connection error
@@ -345,7 +363,7 @@ export function useGateway() {
isConnectingRef.current = true;
setConnectError(null);
client.connect();
- }, [handleAgentEvent, loadHistory, loadSessions]);
+ }, [handleAgentEvent, loadHistory, loadSessions, loadAgentIdentity]);
// On mount: try stored credentials
const initRef = useRef(false);
@@ -461,6 +479,6 @@ export function useGateway() {
return {
status, messages, sessions: enrichedSessions, activeSession, isGenerating, isLoadingHistory,
sendMessage, abort, switchSession, loadSessions, deleteSession,
- authenticated, login, logout, connectError, isConnecting,
+ authenticated, login, logout, connectError, isConnecting, agentIdentity,
};
}
diff --git a/src/types/index.ts b/src/types/index.ts
index 2e0aef8..fc73ccd 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -35,6 +35,13 @@ export interface Session {
lastMessagePreview?: string;
}
+export interface AgentIdentity {
+ name?: string;
+ emoji?: string;
+ avatar?: string;
+ agentId?: string;
+}
+
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected';
export interface GatewayState {