refactor: unify dashboard/terminal selector into single strip, default to dashboard

Remove separate Chat/SE tab toggle. Dashboard is now a button in the
terminal strip alongside T1-T5. Chat only renders when a specific
terminal route is active (/transcript-debug/:n).
This commit is contained in:
2026-02-24 18:16:35 -06:00
parent 4cb0760c50
commit cfb58c3a9f

View File

@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
import { useTranscriptDebug } from '@/composables/transcript-debug'
import { useVoiceInput } from '@/composables/useVoiceInput'
import { useSessionState } from '@/stores/session-state'
import { ChatContainer, AquaticBackground, AgentBadge, NewSessionModal } from '@/components/transcript-debug'
import { ChatContainer, AquaticBackground, AgentBadge, NewSessionModal, SyncEnginePanel } from '@/components/transcript-debug'
import type { AgentName } from '@/types/transcript-debug'
import { isTauri, isMobileTauri, getTauriWindow } from '@/lib/tauri'
import { usePipWindow } from '@/composables/usePipWindow'
@@ -63,6 +63,9 @@ const showSelector = ref(false)
const showNewSessionModal = ref(false)
const isPipWindow = computed(() => route.query.pip === '1')
// Dashboard vs terminal view — driven by route
const isDashboard = computed(() => !route.params.terminalIndex)
// Readability overlay
const savedOverlay = localStorage.getItem('transcript-overlay-opacity')
const overlayOpacity = ref(savedOverlay !== null ? parseFloat(savedOverlay) : 0.55)
@@ -136,6 +139,10 @@ async function handleModalCreateNew(agent: AgentName, initialPrompt: string) {
}
}
function goToDashboard() {
router.push({ name: 'transcript-debug' })
}
function handleTerminalSwitch(sessionId: string) {
const idx = sessionState.terminalRegistry.findIndex(
e => e.transcriptSessionId === sessionId
@@ -219,6 +226,15 @@ onMounted(async () => {
await init()
await voice.init()
syncTerminalFromRoute()
// Signal to the parent window that this PiP page is ready to show
if (isTauri && route.query.pip === '1') {
try {
const { getCurrentWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
const { emitTo } = await import('@tauri-apps/api/event')
await emitTo('main', 'pip:ready', getCurrentWebviewWindow().label)
} catch {}
}
})
onBeforeUnmount(() => {
@@ -252,13 +268,23 @@ onBeforeUnmount(() => {
<!-- Terminal selector strip (hidden in PiP) -->
<div v-if="!isPipWindow" class="terminal-strip">
<div class="strip-left">
<button
:class="['strip-terminal-btn', 'strip-dashboard-btn', { active: isDashboard }]"
@click="goToDashboard"
title="Dashboard"
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
</button>
<button
v-for="(entry, idx) in sessionState.terminalRegistry"
:key="entry.transcriptSessionId"
:class="[
'strip-terminal-btn',
{
active: String(idx + 1) === route.params.terminalIndex || (!route.params.terminalIndex && entry.transcriptSessionId === activeTerminalSessionId),
active: String(idx + 1) === route.params.terminalIndex,
dead: !entry.alive
}
]"
@@ -360,56 +386,62 @@ onBeforeUnmount(() => {
</div>
</Transition>
<ChatContainer
ref="chatRef"
v-if="conversation"
:conversation="conversation"
:processing="processing"
:terminal-ready="terminalReady"
:terminal="ephemeral"
:show-selector="showSelector"
:agents="agents"
:selected-agent="selectedAgent"
:sessions="sessions"
:selected-session-id="selectedSessionId"
:sessions-loading="loading"
:voice-mode="voiceMode"
:whisper-status="whisperStatus"
:audio-devices="audioDevices"
:selected-device-id="selectedDeviceId"
:is-recording="voiceRecording"
:voice-transcript="voiceTranscript + voiceInterim"
:last-audio-url="lastAudioUrl"
:is-playing-audio="isPlayingAudio"
:overlay-opacity="overlayOpacity"
:input-max-lines="inputMaxLines"
:scroll-jump-percent="scrollJumpPercent"
:scroll-nav-mode="scrollNavMode"
:hook-permission-mode="hookMeta.permissionMode"
@send="handleSend"
@switch-agent="handleAgentSwitch"
@select-session="handleSessionSelect"
@create-session="handleCreateSession"
@close-session="closeTerminal"
@start-recording="voice.startRecording()"
@stop-recording="voice.stopRecording()"
@set-voice-mode="voice.setMode($event)"
@select-microphone="voice.selectMicrophone($event)"
@play-last-audio="voice.playLastAudio()"
@update:overlay-opacity="setOverlayOpacity"
@update:input-max-lines="setInputMaxLines"
@update:scroll-jump-percent="setScrollJumpPercent"
@update:scroll-nav-mode="setScrollNavMode"
/>
<!-- Terminal chat (only when a specific terminal is selected) -->
<template v-if="!isDashboard">
<ChatContainer
ref="chatRef"
v-if="conversation"
:conversation="conversation"
:processing="processing"
:terminal-ready="terminalReady"
:terminal="ephemeral"
:show-selector="showSelector"
:agents="agents"
:selected-agent="selectedAgent"
:sessions="sessions"
:selected-session-id="selectedSessionId"
:sessions-loading="loading"
:voice-mode="voiceMode"
:whisper-status="whisperStatus"
:audio-devices="audioDevices"
:selected-device-id="selectedDeviceId"
:is-recording="voiceRecording"
:voice-transcript="voiceTranscript + voiceInterim"
:last-audio-url="lastAudioUrl"
:is-playing-audio="isPlayingAudio"
:overlay-opacity="overlayOpacity"
:input-max-lines="inputMaxLines"
:scroll-jump-percent="scrollJumpPercent"
:scroll-nav-mode="scrollNavMode"
:hook-permission-mode="hookMeta.permissionMode"
@send="handleSend"
@switch-agent="handleAgentSwitch"
@select-session="handleSessionSelect"
@create-session="handleCreateSession"
@close-session="closeTerminal"
@start-recording="voice.startRecording()"
@stop-recording="voice.stopRecording()"
@set-voice-mode="voice.setMode($event)"
@select-microphone="voice.selectMicrophone($event)"
@play-last-audio="voice.playLastAudio()"
@update:overlay-opacity="setOverlayOpacity"
@update:input-max-lines="setInputMaxLines"
@update:scroll-jump-percent="setScrollJumpPercent"
@update:scroll-nav-mode="setScrollNavMode"
/>
<div v-else-if="!transitioning" class="empty-state">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
<span>No active terminal</span>
<small v-if="!sessionState.terminalRegistry.length">No terminals registered</small>
<small v-else>Select a terminal above to begin</small>
</div>
<div v-else-if="!transitioning" class="empty-state">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg>
<span>No active terminal</span>
<small v-if="!sessionState.terminalRegistry.length">No terminals registered</small>
<small v-else>Select a terminal above to begin</small>
</div>
</template>
<!-- Dashboard (default view) -->
<SyncEnginePanel v-if="isDashboard" />
</div>
<!-- New session modal -->
@@ -499,6 +531,16 @@ onBeforeUnmount(() => {
gap: 0.5rem;
}
.strip-dashboard-btn {
gap: 0;
}
.strip-dashboard-btn svg {
opacity: 0.7;
}
.strip-dashboard-btn.active svg {
opacity: 1;
}
.realtime-dot {
color: var(--text-muted);
opacity: 0.4;
@@ -641,6 +683,13 @@ onBeforeUnmount(() => {
ChatContainer glass-transparent overrides (mirrored from FloatingTranscriptDebug)
══════════════════════════════════════════════════════════════════════════════ */
.content-area :deep(.sync-engine-panel) {
position: relative;
z-index: 1;
flex: 1;
overflow-y: auto;
}
.content-area :deep(.chat-container) {
background: transparent !important;
border: none !important;