feat(web): coquille IDE + arbre de projets unifié (route /ide)
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled

components/ide/ : IdeShell (grille barre d'activité + panneau gauche + zone centrale + dock + barre de statut, splitters persistés, réconciliation des ressources ouvertes contre les données live), ActivityBar (Explorer/Git/Sessions/Groupes + Réglages/Aide), PrimarySidebar, ProjectTree + ProjectTreeNode (arbre unique repos -> worktrees -> sessions, fichiers embarqués via FileTree en mode sans chrome, dépliage paresseux), StatusBar.

Route /ide (layout 'ide'), entrée de nav « IDE », namespace i18n ide (EN/FR). Clics de l'arbre pilotent le store (contexte worktree, ouverture de fichier/terminal). Centre et dock en placeholders (remplis en B4/B5). FileTree gagne un mode embedded. 427 tests.
This commit is contained in:
2026-07-17 17:08:35 +02:00
parent 6acd4a16fd
commit c96d929c7a
13 changed files with 446 additions and 11 deletions

View File

@@ -0,0 +1,69 @@
<template>
<div class="flex h-full min-h-0 flex-col">
<div class="flex items-center gap-1 px-3 py-2 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase">
{{ t('ide.projects') }}
<button
type="button"
class="ml-auto rounded p-0.5 hover:bg-surface-2 hover:text-fg"
:title="t('repos.scan')"
:disabled="scanning"
@click="onScan"
>
<ScanSearch :size="14" />
</button>
<button type="button" class="rounded p-0.5 hover:bg-surface-2 hover:text-fg" :title="t('ide.addProject')" @click="showPicker = true">
<Plus :size="14" />
</button>
</div>
<div class="min-h-0 flex-1 overflow-auto pb-2">
<p v-if="repos.length === 0" class="px-3 py-2 text-xs text-fg-subtle">{{ t('ide.noProjects') }}</p>
<ProjectTreeNode v-for="repo in repos" :key="repo.id" :repo="repo" />
</div>
<DirectoryPicker v-if="showPicker" mode="repo" :initial-path="''" @select="onPick" @close="showPicker = false" />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { Plus, ScanSearch } from '@lucide/vue';
import { useWorktreesStore } from '../../stores/worktrees';
import { useToastsStore } from '../../stores/toasts';
import ProjectTreeNode from './ProjectTreeNode.vue';
import DirectoryPicker from '../DirectoryPicker.vue';
const { t } = useI18n();
const worktrees = useWorktreesStore();
const toasts = useToastsStore();
const showPicker = ref(false);
const scanning = ref(false);
const repos = computed(() =>
[...worktrees.repos].filter((r) => !r.hidden).sort((a, b) => a.label.localeCompare(b.label)),
);
async function onScan(): Promise<void> {
scanning.value = true;
try {
const res = await worktrees.discover();
toasts.success(t('repos.scanResult', res.added));
} catch (err) {
toasts.error(err);
} finally {
scanning.value = false;
}
}
async function onPick(path: string): Promise<void> {
showPicker.value = false;
try {
await worktrees.addRepo(path);
toasts.success(t('toast.repoAdded'));
} catch (err) {
toasts.error(err);
}
}
</script>