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) {