Les badges busy/waiting/idle des sessions affichées dans les cartes worktree sont désormais résolus depuis le store des sessions (mis à jour en temps réel), au lieu de l'instantané embarqué dans le worktree — pour que « quelle session attend une réponse » reste live sans attendre un worktree_update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
4.1 KiB
Vue
96 lines
4.1 KiB
Vue
<template>
|
|
<div class="rounded border border-zinc-800 bg-zinc-900/40 p-2">
|
|
<div class="flex items-center gap-2">
|
|
<span class="font-mono text-sm text-zinc-200">{{ branchLabel }}</span>
|
|
<span v-if="worktree.isMain" class="badge bg-zinc-800 text-zinc-400">{{ t('worktrees.main') }}</span>
|
|
<span v-if="worktree.locked" class="badge bg-zinc-800 text-zinc-500">{{ t('worktrees.locked') }}</span>
|
|
<span v-if="worktree.prunable" class="badge bg-zinc-800 text-zinc-500">{{ t('worktrees.prunable') }}</span>
|
|
<span class="ml-auto flex items-center gap-2 text-xs text-zinc-500">
|
|
<span v-if="worktree.git.ahead">↑{{ worktree.git.ahead }}</span>
|
|
<span v-if="worktree.git.behind">↓{{ worktree.git.behind }}</span>
|
|
<span v-if="worktree.git.dirtyCount" class="text-amber-400">● {{ t('worktrees.dirty', { n: worktree.git.dirtyCount }) }}</span>
|
|
<span v-else class="text-zinc-600">{{ t('worktrees.clean') }}</span>
|
|
</span>
|
|
</div>
|
|
<p class="truncate font-mono text-xs text-zinc-500" :title="worktree.path">{{ worktree.path }}</p>
|
|
|
|
<div class="mt-1 flex flex-wrap items-center gap-2">
|
|
<RouterLink
|
|
v-for="s in worktree.sessions"
|
|
:key="s.id"
|
|
:to="{ name: 'session', params: { id: s.id } }"
|
|
class="flex items-center gap-1 rounded bg-zinc-800/60 px-1.5 py-0.5 transition-colors hover:bg-zinc-800"
|
|
>
|
|
<SessionStateBadge :session="liveSession(s)" />
|
|
<span class="font-mono text-[11px] text-zinc-400">{{ s.command }}</span>
|
|
</RouterLink>
|
|
<span v-if="worktree.sessions.length === 0" class="text-xs text-zinc-600">{{ t('worktrees.noSession') }}</span>
|
|
|
|
<div v-if="!worktree.isMain" class="ml-auto flex items-center gap-2">
|
|
<template v-if="confirming">
|
|
<span v-if="error" class="text-xs text-amber-400">{{ error }}</span>
|
|
<button class="btn-danger text-xs" :disabled="busy" @click="onDelete">
|
|
{{ forceNeeded ? t('worktrees.forceDelete') : t('worktrees.confirmDelete') }}
|
|
</button>
|
|
<button class="btn text-xs" @click="reset">{{ t('common.cancel') }}</button>
|
|
</template>
|
|
<button v-else class="btn-danger text-xs" @click="confirming = true">{{ t('worktrees.delete') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import type { SessionSummary, WorktreeSummary } from '@arboretum/shared';
|
|
import { useWorktreesStore } from '../stores/worktrees';
|
|
import { useSessionsStore } from '../stores/sessions';
|
|
import { ApiError } from '../lib/api';
|
|
import SessionStateBadge from './SessionStateBadge.vue';
|
|
|
|
const props = defineProps<{ worktree: WorktreeSummary }>();
|
|
const { t } = useI18n();
|
|
const store = useWorktreesStore();
|
|
const sessions = useSessionsStore();
|
|
|
|
// L'état embarqué dans le worktree est un instantané ; on privilégie la version live du store
|
|
// des sessions (mise à jour en continu) pour que busy/waiting/idle reste temps réel.
|
|
function liveSession(s: SessionSummary): SessionSummary {
|
|
return sessions.sessions.find((x) => x.id === s.id) ?? s;
|
|
}
|
|
|
|
const confirming = ref(false);
|
|
const forceNeeded = ref(false);
|
|
const error = ref<string | null>(null);
|
|
const busy = ref(false);
|
|
|
|
const branchLabel = computed(() =>
|
|
props.worktree.branch ?? (props.worktree.detached ? `${t('worktrees.detached')} ${props.worktree.head.slice(0, 7)}` : '—'),
|
|
);
|
|
|
|
function reset(): void {
|
|
confirming.value = false;
|
|
forceNeeded.value = false;
|
|
error.value = null;
|
|
}
|
|
|
|
async function onDelete(): Promise<void> {
|
|
busy.value = true;
|
|
error.value = null;
|
|
try {
|
|
await store.deleteWorktree(props.worktree.repoId, props.worktree.path, forceNeeded.value);
|
|
// succès : l'événement worktree_removed (ou le retrait local) démonte la carte
|
|
} catch (err) {
|
|
if (err instanceof ApiError && err.status === 409) {
|
|
forceNeeded.value = true; // dirty ou session vivante → proposer la suppression forcée
|
|
error.value = err.message;
|
|
} else {
|
|
error.value = err instanceof Error ? err.message : String(err);
|
|
}
|
|
} finally {
|
|
busy.value = false;
|
|
}
|
|
}
|
|
</script>
|