Skip to content

Commit be0b769

Browse files
committed
Improve mobile approval swipe
1 parent 596fcd5 commit be0b769

File tree

7 files changed

+128
-2
lines changed

7 files changed

+128
-2
lines changed

src/bridge/transport.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import type {
3636
SSESessionConnectedPayload,
3737
SSESessionMessagePayload,
3838
SSETrustUpdatedPayload,
39+
TrustChangeCallback,
3940
WorkspaceFolder,
4041
} from './types';
4142

@@ -76,6 +77,9 @@ export class WebBridge {
7677
/** Callback invoked whenever the chat list or selection changes. */
7778
private onChatListChange: ChatListChangeCallback | null = null;
7879

80+
/** Callback invoked whenever trust mode changes. */
81+
private onTrustChange: TrustChangeCallback | null = null;
82+
7983
/**
8084
* IDs of chats whose full message history has been fetched from the REST API
8185
* and dispatched to the webview. Used to avoid redundant loads when switching
@@ -458,6 +462,32 @@ export class WebBridge {
458462
}
459463
}
460464

465+
// ---------------------------------------------------------------------------
466+
// Trust API (for the shell layer)
467+
// ---------------------------------------------------------------------------
468+
469+
/** Register a listener for trust state changes. */
470+
onTrustChanged(cb: TrustChangeCallback): void {
471+
this.onTrustChange = cb;
472+
// Immediately fire with current state
473+
cb(this.sessionState?.trust ?? false);
474+
}
475+
476+
/** Get the current trust mode. */
477+
getTrust(): boolean {
478+
return this.sessionState?.trust ?? false;
479+
}
480+
481+
/** Toggle trust mode — calls the REST API and lets the SSE event confirm the change. */
482+
async toggleTrust(): Promise<void> {
483+
const newTrust = !(this.sessionState?.trust ?? false);
484+
try {
485+
await this.api.setTrust(newTrust);
486+
} catch (err) {
487+
console.error('[Bridge] Failed to toggle trust:', err);
488+
}
489+
}
490+
461491
// ---------------------------------------------------------------------------
462492
// SSE connection
463493
// ---------------------------------------------------------------------------
@@ -632,7 +662,11 @@ export class WebBridge {
632662

633663
case 'trust:updated': {
634664
const trustData = data as SSETrustUpdatedPayload;
665+
if (this.sessionState) {
666+
this.sessionState.trust = trustData.trust;
667+
}
635668
this.dispatch('server/setTrust', trustData.trust);
669+
this.onTrustChange?.(trustData.trust);
636670
break;
637671
}
638672

src/bridge/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ export interface ChatEntry {
287287
/** Callback signature for chat list change notifications. */
288288
export type ChatListChangeCallback = (chats: ChatEntry[], selectedChatId: string | null) => void;
289289

290+
/** Callback signature for trust state change notifications. */
291+
export type TrustChangeCallback = (trust: boolean) => void;
292+
290293
// ---------------------------------------------------------------------------
291294
// Reconnection types
292295
// ---------------------------------------------------------------------------

src/components/ChatSidebar.css

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,64 @@
227227
color: #f14c4c;
228228
}
229229

230+
/* ---- Trust toggle ---- */
231+
.chat-sidebar-trust {
232+
display: flex;
233+
align-items: center;
234+
gap: 0.5rem;
235+
width: 100%;
236+
padding: 0.6rem 0.75rem;
237+
border: none;
238+
border-top: 1px solid #2a2a3e;
239+
background: transparent;
240+
color: #5a6a7a;
241+
font-size: 0.78rem;
242+
font-family: inherit;
243+
cursor: pointer;
244+
flex-shrink: 0;
245+
transition: background 0.12s, color 0.12s;
246+
}
247+
248+
.chat-sidebar-trust:hover {
249+
background: #1a1a2e;
250+
color: #8a9ab0;
251+
}
252+
253+
.chat-sidebar-trust .codicon {
254+
font-size: 14px;
255+
flex-shrink: 0;
256+
}
257+
258+
.chat-sidebar-trust-label {
259+
flex: 1;
260+
text-align: left;
261+
font-weight: 500;
262+
}
263+
264+
.chat-sidebar-trust-dot {
265+
width: 8px;
266+
height: 8px;
267+
border-radius: 50%;
268+
flex-shrink: 0;
269+
transition: background 0.15s;
270+
}
271+
272+
.chat-sidebar-trust-dot.off {
273+
background: #5a6a7a;
274+
}
275+
276+
.chat-sidebar-trust-dot.on {
277+
background: #f44747;
278+
}
279+
280+
.chat-sidebar-trust.trust-on {
281+
color: #f44747;
282+
}
283+
284+
.chat-sidebar-trust.trust-on:hover {
285+
color: #f77;
286+
}
287+
230288
/* ---- Mobile hamburger button ---- */
231289
.chat-sidebar-toggle {
232290
display: none;

src/components/ChatSidebar.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ interface ChatSidebarProps {
2828
mobileOpen: boolean;
2929
/** Called when the mobile drawer should close (backdrop tap, item select). */
3030
onMobileClose: () => void;
31+
/** Current trust mode state. */
32+
trust?: boolean;
33+
/** Called when the user toggles trust mode. */
34+
onToggleTrust?: () => void;
3135
}
3236

3337
// ---------------------------------------------------------------------------
@@ -51,7 +55,7 @@ export function ChatSidebarToggle({ onClick }: { onClick: () => void }) {
5155
// Main sidebar component
5256
// ---------------------------------------------------------------------------
5357

54-
export function ChatSidebar({ bridge, chats = [], selectedId, workspaceFolders = [], mobileOpen, onMobileClose }: ChatSidebarProps) {
58+
export function ChatSidebar({ bridge, chats = [], selectedId, workspaceFolders = [], mobileOpen, onMobileClose, trust = false, onToggleTrust }: ChatSidebarProps) {
5559
const handleSelect = useCallback(
5660
(chatId: string) => {
5761
bridge?.selectChat(chatId);
@@ -145,6 +149,22 @@ export function ChatSidebar({ bridge, chats = [], selectedId, workspaceFolders =
145149
))
146150
)}
147151
</div>
152+
153+
{onToggleTrust && (
154+
<button
155+
className={`chat-sidebar-trust ${trust ? 'trust-on' : 'trust-off'}`}
156+
onClick={onToggleTrust}
157+
title={trust
158+
? 'Trust ON — tool calls are auto-accepted'
159+
: 'Trust OFF — tool calls require approval'}
160+
>
161+
<i className={`codicon ${trust ? 'codicon-unlock' : 'codicon-lock'}`} />
162+
<span className="chat-sidebar-trust-label">
163+
Trust {trust ? 'ON' : 'OFF'}
164+
</span>
165+
<span className={`chat-sidebar-trust-dot ${trust ? 'on' : 'off'}`} />
166+
</button>
167+
)}
148168
</div>
149169
</>
150170
);

src/pages/RemoteProduct.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export function RemoteProduct() {
4646
const [chatEntries, setChatEntries] = useState<ChatEntry[]>([]);
4747
const [selectedChatId, setSelectedChatId] = useState<string | null>(null);
4848
const [workspaceFolders, setWorkspaceFolders] = useState<(WorkspaceFolder | string)[]>([]);
49+
const [trust, setTrust] = useState(false);
4950
const [discovery, setDiscovery] = useState<DiscoveryProgress | null>(null);
5051
const discoveryAbortRef = useRef<AbortController | null>(null);
5152

@@ -267,6 +268,8 @@ export function RemoteProduct() {
267268
}
268269
};
269270

271+
bridge.onTrustChanged((t) => setTrust(t));
272+
270273
bridge.onChatListChanged((entries, selected) => {
271274
setChatEntries(entries);
272275
setSelectedChatId(selected);
@@ -293,13 +296,18 @@ export function RemoteProduct() {
293296
setChatEntries([]);
294297
setSelectedChatId(null);
295298
setWorkspaceFolders([]);
299+
setTrust(false);
296300
}
297301
}, [activeId]);
298302

299303
const toggleSidebar = useCallback(() => {
300304
setSidebarOpen((prev) => !prev);
301305
}, []);
302306

307+
const handleToggleTrust = useCallback(() => {
308+
activeBridge?.toggleTrust();
309+
}, [activeBridge]);
310+
303311
// --- Render ---
304312

305313
const activeEntry = entries.find((e) => e.id === activeId);
@@ -328,6 +336,8 @@ export function RemoteProduct() {
328336
workspaceFolders={workspaceFolders}
329337
mobileOpen={sidebarOpen}
330338
onMobileClose={() => setSidebarOpen(false)}
339+
trust={trust}
340+
onToggleTrust={handleToggleTrust}
331341
/>
332342
)}
333343

src/styles/web-theme.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ html[data-editor="web"] {
2121

2222
--eca-success-fg: #73c991;
2323
--eca-warning-fg: #cca700;
24+
--eca-approval-fg: #e8850c;
2425
--eca-error-fg: #f14c4c;
2526

2627
--eca-warning-message-fg: #cca700;

0 commit comments

Comments
 (0)