feat(sessions): masquer les sessions externes + terminal plein écran

Masquage persistant des sessions Claude découvertes (lancées hors Arboretum)
qui polluent la liste — calqué sur repos.hidden :
- table hidden_sessions (#9) clé claudeSessionId ; helpers DB
- DiscoveryService pose le champ additif SessionSummary.hidden (resume/fork
  restent possibles, survit à un re-scan)
- routes POST/DELETE /sessions/:id/hide, POST /sessions/hide-discovered
  (masse, un clic), GET /sessions?includeHidden=true
- UI : bouton Masquer par ligne, « Masquer l'historique externe », toggle
  « Afficher les masquées », badge masquée ; store + i18n EN/FR + audit

Terminal plein écran (corrige le sizing) :
- SessionView root s'étire (flex-1) → xterm calcule les bonnes dimensions
- refit robuste (requestAnimationFrame + après resync), h-full sur le wrapper
- bouton plein écran (API Fullscreen), tabbar mobile masquée en fullbleed

DialogPrompt : options en liste verticale pleine largeur (plus de troncature
ni de chevauchement sur les libellés longs / multi-lignes).

Doc : centre d'aide (sessions), README EN/FR, site (showcase), CLAUDE.md.
Tests : discovery (hidden) + route hide/unhide/hide-discovered.
This commit is contained in:
2026-06-22 16:02:27 +02:00
parent bf9c6ba8f3
commit dacff2b98f
22 changed files with 360 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div class="relative min-h-0 bg-[#09090b]">
<div class="relative h-full min-h-0 bg-[#09090b]">
<div ref="container" class="h-full w-full" />
<div
v-if="!controlling && !ended"
@@ -71,6 +71,19 @@ onMounted(async () => {
fit.fit();
const activeTerm = term;
// Re-mesure après stabilisation du layout flex : au `onMounted`, le conteneur peut ne pas avoir sa
// taille finale (la hauteur flex se résout après le 1er paint) → un seul `fit()` donne trop peu de
// lignes et déforme le TUI. On refait donc le calcul en `requestAnimationFrame`, puis on resynchronise
// les dimensions PTY. Sert aussi après un resync (reconnexion) où l'écran est remis à plat.
const refit = (): void => {
try {
fit.fit();
} catch {
/* conteneur sans dimension (démontage en cours) : ignoré */
}
attachment?.resize(activeTerm.cols, activeTerm.rows);
};
try {
attachment = await wsClient.attach({
sessionId: props.sessionId,
@@ -79,7 +92,10 @@ onMounted(async () => {
rows: activeTerm.rows,
sink: {
write: (data, callback) => activeTerm.write(data, callback),
reset: () => activeTerm.reset(),
reset: () => {
activeTerm.reset();
requestAnimationFrame(refit);
},
onDetached: (reason) => {
if (reason === 'session_exit' || reason === 'replaced') ended.value = true;
},
@@ -99,12 +115,10 @@ onMounted(async () => {
return;
}
controlling.value = attachment.controlling;
requestAnimationFrame(refit); // recale la taille une fois le layout fullbleed stabilisé
activeTerm.onData((data) => attachment?.sendStdin(data));
resizeObserver = new ResizeObserver(() => {
fit.fit();
attachment?.resize(activeTerm.cols, activeTerm.rows);
});
resizeObserver = new ResizeObserver(refit);
resizeObserver.observe(container.value);
if (props.mode === 'interactive') activeTerm.focus();
});