diff --git a/packages/web/src/App.vue b/packages/web/src/App.vue
index 0564a34..137a46c 100644
--- a/packages/web/src/App.vue
+++ b/packages/web/src/App.vue
@@ -1,11 +1,8 @@
@@ -13,19 +10,15 @@
diff --git a/packages/web/src/components/AttentionSection.vue b/packages/web/src/components/AttentionSection.vue
deleted file mode 100644
index c088c97..0000000
--- a/packages/web/src/components/AttentionSection.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
{{ t('attention.title') }}
-
{{ t('attention.count', waiting.length) }}
-
-
-
- {{ s.cwd }}
-
-
-
-
-
-
-
diff --git a/packages/web/src/components/CloneRepoModal.vue b/packages/web/src/components/CloneRepoModal.vue
index 12290e1..dbb387b 100644
--- a/packages/web/src/components/CloneRepoModal.vue
+++ b/packages/web/src/components/CloneRepoModal.vue
@@ -9,7 +9,7 @@
{{ t('clone.noCredential') }}
- {{ t('clone.goSettings') }}
+
@@ -75,8 +75,7 @@
diff --git a/packages/web/src/components/CommandPalette.vue b/packages/web/src/components/CommandPalette.vue
index 9483e3b..f2dbc87 100644
--- a/packages/web/src/components/CommandPalette.vue
+++ b/packages/web/src/components/CommandPalette.vue
@@ -52,13 +52,17 @@
diff --git a/packages/web/src/components/NewProjectModal.vue b/packages/web/src/components/NewProjectModal.vue
index 1708183..56751a2 100644
--- a/packages/web/src/components/NewProjectModal.vue
+++ b/packages/web/src/components/NewProjectModal.vue
@@ -49,17 +49,17 @@
diff --git a/packages/web/src/components/RepoSection.vue b/packages/web/src/components/RepoSection.vue
deleted file mode 100644
index bced9ed..0000000
--- a/packages/web/src/components/RepoSection.vue
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
- {{ error }}
-
-
-
-
{{ t('worktrees.noSession') }}
-
-
-
-
-
diff --git a/packages/web/src/components/SessionContextBar.vue b/packages/web/src/components/SessionContextBar.vue
deleted file mode 100644
index ac5e00d..0000000
--- a/packages/web/src/components/SessionContextBar.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
- {{ group.label }}
- →
-
-
- {{ t('terminal.covers') }}
-
-
- {{ e.label }}
- {{ e.branch }}
-
-
-
-
-
diff --git a/packages/web/src/components/TerminalCell.vue b/packages/web/src/components/TerminalCell.vue
deleted file mode 100644
index e90ecd4..0000000
--- a/packages/web/src/components/TerminalCell.vue
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
- {{ title }}
-
- {{ t('groups.spanBadge', spannedCount) }}
-
- {{ session.command }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/web/src/components/TerminalGrid.vue b/packages/web/src/components/TerminalGrid.vue
deleted file mode 100644
index 104e296..0000000
--- a/packages/web/src/components/TerminalGrid.vue
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
{{ t('groups.gridEmpty') }}
-
-
- {{ t('groups.gridCapped', { shown: MAX_CELLS, total: sessions.length }) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/web/src/components/WorktreeCard.vue b/packages/web/src/components/WorktreeCard.vue
deleted file mode 100644
index 233c614..0000000
--- a/packages/web/src/components/WorktreeCard.vue
+++ /dev/null
@@ -1,287 +0,0 @@
-
-
-
- {{ branchLabel }}
- {{ t('worktrees.main') }}
- {{ t('worktrees.locked') }}
- {{ t('worktrees.prunable') }}
-
- ↑{{ worktree.git.ahead }}
- ↓{{ worktree.git.behind }}
- ● {{ t('worktrees.dirty', { n: worktree.git.dirtyCount }) }}
- {{ t('worktrees.clean') }}
-
-
-
{{ worktree.path }}
-
-
-
- {{ t('workspace.open') }}
-
-
-
- {{ s.command }}
-
-
-
-
{{ t('worktrees.noSession') }}
-
-
-
-
-
{{ startError }}
-
-
-
- {{ error }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ gitError }}
-
-
-
-
- {{ promoteConfirmText }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/web/src/components/ide/ActivityBar.vue b/packages/web/src/components/ide/ActivityBar.vue
index 144409c..de7f7d9 100644
--- a/packages/web/src/components/ide/ActivityBar.vue
+++ b/packages/web/src/components/ide/ActivityBar.vue
@@ -20,30 +20,39 @@
-
-
+
diff --git a/packages/web/src/components/ide/AttentionList.vue b/packages/web/src/components/ide/AttentionList.vue
new file mode 100644
index 0000000..129e485
--- /dev/null
+++ b/packages/web/src/components/ide/AttentionList.vue
@@ -0,0 +1,41 @@
+
+
+
+
+ {{ t('attention.title') }}
+
{{ waiting.length }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/web/src/components/ide/ContextMenu.vue b/packages/web/src/components/ide/ContextMenu.vue
new file mode 100644
index 0000000..a48b33e
--- /dev/null
+++ b/packages/web/src/components/ide/ContextMenu.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web/src/components/ide/GroupsPanel.vue b/packages/web/src/components/ide/GroupsPanel.vue
index 4dfcd4f..778cc33 100644
--- a/packages/web/src/components/ide/GroupsPanel.vue
+++ b/packages/web/src/components/ide/GroupsPanel.vue
@@ -2,31 +2,137 @@
{{ t('ide.activity.groups') }}
-
-
-
+
+
{{ t('groups.empty') }}
-
-
- {{ g.label }}
- {{ t('groups.repoCount', groups.reposInGroup(g.id).length) }}
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/web/src/components/ide/IdeShell.vue b/packages/web/src/components/ide/IdeShell.vue
index de76c65..f4b793f 100644
--- a/packages/web/src/components/ide/IdeShell.vue
+++ b/packages/web/src/components/ide/IdeShell.vue
@@ -1,16 +1,18 @@
-
-
-
-
- {{ mobileTitle }}
+
+ {{ mobileTitle }}
+
+
+
+
+
@@ -59,14 +65,17 @@
// Coquille de la vue IDE (layout 'ide', plein écran). Assemble barre d'activité, panneau gauche,
// zone centrale (éditeur), dock terminaux et barre de statut. Propriétaire de la réconciliation
// des ressources ouvertes contre les données live (worktrees / sessions).
-import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
-import { FileCode, FolderTree, GitCompare, Home, SquareTerminal } from '@lucide/vue';
+import { Boxes, FileCode, FolderTree, GitCompare, LifeBuoy, List, LogOut, MoreVertical, Settings, SquareTerminal } from '@lucide/vue';
import { decodeWtKey } from '@arboretum/shared';
import { useIdeStore, wtKey } from '../../stores/ide';
import { useWorktreesStore } from '../../stores/worktrees';
import { useSessionsStore } from '../../stores/sessions';
+import { useModalsStore } from '../../stores/modals';
+import { useContextMenu } from '../../composables/useContextMenu';
+import { useSession } from '../../composables/useSession';
import ActivityBar from './ActivityBar.vue';
import PrimarySidebar from './PrimarySidebar.vue';
import PanelSplitter from './PanelSplitter.vue';
@@ -75,13 +84,31 @@ import EditorArea from './EditorArea.vue';
import TerminalDock from './TerminalDock.vue';
import ProjectTree from './ProjectTree.vue';
import GitPanel from './GitPanel.vue';
+import SessionsPanel from './SessionsPanel.vue';
+import GroupsPanel from './GroupsPanel.vue';
+import ModalHost from './ModalHost.vue';
+import ContextMenu from './ContextMenu.vue';
const { t } = useI18n();
const ide = useIdeStore();
const worktrees = useWorktreesStore();
const sessions = useSessionsStore();
+const modals = useModalsStore();
+const ctx = useContextMenu();
+const { logout } = useSession();
const route = useRoute();
+// Overlays lazy (partagés avec l'ActivityBar desktop) : accès compte depuis le menu mobile.
+const SettingsOverlay = defineAsyncComponent(() => import('./overlays/SettingsOverlay.vue'));
+const HelpOverlay = defineAsyncComponent(() => import('./overlays/HelpOverlay.vue'));
+function openMobileMenu(e: MouseEvent): void {
+ ctx.open(e.clientX, e.clientY, [
+ { label: t('nav.settings'), icon: Settings, onSelect: () => void modals.open(SettingsOverlay) },
+ { label: t('nav.help'), icon: LifeBuoy, onSelect: () => void modals.open(HelpOverlay) },
+ { label: t('common.logout'), icon: LogOut, onSelect: () => void logout() },
+ ]);
+}
+
// --- responsive : panneau unique sous le breakpoint md (768px) ---
const isMobile = ref(false);
let mql: MediaQueryList | null = null;
@@ -100,28 +127,40 @@ const mobilePanels = computed(() => [
{ key: 'editor', icon: FileCode, label: t('workspace.editor') },
{ key: 'terminal', icon: SquareTerminal, label: t('ide.terminals') },
{ key: 'git', icon: GitCompare, label: t('ide.activity.git') },
+ { key: 'sessions', icon: List, label: t('ide.activity.sessions') },
+ { key: 'groups', icon: Boxes, label: t('ide.activity.groups') },
]);
const mobileTitle = computed(() => mobilePanels.value.find((p) => p.key === ide.mobilePanel)?.label ?? '');
const setMobilePanel = (key: string): void => {
ide.mobilePanel = key;
};
-// Deep-link : /workspace/:repoId/:wt (ex. lien de l'extension VS Code) cible un worktree précis.
-// On le rend actif, on déplie son sous-arbre, et on ouvre éventuellement ?file=.
-function applyDeepLink(): void {
- if (route.name !== 'workspace') return;
- const repoId = String(route.params.repoId ?? '');
- const wt = decodeWtKey(String(route.params.wt ?? ''));
- if (!repoId || !wt) return;
- ide.setActivity('explorer');
- ide.setActiveWorktree(repoId, wt);
- if (!ide.expandedRepoIds.includes(repoId)) ide.toggleRepo(repoId);
- if (!ide.expandedWtPaths.includes(wt)) ide.toggleWt(wt);
- const file = route.query.file ? String(route.query.file) : null;
- if (file) ide.openFile(repoId, wt, file);
+// Deep-link / redirections rétro-compat. /workspace/:repoId/:wt (ex. extension VS Code) cible un
+// worktree. Sur /ide, on consomme une query one-shot (terminal / panel / overlay) posée par les
+// redirections des anciennes routes, puis on l'efface pour garder l'URL propre.
+function applyRoute(): void {
+ if (route.name === 'workspace') {
+ const repoId = String(route.params.repoId ?? '');
+ const wt = decodeWtKey(String(route.params.wt ?? ''));
+ if (!repoId || !wt) return;
+ ide.setActivity('explorer');
+ ide.setActiveWorktree(repoId, wt);
+ if (!ide.expandedRepoIds.includes(repoId)) ide.toggleRepo(repoId);
+ if (!ide.expandedWtPaths.includes(wt)) ide.toggleWt(wt);
+ const file = route.query.file ? String(route.query.file) : null;
+ if (file) ide.openFile(repoId, wt, file);
+ return;
+ }
+ // Intention portée par le meta de la route (rétro-compat des anciens chemins) : ouvrir un
+ // terminal, activer un panneau, ou ouvrir un overlay. URL bookmarkable, aucun strip.
+ const m = route.meta;
+ if (m.terminal && route.params.id) ide.openTerminal(String(route.params.id));
+ if (m.panel === 'sessions' || m.panel === 'groups') ide.setActivity(m.panel);
+ if (m.overlay === 'settings') modals.open(SettingsOverlay);
+ else if (m.overlay === 'help') modals.open(HelpOverlay);
}
-onMounted(applyDeepLink);
-watch(() => route.fullPath, applyDeepLink);
+onMounted(applyRoute);
+watch(() => route.fullPath, applyRoute);
function prune(): void {
const live = new Set(sessions.sessions.filter((s) => s.live).map((s) => s.id));
diff --git a/packages/web/src/components/ide/ModalHost.vue b/packages/web/src/components/ide/ModalHost.vue
new file mode 100644
index 0000000..e157404
--- /dev/null
+++ b/packages/web/src/components/ide/ModalHost.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/packages/web/src/components/ide/ProjectTree.vue b/packages/web/src/components/ide/ProjectTree.vue
index 29502f1..6f3bd88 100644
--- a/packages/web/src/components/ide/ProjectTree.vue
+++ b/packages/web/src/components/ide/ProjectTree.vue
@@ -11,7 +11,7 @@
>
-