diff --git a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestRow.tsx b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestRow.tsx
index 9fbcca5b95..1acf09e7a6 100644
--- a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestRow.tsx
+++ b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestRow.tsx
@@ -9,6 +9,7 @@ interface RequestRowProps {
request: InterceptedRequest;
selected: boolean;
onClick: () => void;
+ onSameContext?: (contextKey: string) => void;
style?: React.CSSProperties;
}
@@ -39,7 +40,7 @@ function formatTime(iso: string): string {
}
}
-export function RequestRow({ request, selected, onClick, style }: RequestRowProps) {
+export function RequestRow({ request, selected, onClick, onSameContext, style }: RequestRowProps) {
const pathShort = request.path.length > 32 ? `…${request.path.slice(-30)}` : request.path;
const sc = statusColor(request.status);
@@ -74,9 +75,17 @@ export function RequestRow({ request, selected, onClick, style }: RequestRowProp
{pathShort}
{request.contextKey && (
-
+
+
)}
diff --git a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestStreamingList.tsx b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestStreamingList.tsx
index 112eb2ad93..dd68276783 100644
--- a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestStreamingList.tsx
+++ b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestStreamingList.tsx
@@ -10,6 +10,9 @@ interface RequestStreamingListProps {
selectedId: string | null;
onSelect: (req: InterceptedRequest) => void;
containerHeight: number;
+ onSameContext?: (contextKey: string) => void;
+ sameContextKey?: string;
+ onClearContextFilter?: () => void;
}
export function RequestStreamingList({
@@ -17,6 +20,9 @@ export function RequestStreamingList({
selectedId,
onSelect,
containerHeight,
+ onSameContext,
+ sameContextKey,
+ onClearContextFilter,
}: RequestStreamingListProps) {
const { virtualItems, totalHeight, containerRef, rowRef } = useVirtualList(
requests,
@@ -25,44 +31,73 @@ export function RequestStreamingList({
if (requests.length === 0) {
return (
-
-
-
- network_check
-
-
No requests captured yet.
-
Make sure AgentBridge is running or enable another capture mode.
+
+ {sameContextKey && (
+
+ Filtering: context {sameContextKey.slice(0, 6)}
+
+
+ )}
+
+
+
+ network_check
+
+
No requests captured yet.
+
Make sure AgentBridge is running or enable another capture mode.
+
);
}
return (
-
}
- className="h-full overflow-y-auto relative"
- style={{ contain: "strict" }}
- >
-
- {virtualItems.map(({ index, item, top }) => (
-
+ {sameContextKey && (
+
+ Filtering: context {sameContextKey.slice(0, 6)}
+
- ))}
+ [clear]
+
+
+ )}
+
}
+ className="flex-1 overflow-y-auto relative"
+ style={{ contain: "strict" }}
+ >
+
+ {virtualItems.map(({ index, item, top }) => (
+
+ onSelect(item)}
+ onSameContext={onSameContext}
+ />
+
+ ))}
+
);
diff --git a/src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useTrafficStream.ts b/src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useTrafficStream.ts
index 2f6c8ac8f3..09112748e6 100644
--- a/src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useTrafficStream.ts
+++ b/src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useTrafficStream.ts
@@ -2,6 +2,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { InterceptedRequest, ListFilters, WsEvent } from "@/mitm/inspector/types";
+import type { FiltersState } from "./useTrafficFilters";
const WS_PATH = "/api/tools/traffic-inspector/ws";
const INITIAL_BACKOFF_MS = 500;
@@ -13,6 +14,7 @@ export interface TrafficStreamState {
connected: boolean;
paused: boolean;
total: number;
+ pendingCount: number;
}
export interface TrafficStreamActions {
@@ -22,11 +24,12 @@ export interface TrafficStreamActions {
}
export function useTrafficStream(
- filters: ListFilters
+ filters: FiltersState | ListFilters
): [TrafficStreamState, TrafficStreamActions] {
const [requests, setRequests] = useState
([]);
const [connected, setConnected] = useState(false);
const [paused, setPaused] = useState(false);
+ const [pendingCount, setPendingCount] = useState(0);
const wsRef = useRef(null);
const backoffRef = useRef(INITIAL_BACKOFF_MS);
@@ -44,13 +47,14 @@ export function useTrafficStream(
});
const applyFilter = useCallback((req: InterceptedRequest): boolean => {
- const f = filtersRef.current;
+ const f = filtersRef.current as FiltersState;
if (f.profile === "llm" && req.detectedKind !== "llm") return false;
if (f.profile === "custom" && req.source !== "custom-host") return false;
if (f.host && !req.host.includes(f.host)) return false;
if (f.agent && req.agent !== f.agent) return false;
if (f.source && req.source !== f.source) return false;
if (f.sessionId && req.sessionId !== f.sessionId) return false;
+ if (f.sameContextKey && req.contextKey !== f.sameContextKey) return false;
if (f.status) {
const s = req.status;
if (typeof s === "number") {
@@ -93,7 +97,10 @@ export function useTrafficStream(
}
if (pausedRef.current) {
- if (event.type === "new") pendingRef.current.push(event.data);
+ if (event.type === "new") {
+ pendingRef.current.push(event.data);
+ setPendingCount(pendingRef.current.length);
+ }
if (event.type === "update") {
const idx = pendingRef.current.findIndex((r) => r.id === event.data.id);
if (idx !== -1) pendingRef.current[idx] = event.data;
@@ -157,6 +164,7 @@ export function useTrafficStream(
if (pendingRef.current.length > 0) {
const pending = pendingRef.current.filter(applyFilter);
pendingRef.current = [];
+ setPendingCount(0);
setRequests((prev) => [...pending, ...prev].slice(0, 1000));
}
}, [applyFilter]);
@@ -164,6 +172,7 @@ export function useTrafficStream(
const clear = useCallback(() => {
setRequests([]);
pendingRef.current = [];
+ setPendingCount(0);
}, []);
const state: TrafficStreamState = {
@@ -171,6 +180,7 @@ export function useTrafficStream(
connected,
paused,
total: requests.length,
+ pendingCount,
};
return [state, { pause, resume, clear }];
diff --git a/tests/unit/ui/same-context-filter.test.tsx b/tests/unit/ui/same-context-filter.test.tsx
new file mode 100644
index 0000000000..ddc9aaef0b
--- /dev/null
+++ b/tests/unit/ui/same-context-filter.test.tsx
@@ -0,0 +1,116 @@
+/**
+ * Tests for R5-4: same-context filter wired end-to-end
+ *
+ * Source-grep assertions that:
+ * - useTrafficStream.applyFilter branches on sameContextKey
+ * - RequestRow exports an onSameContext prop
+ * - useTrafficFilters.setSameContext is referenced from TrafficInspectorPageClient
+ */
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.resolve(
+ __dirname,
+ "../../../src/app/(dashboard)/dashboard/tools/traffic-inspector"
+);
+
+function read(rel: string): string {
+ return fs.readFileSync(path.join(ROOT, rel), "utf8");
+}
+
+describe("R5-4 same-context filter end-to-end", () => {
+ it("useTrafficStream.applyFilter has sameContextKey branch", () => {
+ const src = read("hooks/useTrafficStream.ts");
+ assert.ok(
+ src.includes("sameContextKey") && src.includes("contextKey"),
+ "applyFilter should branch on sameContextKey / contextKey"
+ );
+ // Must actually exclude requests where contextKey differs
+ assert.ok(
+ src.includes("req.contextKey !== f.sameContextKey"),
+ "should exclude when contextKey !== sameContextKey"
+ );
+ });
+
+ it("RequestRow accepts onSameContext prop in interface", () => {
+ const src = read("components/RequestRow.tsx");
+ assert.ok(
+ src.includes("onSameContext"),
+ "RequestRow interface should declare onSameContext prop"
+ );
+ assert.ok(
+ src.includes("onSameContext?.("),
+ "RequestRow should call onSameContext on click"
+ );
+ });
+
+ it("RequestRow ctx chip is a button element", () => {
+ const src = read("components/RequestRow.tsx");
+ // The ctx chip should now be a button for keyboard/mouse click
+ const hasButton = src.includes("