diff --git a/frontend/src/components/FloatingTranscriptDebug.vue b/frontend/src/components/FloatingTranscriptDebug.vue
index 06a4324..1a88c7f 100644
--- a/frontend/src/components/FloatingTranscriptDebug.vue
+++ b/frontend/src/components/FloatingTranscriptDebug.vue
@@ -970,29 +970,15 @@ onBeforeUnmount(() => {
pointer-events: none !important;
}
-/* Idle: slide user-input down and fade out (only if empty) */
-.aero-win:not(.chrome-visible) .content :deep(.user-input) {
+/* Idle: slide bottom-overlay down and fade out */
+.aero-win:not(.chrome-visible) .content :deep(.bottom-overlay) {
opacity: 0 !important;
transform: translateY(100%) !important;
pointer-events: none !important;
}
-/* Keep user-input visible when textarea has text */
-.aero-win:not(.chrome-visible) .content :deep(.user-input: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))) {
+/* Keep bottom-overlay visible when textarea has text */
+.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;
@@ -1022,11 +1008,7 @@ onBeforeUnmount(() => {
transition: opacity 0.35s ease, transform 0.35s ease !important;
}
-.content :deep(.user-input) {
- transition: opacity 0.35s ease, transform 0.35s ease !important;
-}
-
-.content :deep(.status-bar) {
+.content :deep(.bottom-overlay) {
transition: opacity 0.35s ease, transform 0.35s ease !important;
}
@@ -1321,16 +1303,23 @@ onBeforeUnmount(() => {
}
-/* Status bar: absolute overlay at very bottom */
-.content :deep(.status-bar) {
+/* Bottom overlay: absolute container for lifecycle + input + status */
+.content :deep(.bottom-overlay) {
position: absolute !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
- z-index: 4 !important;
- background: rgba(0, 6, 18, 0.6) !important;
+ z-index: 3 !important;
+ display: flex !important;
+ flex-direction: column !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;
+}
+
+.content :deep(.status-bar) {
+ background: transparent !important;
border-top: 1px solid rgba(255, 255, 255, 0.04) !important;
border-bottom: none !important;
padding: 0.15rem 0.5rem !important;
@@ -1368,20 +1357,19 @@ onBeforeUnmount(() => {
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) {
- position: absolute !important;
- bottom: 20px !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;
+ background: transparent !important;
+ border-top: 1px solid rgba(255, 255, 255, 0.04) !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 */
.content :deep(.input-container) {
background: rgba(0, 6, 18, 0.8) !important;
diff --git a/frontend/src/components/transcript-debug/ChatContainer.vue b/frontend/src/components/transcript-debug/ChatContainer.vue
index 37f9416..7879a83 100644
--- a/frontend/src/components/transcript-debug/ChatContainer.vue
+++ b/frontend/src/components/transcript-debug/ChatContainer.vue
@@ -17,6 +17,7 @@ import ProgressEvent from './ProgressEvent.vue'
import SystemMessage from './SystemMessage.vue'
import TurnEndDivider from './TurnEndDivider.vue'
import UserInput from './UserInput.vue'
+import SessionLifecycleStatus from './SessionLifecycleStatus.vue'
import ResumeTerminalButton from './ResumeTerminalButton.vue'
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 ──
const permissionMode = computed(() => props.hookPermissionMode || '')
const fullCwd = computed(() => props.conversation.metadata.cwd || '')
@@ -646,24 +660,26 @@ function formatDuration(start: string, end: string): string {
-