Introduit une couche sémantique dans style.css (@theme) : surfaces (surface-0..3), bordures, texte (fg/muted/subtle) et accent, aliasant la palette Tailwind (rendu identique, vérifié dans le CSS produit). Ajoute les métriques du chrome IDE (--ide-*) pour la refonte à venir. Centralise deux sources uniques : lib/terminal-theme.ts (thème + police xterm) et lib/status-tokens.ts (tons de statut, dédupliqués entre BaseBadge et SessionStateBadge). Définit le thème Monaco « arboretum-dark ». Remplace les #09090b épars des composants par bg-surface-0 (ne restent que index.html et les renderers exigeant un hex concret).
71 lines
3.2 KiB
Vue
71 lines
3.2 KiB
Vue
<template>
|
|
<!-- focus-within → anneau visuel : montre quelle cellule reçoit la frappe clavier -->
|
|
<div class="flex min-h-0 flex-col overflow-hidden rounded-lg border border-zinc-800 bg-surface-0 focus-within:ring-2 focus-within:ring-sky-600">
|
|
<header class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1">
|
|
<SessionStateBadge :session="session" />
|
|
<span class="truncate font-mono text-xs text-zinc-300" :title="session.cwd">{{ title }}</span>
|
|
<span
|
|
v-if="spannedCount > 1"
|
|
class="shrink-0 rounded-full border border-sky-800/60 bg-sky-950/40 px-1.5 py-0.5 text-[10px] text-sky-300"
|
|
:title="spannedDirs.join('\n')"
|
|
>
|
|
{{ t('groups.spanBadge', spannedCount) }}
|
|
</span>
|
|
<span class="ml-auto shrink-0 font-mono text-[11px] text-zinc-500">{{ session.command }}</span>
|
|
<button
|
|
class="shrink-0 rounded p-0.5 text-zinc-500 transition-colors hover:text-zinc-200"
|
|
:aria-label="t('terminal.openFullscreen')"
|
|
:title="t('terminal.openFullscreen')"
|
|
@click="emit('open-fullscreen')"
|
|
>
|
|
<Expand class="h-3.5 w-3.5" />
|
|
</button>
|
|
<button
|
|
class="shrink-0 rounded p-0.5 text-zinc-500 transition-colors hover:text-zinc-200"
|
|
:aria-label="maximized ? t('terminal.restore') : t('terminal.maximize')"
|
|
:title="maximized ? t('terminal.restore') : t('terminal.maximize')"
|
|
@click="emit('toggle-maximize')"
|
|
>
|
|
<component :is="maximized ? Minimize2 : Maximize2" class="h-3.5 w-3.5" />
|
|
</button>
|
|
</header>
|
|
<DialogPrompt v-if="isWaiting" :session="session" class="px-2 pt-2" />
|
|
<div class="min-h-0 flex-1">
|
|
<TerminalView :session-id="session.id" mode="interactive" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { Expand, Maximize2, Minimize2 } from '@lucide/vue';
|
|
import type { SessionSummary } from '@arboretum/shared';
|
|
import { useWorktreesStore } from '../stores/worktrees';
|
|
import SessionStateBadge from './SessionStateBadge.vue';
|
|
import DialogPrompt from './DialogPrompt.vue';
|
|
import TerminalView from './TerminalView.vue';
|
|
|
|
const props = defineProps<{ session: SessionSummary; maximized?: boolean }>();
|
|
const emit = defineEmits<{ 'toggle-maximize': []; 'open-fullscreen': [] }>();
|
|
const worktrees = useWorktreesStore();
|
|
const { t } = useI18n();
|
|
|
|
// session de groupe (P6) : couvre plusieurs répertoires via --add-dir → badge « groupe · N dépôts ».
|
|
const spannedDirs = computed(() => [props.session.cwd, ...(props.session.addedDirs ?? [])]);
|
|
const spannedCount = computed(() => spannedDirs.value.length);
|
|
|
|
// libellé : « repo · branche » si le worktree est connu, sinon le dernier segment du cwd.
|
|
const title = computed(() => {
|
|
const wt = worktrees.worktrees.find((w) => w.path === props.session.cwd);
|
|
if (wt) {
|
|
const repo = worktrees.repos.find((r) => r.id === wt.repoId);
|
|
const branch = wt.branch ?? wt.head.slice(0, 7);
|
|
return repo ? `${repo.label} · ${branch}` : branch;
|
|
}
|
|
return props.session.cwd.split('/').filter(Boolean).pop() ?? props.session.cwd;
|
|
});
|
|
|
|
const isWaiting = computed(() => props.session.live && props.session.activity === 'waiting' && !!props.session.dialog);
|
|
</script>
|