diff --git a/internal/gateway/methods/teams_crud.go b/internal/gateway/methods/teams_crud.go index d29f021a..110f813f 100644 --- a/internal/gateway/methods/teams_crud.go +++ b/internal/gateway/methods/teams_crud.go @@ -312,19 +312,23 @@ func (m *TeamsMethods) handleUpdate(ctx context.Context, client *gateway.Client, EscalationActions []string `json:"escalation_actions,omitempty"` WorkspaceScope string `json:"workspace_scope,omitempty"` WorkspaceQuotaMB *int `json:"workspace_quota_mb,omitempty"` - Notifications *struct { + Notifications *struct { Dispatched *bool `json:"dispatched,omitempty"` Progress *bool `json:"progress,omitempty"` Failed *bool `json:"failed,omitempty"` Completed *bool `json:"completed,omitempty"` Commented *bool `json:"commented,omitempty"` NewTask *bool `json:"new_task,omitempty"` + SlowTool *bool `json:"slow_tool,omitempty"` Mode string `json:"mode,omitempty"` } `json:"notifications,omitempty"` MemberRequests *struct { Enabled *bool `json:"enabled,omitempty"` AutoDispatch *bool `json:"auto_dispatch,omitempty"` } `json:"member_requests,omitempty"` + BlockerEscalation *struct { + Enabled *bool `json:"enabled,omitempty"` + } `json:"blocker_escalation,omitempty"` } raw, _ := json.Marshal(params.Settings) var access teamAccessSettings diff --git a/ui/web/src/api/ws-client.ts b/ui/web/src/api/ws-client.ts index 11231ae9..e8cd42dc 100644 --- a/ui/web/src/api/ws-client.ts +++ b/ui/web/src/api/ws-client.ts @@ -21,6 +21,7 @@ export class WsClient { private reconnectAttempts = 0; private authenticated = false; private intentionalClose = false; + private pairingInProgress = false; private connectGeneration = 0; /** Server-assigned role from connect response. */ @@ -90,6 +91,7 @@ export class WsClient { disconnect(): void { this.intentionalClose = true; + this.pairingInProgress = false; if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; @@ -221,10 +223,14 @@ export class WsClient { // Browser pairing: server requires approval if (res?.status === "pending_pairing" && res.pairing_code && res.sender_id) { - this.onPairingRequired?.(res.pairing_code, res.sender_id); + if (!this.pairingInProgress) { + this.pairingInProgress = true; + this.onPairingRequired?.(res.pairing_code, res.sender_id); + } // Keep connection alive for polling browser.pairing.status return; } + this.pairingInProgress = false; // Server accepted connection but assigned viewer role → token is invalid if (this.getToken() && res?.role === "viewer") { @@ -251,6 +257,7 @@ export class WsClient { this.onAuthFailure?.(); return; } + this.intentionalClose = true; this.ws?.close(); } } @@ -329,11 +336,11 @@ export class WsClient { private scheduleReconnect(): void { if (this.reconnectTimer) return; + this.reconnectAttempts++; const delay = Math.min( this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay, ); - this.reconnectAttempts++; this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; diff --git a/ui/web/src/components/graph/sigma-graph-container.tsx b/ui/web/src/components/graph/sigma-graph-container.tsx index 532a41a8..069236d3 100644 --- a/ui/web/src/components/graph/sigma-graph-container.tsx +++ b/ui/web/src/components/graph/sigma-graph-container.tsx @@ -166,7 +166,7 @@ export function SigmaGraphContainer({ onSigmaReady?.(null); } }; - }, [graph, edgeType, compact]); + }, [graph, edgeType, compact, setSigmaRef]); // --- Update theme colors without re-init --- useEffect(() => { diff --git a/ui/web/src/components/graph/sigma-graph-search.tsx b/ui/web/src/components/graph/sigma-graph-search.tsx index f6260645..ca5e96a1 100644 --- a/ui/web/src/components/graph/sigma-graph-search.tsx +++ b/ui/web/src/components/graph/sigma-graph-search.tsx @@ -36,13 +36,15 @@ export function SigmaGraphSearch({ sigma, graph, onNodeSelect, placeholder = "Se } const lower = q.toLowerCase(); const matches: SearchResult[] = []; - graph.forEachNode((node, attrs) => { - if (matches.length >= 10) return; + const nodes = graph.nodes(); + for (let i = 0; i < nodes.length && matches.length < 10; i++) { + const node = nodes[i]!; + const attrs = graph.getNodeAttributes(node); const label = (attrs.label as string) || ""; if (label.toLowerCase().includes(lower)) { matches.push({ id: node, label, color: attrs.color as string }); } - }); + } setResults(matches); setOpen(matches.length > 0); setActiveIndex(0); diff --git a/ui/web/src/components/layout/app-layout.tsx b/ui/web/src/components/layout/app-layout.tsx index 361f19ba..6095d542 100644 --- a/ui/web/src/components/layout/app-layout.tsx +++ b/ui/web/src/components/layout/app-layout.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { Outlet, useLocation } from "react-router"; import { WifiOff } from "lucide-react"; import { useTranslation } from "react-i18next"; @@ -29,6 +30,13 @@ export function AppLayout() { const connected = useAuthStore((s) => s.connected); const isMobile = useIsTablet(); + // Close mobile sidebar on route change (e.g. programmatic navigation, back button) + useEffect(() => { + if (isMobile && mobileSidebarOpen) { + setMobileSidebarOpen(false); + } + }, [location.pathname]); // eslint-disable-line react-hooks/exhaustive-deps + return (
{isMobile ? ( diff --git a/ui/web/src/components/layout/sidebar.tsx b/ui/web/src/components/layout/sidebar.tsx index 7164bf43..5fb40f50 100644 --- a/ui/web/src/components/layout/sidebar.tsx +++ b/ui/web/src/components/layout/sidebar.tsx @@ -84,7 +84,7 @@ export function Sidebar({ collapsed, onNavItemClick }: SidebarProps) {