From 62ef88300a73c9f38103a9f561b2bc4d17b69a16 Mon Sep 17 00:00:00 2001 From: 777genius Date: Mon, 25 May 2026 14:54:28 +0300 Subject: [PATCH] feat(logs): compact team log source controls --- .../adapters/MemberLogStreamSection.tsx | 95 +++++-------------- .../renderer/hooks/useMemberLogStream.ts | 2 +- .../member-log-stream/renderer/index.ts | 4 +- .../renderer/ui/ExecutionLogStreamView.tsx | 82 ++++++++-------- .../renderer/ui/executionLogStreamUtils.ts | 29 ++++++ .../components/team/ClaudeLogsPanel.tsx | 66 ++++++------- .../components/team/ClaudeLogsSection.tsx | 56 +++++------ .../components/team/ClaudeLogsSection.test.ts | 47 +++++++-- 8 files changed, 195 insertions(+), 186 deletions(-) create mode 100644 src/features/member-log-stream/renderer/ui/executionLogStreamUtils.ts diff --git a/src/features/member-log-stream/renderer/adapters/MemberLogStreamSection.tsx b/src/features/member-log-stream/renderer/adapters/MemberLogStreamSection.tsx index b38401a6..627f2acb 100644 --- a/src/features/member-log-stream/renderer/adapters/MemberLogStreamSection.tsx +++ b/src/features/member-log-stream/renderer/adapters/MemberLogStreamSection.tsx @@ -1,15 +1,13 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; -import { api } from '@renderer/api'; import { useStore } from '@renderer/store'; import { selectResolvedMembersForTeamName } from '@renderer/store/slices/teamSlice'; import { useMemberLogStream } from '../hooks/useMemberLogStream'; import { ExecutionLogStreamView } from '../ui/ExecutionLogStreamView'; -import { MemberRuntimeProcessLogsPanel } from '../ui/MemberRuntimeProcessLogsPanel'; -import type { MemberLogStreamSegment, MemberRuntimeLogKind } from '../../contracts'; +import type { MemberLogStreamSegment } from '../../contracts'; import type { ResolvedTeamMember } from '@shared/types'; interface MemberLogStreamSectionProps { @@ -19,10 +17,6 @@ interface MemberLogStreamSectionProps { onInitialLoadErrorChange?: (hasError: boolean) => void; } -function describeMemberStream(): string { - return 'Member-scoped transcript and runtime logs rendered with the same execution-log components used in Task Log Stream.'; -} - function getSegmentMetaLabel(segment: MemberLogStreamSegment): string { const details = [segment.source.label]; if (segment.source.laneId) { @@ -38,24 +32,15 @@ function buildMemberSegmentRenderKey(segment: MemberLogStreamSegment): string { return `${segment.id}:${firstChunkId ?? segment.startTimestamp}`; } -export function MemberLogStreamSection({ +export const MemberLogStreamSection = ({ teamName, member, enabled = true, onInitialLoadErrorChange, -}: Readonly): React.JSX.Element { +}: Readonly): React.JSX.Element => { const { t } = useAppTranslation('team'); - const [selectedLogView, setSelectedLogView] = useState<'execution' | 'process'>('execution'); const teamMembers = useStore((s) => selectResolvedMembersForTeamName(s, teamName)); const { stream, loading, error } = useMemberLogStream({ teamName, member, enabled }); - const loadRuntimeLogTail = useCallback( - (input: { - readonly kind: MemberRuntimeLogKind; - readonly maxBytes: number; - readonly forceRefresh?: boolean; - }) => api.memberLogStream.getMemberRuntimeLogTail(teamName, member.name, input), - [member.name, teamName] - ); const hasInitialLoadError = Boolean(error && !stream && !loading); const boundedHistoryNote = useMemo(() => { if (!stream) return null; @@ -70,56 +55,24 @@ export function MemberLogStreamSection({ }, [hasInitialLoadError, onInitialLoadErrorChange]); return ( -
-
- - -
- - {selectedLogView === 'execution' ? ( - - ) : ( - - )} -
+ ); -} +}; diff --git a/src/features/member-log-stream/renderer/hooks/useMemberLogStream.ts b/src/features/member-log-stream/renderer/hooks/useMemberLogStream.ts index 96adf5e6..52440d6d 100644 --- a/src/features/member-log-stream/renderer/hooks/useMemberLogStream.ts +++ b/src/features/member-log-stream/renderer/hooks/useMemberLogStream.ts @@ -7,7 +7,7 @@ import { type MemberLogStreamResponse, normalizeMemberLogStreamResponse, } from '../../contracts'; -import { normalizeExecutionLogStream } from '../ui/ExecutionLogStreamView'; +import { normalizeExecutionLogStream } from '../ui/executionLogStreamUtils'; import type { ResolvedTeamMember } from '@shared/types'; diff --git a/src/features/member-log-stream/renderer/index.ts b/src/features/member-log-stream/renderer/index.ts index c9cc9831..6fab3461 100644 --- a/src/features/member-log-stream/renderer/index.ts +++ b/src/features/member-log-stream/renderer/index.ts @@ -1,7 +1,7 @@ export { MemberLogStreamSection } from './adapters/MemberLogStreamSection'; export { buildDefaultExecutionSegmentRenderKey, - ExecutionLogStreamView, normalizeExecutionLogStream, -} from './ui/ExecutionLogStreamView'; +} from './ui/executionLogStreamUtils'; +export { ExecutionLogStreamView } from './ui/ExecutionLogStreamView'; export { isMemberLogStreamUiEnabled } from './utils/featureGates'; diff --git a/src/features/member-log-stream/renderer/ui/ExecutionLogStreamView.tsx b/src/features/member-log-stream/renderer/ui/ExecutionLogStreamView.tsx index 7b3d3df2..6e165141 100644 --- a/src/features/member-log-stream/renderer/ui/ExecutionLogStreamView.tsx +++ b/src/features/member-log-stream/renderer/ui/ExecutionLogStreamView.tsx @@ -10,23 +10,14 @@ import { getThemedText, } from '@renderer/constants/teamColors'; import { useTheme } from '@renderer/hooks/useTheme'; -import { asEnhancedChunkArray } from '@renderer/types/data'; import { buildMemberColorMap } from '@renderer/utils/memberHelpers'; import { isLeadMember } from '@shared/utils/leadDetection'; import { AlertCircle, Clock, FileText, Loader2 } from 'lucide-react'; -import type { - BoardTaskLogActor, - BoardTaskLogParticipant, - BoardTaskLogSegment, - ResolvedTeamMember, -} from '@shared/types'; +import { buildDefaultExecutionSegmentRenderKey } from './executionLogStreamUtils'; -interface ExecutionLogStreamLike { - participants: BoardTaskLogParticipant[]; - defaultFilter: string; - segments: BoardTaskLogSegment[]; -} +import type { ExecutionLogStreamLike } from './executionLogStreamUtils'; +import type { BoardTaskLogActor, BoardTaskLogSegment, ResolvedTeamMember } from '@shared/types'; interface ParticipantVisual { name: string; @@ -47,6 +38,8 @@ export interface ExecutionLogStreamViewProps string; getSegmentMetaLabel?: (segment: TStream['segments'][number]) => string | null; } @@ -72,26 +65,6 @@ function actorLabel(actor: BoardTaskLogActor): string { return `member session ${actor.sessionId.slice(0, 8)}`; } -export function normalizeExecutionLogStream( - response: TStream -): TStream { - return { - ...response, - segments: response.segments.map((segment) => ({ - ...segment, - chunks: asEnhancedChunkArray(segment.chunks) ?? [], - })), - }; -} - -export function buildDefaultExecutionSegmentRenderKey(segment: BoardTaskLogSegment): string { - const firstChunkId = segment.chunks[0]?.id; - if (firstChunkId) { - return `${segment.participantKey}:${firstChunkId}`; - } - return `${segment.participantKey}:${segment.startTimestamp}`; -} - function buildParticipantVisualMap( stream: ExecutionLogStreamLike | null, members: readonly ResolvedTeamMember[], @@ -129,14 +102,16 @@ const SegmentMarker = ({ visual, teamName, metaLabel, + showParticipantBadge, }: { segment: TSegment; visual?: ParticipantVisual; teamName: string; metaLabel?: string | null; + showParticipantBadge: boolean; }): React.JSX.Element => (
- {visual ? ( + {showParticipantBadge && visual ? ( ({ teamName, visual, metaLabel, + showParticipantBadge, }: { segment: TSegment; showHeader: boolean; teamName: string; visual?: ParticipantVisual; metaLabel?: string | null; + showParticipantBadge: boolean; }): React.JSX.Element => (
{showHeader ? ( - + ) : null} ({ +export const ExecutionLogStreamView = ({ title, description, stream, @@ -235,9 +218,11 @@ export function ExecutionLogStreamView({ selectionResetKey, boundedHistoryNote, forceSegmentHeaders = false, + showIntro = true, + showSegmentParticipantBadge = true, buildSegmentRenderKey, getSegmentMetaLabel, -}: Readonly>): React.JSX.Element { +}: Readonly>): React.JSX.Element => { const { t } = useAppTranslation('team'); const [selectedParticipantKey, setSelectedParticipantKey] = useState('all'); const appliedSelectionResetKeyRef = useRef(null); @@ -291,7 +276,11 @@ export function ExecutionLogStreamView({ if (loading) { return (
-

{title}

+ {showIntro ? ( +

+ {title} +

+ ) : null}
{loadingText} @@ -303,7 +292,11 @@ export function ExecutionLogStreamView({ if (error) { return (
-

{title}

+ {showIntro ? ( +

+ {title} +

+ ) : null}
{error} @@ -314,8 +307,14 @@ export function ExecutionLogStreamView({ return (
-

{title}

-

{description}

+ {showIntro ? ( + <> +

+ {title} +

+

{description}

+ + ) : null} {boundedHistoryNote ? (

{boundedHistoryNote}

) : null} @@ -362,10 +361,11 @@ export function ExecutionLogStreamView({ teamName={teamName} visual={participantVisuals.get(segment.participantKey)} metaLabel={getSegmentMetaLabel?.(segment)} + showParticipantBadge={showSegmentParticipantBadge} /> ))}
)}
); -} +}; diff --git a/src/features/member-log-stream/renderer/ui/executionLogStreamUtils.ts b/src/features/member-log-stream/renderer/ui/executionLogStreamUtils.ts new file mode 100644 index 00000000..84aec012 --- /dev/null +++ b/src/features/member-log-stream/renderer/ui/executionLogStreamUtils.ts @@ -0,0 +1,29 @@ +import { asEnhancedChunkArray } from '@renderer/types/data'; + +import type { BoardTaskLogParticipant, BoardTaskLogSegment } from '@shared/types'; + +export interface ExecutionLogStreamLike { + participants: BoardTaskLogParticipant[]; + defaultFilter: string; + segments: BoardTaskLogSegment[]; +} + +export function normalizeExecutionLogStream( + response: TStream +): TStream { + return { + ...response, + segments: response.segments.map((segment) => ({ + ...segment, + chunks: asEnhancedChunkArray(segment.chunks) ?? [], + })), + }; +} + +export function buildDefaultExecutionSegmentRenderKey(segment: BoardTaskLogSegment): string { + const firstChunkId = segment.chunks[0]?.id; + if (firstChunkId) { + return `${segment.participantKey}:${firstChunkId}`; + } + return `${segment.participantKey}:${segment.startTimestamp}`; +} diff --git a/src/renderer/components/team/ClaudeLogsPanel.tsx b/src/renderer/components/team/ClaudeLogsPanel.tsx index 407b0491..ec7e8184 100644 --- a/src/renderer/components/team/ClaudeLogsPanel.tsx +++ b/src/renderer/components/team/ClaudeLogsPanel.tsx @@ -97,40 +97,40 @@ export const ClaudeLogsPanel = ({
{toolbarControlsStart}
) : null} {data.total > 0 ? ( - <> -
- - setSearchQuery(e.target.value)} - className="min-w-0 flex-1 bg-transparent text-xs text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none" - /> - {searchQuery && ( - - )} -
- {toolbarAccessory} - + + setSearchQuery(e.target.value)} + className="min-w-0 flex-1 bg-transparent text-xs text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none" /> - + {searchQuery && ( + + )} +
+ ) : null} + {toolbarAccessory} + {data.total > 0 ? ( + ) : null} {pendingNewCount > 0 && (