feat(ui): polish messages timeline and graph toolbar
This commit is contained in:
parent
e5d6911025
commit
674c1d8b13
11 changed files with 535 additions and 339 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useState, type ReactNode } from 'react';
|
||||
|
||||
import { CARD_BG, CARD_BORDER_STYLE, CARD_ICON_MUTED } from '@renderer/constants/cssVariables';
|
||||
import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors';
|
||||
|
|
@ -19,6 +19,7 @@ interface ActiveTasksBlockProps {
|
|||
tasks: TeamTaskWithKanban[];
|
||||
/** Start collapsed (e.g. when rendered inside the sidebar where MemberList already shows status). */
|
||||
defaultCollapsed?: boolean;
|
||||
headerRight?: ReactNode;
|
||||
onMemberClick?: (member: ResolvedTeamMember) => void;
|
||||
onTaskClick?: (task: TeamTaskWithKanban) => void;
|
||||
}
|
||||
|
|
@ -34,6 +35,7 @@ export const ActiveTasksBlock = ({
|
|||
members,
|
||||
tasks,
|
||||
defaultCollapsed = false,
|
||||
headerRight,
|
||||
onMemberClick,
|
||||
onTaskClick,
|
||||
}: ActiveTasksBlockProps): React.JSX.Element | null => {
|
||||
|
|
@ -70,23 +72,26 @@ export const ActiveTasksBlock = ({
|
|||
|
||||
return (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text-secondary)]"
|
||||
onClick={() => setCollapsed((v) => !v)}
|
||||
aria-label={collapsed ? 'Expand in progress' : 'Collapse in progress'}
|
||||
>
|
||||
<ChevronRight
|
||||
size={10}
|
||||
className={`shrink-0 transition-transform duration-150 ${collapsed ? '' : 'rotate-90'}`}
|
||||
/>
|
||||
<span>In progress</span>
|
||||
{collapsed && (
|
||||
<span className="rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0.5 text-[10px] font-medium tabular-nums leading-none text-[var(--color-text-muted)]">
|
||||
{entries.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="flex min-w-0 items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text-secondary)]"
|
||||
onClick={() => setCollapsed((v) => !v)}
|
||||
aria-label={collapsed ? 'Expand in progress' : 'Collapse in progress'}
|
||||
>
|
||||
<ChevronRight
|
||||
size={10}
|
||||
className={`shrink-0 transition-transform duration-150 ${collapsed ? '' : 'rotate-90'}`}
|
||||
/>
|
||||
<span>In progress</span>
|
||||
{collapsed && (
|
||||
<span className="rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0.5 text-[10px] font-medium tabular-nums leading-none text-[var(--color-text-muted)]">
|
||||
{entries.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{headerRight ? <div className="shrink-0">{headerRight}</div> : null}
|
||||
</div>
|
||||
{!collapsed &&
|
||||
entries.map(({ member, task, taskId, kind }) => {
|
||||
const colors = getTeamColorSet(colorMap.get(member.name) ?? '');
|
||||
|
|
|
|||
|
|
@ -881,6 +881,132 @@ export const ActivityItem = memo(
|
|||
onToggleCollapse?.(collapseToggleKey);
|
||||
}
|
||||
}, [collapseToggleKey, isHeaderClickable, onToggleCollapse]);
|
||||
const useCompactCollapsedHeader = compactHeader && !isExpanded;
|
||||
|
||||
const senderBadge = isSlashCommandResult ? (
|
||||
<span className="inline-flex items-center rounded-full bg-amber-500/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-300">
|
||||
result
|
||||
</span>
|
||||
) : (
|
||||
<MemberBadge
|
||||
name={senderName}
|
||||
color={senderColor}
|
||||
teamName={teamName}
|
||||
hideAvatar={senderHideAvatar || compactHeader}
|
||||
onClick={onMemberNameClick}
|
||||
disableHoverCard={crossTeamOrigin != null}
|
||||
/>
|
||||
);
|
||||
|
||||
const messageTypeBadge = systemLabel ? (
|
||||
<span className="text-[10px] uppercase tracking-wide" style={{ color: CARD_ICON_MUTED }}>
|
||||
{systemLabel}
|
||||
</span>
|
||||
) : isSlashCommandResult && message.commandOutput ? (
|
||||
<span
|
||||
className={[
|
||||
'inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] uppercase tracking-wide',
|
||||
isCommandOutputError ? 'bg-rose-500/15 text-rose-300' : 'bg-amber-500/15 text-amber-300',
|
||||
].join(' ')}
|
||||
>
|
||||
{message.commandOutput.stream}
|
||||
</span>
|
||||
) : isSlashCommandMessage ? (
|
||||
<span className="text-[10px] uppercase tracking-wide text-amber-400">command</span>
|
||||
) : messageType ? (
|
||||
<span className="text-[10px] uppercase tracking-wide" style={{ color: CARD_ICON_MUTED }}>
|
||||
{messageType}
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const leadSourceBadge =
|
||||
message.source === 'lead_session' && !isSlashCommandResult ? (
|
||||
<span className="text-[10px] uppercase tracking-wide" style={{ color: CARD_ICON_MUTED }}>
|
||||
session
|
||||
</span>
|
||||
) : message.source === 'lead_process' && !isSlashCommandResult ? (
|
||||
<span className="text-[10px] uppercase tracking-wide" style={{ color: CARD_ICON_MUTED }}>
|
||||
live
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const statusBadge = rateLimited ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
||||
<AlertTriangle size={10} />
|
||||
Rate Limited
|
||||
</span>
|
||||
) : isApiError ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
||||
<AlertTriangle size={10} />
|
||||
API Error
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const recipientBadge =
|
||||
message.to && message.to !== message.from ? (
|
||||
<>
|
||||
<MoveRight size={10} style={{ color: CARD_ICON_MUTED }} className="shrink-0" />
|
||||
{crossTeamTarget ? (
|
||||
<CrossTeamTeamBadge teamName={crossTeamTarget} onClick={onTeamClick} />
|
||||
) : null}
|
||||
{crossTeamSentMemberName || !crossTeamTarget ? (
|
||||
<MemberBadge
|
||||
name={crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to}
|
||||
color={crossTeamTarget ? undefined : recipientColor}
|
||||
teamName={crossTeamTarget ? undefined : teamName}
|
||||
hideAvatar={
|
||||
compactHeader ||
|
||||
(crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to) === 'user'
|
||||
}
|
||||
onClick={onMemberNameClick}
|
||||
disableHoverCard={crossTeamTarget != null}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const summaryContent =
|
||||
isSlashCommandResult && message.commandOutput ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-1.5">
|
||||
<Command
|
||||
size={12}
|
||||
className={['shrink-0', isCommandOutputError ? 'text-rose-400' : 'text-amber-400'].join(
|
||||
' '
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={[
|
||||
'shrink-0 font-mono text-[11px]',
|
||||
isCommandOutputError ? 'text-rose-300' : 'text-amber-300',
|
||||
].join(' ')}
|
||||
>
|
||||
{message.commandOutput.commandLabel}
|
||||
</span>
|
||||
<span className="min-w-0 truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{message.summary || getCommandOutputSummary(message.text) || rawSummary}
|
||||
</span>
|
||||
</span>
|
||||
) : isSlashCommandMessage && slashCommandMeta ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-1.5">
|
||||
<Command size={12} className="shrink-0 text-amber-400" />
|
||||
<span className="shrink-0 font-mono text-[11px] text-amber-300">
|
||||
{slashCommandMeta.command}
|
||||
</span>
|
||||
{slashCommandMeta.args ? (
|
||||
<span className="min-w-0 truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{slashCommandMeta.args.replace(/\n+/g, ' ')}
|
||||
</span>
|
||||
) : (slashCommandMeta.knownDescription ?? knownSlashCommand?.description) ? (
|
||||
<span className="min-w-0 truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{slashCommandMeta.knownDescription ?? knownSlashCommand?.description}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : onTaskIdClick ? (
|
||||
renderInlineBoldSummary(rawSummary, onTaskIdClick)
|
||||
) : (
|
||||
renderInlineBoldSummary(rawSummary)
|
||||
);
|
||||
|
||||
return (
|
||||
<article
|
||||
|
|
@ -933,7 +1059,9 @@ export const ActivityItem = memo(
|
|||
role={isHeaderClickable ? 'button' : undefined}
|
||||
tabIndex={isHeaderClickable ? 0 : undefined}
|
||||
className={[
|
||||
'flex min-w-0 items-center gap-2 px-3 py-2',
|
||||
useCompactCollapsedHeader
|
||||
? 'min-w-0 px-3 py-2'
|
||||
: 'flex min-w-0 items-center gap-2 px-3 py-2',
|
||||
isHeaderClickable ? 'cursor-pointer select-none' : '',
|
||||
].join(' ')}
|
||||
onClick={handleHeaderToggle}
|
||||
|
|
@ -948,207 +1076,125 @@ export const ActivityItem = memo(
|
|||
: undefined
|
||||
}
|
||||
>
|
||||
{isUnread ? (
|
||||
<span className="size-2 shrink-0 rounded-full bg-blue-500" title="Unread" aria-hidden />
|
||||
) : null}
|
||||
{/* Chevron for collapsible messages */}
|
||||
{showChevron ? (
|
||||
<ChevronRight
|
||||
className="size-3 shrink-0 transition-transform duration-150"
|
||||
style={{
|
||||
color: CARD_ICON_MUTED,
|
||||
transform: isExpanded ? 'rotate(90deg)' : undefined,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* Sender avatar + name badge */}
|
||||
{crossTeamOrigin ? (
|
||||
<CrossTeamTeamBadge teamName={crossTeamOrigin.teamName} onClick={onTeamClick} />
|
||||
) : null}
|
||||
{isSlashCommandResult ? (
|
||||
<span className="inline-flex items-center rounded-full bg-amber-500/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-300">
|
||||
result
|
||||
</span>
|
||||
) : (
|
||||
<MemberBadge
|
||||
name={senderName}
|
||||
color={senderColor}
|
||||
teamName={teamName}
|
||||
hideAvatar={senderHideAvatar || compactHeader}
|
||||
onClick={onMemberNameClick}
|
||||
disableHoverCard={crossTeamOrigin != null}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Role */}
|
||||
{!compactHeader && formattedRole && !isSlashCommandResult ? (
|
||||
<span className="text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{formattedRole}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{/* Message type label or system label */}
|
||||
{systemLabel ? (
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{systemLabel}
|
||||
</span>
|
||||
) : isSlashCommandResult && message.commandOutput ? (
|
||||
<span
|
||||
className={[
|
||||
'inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] uppercase tracking-wide',
|
||||
isCommandOutputError
|
||||
? 'bg-rose-500/15 text-rose-300'
|
||||
: 'bg-amber-500/15 text-amber-300',
|
||||
].join(' ')}
|
||||
>
|
||||
{message.commandOutput.stream}
|
||||
</span>
|
||||
) : isSlashCommandMessage ? (
|
||||
<span className="text-[10px] uppercase tracking-wide text-amber-400">command</span>
|
||||
) : messageType ? (
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{messageType}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{/* Lead session marker */}
|
||||
{message.source === 'lead_session' && !isSlashCommandResult ? (
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
session
|
||||
</span>
|
||||
) : message.source === 'lead_process' && !isSlashCommandResult ? (
|
||||
<span
|
||||
className="text-[10px] uppercase tracking-wide"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
live
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{/* Rate limit warning badge */}
|
||||
{rateLimited ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
||||
<AlertTriangle size={10} />
|
||||
Rate Limited
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{/* API Error warning badge */}
|
||||
{isApiError && !rateLimited ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-red-500/20 px-1.5 py-0.5 text-[10px] font-medium text-red-400">
|
||||
<AlertTriangle size={10} />
|
||||
API Error
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{/* Recipient — arrow + avatar + badge */}
|
||||
{message.to && message.to !== message.from ? (
|
||||
<>
|
||||
<MoveRight size={10} style={{ color: CARD_ICON_MUTED }} className="shrink-0" />
|
||||
{crossTeamTarget ? (
|
||||
<CrossTeamTeamBadge teamName={crossTeamTarget} onClick={onTeamClick} />
|
||||
) : null}
|
||||
{crossTeamSentMemberName || !crossTeamTarget ? (
|
||||
<MemberBadge
|
||||
name={crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to}
|
||||
color={crossTeamTarget ? undefined : recipientColor}
|
||||
teamName={crossTeamTarget ? undefined : teamName}
|
||||
hideAvatar={
|
||||
compactHeader ||
|
||||
(crossTeamSentMemberName ?? qualifiedRecipient?.memberName ?? message.to) ===
|
||||
'user'
|
||||
}
|
||||
onClick={onMemberNameClick}
|
||||
disableHoverCard={crossTeamTarget != null}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* Summary */}
|
||||
<span className="min-w-0 flex-1 truncate text-xs" style={{ color: CARD_TEXT_LIGHT }}>
|
||||
{isSlashCommandResult && message.commandOutput ? (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Command
|
||||
size={12}
|
||||
className={[
|
||||
'shrink-0',
|
||||
isCommandOutputError ? 'text-rose-400' : 'text-amber-400',
|
||||
].join(' ')}
|
||||
/>
|
||||
<span
|
||||
className={[
|
||||
'font-mono text-[11px]',
|
||||
isCommandOutputError ? 'text-rose-300' : 'text-amber-300',
|
||||
].join(' ')}
|
||||
>
|
||||
{message.commandOutput.commandLabel}
|
||||
</span>
|
||||
<span className="truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{message.summary || getCommandOutputSummary(message.text) || rawSummary}
|
||||
</span>
|
||||
</span>
|
||||
) : isSlashCommandMessage && slashCommandMeta ? (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Command size={12} className="shrink-0 text-amber-400" />
|
||||
<span className="font-mono text-[11px] text-amber-300">
|
||||
{slashCommandMeta.command}
|
||||
</span>
|
||||
{slashCommandMeta.args ? (
|
||||
<span className="truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{slashCommandMeta.args.replace(/\n+/g, ' ')}
|
||||
{useCompactCollapsedHeader ? (
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
||||
{isUnread ? (
|
||||
<span
|
||||
className="size-2 shrink-0 rounded-full bg-blue-500"
|
||||
title="Unread"
|
||||
aria-hidden
|
||||
/>
|
||||
) : null}
|
||||
{crossTeamOrigin ? (
|
||||
<CrossTeamTeamBadge teamName={crossTeamOrigin.teamName} onClick={onTeamClick} />
|
||||
) : null}
|
||||
{senderBadge}
|
||||
{messageTypeBadge}
|
||||
{leadSourceBadge}
|
||||
{statusBadge}
|
||||
{recipientBadge}
|
||||
</div>
|
||||
<div className="relative flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{timestamp}
|
||||
</span>
|
||||
) : (slashCommandMeta.knownDescription ?? knownSlashCommand?.description) ? (
|
||||
<span className="truncate text-[11px] text-[var(--color-text-secondary)]">
|
||||
{slashCommandMeta.knownDescription ?? knownSlashCommand?.description}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : onTaskIdClick ? (
|
||||
renderInlineBoldSummary(rawSummary, onTaskIdClick)
|
||||
) : (
|
||||
renderInlineBoldSummary(rawSummary)
|
||||
)}
|
||||
</span>
|
||||
|
||||
{/* Timestamp / expand */}
|
||||
<div className="relative flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{timestamp}
|
||||
</span>
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand message"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand message"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mt-1 min-w-0 truncate text-[11px]"
|
||||
style={{ color: CARD_TEXT_LIGHT }}
|
||||
title={summaryText || rawSummary}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{summaryContent}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{isUnread ? (
|
||||
<span
|
||||
className="size-2 shrink-0 rounded-full bg-blue-500"
|
||||
title="Unread"
|
||||
aria-hidden
|
||||
/>
|
||||
) : null}
|
||||
{showChevron ? (
|
||||
<ChevronRight
|
||||
className="size-3 shrink-0 transition-transform duration-150"
|
||||
style={{
|
||||
color: CARD_ICON_MUTED,
|
||||
transform: isExpanded ? 'rotate(90deg)' : undefined,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{crossTeamOrigin ? (
|
||||
<CrossTeamTeamBadge teamName={crossTeamOrigin.teamName} onClick={onTeamClick} />
|
||||
) : null}
|
||||
{senderBadge}
|
||||
{!compactHeader && formattedRole && !isSlashCommandResult ? (
|
||||
<span className="text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{formattedRole}
|
||||
</span>
|
||||
) : null}
|
||||
{messageTypeBadge}
|
||||
{leadSourceBadge}
|
||||
{statusBadge}
|
||||
{recipientBadge}
|
||||
<span className="min-w-0 flex-1 truncate text-xs" style={{ color: CARD_TEXT_LIGHT }}>
|
||||
{summaryContent}
|
||||
</span>
|
||||
<div className="relative flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{timestamp}
|
||||
</span>
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand message"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content — collapsed for system messages, expanded for others */}
|
||||
|
|
|
|||
|
|
@ -751,6 +751,13 @@ const LeadThoughtsGroupRowComponent = ({
|
|||
});
|
||||
}, []);
|
||||
|
||||
const timestampLabel =
|
||||
formatTime(oldest.timestamp) === formatTime(newest.timestamp)
|
||||
? formatTime(oldest.timestamp)
|
||||
: `${formatTime(oldest.timestamp)}–${formatTime(newest.timestamp)}`;
|
||||
const useCompactCollapsedHeader = compactHeader && !isBodyVisible;
|
||||
const compactPreviewText = headerTextPreview ?? totalToolSummary;
|
||||
|
||||
return (
|
||||
<AnimatedHeightReveal animate={isNew} containerRef={ref} style={{ overflowAnchor: 'none' }}>
|
||||
<article
|
||||
|
|
@ -767,7 +774,9 @@ const LeadThoughtsGroupRowComponent = ({
|
|||
role={canToggleBodyVisibility ? 'button' : undefined}
|
||||
tabIndex={canToggleBodyVisibility ? 0 : undefined}
|
||||
className={[
|
||||
'flex select-none items-center gap-2 px-3 py-1.5',
|
||||
useCompactCollapsedHeader
|
||||
? 'select-none px-3 py-2'
|
||||
: 'flex select-none items-center gap-2 px-3 py-1.5',
|
||||
canToggleBodyVisibility ? 'cursor-pointer' : '',
|
||||
].join(' ')}
|
||||
style={hasApiError ? { backgroundColor: 'rgba(248, 113, 113, 0.08)' } : undefined}
|
||||
|
|
@ -783,101 +792,148 @@ const LeadThoughtsGroupRowComponent = ({
|
|||
: undefined
|
||||
}
|
||||
>
|
||||
{/* Chevron for collapse mode */}
|
||||
{canToggleBodyVisibility && !compactHeader ? (
|
||||
<ChevronRight
|
||||
className="size-3 shrink-0 transition-transform duration-150"
|
||||
style={{
|
||||
color: CARD_ICON_MUTED,
|
||||
transform: isBodyVisible ? 'rotate(90deg)' : undefined,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{/* Lead avatar with optional live indicator */}
|
||||
{!compactHeader ? (
|
||||
<div className="relative shrink-0">
|
||||
<img
|
||||
src={agentAvatarUrl(leadName, 24)}
|
||||
alt=""
|
||||
className="size-5 rounded-full bg-[var(--color-surface-raised)]"
|
||||
loading="lazy"
|
||||
/>
|
||||
<LiveThoughtStatusBadge
|
||||
canBeLive={canBeLive}
|
||||
isTeamAlive={isTeamAlive}
|
||||
leadActivity={leadActivity}
|
||||
leadContextUpdatedAt={leadContextUpdatedAt}
|
||||
newestTimestamp={newest.timestamp}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<MemberBadge name={leadName} color={memberColor} hideAvatar />
|
||||
<span className="text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{thoughts.length} thoughts
|
||||
</span>
|
||||
{!isBodyVisible && headerTextPreview ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className="min-w-0 flex-1 cursor-default truncate text-[10px]"
|
||||
style={{ color: CARD_TEXT_LIGHT }}
|
||||
{useCompactCollapsedHeader ? (
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
||||
<MemberBadge name={leadName} color={memberColor} hideAvatar />
|
||||
<span className="shrink-0 text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{thoughts.length} thoughts
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{timestampLabel}
|
||||
</span>
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand thoughts"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{compactPreviewText ? (
|
||||
<div
|
||||
className="mt-1 min-w-0 truncate text-[11px]"
|
||||
style={{ color: headerTextPreview ? CARD_TEXT_LIGHT : CARD_ICON_MUTED }}
|
||||
title={compactPreviewText}
|
||||
>
|
||||
{headerTextPreview}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
{totalToolSummary ? (
|
||||
<TooltipContent side="bottom" className="max-w-[420px] font-mono text-[11px]">
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={allToolCalls}
|
||||
toolSummary={totalToolSummary}
|
||||
/>
|
||||
</TooltipContent>
|
||||
{compactPreviewText}
|
||||
</div>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
) : totalToolSummary ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-default text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{totalToolSummary}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-[420px] font-mono text-[11px]">
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={allToolCalls}
|
||||
toolSummary={totalToolSummary}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{canToggleBodyVisibility && !compactHeader ? (
|
||||
<ChevronRight
|
||||
className="size-3 shrink-0 transition-transform duration-150"
|
||||
style={{
|
||||
color: CARD_ICON_MUTED,
|
||||
transform: isBodyVisible ? 'rotate(90deg)' : undefined,
|
||||
}}
|
||||
/>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div className="relative ml-auto flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{formatTime(oldest.timestamp) === formatTime(newest.timestamp)
|
||||
? formatTime(oldest.timestamp)
|
||||
: `${formatTime(oldest.timestamp)}–${formatTime(newest.timestamp)}`}
|
||||
</span>
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand thoughts"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{!compactHeader ? (
|
||||
<div className="relative shrink-0">
|
||||
<img
|
||||
src={agentAvatarUrl(leadName, 24)}
|
||||
alt=""
|
||||
className="size-5 rounded-full bg-[var(--color-surface-raised)]"
|
||||
loading="lazy"
|
||||
/>
|
||||
<LiveThoughtStatusBadge
|
||||
canBeLive={canBeLive}
|
||||
isTeamAlive={isTeamAlive}
|
||||
leadActivity={leadActivity}
|
||||
leadContextUpdatedAt={leadContextUpdatedAt}
|
||||
newestTimestamp={newest.timestamp}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<MemberBadge name={leadName} color={memberColor} hideAvatar />
|
||||
<span className="text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{thoughts.length} thoughts
|
||||
</span>
|
||||
{!isBodyVisible && headerTextPreview ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className="min-w-0 flex-1 cursor-default truncate text-[10px]"
|
||||
style={{ color: CARD_TEXT_LIGHT }}
|
||||
>
|
||||
{headerTextPreview}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
{totalToolSummary ? (
|
||||
<TooltipContent side="bottom" className="max-w-[420px] font-mono text-[11px]">
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={allToolCalls}
|
||||
toolSummary={totalToolSummary}
|
||||
/>
|
||||
</TooltipContent>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
) : totalToolSummary ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-default text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{totalToolSummary}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-[420px] font-mono text-[11px]">
|
||||
<ToolSummaryTooltipContent
|
||||
toolCalls={allToolCalls}
|
||||
toolSummary={totalToolSummary}
|
||||
/>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<div className="relative ml-auto flex shrink-0 items-center">
|
||||
<span
|
||||
className={
|
||||
onExpand && expandItemKey
|
||||
? 'text-[10px] transition-opacity group-hover:opacity-0'
|
||||
: 'text-[10px]'
|
||||
}
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
>
|
||||
{timestampLabel}
|
||||
</span>
|
||||
{onExpand && expandItemKey && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Expand thoughts"
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 opacity-0 transition-opacity focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500/50 group-hover:opacity-100"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onExpand(expandItemKey);
|
||||
}}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Scrollable body — live thoughts follow bottom unless user scrolls up */}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Loader2, ShieldQuestion, Users } from 'lucide-react';
|
|||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import type { ResolvedTeamMember } from '@shared/types';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface PendingCrossTeamReply {
|
||||
teamName: string;
|
||||
|
|
@ -26,6 +27,7 @@ interface PendingRepliesBlockProps {
|
|||
members: ResolvedTeamMember[];
|
||||
pendingRepliesByMember: Record<string, number>;
|
||||
pendingCrossTeamReplies?: PendingCrossTeamReply[];
|
||||
headerRight?: ReactNode;
|
||||
onMemberClick?: (member: ResolvedTeamMember) => void;
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +35,7 @@ export const PendingRepliesBlock = ({
|
|||
members,
|
||||
pendingRepliesByMember,
|
||||
pendingCrossTeamReplies = [],
|
||||
headerRight,
|
||||
onMemberClick,
|
||||
}: PendingRepliesBlockProps): React.JSX.Element | null => {
|
||||
const { isLight } = useTheme();
|
||||
|
|
@ -70,9 +73,12 @@ export const PendingRepliesBlock = ({
|
|||
|
||||
return (
|
||||
<div className="mb-3 space-y-1.5">
|
||||
<p className="text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]">
|
||||
Awaiting replies
|
||||
</p>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]">
|
||||
Awaiting replies
|
||||
</p>
|
||||
{headerRight ? <div className="shrink-0">{headerRight}</div> : null}
|
||||
</div>
|
||||
{pending.map((entry) => {
|
||||
const since = formatDistanceToNowStrict(entry.sentAtMs, { addSuffix: true });
|
||||
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ export const MessageComposer = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={cn('relative', isCompactLayout ? 'pb-1' : 'mb-3 pb-3')}
|
||||
className={cn('relative', isCompactLayout ? 'pb-1' : 'mb-1.5 pb-1.5')}
|
||||
role="group"
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
|
|
|
|||
|
|
@ -648,6 +648,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|||
tasks={tasks}
|
||||
messages={effectiveMessages}
|
||||
pendingRepliesByMember={pendingRepliesByMember}
|
||||
layout="flow"
|
||||
position="inline"
|
||||
onMemberClick={onMemberClick}
|
||||
onTaskClick={onTaskClick}
|
||||
|
|
@ -831,6 +832,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|||
tasks={tasks}
|
||||
messages={effectiveMessages}
|
||||
pendingRepliesByMember={pendingRepliesByMember}
|
||||
layout="flow"
|
||||
position="sidebar"
|
||||
onMemberClick={onMemberClick}
|
||||
onTaskClick={onTaskClick}
|
||||
|
|
|
|||
|
|
@ -44,15 +44,13 @@ export const StatusBlock = ({
|
|||
() => computePendingCrossTeamReplies(messages, nowMs),
|
||||
[messages, nowMs]
|
||||
);
|
||||
|
||||
/** Whether the Status block has any visible items. */
|
||||
const hasItems = useMemo(() => {
|
||||
const hasPendingReplies = Object.keys(pendingRepliesByMember).some((name) =>
|
||||
const hasPendingReplies = useMemo(() => {
|
||||
const hasMemberPendingReplies = Object.keys(pendingRepliesByMember).some((name) =>
|
||||
members.some((m) => m.name === name)
|
||||
);
|
||||
if (hasPendingReplies) return true;
|
||||
if (pendingCrossTeamReplies.length > 0) return true;
|
||||
|
||||
return hasMemberPendingReplies || pendingCrossTeamReplies.length > 0;
|
||||
}, [members, pendingRepliesByMember, pendingCrossTeamReplies.length]);
|
||||
const hasActiveTasks = useMemo(() => {
|
||||
const tMap = new Map(tasks.map((t) => [t.id, t]));
|
||||
return members.some((m) => {
|
||||
if (!m.currentTaskId) return false;
|
||||
|
|
@ -60,7 +58,13 @@ export const StatusBlock = ({
|
|||
if (task && (task.reviewState === 'approved' || task.status === 'completed')) return false;
|
||||
return true;
|
||||
});
|
||||
}, [members, tasks, pendingRepliesByMember, pendingCrossTeamReplies.length]);
|
||||
}, [members, tasks]);
|
||||
|
||||
/** Whether the Status block has any visible items. */
|
||||
const hasItems = useMemo(() => {
|
||||
if (hasPendingReplies) return true;
|
||||
return hasActiveTasks;
|
||||
}, [hasActiveTasks, hasPendingReplies]);
|
||||
|
||||
// Only run the 1-second timer when the block actually has content to show.
|
||||
useEffect(() => {
|
||||
|
|
@ -85,6 +89,7 @@ export const StatusBlock = ({
|
|||
Status
|
||||
</button>
|
||||
);
|
||||
const flowInlineToggle = layout === 'flow' && !collapsed ? toggleButton : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -92,21 +97,25 @@ export const StatusBlock = ({
|
|||
<div className="relative h-0">
|
||||
<div className="absolute -top-[19px] right-0 z-10">{toggleButton}</div>
|
||||
</div>
|
||||
) : (
|
||||
) : collapsed ? (
|
||||
<div className="mb-2 flex justify-end">{toggleButton}</div>
|
||||
)}
|
||||
) : null}
|
||||
{!collapsed && (
|
||||
<div className={layout === 'overlay' ? 'mt-5' : ''}>
|
||||
<PendingRepliesBlock
|
||||
members={members}
|
||||
pendingRepliesByMember={pendingRepliesByMember}
|
||||
pendingCrossTeamReplies={pendingCrossTeamReplies}
|
||||
onMemberClick={onMemberClick}
|
||||
/>
|
||||
{hasPendingReplies ? (
|
||||
<PendingRepliesBlock
|
||||
members={members}
|
||||
pendingRepliesByMember={pendingRepliesByMember}
|
||||
pendingCrossTeamReplies={pendingCrossTeamReplies}
|
||||
headerRight={flowInlineToggle}
|
||||
onMemberClick={onMemberClick}
|
||||
/>
|
||||
) : null}
|
||||
<ActiveTasksBlock
|
||||
members={members}
|
||||
tasks={tasks}
|
||||
defaultCollapsed={position === 'sidebar'}
|
||||
headerRight={!hasPendingReplies ? flowInlineToggle : undefined}
|
||||
onMemberClick={onMemberClick}
|
||||
onTaskClick={onTaskClick}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1102,10 +1102,10 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
|
|||
const orbitTrackWidth = 1;
|
||||
const orbitStrokeWidth = 1.35;
|
||||
const orbitGlowWidth = 3;
|
||||
const orbitInset = orbitTrackWidth / 2;
|
||||
const orbitWidth = Math.max(surfaceShellMetrics.width - orbitTrackWidth, 0);
|
||||
const orbitHeight = Math.max(surfaceShellMetrics.height - orbitTrackWidth, 0);
|
||||
const orbitRadius = Math.max(surfaceShellMetrics.borderRadius - orbitInset, 0);
|
||||
const orbitInset = 0;
|
||||
const orbitWidth = Math.max(surfaceShellMetrics.width, 0);
|
||||
const orbitHeight = Math.max(surfaceShellMetrics.height, 0);
|
||||
const orbitRadius = Math.max(surfaceShellMetrics.borderRadius, 0);
|
||||
const orbitRight = orbitInset + orbitWidth;
|
||||
const orbitBottom = orbitInset + orbitHeight;
|
||||
const orbitMidX = orbitInset + orbitWidth / 2;
|
||||
|
|
@ -1134,7 +1134,7 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
|
|||
surfaceShellMetrics.width > 0 &&
|
||||
surfaceShellMetrics.height > 0 ? (
|
||||
<svg
|
||||
className="message-composer-orbit-svg pointer-events-none absolute inset-0 z-[1] h-full w-full"
|
||||
className="message-composer-orbit-svg pointer-events-none absolute inset-0 z-[16] h-full w-full"
|
||||
viewBox={`0 0 ${surfaceShellMetrics.width} ${surfaceShellMetrics.height}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export const TeamGraphOverlay = ({
|
|||
events={events}
|
||||
onRequestClose={onClose}
|
||||
onRequestPinAsTab={onPinAsTab}
|
||||
className="min-w-0 flex-1"
|
||||
className="team-graph-view min-w-0 flex-1"
|
||||
renderEdgeOverlay={({ edge, sourceNode, targetNode, onClose: closeEdge, onSelectNode }) => (
|
||||
<GraphBlockingEdgePopover
|
||||
teamName={teamName}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export const TeamGraphTab = ({
|
|||
<GraphView
|
||||
data={graphData}
|
||||
events={events}
|
||||
className="size-full"
|
||||
className="team-graph-view size-full"
|
||||
suspendAnimation={!isActive}
|
||||
onRequestFullscreen={() => setFullscreen(true)}
|
||||
renderEdgeOverlay={({ edge, sourceNode, targetNode, onClose, onSelectNode }) => (
|
||||
|
|
|
|||
|
|
@ -1367,7 +1367,7 @@ body.theme-transitioning {
|
|||
.message-composer-orbit-glow {
|
||||
stroke-dasharray: 9 91;
|
||||
animation: composer-orbit-border 19s linear infinite;
|
||||
stroke-linecap: round;
|
||||
stroke-linecap: butt;
|
||||
stroke-linejoin: round;
|
||||
will-change: stroke-dashoffset;
|
||||
}
|
||||
|
|
@ -1418,6 +1418,78 @@ body.theme-transitioning {
|
|||
stroke: rgba(138, 130, 118, 0.2);
|
||||
}
|
||||
|
||||
:root.light .team-graph-view > .absolute.left-20.top-3 {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:root.light .team-graph-view > .absolute.right-3.top-3 > div:not(.relative),
|
||||
:root.light .team-graph-view > .absolute.right-3.top-3 > .relative > div:first-child,
|
||||
:root.light .team-graph-view > .absolute.bottom-3.right-3 > div,
|
||||
:root.light .team-graph-view > .absolute.bottom-3.left-3 > div {
|
||||
background: rgba(248, 244, 237, 0.9) !important;
|
||||
border-color: rgba(120, 113, 108, 0.18) !important;
|
||||
box-shadow: 0 10px 24px rgba(120, 113, 108, 0.1);
|
||||
}
|
||||
|
||||
:root.light .team-graph-view > .absolute.right-3.top-3 > .relative > .absolute {
|
||||
background: rgba(248, 244, 237, 0.96) !important;
|
||||
border-color: rgba(120, 113, 108, 0.18) !important;
|
||||
box-shadow: 0 16px 32px rgba(120, 113, 108, 0.12);
|
||||
}
|
||||
|
||||
.team-graph-view > .absolute.right-3.top-3 > div:not(.relative),
|
||||
.team-graph-view > .absolute.right-3.top-3 > .relative > div:first-child {
|
||||
width: 32px !important;
|
||||
min-width: 32px !important;
|
||||
height: 32px !important;
|
||||
min-height: 32px !important;
|
||||
padding: 0 !important;
|
||||
justify-content: center !important;
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
|
||||
.team-graph-view > .absolute.right-3.top-3 > div:not(.relative) > button,
|
||||
.team-graph-view > .absolute.right-3.top-3 > .relative > div:first-child > button {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-width: 100% !important;
|
||||
min-height: 100% !important;
|
||||
padding: 6px !important;
|
||||
justify-content: center !important;
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.team-graph-view > .absolute.right-3.top-3 > div:not(.relative) > button svg,
|
||||
.team-graph-view > .absolute.right-3.top-3 > .relative > div:first-child > button svg {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.team-graph-view > .absolute.right-3.top-3 > div:not(.relative) > button span,
|
||||
.team-graph-view > .absolute.right-3.top-3 > .relative > div:first-child > button span {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:root.light .team-graph-view button[class*='font-mono'] {
|
||||
color: rgba(70, 85, 125, 0.9) !important;
|
||||
}
|
||||
|
||||
:root.light .team-graph-view button[class*='font-mono']:hover {
|
||||
color: #334155 !important;
|
||||
background: rgba(79, 70, 229, 0.08) !important;
|
||||
}
|
||||
|
||||
:root.light
|
||||
.team-graph-view
|
||||
button[class*='font-mono'][class*='bg-\\[rgba\\(100\\,200\\,255\\,0\\.14\\)\\]'] {
|
||||
color: #4338ca !important;
|
||||
background: rgba(79, 70, 229, 0.12) !important;
|
||||
}
|
||||
|
||||
:root.light .team-graph-view button[class*='font-mono'] svg {
|
||||
opacity: 0.96;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.message-composer-orbit-path,
|
||||
.message-composer-orbit-glow {
|
||||
|
|
|
|||
Loading…
Reference in a new issue