Files
arboretum/packages/site/src/components/WorkspaceShowcase.vue
T
johanleroy 63f2697745
CI / Build & test (Node 22) (push) Successful in 11m12s
CI / Build & test (Node 24) (push) Successful in 10m14s
CI / No em/en dashes (push) Successful in 3s
Deploy site (production) / build-and-deploy (push) Successful in 19s
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
release: git-arboretum 3.4.0 (visibilité temps réel, historisation), desktop 0.2.0 (Windows, logo), vscode 0.4.1, site 0.4.0
Tout est additif : PROTOCOL_VERSION inchangé, aucune rupture d'API.

Temps réel réellement armé
- `pinSession` n'était appelé nulle part : une session vivante épingle désormais le watcher FS de son
  worktree (`WorktreeManager.syncSessionPin` + `resolveWorktreeForCwd`), donc un worktree où un agent
  écrit se rafraîchit même si personne ne le regarde (mesuré ~350 ms).
- Les abonnements `watch` sortent de `GitPanel`, démonté dès qu'on quitte son onglet, ce qui coupait le
  seul abonnement de toute l'app : `composables/useWatchedWorktrees.ts` (monté dans App.vue) suit le
  worktree actif et les dépôts dépliés, borné à 40.
- Une coupure WS ne laisse plus l'UI sur des listes périmées : rechargement complet au retour.
- `worktree_changes` alimente `worktrees.changeVersion`, consommé par l'arbre de fichiers, le diff
  (son `:version` était câblé à 0) et l'éditeur, qui recharge un tampon propre ou lève la bannière de
  conflit avant la sauvegarde au lieu d'attendre le 409.

Corrélation session ↔ worktree par contenance (`@arboretum/shared/path-match.ts`)
- Un terminal lancé dans un sous-répertoire (« Démarrer le projet ») ou une session de groupe reliée
  par `--add-dir` apparaissent enfin sous leur worktree ; le worktree le plus spécifique gagne.
- Règle unique partagée par le daemon, le web et l'extension.

Historisation
- `commitLog` / `commitDiff` purs, `GET /repos/:id/worktrees/log` et `diff?commit=` (hash strictement
  validé, mêmes bornes que les diffs de fichiers).
- `CommitHistory.vue` sous le panneau Git : commits, marquage des non poussés, diff déplié sur place.

Visibilité
- Compteurs git complets sur chaque worktree de l'arbre et du panneau Groupes (ils n'existaient qu'en
  barre de statut, pour le seul worktree actif), avec upstream et dernier commit en infobulle ;
  `locked`, `prunable` et un dépôt invalide sont désormais visibles.
- Le panneau Groupes montre sa composition réelle (dépôts, worktrees, sessions) et teinte l'explorateur.

Polish visuel
- Les toasts d'erreur, persistants, s'empilaient derrière les modals : téléportés au-dessus.
- Sur mobile, ouvrir un terminal ou changer d'activité n'avait aucun effet visible.
- Tailles de panneaux clampées sur la fenêtre, barres d'onglets sans scrollbar parasite, états de
  chargement et d'erreur dans les trois panneaux, accessibilité des 11 modals centralisée dans
  ModalHost, splitters au clavier, numéros de diff collants, `window.confirm` remplacé.

Windows (daemon et packaging)
- `where.exe`, PowerShell comme shell de lancement, askpass `.cmd` (clone/push HTTPS par PAT),
  `taskkill /T`, `%APPDATA%`, `arboretum install` via tâche planifiée.
- Scripts de build exécutables sur un hôte Windows (`npm.cmd`, extraction sans `unzip` ni `bash`).
- Job CI `windows-latest` conditionné par ENABLE_WINDOWS_BUILD ; procédure runner dans docs/CI_RUNNERS.md.

Logo Debian : cause racine
- Une icône unique de 895×895 atterrissait dans `hicolor/895x895`, répertoire absent d'`index.theme`
  donc ignoré par la spécification freedesktop ; et `executableName` dérivait du nom scopé du paquet
  (`@arboretumdesktop`). Jeu d'icônes standard généré + `executableName: arboretum`, plus
  `deb.synopsis` (description courte vide dans apt) et `Section: devel`.
- Runtime Node embarqué élagué : 205 → 118 Mo.
- Auto-update réparé : la release flottante `desktop-latest` que les binaires interrogent n'existait pas.

Doc et vitrine
- README/README.fr : installation par plateforme, mode serveur web (nginx, LAN), dépannage, variables
  d'environnement, flags manquants.
- Doc in-app réécrite (elle renvoyait aux pages Worktrees et Sessions supprimées).
- Section « Accès distant » dans les Réglages ; le 403 BAD_ORIGIN nomme le flag à ajouter.
- Site : prérequis et registre npm privé (le `npx` affiché renvoyait un 404), téléchargements réels par
  plateforme, section « trois façons de l'utiliser », navigation complétée, 16 clés i18n mortes purgées.

Vérifications : 483 tests unitaires, 14 acceptances E2E vertes (dont p14/p15 nouvelles), captures de
rendu sans erreur console (nouveau `verify-ui.mjs`), .deb reconstruit et contrôlé (icônes aux tailles
standard, entrée .desktop valide).
2026-08-04 13:02:11 +02:00

187 lines
13 KiB
Vue

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
// Lignes du diff (fidèle a DiffViewer : fond teinté add/del, texte neutre).
type DiffLine = { o: string; n: string; type: 'ctx' | 'add' | 'del' | 'hunk'; text: string };
const diffLines: DiffLine[] = [
{ o: '', n: '', type: 'hunk', text: '@@ -12,7 +12,9 @@ export function session(' },
{ o: '12', n: '12', type: 'ctx', text: ' const claims = verify(req);' },
{ o: '13', n: '13', type: 'ctx', text: ' if (!claims) return null;' },
{ o: '14', n: '', type: 'del', text: ' return signLegacyCookie(claims);' },
{ o: '', n: '14', type: 'add', text: ' const token = sign(claims, { ttl: 900 });' },
{ o: '', n: '15', type: 'add', text: ' return refreshable(token);' },
{ o: '15', n: '16', type: 'ctx', text: '}' },
];
function rowClass(type: DiffLine['type']): string {
if (type === 'add') return 'diff-add';
if (type === 'del') return 'diff-del';
return 'diff-ctx';
}
function marker(type: DiffLine['type']): string {
return type === 'add' ? '+' : type === 'del' ? '-' : ' ';
}
</script>
<template>
<section id="workspace" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
<div v-reveal class="mb-[40px] max-w-[760px]">
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('wsKicker') }}</div>
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
{{ t('wsTitle') }}
</h2>
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('wsBody') }}</p>
</div>
<!-- maquette IDE multi-projet : barre d'activite, arbre unifie, onglets d'editeur, dock bas -->
<div v-reveal class="overflow-hidden rounded-[14px] border border-border bg-surface-0 shadow-card">
<!-- barre superieure -->
<header class="flex items-center gap-3 border-b border-border px-3 py-2">
<span class="flex items-center gap-2 text-sm font-medium text-fg">
<img src="/assets/arboretum-mark.png" alt="" width="16" height="16" class="h-4 w-4 object-contain" />
Arboretum
</span>
<span class="ml-auto flex items-center gap-1.5 text-[11px] text-fg-subtle">
<span class="h-1.5 w-1.5 rounded-full bg-warn"></span>{{ t('waiting') }}
</span>
</header>
<div class="flex">
<!-- barre d'activite -->
<nav class="flex w-11 flex-none flex-col items-center gap-1 border-r border-border bg-surface-1 py-2 text-fg-subtle">
<span class="relative flex h-9 w-9 items-center justify-center rounded-lg bg-surface-2 text-accent" :title="t('wsFiles')">
<span class="absolute top-1/2 left-0 h-5 w-0.5 -translate-y-1/2 rounded-full bg-accent"></span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 14 3h-3a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /><path d="M3 5a2 2 0 0 0 2 2h3" /><path d="M3 3v13a2 2 0 0 0 2 2h3" /></svg>
</span>
<span class="flex h-9 w-9 items-center justify-center rounded-lg" :title="t('wsChanges')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M13 6h3a2 2 0 0 1 2 2v7" /><path d="M11 18H8a2 2 0 0 1-2-2V9" /></svg>
</span>
<span class="flex h-9 w-9 items-center justify-center rounded-lg" :title="t('wsTerminal')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" /></svg>
</span>
<!-- 4e onglet : Groupes (l'app en a quatre, le mockup n'en montrait que trois) -->
<span class="flex h-9 w-9 items-center justify-center rounded-lg" :title="t('wsGroups')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7.5 4.27 9 5.15" /><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" /><path d="m3.3 7 8.7 5 8.7-5" /><path d="M12 22V12" /></svg>
</span>
</nav>
<!-- arbre unifie : plusieurs projets a la fois -->
<aside class="hidden w-[248px] flex-none flex-col border-r border-border md:flex">
<div class="label-mono px-3 py-2">Projects</div>
<div class="px-1 pb-2 font-mono text-xs">
<!-- projet api (ouvert) -->
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
<span class="font-medium">api</span>
</div>
<!-- worktree feat/auth (actif) -->
<div class="flex items-center gap-1 rounded bg-surface-2 py-0.5 pr-2 pl-[18px] text-fg">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
<span class="truncate text-accent">feat/auth</span>
<!-- compteurs git compacts : ahead / indexés / non indexés, comme dans l'app -->
<span class="ml-auto flex shrink-0 items-center gap-1 text-[11px]">
<span class="text-accent">↑2</span>
<span class="text-accent">●1</span>
<span class="text-warn">○1</span>
</span>
</div>
<!-- session claude corrélée -->
<div class="flex items-center gap-1.5 rounded py-0.5 pr-2 pl-[38px] text-fg-muted">
<span class="inline-flex items-center gap-1 rounded-full bg-info/15 px-1.5 py-[1px] text-[10px] text-info"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>{{ t('busy') }}</span>
<span class="truncate">claude</span>
</div>
<!-- fichiers du worktree -->
<div class="flex items-center justify-between gap-1 rounded bg-surface-2 py-0.5 pr-2 pl-[30px] text-fg">
<span class="truncate">session.ts</span><span class="font-bold text-warn">M</span>
</div>
<div class="flex items-center justify-between gap-1 rounded py-0.5 pr-2 pl-[30px] text-fg-muted">
<span class="truncate">token.ts</span><span class="font-bold text-accent">A</span>
</div>
<!-- projet web (replie) -->
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg-muted">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
<span class="font-medium">web</span>
<span class="ml-auto shrink-0 text-[11px] text-warn">○3</span>
</div>
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg-muted">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
<span class="font-medium">docs</span>
</div>
</div>
</aside>
<!-- centre : onglets d'editeur + diff + dock terminaux -->
<main class="flex min-w-0 flex-1 flex-col">
<!-- onglets d'editeur (plusieurs fichiers, potentiellement de projets differents) -->
<div class="flex items-stretch overflow-x-auto border-b border-border text-xs">
<span class="flex items-center gap-1.5 border-r border-border bg-surface-0 px-3 py-1.5 text-fg">
session.ts <span class="h-1.5 w-1.5 rounded-full bg-warn"></span>
</span>
<span class="flex items-center gap-1.5 border-r border-border px-3 py-1.5 text-fg-muted">token.ts</span>
<span class="flex items-center gap-1.5 border-r border-border px-3 py-1.5 text-fg-subtle">app.vue</span>
</div>
<!-- barre : bascule Editor/Diff + chemin -->
<div class="flex items-center gap-2 border-b border-border px-2 py-1">
<div class="inline-flex rounded-lg border border-border bg-surface-0/40 p-0.5">
<span class="rounded-[9px] px-2.5 py-1 text-xs font-medium text-fg-muted">{{ t('wsEditor') }}</span>
<span class="rounded-[9px] bg-surface-2 px-2.5 py-1 text-xs font-medium text-fg">{{ t('wsDiff') }}</span>
</div>
<span class="truncate font-mono text-[11px] text-fg-subtle">api · src/core/session.ts</span>
<span class="ml-auto inline-flex items-center gap-1.5 rounded-[9px] bg-accent-solid px-2.5 py-1 text-xs font-semibold text-white">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
{{ t('wsCommitBtn') }}
</span>
</div>
<!-- diff (fidele a DiffViewer) -->
<div class="overflow-x-auto">
<table class="w-full border-collapse font-mono text-xs leading-5">
<tbody>
<tr v-for="(l, i) in diffLines" :key="i" :class="l.type === 'hunk' ? 'bg-surface-1/60 text-info/80' : rowClass(l.type)">
<td class="w-10 px-2 text-right text-fg-subtle select-none">{{ l.o }}</td>
<td class="w-10 px-2 text-right text-fg-subtle select-none">{{ l.n }}</td>
<td class="px-2 whitespace-pre">{{ l.type === 'hunk' ? l.text : marker(l.type) + l.text }}</td>
</tr>
</tbody>
</table>
</div>
<!-- dock terminaux en bas (onglets par session) -->
<div class="border-t border-border">
<div class="flex items-stretch border-b border-border text-[11px]">
<span class="label-mono flex items-center gap-1 px-2 py-1.5">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" /></svg>
{{ t('wsTerminal') }}
</span>
<span class="flex items-center gap-1.5 border-l border-border bg-surface-0 px-2.5 py-1.5 font-mono text-fg">
<span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>api · feat/auth
</span>
<span class="flex items-center gap-1.5 border-l border-border px-2.5 py-1.5 font-mono text-fg-muted">web · main</span>
</div>
<div class="min-h-[112px] p-[11px] font-mono text-[11px] leading-[1.7] text-fg-muted">
<div class="text-accent">● Edit session.ts</div>
<div class="text-accent">+ const token = sign(...)</div>
<div class="text-fg-subtle">running tests…</div>
<div class="text-accent">✓ 24 passed</div>
<div><span class="text-accent">$</span><span class="ml-1 inline-block h-[11px] w-1.5 animate-blink bg-fg align-middle"></span></div>
</div>
</div>
</main>
</div>
<!-- barre de statut -->
<footer class="flex items-center gap-3 border-t border-border px-3 py-1 text-[11px] text-fg-subtle">
<span class="flex items-center gap-1 font-mono text-fg-muted">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
feat/auth
</span>
<span class="text-accent">↑2</span>
<span class="text-warn">○3</span>
<span class="ml-auto">api · {{ t('busy') }}</span>
</footer>
</div>
</section>
</template>