diff --git a/ui/src/components/logs/logs-filters.tsx b/ui/src/components/logs/logs-filters.tsx
index 4d00d830..7080a1a9 100644
--- a/ui/src/components/logs/logs-filters.tsx
+++ b/ui/src/components/logs/logs-filters.tsx
@@ -44,7 +44,7 @@ export interface LogsFiltersProps {
onRequestIdChange?: (v: string) => void;
timeWindow?: LogsTimeWindow;
onTimeWindowChange?: (v: LogsTimeWindow) => void;
- /** When true, hides entries from `web-server:*` sources. Default ON. */
+ /** When true, hides entries from `web-server:*` sources. Default OFF. */
hideDashboardInternals?: boolean;
onHideDashboardInternalsChange?: (next: boolean) => void;
onClearAll?: () => void;
@@ -91,7 +91,7 @@ export function LogsFilters({
onRequestIdChange,
timeWindow = 'all',
onTimeWindowChange,
- hideDashboardInternals = true,
+ hideDashboardInternals = false,
onHideDashboardInternalsChange,
onClearAll,
}: LogsFiltersProps) {
@@ -324,11 +324,10 @@ export function LogsFilters({
htmlFor="logs-hide-internals"
className="block text-[12px] font-medium text-foreground"
>
- Hide dashboard internals
+ Hide dashboard web-server logs
- Suppress web-server:*{' '}
- self-polling.
+ Optional noise reduction. Audit entries are visible by default.
onHideDashboardInternalsChange(e.target.checked)}
className={cn('mt-0.5 h-4 w-4 cursor-pointer accent-foreground', FOCUS_RING)}
- aria-label="Hide dashboard internals"
+ aria-label="Hide dashboard web-server logs"
/>
) : null}
diff --git a/ui/src/hooks/use-logs.ts b/ui/src/hooks/use-logs.ts
index 702db1f6..b27dc0ae 100644
--- a/ui/src/hooks/use-logs.ts
+++ b/ui/src/hooks/use-logs.ts
@@ -116,10 +116,10 @@ export function useLogsWorkspace() {
const [limit, setLimit] = useState(DEFAULT_LIMIT);
const [selectedEntryId, setSelectedEntryId] = useState(null);
const [isPaused, setIsPaused] = useState(false);
- // Default ON: dashboard self-polling generates 100s of identical entries
- // per refresh which drown out real provider activity. Users can opt in to
- // see internals via the advanced filter toggle.
- const [hideDashboardInternals, setHideDashboardInternals] = useState(true);
+ // Default OFF: web-server:* entries include dashboard access and WebSocket
+ // audit evidence, so keep them visible unless the operator opts into noise
+ // reduction from the advanced filter toggle.
+ const [hideDashboardInternals, setHideDashboardInternals] = useState(false);
const frozenIdsRef = useRef>(new Set());
const deferredSearch = useDeferredValue(search.trim());
@@ -196,8 +196,9 @@ export function useLogsWorkspace() {
const ts = Date.parse(entry.timestamp);
if (Number.isFinite(ts) && now - ts > cutoffMs) return false;
}
- // Hide dashboard self-polling unless user opted in. Preserves the
- // signal-to-noise ratio for fresh-load investigations.
+ // Optional noise reduction only: web-server:* entries can contain
+ // security-relevant dashboard access and WebSocket audit evidence, so
+ // they remain visible by default.
if (hideDashboardInternals && /^web-server:/i.test(entry.source)) return false;
return true;
});
@@ -273,7 +274,7 @@ export function useLogsWorkspace() {
setStageFilter('');
setRequestIdFilter('');
setTimeWindow('all');
- setHideDashboardInternals(true);
+ setHideDashboardInternals(false);
}, []);
return {
diff --git a/ui/tests/unit/hooks/use-logs.test.tsx b/ui/tests/unit/hooks/use-logs.test.tsx
new file mode 100644
index 00000000..f34c6b7a
--- /dev/null
+++ b/ui/tests/unit/hooks/use-logs.test.tsx
@@ -0,0 +1,120 @@
+import { act, renderHook, waitFor } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import type { ReactNode } from 'react';
+import { AllProviders } from '../../setup/test-utils';
+import { useLogsWorkspace } from '@/hooks/use-logs';
+import type { LogsEntry } from '@/lib/api-client';
+
+function createJsonResponse(body: Record, status = 200): Response {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { 'Content-Type': 'application/json' },
+ });
+}
+
+const entries: LogsEntry[] = [
+ {
+ id: 'http-audit-1',
+ timestamp: '2026-05-29T00:00:00.000Z',
+ level: 'error',
+ source: 'web-server:http',
+ event: 'request.completed',
+ message: 'Dashboard request completed',
+ processId: 1,
+ runId: null,
+ requestId: 'dashboard-audit',
+ },
+ {
+ id: 'ws-audit-1',
+ timestamp: '2026-05-29T00:00:01.000Z',
+ level: 'warn',
+ source: 'web-server:websocket',
+ event: 'message.invalid',
+ message: 'WebSocket client sent invalid JSON',
+ processId: 1,
+ runId: null,
+ requestId: 'dashboard-audit',
+ },
+ {
+ id: 'provider-1',
+ timestamp: '2026-05-29T00:00:02.000Z',
+ level: 'info',
+ source: 'provider:codex',
+ event: 'request.completed',
+ message: 'Provider request completed',
+ processId: 2,
+ runId: null,
+ requestId: 'provider-trace',
+ },
+];
+
+function mockLogsApi(): void {
+ vi.stubGlobal(
+ 'fetch',
+ vi.fn(async (input: RequestInfo | URL) => {
+ const url = String(input);
+ if (url.startsWith('/api/logs/config')) {
+ return createJsonResponse({
+ logging: {
+ enabled: true,
+ level: 'info',
+ rotate_mb: 10,
+ retain_days: 7,
+ redact: true,
+ live_buffer_size: 150,
+ },
+ });
+ }
+ if (url.startsWith('/api/logs/sources')) {
+ return createJsonResponse({
+ sources: entries.map((entry) => ({
+ source: entry.source,
+ label: entry.source,
+ kind: 'native',
+ count: 1,
+ lastTimestamp: entry.timestamp,
+ })),
+ });
+ }
+ if (url.startsWith('/api/logs/entries')) {
+ return createJsonResponse({ entries });
+ }
+ return createJsonResponse({ error: `Unexpected URL: ${url}` }, 404);
+ })
+ );
+}
+
+describe('useLogsWorkspace', () => {
+ it('keeps dashboard audit events visible by default and only hides them on opt-in', async () => {
+ mockLogsApi();
+
+ const wrapper = ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+ const { result } = renderHook(() => useLogsWorkspace(), { wrapper });
+
+ await waitFor(() => {
+ expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual([
+ 'http-audit-1',
+ 'ws-audit-1',
+ 'provider-1',
+ ]);
+ });
+
+ act(() => result.current.setHideDashboardInternals(true));
+
+ await waitFor(() => {
+ expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual(['provider-1']);
+ });
+
+ act(() => result.current.clearAdvancedFilters());
+
+ await waitFor(() => {
+ expect(result.current.entriesQuery.data?.map((entry) => entry.id)).toEqual([
+ 'http-audit-1',
+ 'ws-audit-1',
+ 'provider-1',
+ ]);
+ });
+ });
+});