feat: add SessionLifecycleStatus component with bottom-overlay layout
- New SessionLifecycleStatus.vue: shows current hook lifecycle event with color-coded dot, event name, and detail text. Auto-detects mock vs real mode (falls back to demo cycle when no real data). - Wrap lifecycle + UserInput + status-bar in .bottom-overlay container to eliminate fragile hardcoded pixel offsets in FloatingTranscriptDebug. - Remove old agent-status-indicator square dot from status bar. - Add lastHookEvent/lastHookDetail to client AgentSessionState type. - Simplify idle-mode CSS: single .bottom-overlay rule replaces three separate show/hide rules for user-input, status-bar, lifecycle-ribbon.
This commit is contained in:
@@ -970,29 +970,15 @@ onBeforeUnmount(() => {
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Idle: slide user-input down and fade out (only if empty) */
|
/* Idle: slide bottom-overlay down and fade out */
|
||||||
.aero-win:not(.chrome-visible) .content :deep(.user-input) {
|
.aero-win:not(.chrome-visible) .content :deep(.bottom-overlay) {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
transform: translateY(100%) !important;
|
transform: translateY(100%) !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keep user-input visible when textarea has text */
|
/* Keep bottom-overlay visible when textarea has text */
|
||||||
.aero-win:not(.chrome-visible) .content :deep(.user-input:has(.input-field:not(:placeholder-shown))) {
|
.aero-win:not(.chrome-visible) .content :deep(.bottom-overlay:has(.input-field:not(:placeholder-shown))) {
|
||||||
opacity: 1 !important;
|
|
||||||
transform: none !important;
|
|
||||||
pointer-events: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Idle: hide status bar */
|
|
||||||
.aero-win:not(.chrome-visible) .content :deep(.status-bar) {
|
|
||||||
opacity: 0 !important;
|
|
||||||
transform: translateY(100%) !important;
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep status-bar visible when input has text */
|
|
||||||
.aero-win:not(.chrome-visible) .content :deep(.status-bar:has(~ .user-input .input-field:not(:placeholder-shown))) {
|
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
pointer-events: auto !important;
|
pointer-events: auto !important;
|
||||||
@@ -1022,11 +1008,7 @@ onBeforeUnmount(() => {
|
|||||||
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content :deep(.user-input) {
|
.content :deep(.bottom-overlay) {
|
||||||
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content :deep(.status-bar) {
|
|
||||||
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
transition: opacity 0.35s ease, transform 0.35s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1321,16 +1303,23 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Status bar: absolute overlay at very bottom */
|
/* Bottom overlay: absolute container for lifecycle + input + status */
|
||||||
.content :deep(.status-bar) {
|
.content :deep(.bottom-overlay) {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
bottom: 0 !important;
|
bottom: 0 !important;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
z-index: 4 !important;
|
z-index: 3 !important;
|
||||||
background: rgba(0, 6, 18, 0.6) !important;
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
background: rgba(0, 6, 18, 0.5) !important;
|
||||||
backdrop-filter: blur(8px) !important;
|
backdrop-filter: blur(8px) !important;
|
||||||
-webkit-backdrop-filter: blur(8px) !important;
|
-webkit-backdrop-filter: blur(8px) !important;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.06) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep(.status-bar) {
|
||||||
|
background: transparent !important;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.04) !important;
|
border-top: 1px solid rgba(255, 255, 255, 0.04) !important;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
padding: 0.15rem 0.5rem !important;
|
padding: 0.15rem 0.5rem !important;
|
||||||
@@ -1368,20 +1357,19 @@ onBeforeUnmount(() => {
|
|||||||
color: rgba(255,255,255,0.2) !important;
|
color: rgba(255,255,255,0.2) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* UserInput: absolute overlay above status bar */
|
/* UserInput: inside bottom-overlay, no absolute positioning needed */
|
||||||
.content :deep(.user-input) {
|
.content :deep(.user-input) {
|
||||||
position: absolute !important;
|
background: transparent !important;
|
||||||
bottom: 20px !important;
|
border-top: 1px solid rgba(255, 255, 255, 0.04) !important;
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
z-index: 3 !important;
|
|
||||||
background: rgba(0, 6, 18, 0.5) !important;
|
|
||||||
backdrop-filter: blur(8px) !important;
|
|
||||||
-webkit-backdrop-filter: blur(8px) !important;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.06) !important;
|
|
||||||
padding: 0.3rem 0.5rem !important;
|
padding: 0.3rem 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lifecycle ribbon: inside bottom-overlay, flows naturally above user-input */
|
||||||
|
.content :deep(.lifecycle-ribbon) {
|
||||||
|
background: transparent !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dark overlay on input-container so text is readable */
|
/* Dark overlay on input-container so text is readable */
|
||||||
.content :deep(.input-container) {
|
.content :deep(.input-container) {
|
||||||
background: rgba(0, 6, 18, 0.8) !important;
|
background: rgba(0, 6, 18, 0.8) !important;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import ProgressEvent from './ProgressEvent.vue'
|
|||||||
import SystemMessage from './SystemMessage.vue'
|
import SystemMessage from './SystemMessage.vue'
|
||||||
import TurnEndDivider from './TurnEndDivider.vue'
|
import TurnEndDivider from './TurnEndDivider.vue'
|
||||||
import UserInput from './UserInput.vue'
|
import UserInput from './UserInput.vue'
|
||||||
|
import SessionLifecycleStatus from './SessionLifecycleStatus.vue'
|
||||||
import ResumeTerminalButton from './ResumeTerminalButton.vue'
|
import ResumeTerminalButton from './ResumeTerminalButton.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -75,6 +76,19 @@ const agentStatus = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ── Lifecycle hook event (for SessionLifecycleStatus) ──
|
||||||
|
const lifecycleEvent = computed(() => {
|
||||||
|
const agent = props.selectedAgent
|
||||||
|
if (!agent) return null
|
||||||
|
return sessionStore.agents[agent]?.lastHookEvent ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
const lifecycleDetail = computed(() => {
|
||||||
|
const agent = props.selectedAgent
|
||||||
|
if (!agent) return ''
|
||||||
|
return sessionStore.agents[agent]?.lastHookDetail ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
// ── Derived display values ──
|
// ── Derived display values ──
|
||||||
const permissionMode = computed(() => props.hookPermissionMode || '')
|
const permissionMode = computed(() => props.hookPermissionMode || '')
|
||||||
const fullCwd = computed(() => props.conversation.metadata.cwd || '')
|
const fullCwd = computed(() => props.conversation.metadata.cwd || '')
|
||||||
@@ -646,6 +660,12 @@ function formatDuration(start: string, end: string): string {
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
<div class="bottom-overlay">
|
||||||
|
<SessionLifecycleStatus
|
||||||
|
:current-event="lifecycleEvent"
|
||||||
|
:event-detail="lifecycleDetail"
|
||||||
|
/>
|
||||||
|
|
||||||
<UserInput
|
<UserInput
|
||||||
:processing="props.processing"
|
:processing="props.processing"
|
||||||
:terminal-ready="props.terminalReady"
|
:terminal-ready="props.terminalReady"
|
||||||
@@ -660,10 +680,6 @@ function formatDuration(start: string, end: string): string {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<span v-if="agentStatus" class="agent-status-indicator" :title="agentStatus.label">
|
|
||||||
<span class="agent-status-dot" :class="agentStatus.status" :style="{ background: agentStatus.color }" />
|
|
||||||
<span class="agent-status-label">{{ agentStatus.label }}</span>
|
|
||||||
</span>
|
|
||||||
<span v-if="permissionMode" class="meta-badge mode">{{ permissionMode }}</span>
|
<span v-if="permissionMode" class="meta-badge mode">{{ permissionMode }}</span>
|
||||||
<span v-if="displayCwd" class="meta-badge origin" tabindex="0">{{ fullCwd }}</span>
|
<span v-if="displayCwd" class="meta-badge origin" tabindex="0">{{ fullCwd }}</span>
|
||||||
<span class="meta-count">{{ conversation.messages.length }} msgs</span>
|
<span class="meta-count">{{ conversation.messages.length }} msgs</span>
|
||||||
@@ -708,6 +724,7 @@ function formatDuration(start: string, end: string): string {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -791,6 +808,13 @@ function formatDuration(start: string, end: string): string {
|
|||||||
color: #22c55e;
|
color: #22c55e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Bottom overlay (lifecycle + input + status bar) ── */
|
||||||
|
.bottom-overlay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Status bar (below input) ── */
|
/* ── Status bar (below input) ── */
|
||||||
.status-bar {
|
.status-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1347,40 +1371,5 @@ function formatDuration(start: string, end: string): string {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Agent status indicator ── */
|
|
||||||
.agent-status-indicator {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-status-dot {
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-status-dot.thinking {
|
|
||||||
animation: pulse-status 1.5s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse-status {
|
|
||||||
0%, 100% { opacity: 0.5; }
|
|
||||||
50% { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-status-label {
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
color: var(--text-muted, rgba(255,255,255,0.4));
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 120px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
type LifecycleEvent =
|
||||||
|
| 'SessionStart' | 'UserPromptSubmit'
|
||||||
|
| 'PreToolUse' | 'PermissionRequest' | 'PostToolUse' | 'PostToolUseFailure'
|
||||||
|
| 'Notification' | 'SubagentStart' | 'SubagentStop'
|
||||||
|
| 'Stop' | 'TeammateIdle' | 'TaskCompleted'
|
||||||
|
| 'ConfigChange' | 'PreCompact' | 'SessionEnd'
|
||||||
|
|
||||||
|
type Category = 'session' | 'user' | 'tool' | 'agent' | 'system'
|
||||||
|
|
||||||
|
interface LifecycleInfo {
|
||||||
|
color: string
|
||||||
|
label: string
|
||||||
|
category: Category
|
||||||
|
processing?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIFECYCLE_DISPLAY: Record<LifecycleEvent, LifecycleInfo> = {
|
||||||
|
SessionStart: { color: '#60a5fa', label: 'Session started', category: 'session' },
|
||||||
|
UserPromptSubmit: { color: '#a78bfa', label: 'Prompt submitted', category: 'user' },
|
||||||
|
PreToolUse: { color: '#fbbf24', label: 'Tool starting', category: 'tool', processing: true },
|
||||||
|
PermissionRequest: { color: '#fb923c', label: 'Permission required', category: 'tool', processing: true },
|
||||||
|
PostToolUse: { color: '#4ade80', label: 'Tool completed', category: 'tool' },
|
||||||
|
PostToolUseFailure: { color: '#f87171', label: 'Tool failed', category: 'tool' },
|
||||||
|
Notification: { color: '#38bdf8', label: 'Notification', category: 'system' },
|
||||||
|
SubagentStart: { color: '#c084fc', label: 'Subagent spawned', category: 'agent', processing: true },
|
||||||
|
SubagentStop: { color: '#a855f7', label: 'Subagent finished', category: 'agent' },
|
||||||
|
Stop: { color: '#22d3ee', label: 'Response complete', category: 'session' },
|
||||||
|
TeammateIdle: { color: '#94a3b8', label: 'Teammate idle', category: 'agent' },
|
||||||
|
TaskCompleted: { color: '#34d399', label: 'Task completed', category: 'system' },
|
||||||
|
ConfigChange: { color: '#e879f9', label: 'Config changed', category: 'system' },
|
||||||
|
PreCompact: { color: '#f59e0b', label: 'Compacting context', category: 'system', processing: true },
|
||||||
|
SessionEnd: { color: '#6b7280', label: 'Session ended', category: 'session' },
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MockStep {
|
||||||
|
event: LifecycleEvent
|
||||||
|
duration: number
|
||||||
|
detail?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_SEQUENCE: MockStep[] = [
|
||||||
|
{ event: 'SessionStart', duration: 2000, detail: 'claude-opus-4-6' },
|
||||||
|
{ event: 'UserPromptSubmit', duration: 1500, detail: '"Fix the login bug"' },
|
||||||
|
{ event: 'PreToolUse', duration: 800, detail: 'Grep' },
|
||||||
|
{ event: 'PostToolUse', duration: 1200, detail: 'Grep' },
|
||||||
|
{ event: 'PreToolUse', duration: 600, detail: 'Read' },
|
||||||
|
{ event: 'PostToolUse', duration: 1000, detail: 'Read' },
|
||||||
|
{ event: 'PreToolUse', duration: 500, detail: 'Edit' },
|
||||||
|
{ event: 'PermissionRequest', duration: 3000, detail: 'Edit src/auth.ts' },
|
||||||
|
{ event: 'PostToolUse', duration: 1500, detail: 'Edit' },
|
||||||
|
{ event: 'PreToolUse', duration: 400, detail: 'Bash' },
|
||||||
|
{ event: 'PermissionRequest', duration: 2500, detail: 'npm test' },
|
||||||
|
{ event: 'PostToolUseFailure', duration: 2000, detail: 'Bash (exit 1)' },
|
||||||
|
{ event: 'Notification', duration: 1500, detail: 'Test failed, retrying' },
|
||||||
|
{ event: 'PreToolUse', duration: 500, detail: 'Edit' },
|
||||||
|
{ event: 'PostToolUse', duration: 1000, detail: 'Edit' },
|
||||||
|
{ event: 'PreToolUse', duration: 400, detail: 'Bash' },
|
||||||
|
{ event: 'PostToolUse', duration: 1500, detail: 'Bash (tests pass)' },
|
||||||
|
{ event: 'SubagentStart', duration: 1000, detail: 'code-review' },
|
||||||
|
{ event: 'SubagentStop', duration: 1500, detail: 'code-review' },
|
||||||
|
{ event: 'PreCompact', duration: 2000 },
|
||||||
|
{ event: 'TaskCompleted', duration: 1500, detail: 'Login bug fixed' },
|
||||||
|
{ event: 'Stop', duration: 3000, detail: 'Done' },
|
||||||
|
{ event: 'TeammateIdle', duration: 1500 },
|
||||||
|
{ event: 'ConfigChange', duration: 1200, detail: '.claude/settings.json' },
|
||||||
|
{ event: 'SessionEnd', duration: 4000 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
currentEvent?: string | null
|
||||||
|
eventDetail?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const isMock = computed(() => !props.currentEvent || !(props.currentEvent in LIFECYCLE_DISPLAY))
|
||||||
|
|
||||||
|
const mockIndex = ref(0)
|
||||||
|
let mockTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
const activeEvent = computed<LifecycleEvent>(() => {
|
||||||
|
if (!isMock.value) return props.currentEvent as LifecycleEvent
|
||||||
|
return MOCK_SEQUENCE[mockIndex.value].event
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeDetail = computed(() => {
|
||||||
|
if (!isMock.value) return props.eventDetail || ''
|
||||||
|
return MOCK_SEQUENCE[mockIndex.value].detail || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayInfo = computed(() => LIFECYCLE_DISPLAY[activeEvent.value])
|
||||||
|
const isProcessing = computed(() => !!displayInfo.value.processing)
|
||||||
|
|
||||||
|
function advanceMock() {
|
||||||
|
mockIndex.value = (mockIndex.value + 1) % MOCK_SEQUENCE.length
|
||||||
|
scheduleMock()
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleMock() {
|
||||||
|
if (mockTimer) clearTimeout(mockTimer)
|
||||||
|
mockTimer = setTimeout(advanceMock, MOCK_SEQUENCE[mockIndex.value].duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isMock, (mock) => {
|
||||||
|
if (mock) {
|
||||||
|
scheduleMock()
|
||||||
|
} else if (mockTimer) {
|
||||||
|
clearTimeout(mockTimer)
|
||||||
|
mockTimer = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (isMock.value) scheduleMock()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (mockTimer) {
|
||||||
|
clearTimeout(mockTimer)
|
||||||
|
mockTimer = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="lifecycle-ribbon" :class="['category-' + displayInfo.category]">
|
||||||
|
<Transition name="lc" mode="out-in">
|
||||||
|
<div class="lc-content" :key="activeEvent + activeDetail">
|
||||||
|
<span
|
||||||
|
class="lc-dot"
|
||||||
|
:class="{ processing: isProcessing }"
|
||||||
|
:style="{ background: displayInfo.color }"
|
||||||
|
/>
|
||||||
|
<span class="lc-event" :style="{ color: displayInfo.color }">{{ activeEvent }}</span>
|
||||||
|
<span v-if="activeDetail" class="lc-detail">{{ activeDetail }}</span>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.lifecycle-ribbon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
|
transition: border-top-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lifecycle-ribbon.category-session { border-top-color: rgba(96, 165, 250, 0.15); }
|
||||||
|
.lifecycle-ribbon.category-user { border-top-color: rgba(167, 139, 250, 0.15); }
|
||||||
|
.lifecycle-ribbon.category-tool { border-top-color: rgba(251, 191, 36, 0.15); }
|
||||||
|
.lifecycle-ribbon.category-agent { border-top-color: rgba(192, 132, 252, 0.15); }
|
||||||
|
.lifecycle-ribbon.category-system { border-top-color: rgba(56, 189, 248, 0.15); }
|
||||||
|
|
||||||
|
.lc-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-dot {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-dot.processing {
|
||||||
|
animation: pulse-lifecycle 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-lifecycle {
|
||||||
|
0%, 100% { opacity: 0.5; transform: scale(0.8); }
|
||||||
|
50% { opacity: 1; transform: scale(1.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-event {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-detail {
|
||||||
|
font-size: 9px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transition classes */
|
||||||
|
.lc-enter-active {
|
||||||
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-leave-active {
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -71,6 +71,8 @@ export interface AgentSessionState {
|
|||||||
pendingApprovals: PendingApproval[]
|
pendingApprovals: PendingApproval[]
|
||||||
terminal: AgentTerminalInfo
|
terminal: AgentTerminalInfo
|
||||||
notifications: SessionNotification[]
|
notifications: SessionNotification[]
|
||||||
|
lastHookEvent: string | null
|
||||||
|
lastHookDetail: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// WS message types
|
// WS message types
|
||||||
|
|||||||
Reference in New Issue
Block a user