feat: refine team provisioning and UI components for improved user experience
- Updated TeamProvisioningService to prevent duplicate display of provisioning narration in the Activity timeline. - Enhanced CompactBoundary component with a cleaner design and improved interaction for expanding/collapsing content. - Added new CSS variables for compact phase badge styling to enhance visual consistency. - Modified TeamDetailView to include a collapsible status block for better organization of team information. - Improved MessageComposer to reflect provisioning status in placeholders and notifications, enhancing user awareness during team launches. - Updated ActivityTimeline to suppress session separators for reconnects within the same team, streamlining the user experience.
This commit is contained in:
parent
3f923c480e
commit
afb6e2794f
8 changed files with 157 additions and 109 deletions
|
|
@ -3154,14 +3154,9 @@ export class TeamProvisioningService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pre-ready: also push to live cache so Messages shows early narration
|
// Pre-ready: provisioning narration is shown in the ProvisioningProgressBlock banner
|
||||||
// once team:getData becomes readable. The banner still uses provisioningOutputParts.
|
// (via provisioningOutputParts). Do NOT push to live cache to avoid duplicate display
|
||||||
if (!run.silentUserDmForward && !hasCapturedSendMessage) {
|
// and leaking internal provisioning monologue into the Activity timeline.
|
||||||
const cleanText = stripAgentBlocks(text).trim();
|
|
||||||
if (cleanText.length > 0) {
|
|
||||||
this.pushLiveLeadTextMessage(run, cleanText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import {
|
||||||
CODE_BORDER,
|
CODE_BORDER,
|
||||||
COLOR_TEXT_MUTED,
|
COLOR_TEXT_MUTED,
|
||||||
COLOR_TEXT_SECONDARY,
|
COLOR_TEXT_SECONDARY,
|
||||||
TOOL_CALL_BG,
|
|
||||||
TOOL_CALL_BORDER,
|
|
||||||
TOOL_CALL_TEXT,
|
TOOL_CALL_TEXT,
|
||||||
} from '@renderer/constants/cssVariables';
|
} from '@renderer/constants/cssVariables';
|
||||||
import { REHYPE_PLUGINS } from '@renderer/utils/markdownPlugins';
|
import { REHYPE_PLUGINS } from '@renderer/utils/markdownPlugins';
|
||||||
|
|
@ -27,14 +25,8 @@ interface CompactBoundaryProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CompactBoundary displays an interactive, collapsible marker indicating where
|
* CompactBoundary displays a horizontal divider indicating where
|
||||||
* the conversation was compacted.
|
* the conversation was compacted. Click to expand the compacted summary.
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Minimalist design with subtle border and hover states
|
|
||||||
* - Click to expand/collapse compacted content
|
|
||||||
* - Scrollable content area with enforced max-height
|
|
||||||
* - Linear/Notion-inspired aesthetics
|
|
||||||
*/
|
*/
|
||||||
export const CompactBoundary = ({
|
export const CompactBoundary = ({
|
||||||
compactGroup,
|
compactGroup,
|
||||||
|
|
@ -64,70 +56,71 @@ export const CompactBoundary = ({
|
||||||
const compactContent = getCompactContent();
|
const compactContent = getCompactContent();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-6">
|
<div className="my-4">
|
||||||
{/* Collapsible Header - Amber/orange accent for distinction */}
|
{/* Divider with centered label */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="group flex w-full cursor-pointer items-center gap-3 overflow-hidden rounded-lg px-4 py-2.5 transition-all duration-200"
|
className="group flex w-full cursor-pointer items-center transition-opacity hover:opacity-90"
|
||||||
style={{
|
|
||||||
backgroundColor: TOOL_CALL_BG,
|
|
||||||
border: `1px solid ${TOOL_CALL_BORDER}`,
|
|
||||||
}}
|
|
||||||
aria-expanded={isExpanded}
|
aria-expanded={isExpanded}
|
||||||
aria-label="Toggle compacted content"
|
aria-label="Toggle compacted content"
|
||||||
>
|
>
|
||||||
{/* Icon Stack */}
|
{/* Left line */}
|
||||||
<div
|
<div className="h-px flex-1" style={{ backgroundColor: TOOL_CALL_TEXT, opacity: 0.3 }} />
|
||||||
className="flex shrink-0 items-center gap-2 transition-colors"
|
|
||||||
style={{ color: TOOL_CALL_TEXT }}
|
{/* Center content */}
|
||||||
>
|
<div className="flex shrink-0 items-center gap-2 px-3">
|
||||||
<ChevronRight
|
<ChevronRight
|
||||||
size={16}
|
size={12}
|
||||||
className={`transition-transform duration-200 ${isExpanded ? 'rotate-90' : ''}`}
|
className="transition-transform duration-200"
|
||||||
|
style={{
|
||||||
|
color: TOOL_CALL_TEXT,
|
||||||
|
transform: isExpanded ? 'rotate(90deg)' : undefined,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Layers size={16} />
|
<Layers size={12} style={{ color: TOOL_CALL_TEXT }} />
|
||||||
|
<span
|
||||||
|
className="whitespace-nowrap text-[11px] font-medium"
|
||||||
|
style={{ color: TOOL_CALL_TEXT }}
|
||||||
|
>
|
||||||
|
Context compacted
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Token delta */}
|
||||||
|
{compactGroup.tokenDelta && (
|
||||||
|
<span
|
||||||
|
className="whitespace-nowrap text-[10px] tabular-nums"
|
||||||
|
style={{ color: COLOR_TEXT_MUTED }}
|
||||||
|
>
|
||||||
|
{formatTokens(compactGroup.tokenDelta.preCompactionTokens)} →{' '}
|
||||||
|
{formatTokens(compactGroup.tokenDelta.postCompactionTokens)}
|
||||||
|
<span style={{ color: 'var(--diff-added-text)' }}>
|
||||||
|
{' '}
|
||||||
|
({formatTokens(Math.abs(compactGroup.tokenDelta.delta))} freed)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Phase badge */}
|
||||||
|
{compactGroup.startingPhaseNumber && (
|
||||||
|
<span
|
||||||
|
className="whitespace-nowrap rounded px-1.5 py-0.5 text-[10px]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--compact-phase-bg)',
|
||||||
|
color: 'var(--compact-phase-text)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Phase {compactGroup.startingPhaseNumber}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Timestamp */}
|
||||||
|
<span className="whitespace-nowrap text-[10px]" style={{ color: COLOR_TEXT_MUTED }}>
|
||||||
|
{format(timestamp, 'h:mm:ss a')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Label */}
|
{/* Right line */}
|
||||||
<span
|
<div className="h-px flex-1" style={{ backgroundColor: TOOL_CALL_TEXT, opacity: 0.3 }} />
|
||||||
className="shrink-0 whitespace-nowrap text-sm font-medium transition-colors"
|
|
||||||
style={{ color: TOOL_CALL_TEXT }}
|
|
||||||
>
|
|
||||||
Compacted
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Token delta info */}
|
|
||||||
{compactGroup.tokenDelta && (
|
|
||||||
<span
|
|
||||||
className="ml-2 min-w-0 truncate text-xs tabular-nums"
|
|
||||||
style={{ color: COLOR_TEXT_MUTED }}
|
|
||||||
>
|
|
||||||
{formatTokens(compactGroup.tokenDelta.preCompactionTokens)} →{' '}
|
|
||||||
{formatTokens(compactGroup.tokenDelta.postCompactionTokens)}
|
|
||||||
<span style={{ color: '#4ade80' }}>
|
|
||||||
{' '}
|
|
||||||
({formatTokens(Math.abs(compactGroup.tokenDelta.delta))} freed)
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Phase badge */}
|
|
||||||
{compactGroup.startingPhaseNumber && (
|
|
||||||
<span
|
|
||||||
className="shrink-0 whitespace-nowrap rounded px-1.5 py-0.5 text-[10px]"
|
|
||||||
style={{ backgroundColor: 'rgba(99, 102, 241, 0.15)', color: '#818cf8' }}
|
|
||||||
>
|
|
||||||
Phase {compactGroup.startingPhaseNumber}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Timestamp */}
|
|
||||||
<span
|
|
||||||
className="ml-auto shrink-0 whitespace-nowrap text-xs transition-colors"
|
|
||||||
style={{ color: COLOR_TEXT_MUTED }}
|
|
||||||
>
|
|
||||||
{format(timestamp, 'h:mm:ss a')}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Expanded Content */}
|
{/* Expanded Content */}
|
||||||
|
|
@ -144,7 +137,7 @@ export const CompactBoundary = ({
|
||||||
{/* Content - scrollable with left accent bar */}
|
{/* Content - scrollable with left accent bar */}
|
||||||
<div
|
<div
|
||||||
className="max-h-96 overflow-y-auto border-l-2 px-4 py-3"
|
className="max-h-96 overflow-y-auto border-l-2 px-4 py-3"
|
||||||
style={{ borderColor: 'var(--chat-ai-border)' }}
|
style={{ borderColor: TOOL_CALL_TEXT }}
|
||||||
>
|
>
|
||||||
{compactContent ? (
|
{compactContent ? (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import {
|
||||||
CheckCheck,
|
CheckCheck,
|
||||||
ChevronsDownUp,
|
ChevronsDownUp,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
|
ChevronRight,
|
||||||
Clock,
|
Clock,
|
||||||
Code,
|
Code,
|
||||||
Columns3,
|
Columns3,
|
||||||
|
|
@ -316,6 +317,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
});
|
});
|
||||||
const [messagesFilterOpen, setMessagesFilterOpen] = useState(false);
|
const [messagesFilterOpen, setMessagesFilterOpen] = useState(false);
|
||||||
const [messagesCollapsed, setMessagesCollapsed] = useState(true);
|
const [messagesCollapsed, setMessagesCollapsed] = useState(true);
|
||||||
|
const [statusBlockCollapsed, setStatusBlockCollapsed] = useState(false);
|
||||||
|
|
||||||
// Open editor overlay when a file reveal is requested (e.g. from chip click)
|
// Open editor overlay when a file reveal is requested (e.g. from chip click)
|
||||||
const pendingRevealFile = useStore((s) => s.editorPendingRevealFile);
|
const pendingRevealFile = useStore((s) => s.editorPendingRevealFile);
|
||||||
|
|
@ -523,7 +525,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
);
|
);
|
||||||
|
|
||||||
// Filter sessions to team-only using sessionHistory + leadSessionId
|
// Filter sessions to team-only using sessionHistory + leadSessionId
|
||||||
const teamSessions = useMemo(() => {
|
const teamSessionIds = useMemo(() => {
|
||||||
const sessionIds = new Set<string>();
|
const sessionIds = new Set<string>();
|
||||||
if (data?.config.leadSessionId) {
|
if (data?.config.leadSessionId) {
|
||||||
sessionIds.add(data.config.leadSessionId);
|
sessionIds.add(data.config.leadSessionId);
|
||||||
|
|
@ -533,10 +535,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
sessionIds.add(id);
|
sessionIds.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return sessionIds;
|
||||||
|
}, [data?.config.leadSessionId, data?.config.sessionHistory]);
|
||||||
|
|
||||||
|
const teamSessions = useMemo(() => {
|
||||||
// If no session IDs known (backward compat), show all sessions
|
// If no session IDs known (backward compat), show all sessions
|
||||||
if (sessionIds.size === 0) return sessions;
|
if (teamSessionIds.size === 0) return sessions;
|
||||||
return sessions.filter((s) => sessionIds.has(s.id));
|
return sessions.filter((s) => teamSessionIds.has(s.id));
|
||||||
}, [sessions, data?.config.leadSessionId, data?.config.sessionHistory]);
|
}, [sessions, teamSessionIds]);
|
||||||
|
|
||||||
// Auto-reset session filter if the selected session is no longer in teamSessions
|
// Auto-reset session filter if the selected session is no longer in teamSessions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -1563,17 +1569,33 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mb-[35px]">
|
<div className="mb-[35px]">
|
||||||
<PendingRepliesBlock
|
<button
|
||||||
members={data.members}
|
type="button"
|
||||||
pendingRepliesByMember={pendingRepliesByMember}
|
className="mb-1.5 flex items-center gap-1 text-[10px] font-medium uppercase tracking-wide text-[var(--color-text-muted)] transition-colors hover:text-[var(--color-text-secondary)]"
|
||||||
onMemberClick={setSelectedMember}
|
onClick={() => setStatusBlockCollapsed((prev) => !prev)}
|
||||||
/>
|
aria-label={statusBlockCollapsed ? 'Expand status' : 'Collapse status'}
|
||||||
<ActiveTasksBlock
|
>
|
||||||
members={data.members}
|
<ChevronRight
|
||||||
tasks={data.tasks}
|
size={12}
|
||||||
onMemberClick={setSelectedMember}
|
className={`shrink-0 transition-transform duration-150 ${statusBlockCollapsed ? '' : 'rotate-90'}`}
|
||||||
onTaskClick={setSelectedTask}
|
/>
|
||||||
/>
|
Status
|
||||||
|
</button>
|
||||||
|
{!statusBlockCollapsed && (
|
||||||
|
<>
|
||||||
|
<PendingRepliesBlock
|
||||||
|
members={data.members}
|
||||||
|
pendingRepliesByMember={pendingRepliesByMember}
|
||||||
|
onMemberClick={setSelectedMember}
|
||||||
|
/>
|
||||||
|
<ActiveTasksBlock
|
||||||
|
members={data.members}
|
||||||
|
tasks={data.tasks}
|
||||||
|
onMemberClick={setSelectedMember}
|
||||||
|
onTaskClick={setSelectedTask}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ActivityTimeline
|
<ActivityTimeline
|
||||||
messages={filteredMessages}
|
messages={filteredMessages}
|
||||||
|
|
@ -1583,6 +1605,7 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
||||||
allCollapsed={messagesCollapsed}
|
allCollapsed={messagesCollapsed}
|
||||||
expandOverrides={expandedSet}
|
expandOverrides={expandedSet}
|
||||||
onToggleExpandOverride={toggleExpandOverride}
|
onToggleExpandOverride={toggleExpandOverride}
|
||||||
|
teamSessionIds={teamSessionIds}
|
||||||
onMemberClick={setSelectedMember}
|
onMemberClick={setSelectedMember}
|
||||||
onCreateTaskFromMessage={(subject, description) => {
|
onCreateTaskFromMessage={(subject, description) => {
|
||||||
openCreateTaskDialog(subject, description);
|
openCreateTaskDialog(subject, description);
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,9 @@ export const TeamProvisioningBanner = ({
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="mb-2 flex items-center gap-2 rounded-md border border-[var(--step-done-border)] bg-[var(--step-done-bg)] px-3 py-2">
|
<div className="mb-2 flex items-center gap-2 rounded-md border border-[var(--step-done-border)] bg-[var(--step-done-bg)] px-3 py-2">
|
||||||
<CheckCircle2 size={14} className="shrink-0 text-[var(--step-done-text)]" />
|
<CheckCircle2 size={14} className="shrink-0 text-[var(--step-done-text)]" />
|
||||||
<p className="flex-1 text-xs text-[var(--step-success-text)]">Team launched — process alive</p>
|
<p className="flex-1 text-xs text-[var(--step-success-text)]">
|
||||||
|
Team launched — teammates may still be starting
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ interface ActivityTimelineProps {
|
||||||
expandOverrides?: Set<string>;
|
expandOverrides?: Set<string>;
|
||||||
/** Called when user toggles expand/collapse override on a specific message. */
|
/** Called when user toggles expand/collapse override on a specific message. */
|
||||||
onToggleExpandOverride?: (key: string) => void;
|
onToggleExpandOverride?: (key: string) => void;
|
||||||
|
/**
|
||||||
|
* All session IDs belonging to this team (current + history).
|
||||||
|
* When two adjacent messages have different leadSessionId but both are in this set,
|
||||||
|
* the "New session" separator is suppressed (it's a reconnect, not a new session).
|
||||||
|
*/
|
||||||
|
teamSessionIds?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIEWPORT_THRESHOLD = 0.15;
|
const VIEWPORT_THRESHOLD = 0.15;
|
||||||
|
|
@ -148,6 +154,7 @@ export const ActivityTimeline = ({
|
||||||
allCollapsed,
|
allCollapsed,
|
||||||
expandOverrides,
|
expandOverrides,
|
||||||
onToggleExpandOverride,
|
onToggleExpandOverride,
|
||||||
|
teamSessionIds,
|
||||||
}: ActivityTimelineProps): React.JSX.Element => {
|
}: ActivityTimelineProps): React.JSX.Element => {
|
||||||
const [visibleCount, setVisibleCount] = useState(MESSAGES_PAGE_SIZE);
|
const [visibleCount, setVisibleCount] = useState(MESSAGES_PAGE_SIZE);
|
||||||
|
|
||||||
|
|
@ -354,18 +361,26 @@ export const ActivityTimeline = ({
|
||||||
const prevSessionId = getItemSessionId(timelineItems[realIndex - 1]);
|
const prevSessionId = getItemSessionId(timelineItems[realIndex - 1]);
|
||||||
const currSessionId = getItemSessionId(item);
|
const currSessionId = getItemSessionId(item);
|
||||||
if (prevSessionId && currSessionId && prevSessionId !== currSessionId) {
|
if (prevSessionId && currSessionId && prevSessionId !== currSessionId) {
|
||||||
sessionSeparator = (
|
// Suppress separator when both sessions belong to the same team
|
||||||
<div
|
// (reconnects produce new session IDs but are not "new sessions" from user's perspective)
|
||||||
className="flex items-center gap-3"
|
const isSameTeam =
|
||||||
style={{ paddingTop: 45, paddingBottom: 45 }}
|
teamSessionIds &&
|
||||||
>
|
teamSessionIds.has(prevSessionId) &&
|
||||||
<div className="h-px flex-1 bg-blue-600/30 dark:bg-blue-400/30" />
|
teamSessionIds.has(currSessionId);
|
||||||
<span className="whitespace-nowrap text-[11px] font-medium text-blue-600 dark:text-blue-400">
|
if (!isSameTeam) {
|
||||||
New session
|
sessionSeparator = (
|
||||||
</span>
|
<div
|
||||||
<div className="h-px flex-1 bg-blue-600/30 dark:bg-blue-400/30" />
|
className="flex items-center gap-3"
|
||||||
</div>
|
style={{ paddingTop: 45, paddingBottom: 45 }}
|
||||||
);
|
>
|
||||||
|
<div className="h-px flex-1 bg-blue-600/30 dark:bg-blue-400/30" />
|
||||||
|
<span className="whitespace-nowrap text-[11px] font-medium text-blue-600 dark:text-blue-400">
|
||||||
|
New session
|
||||||
|
</span>
|
||||||
|
<div className="h-px flex-1 bg-blue-600/30 dark:bg-blue-400/30" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,13 @@ export const MessageComposer = ({
|
||||||
}, [members, recipient]);
|
}, [members, recipient]);
|
||||||
|
|
||||||
const projectPath = useStore((s) => s.selectedTeamData?.config.projectPath ?? null);
|
const projectPath = useStore((s) => s.selectedTeamData?.config.projectPath ?? null);
|
||||||
|
const isProvisioning = useStore((s) =>
|
||||||
|
Object.values(s.provisioningRuns).some(
|
||||||
|
(run) =>
|
||||||
|
run.teamName === teamName &&
|
||||||
|
!['ready', 'disconnected', 'failed', 'cancelled'].includes(run.state)
|
||||||
|
)
|
||||||
|
);
|
||||||
const draft = useComposerDraft(teamName);
|
const draft = useComposerDraft(teamName);
|
||||||
|
|
||||||
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
|
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
|
||||||
|
|
@ -151,6 +158,7 @@ export const MessageComposer = ({
|
||||||
trimmed.length > 0 &&
|
trimmed.length > 0 &&
|
||||||
trimmed.length <= MAX_TEXT_LENGTH &&
|
trimmed.length <= MAX_TEXT_LENGTH &&
|
||||||
!sending &&
|
!sending &&
|
||||||
|
!isProvisioning &&
|
||||||
!attachmentsBlocked;
|
!attachmentsBlocked;
|
||||||
|
|
||||||
// Track whether we initiated a send — clear draft only on confirmed success
|
// Track whether we initiated a send — clear draft only on confirmed success
|
||||||
|
|
@ -335,7 +343,11 @@ export const MessageComposer = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||||
{!isTeamAlive ? (
|
{isProvisioning ? (
|
||||||
|
<span className="text-[10px]" style={{ color: 'var(--warning-text)' }}>
|
||||||
|
Launching...
|
||||||
|
</span>
|
||||||
|
) : !isTeamAlive ? (
|
||||||
<span className="text-[10px]" style={{ color: 'var(--warning-text)' }}>
|
<span className="text-[10px]" style={{ color: 'var(--warning-text)' }}>
|
||||||
Team offline
|
Team offline
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -448,7 +460,11 @@ export const MessageComposer = ({
|
||||||
|
|
||||||
<MentionableTextarea
|
<MentionableTextarea
|
||||||
id={`compose-${teamName}`}
|
id={`compose-${teamName}`}
|
||||||
placeholder="Write a message... (Enter to send, Shift+Enter for new line)"
|
placeholder={
|
||||||
|
isProvisioning
|
||||||
|
? 'Team is launching... Please wait.'
|
||||||
|
: 'Write a message... (Enter to send, Shift+Enter for new line)'
|
||||||
|
}
|
||||||
value={draft.text}
|
value={draft.text}
|
||||||
onValueChange={draft.setText}
|
onValueChange={draft.setText}
|
||||||
suggestions={mentionSuggestions}
|
suggestions={mentionSuggestions}
|
||||||
|
|
@ -460,7 +476,7 @@ export const MessageComposer = ({
|
||||||
minRows={2}
|
minRows={2}
|
||||||
maxRows={6}
|
maxRows={6}
|
||||||
maxLength={MAX_TEXT_LENGTH}
|
maxLength={MAX_TEXT_LENGTH}
|
||||||
disabled={sending}
|
disabled={sending || isProvisioning}
|
||||||
cornerAction={
|
cornerAction={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* NOTE: ContextRing disabled — usage formula is inaccurate */}
|
{/* NOTE: ContextRing disabled — usage formula is inaccurate */}
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,10 @@
|
||||||
--color-section-bg-open: rgba(255, 255, 255, 0.07);
|
--color-section-bg-open: rgba(255, 255, 255, 0.07);
|
||||||
--color-section-hover: rgba(255, 255, 255, 0.08);
|
--color-section-hover: rgba(255, 255, 255, 0.08);
|
||||||
--color-section-hover-open: rgba(255, 255, 255, 0.1);
|
--color-section-hover-open: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
/* Compact boundary — phase badge */
|
||||||
|
--compact-phase-bg: rgba(99, 102, 241, 0.15);
|
||||||
|
--compact-phase-text: #818cf8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* File icon glow — halo so dark icons stay visible on dark backgrounds */
|
/* File icon glow — halo so dark icons stay visible on dark backgrounds */
|
||||||
|
|
@ -502,6 +506,10 @@
|
||||||
--color-section-bg-open: rgba(0, 0, 0, 0.07);
|
--color-section-bg-open: rgba(0, 0, 0, 0.07);
|
||||||
--color-section-hover: rgba(0, 0, 0, 0.08);
|
--color-section-hover: rgba(0, 0, 0, 0.08);
|
||||||
--color-section-hover-open: rgba(0, 0, 0, 0.1);
|
--color-section-hover-open: rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
/* Compact boundary — phase badge */
|
||||||
|
--compact-phase-bg: rgba(67, 56, 202, 0.12);
|
||||||
|
--compact-phase-text: #4338ca;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rehype-highlight (highlight.js) — map hljs classes to app theme variables */
|
/* rehype-highlight (highlight.js) — map hljs classes to app theme variables */
|
||||||
|
|
|
||||||
|
|
@ -317,9 +317,6 @@ const KNOWN_DIRS = new Set([
|
||||||
'services',
|
'services',
|
||||||
'hooks',
|
'hooks',
|
||||||
'store',
|
'store',
|
||||||
'renderer',
|
|
||||||
'main',
|
|
||||||
'preload',
|
|
||||||
'public',
|
'public',
|
||||||
'assets',
|
'assets',
|
||||||
'config',
|
'config',
|
||||||
|
|
@ -340,7 +337,6 @@ const KNOWN_DIRS = new Set([
|
||||||
'middleware',
|
'middleware',
|
||||||
'api',
|
'api',
|
||||||
'common',
|
'common',
|
||||||
'shared',
|
|
||||||
'core',
|
'core',
|
||||||
'modules',
|
'modules',
|
||||||
'client',
|
'client',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue