Commit Graph
26 Commits
Author SHA1 Message Date
johanleroy 8aea0ae32d fix(server, web, vscode): les terminaux ne restent plus noirs, panes côte à côte, git scopé au terminal
Un terminal pouvait rester tout noir alors que sa session tournait. Le PTY était
vivant et avait bien écrit sa sortie : la panne était dans le transport. Le replay
d'attache est une frame BINAIRE, mais un client n'apprend son numéro de canal
qu'avec le message `attached` ; le serveur envoyait le replay AVANT, donc tout
client le jetait sur un canal inconnu. Rien n'était peint, et un TUI au repos
(Claude à son prompt) ne réémet jamais rien de lui-même. `attach()` renvoie
désormais le replay et la gateway l'émet APRÈS `attached` : un seul correctif
serveur répare le web, l'app de bureau et l'extension VS Code, qui portaient le
même défaut client. Le resize de l'attache masquait le bug en provoquant un
SIGWINCH, d'où son apparence intermittente.

Seconde moitié du symptôme (« je tape et rien ne se passe ») : le dock montait
avant la liste des sessions, en déduisait « non attachable » et s'attachait en
observateur, à vie et en silence. Un pane n'attache plus avant de connaître sa
session (`sessions.loaded`).

Attaches sans écran : le message `attach` accepte un `screen` optionnel (défaut
true). Un client qui n'affiche rien et veut seulement répondre à un dialogue ne
prend plus le contrôle de la session, ne lui impose plus ses dimensions (ce qui
figeait la géométrie du vrai terminal) et ne reçoit plus le flux pour le jeter.

Rendre les pannes visibles : la raison d'un exit est écrite dans le terminal
(`[arboretum] bash exited with code 3`) avant le détachement ; un repaint est
forcé si rien n'arrive 1,2 s après l'attache, puis annoncé avec « Refresh
screen » ; les refus de canal remontent à l'écran au lieu d'un console.warn ; le
chemin du CLI claude est revalidé (périmé après une bascule nvm/asdf, le PTY
mourait sans un octet).

Colonnes de terminaux : le dock devient une rangée de colonnes redimensionnables
(3 max), chacune avec ses onglets. Algèbre pure dans lib/dock-model.ts, cinq
invariants documentés, ratios plutôt que pixels. `dockSessionIds` et
`activeDockSessionId` deviennent des computed dérivés : aucun consommateur ni
test existant ne change. Alt+clic ouvre à côté depuis les quatre panneaux. Le
plafond de hauteur du dock suit le viewport au lieu d'un 640 px figé. Correctif
préexistant au passage : PanelSplitter passait ses bornes par valeur, figées au
premier rendu, alors que le clavier les relisait.

Portée git : la vue Changements suit le worktree du terminal focalisé, ou tous
les dépôts de son groupe pour une session de groupe, avec « tout voir » à un
clic. L'index Git de la sidebar reste global (c'est la sortie d'une portée
étroite) et le badge d'activité aussi (il sert à signaler le travail qu'on ne
regarde pas). Seul le TERMINAL impose le contexte : le repli sur l'onglet
éditeur, essayé d'abord, rétrécissait la vue multi-projet dès qu'on ouvrait un
fichier.

Vérifications : acceptance-p17.mjs prouve l'ordre des trames sur un vrai
WebSocket (avec l'ancien ordre : 0 octet rejoué, échec), verify-terminals.mjs
prouve par interaction réelle que le terminal peint, que deux colonnes
coexistent, que la frappe atteint le bon PTY (fichier témoin par cwd) et que la
vue suit le terminal.
2026-08-05 10:56:42 +02:00
johanleroy ce224fd470 fix(web): les fichiers s'ouvrent enfin, et le travail git par projet au centre
Deux défauts d'usage de la zone centrale de l'IDE.

1. Ouvrir un fichier affichait une page vide. Le conteneur de l'unique instance
   Monaco vivait sous le `v-if="activeTab"` : monté sans onglet actif (premier
   usage, ou après avoir tout fermé), `onMounted` sortait en silence sur
   `!host.value`, `ready` restait faux pour toujours, et les deux watchers
   d'affichage étaient gardés par lui. Le conteneur est désormais toujours monté
   (v-show, jamais v-if : sous un v-if, fermer le dernier onglet détruisait le
   DOM et l'éditeur gardait une référence sur un noeud détaché) et l'éditeur est
   créé paresseusement depuis `showTab`, devenu l'unique entonnoir d'affichage.
   `useMonaco` ne mémoïse plus un échec, l'attente et l'erreur sont visibles, et
   la relecture disque ne se tait plus. Sur mobile, ouvrir un fichier amène enfin
   la zone centrale au premier plan et la valeur morte `mobilePanel = 'sidebar'`
   disparaît (migrée).

   Trouvé par l'E2E : seul l'`editor.worker` était fourni, donc ouvrir un `.ts`
   lançait le service TypeScript contre un worker qui n'en connaissait pas les
   méthodes, et jetait un rejet non géré à chaque ouverture. Les workers de
   langage sont fournis (chunks à la demande), diagnostics sémantiques coupés
   (sans tsconfig ni node_modules, ils inventeraient des erreurs).

2. Le statut git était à l'étroit et mono-projet. La zone centrale porte un mode
   « Fichiers / Changements » (bascule à icônes dans la barre d'onglets, donc
   accessible même sans onglet ouvert). La vue Changements montre un bloc par
   worktree pour TOUS les projets ouverts : fichiers indexés et non indexés, diff
   déplié sur place, commit, push, fetch, pull et historique par bloc. Le panneau
   Git de la barre latérale devient l'index (compteurs déjà diffusés par WS, donc
   zéro requête) et le badge de la barre d'activité, codé à 0, compte enfin les
   worktrees à traiter.

   Au passage : `canPush` était faux dans les deux sens (actif sans rien à
   pousser, bloqué en étant seulement en retard), l'amend d'un message seul est
   accepté comme le serveur le permettait déjà, le rebase est proposé dès qu'on
   est en retard, une confirmation d'abandon ne survit plus au rechargement de la
   liste, et `commit`/`fetch`/`push` émettent enfin `worktree_changes` (le
   compteur « n non poussés » restait périmé juste après un push).

Vérifié : 528 tests, acceptations P7/P9/P14/P15, `verify-ui` (4 captures
nouvelles) et un `verify-editor` qui prouve par clics réels l'ouverture d'un
fichier sans onglet persisté, la fermeture/réouverture, la bascule mobile et le
chunk éditeur absent. Ce dernier échoue bien si l'on remet le conteneur sous un
v-if.
2026-08-04 23:18:40 +02:00
johanleroy bde5358ea8 release: git-arboretum 3.5.0 (cache SPA, copier/coller terminal, fichiers des groupes), desktop 0.2.1
CI / No em/en dashes (push) Successful in 3s
CI / Build & test (Node 24) (push) Successful in 10m50s
CI / Build & test (Node 22) (push) Successful in 11m2s
Release / Publish to Gitea npm registry (push) Successful in 10m56s
Desktop Release / Build Linux (AppImage + deb) (push) Successful in 13m21s
Desktop Release / Publish floating desktop-latest release (push) Successful in 11s
CI / Pack & boot smoke (Node 22) (push) Successful in 9m53s
Desktop Release / Build Windows (NSIS + portable) (push) Canceled after 0s
Cause racine de l'ecran noir apres mise a jour : l'etag faible de @fastify/static derive de
taille+mtime, et npm pack fige le mtime de tout le tarball a une date constante (1985-10-26). Deux
index.html de versions differentes mais de meme taille partageaient donc le meme etag : le client
recevait un 304, gardait son index perime, et demandait des /assets/<hash> disparus ; le fallback SPA
repondait index.html en text/html pour ces modules, le navigateur refusait le script, rien ne peignait.

- fix(server): index.html et tous les fichiers non haches servis en no-store, validation
  conditionnelle desactivee (etag/lastModified) pour qu'un client bloque sur un index perime se
  repare seul ; /assets/ (noms haches) passent en immutable un an
- feat(web): copier/coller dans le terminal xterm, dont la selection n'est pas une selection DOM :
  Ctrl+Maj+C / Ctrl+Maj+V, Cmd+C / Cmd+V sur macOS, Ctrl+Inser / Maj+Inser, plus interception de
  l'evenement DOM copy pour que le Copier natif fonctionne. Ctrl+C reste SIGINT
- fix(web): script anti-FOUC sorti dans /theme-boot.js, la CSP script-src 'self' du daemon refusait
  de l'executer inline (le theme n'etait donc pose qu'au montage de la SPA)
- feat(web): les worktrees d'un groupe se deplient sur leur arborescence de fichiers dans le panneau
  Groupes (meme composant et meme etat d'expansion que l'Explorateur), et un worktree ainsi deplie
  est desormais surveille en temps reel
- fix: octet nul litteral remplace par \0 dans trois sources (stores/ide.ts, GitPanel.vue,
  vscode/repos-tree.ts) : git et grep les traitaient comme binaires, leurs diffs etaient
  illisibles en revue et la garde CI lint-dashes (git grep -I) les sautait en silence
- test: cacheControlFor et clipboardIntent en tests purs, verify-clipboard.mjs (E2E Chromium CDP :
  copie, collage et SIGINT prouves par le presse-papier reel et par le systeme de fichiers),
  capture groups-dark-desktop ajoutee a verify-ui.mjs
2026-08-04 16:23:33 +02:00
johanleroy 63f2697745 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
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
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
johanleroy a7e04278fd release: git-arboretum 3.3.0 (« Démarrer le projet » : lancement multi-terminaux), vscode 0.4.0, desktop 0.1.3
CI / Build & test (Node 22) (push) Successful in 10m17s
CI / Build & test (Node 24) (push) Successful in 10m13s
CI / No em/en dashes (push) Successful in 4s
Deploy site (production) / build-and-deploy (push) Successful in 24s
Release / Publish to Gitea npm registry (push) Successful in 10m17s
VSCode Release / Package VSIX (push) Successful in 9m39s
Desktop Release / Build Linux (AppImage + deb) (push) Successful in 15m41s
CI / Pack & boot smoke (Node 22) (push) Successful in 10m3s
« Démarrer le projet » : un repo définit une fois ses commandes de démarrage
(serveur de dev, API, base de données), un clic ouvre un terminal PTY par
commande dans le dock IDE.

Serveur (additif, PROTOCOL_VERSION inchangé) :
- LaunchCommand[] persistées sur repos.launch_commands (migration 13) ; champ additif SessionSummary.launchRunId.
- POST /repos/:id/launch : résolution du worktree côté serveur, cwd de commande borné (anti-traversal), commandIds outrepasse enabled.
- GET /repos/:id/launch/detect : détection package.json / Procfile / docker-compose.
- Shell de login interactif ($SHELL -l -i, charge le PATH nvm/asdf) + auto-type de la commande ; le shell survit à la commande (échec visible).

Web : LaunchProjectModal + actions (ProjectTreeNode, SessionsPanel, CommandPalette), stores sessions/worktrees, i18n EN/FR.

Alignement du reste du projet :
- Extension VS Code 0.4.0 : commande Start Project (repo/worktree), Stop Launch, badge « launch » dans l'arbre, méthode REST startLaunch.
- Site vitrine : 16e feature card (Rocket) + section showcase « Start the project » (mockup fidèle au modal), i18n EN/FR.
- Documentation : README (EN + FR), help-content (EN + FR), CHANGELOGs server + vscode.

Vérifié : 430 tests, typecheck, build (web + site + vscode), acceptance-p13 ALL GREEN, VSIX packagé, garde anti-tirets, vérif visuelle du site (thèmes clair et sombre).
2026-07-21 13:54:16 +02:00
johanleroy 65ef616867 chore(typo): retire tous les tirets cadratins/demi-cadratins + garde CI
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
Deploy site (production) / build-and-deploy (push) Successful in 20s
Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts).

Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
2026-07-17 16:44:00 +02:00
johanleroy 08695a707d feat(p12a): services git distants (PAT/app_password) + clone HTTPS
Modèle de données :
- migration #11 git_credentials (secrets chiffrés SecretBox : secret_encrypted ; colonnes ssh/oauth posées pour P12b/P12c) ; #12 repos ALTER remote_url/git_service/credential_id (pas de FK)
- types partagés api.ts (GitCredentialSummary sans secret + hasSecret/secretLast4, CRUD, RemoteRepoSummary, Clone*) ; protocole additif : topic 'clones' + message clone_update (CloneOperation)

Backend :
- core/git-credentials.ts (GitCredentialsManager(db, box)) : CRUD chiffré, test() (GET /user), getSecret()/authContext() internes, NULLification de repos.credential_id à la suppression
- core/git-clients/ (github/gitlab/gitea) via fetch, sans dépendance : verify()+listRepos() paginés, erreurs typées AUTH_FAILED/RATE_LIMITED/UNREACHABLE, SSRF base_url http(s)
- core/git-auth.ts : withGitAuth (GIT_ASKPASS éphémère 0o700, secret par env, GIT_TERMINAL_PROMPT=0, jamais dans l'URL/.git/config, nettoyage finally)
- core/git.ts cloneRepo (spawn git clone --progress, parse progression, timeout)
- core/clone-manager.ts (EventEmitter) : clone async, dest confiné sous scanRoots + non existant, auto-enregistrement via addRepo + métadonnées de provenance, nettoyage du clone partiel, events topic 'clones'
- routes/git-connections.ts (CRUD + /test + /:id/repos + POST /repos/clone 202 + GET /repos/clone/:id) ; app.ts câble box→GitCredentialsManager + CloneManager→gateway ; gateway relaie 'clones'

Frontend :
- ws-client subscribeClones ; stores git-connections + clone (suivi WS)
- components/settings/GitConnectionsSection (liste + formulaire pat/app_password, secret jamais ré-affiché) inséré dans SettingsView ; CloneRepoModal (connexion → repos distants paginés → dest scanRoots[0] → barre de progression WS → redirection) ; bouton « Cloner » dans DashboardView ; i18n EN+FR

Tests : git-credentials (round-trip SecretBox, résumé sans secret, NULLification) ; acceptance-p12.mjs (clone bare local file:// → clone_update done + repo enregistré + secret ABSENT de l'API, de la DB et du .git/config)

Sous-phases restantes : P12b (SSH), P12c (OAuth device flow).
2026-06-27 14:27:18 +02:00
johanleroy e8d10b7ec0 feat(p11): temps réel complet (watcher checkout principal + topic settings)
P11-A — branche du checkout principal modifiée hors Arboretum :
- fs-watcher: pin « permanent » non évinçable (pinRepo/unpinRepo, repoPins dans evictIfNeeded)
- worktree-manager: armMainCheckoutWatchers() + arm/désarm sur addRepo/découverte/removeRepo/hidden
- index.ts: armMainCheckoutWatchers() dans runDaemon → git checkout CLI sur le principal → worktree_update <500ms sans watch client

P11-B — réglages en temps réel :
- protocole additif: type SettingsBroadcast (source unique, réutilisé par SettingsResponse), topic 'settings', message settings_update (validés parseClientMessage + gateway)
- core/settings-bus.ts (EventEmitter) ; routes/settings émet après PATCH ; gateway relaie aux abonnés 'settings'
- web: ws-client subscribeSettings + routage ; store settings applySettings/startRealtime ; AppShell abonne globalement ; SettingsView re-sync des drafts scalaires sans écraser une saisie en cours
- tests: protocol (topic settings) + fs-watcher (pinRepo non évincé) ; acceptance-p11.mjs (checkout principal <500ms + settings_update)
2026-06-27 14:08:44 +02:00
johanleroy 92670a796a feat(p9): commit/push avancé dans l'IDE (staging sélectif, discard, amend, fetch/pull)
- components/workspace/CommitPanel.vue : staging sélectif par fichier (stage/unstage/stage all/unstage all), discard avec confirmation (includeUntracked), zone de commit (message + amend), commit mode 'staged', push, fetch, pull --ff-only (rebase proposé en cas de divergence), gestion 409 ALREADY_PUSHED
- components/workspace/FileRow.vue : ligne de fichier réutilisable (badge statut M/A/D/?/U + clic → diff + slot actions)
- WorkspaceView : CommitPanel remplace ChangedFilesPanel ; @changed → reload des changements
- stores/worktrees : applyWorktree (upsert depuis les mutations git → header live)
- i18n EN+FR (commit.*) ; acceptance-p9.mjs (staging sélectif, amend OK/409, pull ff-only OK + divergence refusée)
2026-06-27 13:53:05 +02:00
johanleroy 75efecf93f feat(p8): vue IDE /workspace (arbre + Monaco + diff + terminal)
- backend: garde-fou conflit d'édition par mtime (GET renvoie mtime, PUT refuse en 409 STALE_FILE si baseMtime périmé)
- lib: wt-key (base64url du chemin worktree), diff-parse (parseur pur du diff unifié → hunks)
- composables: useMonaco (import dynamique → chunk isolé), useWorkspaceLayout (largeurs/panneau persistés)
- components/workspace: GitStatusBadge, FileTree+FileTreeNode (lazy fs/list includeFiles), ChangedFilesPanel, DiffViewer (coloration +/-), MonacoEditor (Ctrl+S + bannière conflit reload/overwrite)
- views/WorkspaceView: 3 colonnes desktop redimensionnables + SegmentedControl mobile, header live (GitStatusBadge), watchWorktree → refresh diff/changements, terminal de la session corrélée
- router meta 'ide' + route /workspace/:repoId/:wt ; App.vue plein écran sans AppShell ; WorktreeCard bouton « Ouvrir l'IDE »
- deps: monaco-editor (lazy, vendor-monaco isolé, 0 impact bundle initial) ; vite manualChunks
- i18n EN+FR (workspace/editor/diff/git) ; tests diff-parse + wt-key ; acceptance-p8.mjs (mtime/409 + diff)
2026-06-27 13:47:10 +02:00
johanleroy c8dd539571 feat(p10): archivage automatique des sessions terminées (rétention configurable)
- DB: migration #10 (sessions.archived_at + index), helpers archive/unarchive/archiveExpired (soft-archive, jamais de DELETE)
- core/retention-settings.ts: session_retention_days (défaut 30, 0=jamais, 1-3650) + session_purge_days (off)
- core/session-archive.ts: scheduler start/stop/sweep (calqué DiscoveryService), câblé dans runDaemon
- pty-manager.list({includeArchived}) + SessionSummary.archived + emitHistoricalUpdate
- routes: GET /sessions?includeArchived, POST/DELETE /sessions/:id/archive, POST /sessions/archive-now
- protocole additif: message WS session_archived (relayé par la gateway, abonnés 'sessions')
- settings: retentionDays/purgeDays exposés et validés dans PATCH /settings
- web: store sessions (showArchived + archive/unarchive), SessionsListView (toggle/badge/actions), SettingsView (slider rétention), i18n EN+FR
- tests: retention-settings + session-archive (vitest) + settings-routes étendu ; acceptance-p10.mjs (sweep, event WS, resume d'une session archivée → 201, rétention=0)
2026-06-27 13:31:28 +02:00
johanleroy be43911dc0 feat(p7): moteur git détaillé, API fichiers et watcher FS temps réel
Fondations de la refonte « vrai IDE IA worktree » (chantier P7→P12).

- git.ts : listChanges (status --porcelain=v2 -z + --numstat fusionnés),
  fileDiff (diff unifié borné, refus binaire, untracked via --no-index),
  stage/unstage/restore/clean, commitStaged, amendCommit (refus si poussé),
  fetchRemote, pull (ff-only|rebase), lastCommit, resolveGitDir,
  isSafeRelativePath ; worktreeStatus enrichi (compteurs staged/unstaged/
  conflict + dernier commit), champs additifs WorktreeGitStatus.
- core/fs-watcher.ts : FsWatcherService (chokidar, refcount + pool LRU borné,
  ignore .git sauf HEAD/index, débounce) → invalide factsCache, rediffuse
  worktree_update et émet worktree_changes.
- WorktreeManager : getWorktreeChanges/getFileDiff/stage/unstage/discard/
  fetch/pull/watch/unwatch/assertPathInWorktree ; commitWorktree { mode, amend }.
- routes/git.ts (changes/diff/stage/unstage/discard/fetch/pull) et
  routes/files.ts (GET/PUT contenu fichier, bornage strict au worktree) ;
  fs/list?includeFiles=1 (flag isFile).
- protocole ADDITIF (PROTOCOL_VERSION inchangé) : messages WS watch/unwatch
  (validés dans parseClientMessage) + worktree_changes poussé ciblé par la
  gateway ; front wsClient.watchWorktree + lib/git-api.ts + api.put.
- tests git/fs-watcher/protocole + acceptance-p7.mjs (ALL GREEN).
- dépendance : chokidar (serveur).
2026-06-27 12:15:00 +02:00
johanleroy f4fb6c3b52 feat(worktrees): création intelligente (auto) + commit/push/promotion + grille agrandissable
- addWorktree résout la branche par dépôt (branchExists) : checkout si présente, suivi de origin/<b> si seulement remote, sinon création (-b) depuis la branche par défaut. Corrige le 'fatal: invalid reference' sur les groupes hétérogènes ; newBranch -> mode (déprécié, mappé en route).
- Nouvelles opérations git : commit (add -A), push (upstream auto), promotion « en principal » (la branche du worktree devient le checkout principal, sans merge ni conflit, ancienne branche conservée). Routes /worktrees/{commit,push,promote} auditées + GET /repos/:id/branches.
- Web : actions Commit/Push/Promouvoir sur WorktreeCard, sélecteur de branche de base (datalist) dans RepoSection et GroupSessionModal, agrandissement d'un terminal au choix dans TerminalGrid.
- Court-circuite la session de groupe quand aucun worktree n'a pu être créé (fini le masquage par « no resolvable worktree »).
- Tests : git.ts (branchExists/listBranches/commitAll/auto), worktree-manager (commit/promote/branches) ; acceptance P5 étend le scénario worktree de groupe sur branche neuve + commit + promotion.
2026-06-22 19:55:45 +02:00
johanleroy 1b57c3801c feat(groups): session de groupe au parent commun + redirection vers le terminal
Le cwd d'une session de groupe devient le PARENT COMMUN des répertoires
couverts (racine neutre, chemins relatifs cross-repo naturels) au lieu du
premier repo arbitraire ; chaque repo reste relié en --add-dir. Garde-fou :
si le parent commun est la racine du FS, on retombe sur le premier répertoire.
Nouveau module pur resolveGroupCwd (core/group-session.ts). Côté web, un
lancement réussi redirige vers le terminal plein écran de la session, avec
un toast (succès ou couverture partielle).
2026-06-22 14:41:15 +02:00
johanleroy 4e3b1f5604 fix(sessions): reprendre/forker une session managée morte par UUID (résout le 404)
Le bouton « Reprendre » envoie l'UUID Arboretum, mais resume/fork ne
résolvaient l'id que via la découverte (indexée par claudeSessionId, qui
exclut justement les sessions managées) → 404 systématique après fermeture.
Ajoute PtyManager.resumeTargetById (cwd d'origine + claudeSessionId + groupe
lus en DB par UUID) et un résolveur unifié dans resume ET fork : session
managée morte par UUID, sinon session claude externe découverte par
claudeSessionId. Garde-fous anti-resume d'une session vivante conservés.
2026-06-22 14:41:06 +02:00
johanleroy 915fdf185d feat(worktrees): démarrer une session sur la branche principale ou tout worktree
Ajoute le choix « créer un worktree OU bosser sur la branche principale » au
moment de démarrer le travail, sur deux surfaces :
- bouton « Démarrer » (claude/bash) sur chaque WorktreeCard, y compris le
  checkout principal (badge « principal ») — comblait un manque : impossible
  jusqu'ici de lancer une session sur un worktree existant depuis le dashboard ;
- toggle de mode (Worktree / Branche principale) dans le formulaire d'entête
  de RepoSection.

Option git « créer & basculer » : nouvel endpoint POST /api/v1/repos/:id/session
qui lance UNE session dans le checkout principal (repo.path), avec création ou
bascule de branche optionnelle (git switch[-c]) — refusée en 409 si l'arbre est
sale. Pas de hooks ni de pré-trust (c'est le dépôt réel de l'utilisateur).

- shared : type additif StartRepoSessionRequest.
- server : git.switchBranch + WorktreeManager.startMainSession + route.
- web : store worktrees.startMainSession, UI WorktreeCard/RepoSection, i18n fr/en.
- tests : +5 unitaires (switchBranch, startMainSession) ; acceptance-p3 étendue.
2026-06-22 11:30:40 +02:00
johanleroy c6ea9f7930 feat(groups): session Claude unique par groupe via --add-dir (P6)
Deploy site (production) / build-and-deploy (push) Successful in 21s
Un groupe lance désormais UNE seule session Claude couvrant tous ses
repos/worktrees (--add-dir) au lieu d'une session par repo, pour
travailler en simultané dans une conversation partagée. Le transport WS,
le flow control et l'attach sont inchangés (toujours 1 session, 1 channel).

- shared: SessionSummary.addedDirs/groupId (champs additifs optionnels) ;
  CreateGroupSessionRequest / GroupSessionResponse.
- server: --add-dir dans claude-launcher ; spawn({addDirs,groupId}) +
  persistance (migration #8 : sessions.added_dirs/group_id) ;
  POST /api/v1/groups/:id/session (résolution des dirs cote serveur) ;
  resume re-relie les dirs ; deleteGroup nullifie group_id.
- web: GroupSessionModal (modes feature / checkouts principaux) ;
  createGroupFeature/createGroupSession ; badge « groupe · N depots » ;
  sessionsInGroup dedupliquee.
- site: textes EN/FR « une seule session Claude » + diagramme hub.
- tests: claude-launcher, pty-manager (groupe), group-manager ;
  acceptation P5 etendue (session de groupe multi-repo). 272 tests verts.
2026-06-22 10:06:23 +02:00
johanleroy 073f2cf9ca fix(packaging): inline @arboretum/shared dans dist/ — tarball autonome (abandon de bundleDependencies)
bundleDependencies d'un paquet *workspace* est instable selon l'environnement npm
(mode -w, exécution en root sur un runner CI) : npm voyait shared comme un lien
workspace et n'embarquait AUCUN fichier ("bundled files: 0"), publiant un tarball
cassé — `Cannot find module '@arboretum/shared'` au démarrage chez le consommateur.
C'est ce que détectait (à juste titre) le job pack-smoke.

Nouveau mécanisme, identique sur tout environnement : scripts/inline-shared.mjs
(hook prepack) copie le JS compilé de shared dans dist/_shared/ et réécrit l'import
bare '@arboretum/shared' des .js du serveur vers ce chemin relatif. shared passe en
devDependency (résolu en dev via le symlink workspace, jamais exigé du consommateur).
Plus de node_modules embarqué ni de symlink dans le tarball.

ci.yml (pack-smoke) packe désormais en -w (comme release.yml) et asserte l'autonomie
(dist/_shared présent + zéro import bare) avant le boot smoke.

Validé de bout en bout sur checkout propre : npm ci + build + pack -w + install dans
projet vierge + boot -> HTTP 401, et 263 tests verts.
2026-06-19 09:46:05 +02:00
johanleroy fe2a3e66c7 feat: découverte automatique des dépôts git (scan + montrer/cacher)
Scan borné du système de fichiers (racines configurables, défaut home ; profondeur/nombre/timeout bornés ; symlinks non suivis ; exclusions node_modules/dotdirs) qui auto-enregistre les nouveaux dépôts. Insertion atomique ON CONFLICT DO NOTHING (idempotence + anti-résurrection d'un dépôt masqué + anti-course). Scan au démarrage + bouton manuel + re-scan périodique (RepoDiscoveryService, démarré dans runDaemon). Colonne repos.hidden : masquer = conservé en DB mais exclu du dashboard et jamais ré-ajouté ; supprimer = re-découvrable. UI : bouton œil par dépôt + bascule afficher-les-masqués sur le dashboard, section Découverte dans les Réglages (racines + intervalle, allow-list stricte). Robustesse : listAllWorktrees tolère l'échec git par dépôt ; flag --no-discover (escape hatch + hermétisme des acceptations).
2026-06-18 14:45:12 +02:00
johanleroy e38b3a5d5e feat: groupes de travail (P5)
Ajoute la notion de groupe — collection nommée de repos (membership légère
persistée, many-to-many) — pour piloter plusieurs repos en simultané :

- DB : migration #5 (tables groups + group_repos, FK ON DELETE CASCADE).
- GroupManager (synchrone, EventEmitter) + routes REST /api/v1/groups ;
  ne persiste que la membership, worktrees/sessions filtrés par repoId côté client.
- Protocole : GroupSummary, DTOs, topic WS « groups », events group_update/removed
  (additifs, PROTOCOL_VERSION inchangé).
- Web : store groups, GroupsListView, GroupView (réutilise RepoSection),
  grille multi-terminaux (TerminalGrid/TerminalCell), action « feature cross-repo »
  (orchestration client, tolérante aux échecs partiels), routes + i18n EN/FR.
- Tests : group-manager.test.ts + extension protocol.test.ts ; acceptance-p5.mjs.
2026-06-18 10:03:19 +02:00
johanleroy 11c45ff060 release: @johanleroy/git-arboretum 1.0.1
Release / Publish to Gitea npm registry (push) Has been cancelled
Republie pour rafraîchir le README embarqué (page du paquet) :
- doc d'install sans token (paquet public)
- README français + sélecteur de langue
- copy-meta réécrit le lien FR en absolu (page du paquet)
2026-06-17 14:32:28 +02:00
johanleroy 08166c108b release: @johanleroy/git-arboretum 1.0.0 — publication sur le registre Gitea
Release / Publish to Gitea npm registry (push) Has been cancelled
- renomme le paquet en @johanleroy/git-arboretum (scope routé vers le registre npm Gitea privé)
- embarque @arboretum/shared via bundleDependencies (scripts/vendor-shared.mjs au prepack)
- joint README/LICENSE au tarball (scripts/copy-meta.mjs) + metadata, keywords, publishConfig
- CI pack-smoke en mono-tarball avec assertion bundleDep ; nouveau workflow release.yml (publish sur tag v*)
- version 1.0.0 ; README mis à jour (install scopé + service systemd)
2026-06-17 13:53:30 +02:00
johanleroy 32e6ffba70 P4-C: PWA installable, service worker push & campagne de fiabilité dialogues
- PWA: public/manifest.webmanifest + icon.svg + meta index.html (theme-color, apple-touch-icon)
- public/sw.js: service worker push-only (push → showNotification tag=sessionId ;
  notificationclick → focus/ouvre /sessions/<id>) — pas de précache (hors périmètre)
- lib/push.ts + stores/push.ts: enable/disable, abonnement VAPID, enregistrement SW au boot
- DashboardView: toggle « Notifications » (gardé par pushSupported) + i18n en/fr
- campagne de fiabilité (verdict S3): classification de tous les types de dialogue sur captures
  réelles (trust/permission×2/question) + refus Esc (deny-esc2) + plan (synthétique)
- acceptance-p4.mjs: VAPID/auth/Origin, subscribe idempotent/unsubscribe, answer (rejets)

Choix: SW écrit à la main plutôt que vite-plugin-pwa (qui tirait ~295 paquets Workbox +
2 vulns esbuild high pour un SW push-only). Zéro dépendance front nouvelle. 175 tests verts,
acceptance p1..p4 ALL GREEN.
2026-06-15 12:23:00 +02:00
johanleroy fae288f511 P3-C: dashboard worktree-first + acceptance P3 (MVP complet)
Vue racine consolidée : repos → worktrees avec état git ET état de session
corrélé. Complète le MVP (P2 découverte/reprise + P3-A worktrees + P3-B états fins).

- web/lib/ws-client: abonnement multi-topics (sessions + worktrees), subscribeWorktrees.
- web/stores/worktrees: repos + worktrees, CRUD, temps réel (repo_update/worktree_*).
- web/views/DashboardView (route racine /), components RepoSection + WorktreeCard ;
  SessionStateBadge réutilisé pour l'état des sessions corrélées.
- router: / = dashboard, /sessions = liste à plat (sessions hors worktree), nav croisée.
- i18n EN/FR (dashboard/repos/worktrees).
- fix: @xterm/headless est CommonJS → chargé via createRequire (l'import nommé ESM
  échouait sous Node natif, masqué par esbuild en test) ; détecté par l'acceptation.
- scripts/acceptance-p3.mjs: repo git tmp → enregistrement, worktree + hook, broadcast
  WS worktree_update, corrélation session bash, suppression (409 sans force, 200 avec).

Vérifs : typecheck, 159 tests, build (vue-tsc), acceptations P1/P2/P3 ALL GREEN.
2026-06-12 18:46:45 +02:00
johanleroy 92c48f0978 P2: découverte & reprise des sessions Claude
Arboretum découvre désormais toutes les sessions Claude de la machine
(scan ~/.claude/projects + registre ~/.claude/sessions), distingue
vivantes/mortes par pid+procStart, et permet de reprendre une morte
(--resume dans son cwd d'origine) ou forker une vivante sans la corrompre.

- shared: SessionSummary enrichi (source, claudeSessionId, pid, resumable,
  attachable, registryStatus) — additif, PROTOCOL_VERSION inchangé ;
  types REST resume/fork.
- db: migration id:2 (claude_session_id, resumed_from).
- core: jsonl-discovery (parseur tolérant, scan asynchrone non bloquant),
  session-registry (vivacité pid+procStart), discovery-service (cache +
  refresh périodique + diff/broadcast), pty-manager (resume/fork + capture
  du claudeSessionId via le registre).
- routes: /sessions/:id/resume (garde-fou 409 anti-corruption sur session
  vivante) et /fork ; GET fusionné managées + découvertes ; relais WS.
- web: badges managed/discovered + busy/idle/waiting, actions conditionnelles
  (Open/Observe/Kill vs Fork/View vs Resume/Fork), vue read-only des sessions
  externes, i18n EN/FR.
- tests: jsonl-discovery, session-registry, discovery-service + resume/fork
  (130 verts) ; acceptation E2E acceptance-p2.mjs (sans quota) ALL GREEN.

Conforme aux verdicts S1 (resume dans cwd d'origine, vivacité pid+procStart)
et S4 (munge cwd, parseur tête+queue, priorité de titre).
2026-06-12 18:20:21 +02:00
johanleroy e9404cb567 P1 complete: web front, test suite, CI — acceptance ALL GREEN
Fan-out integration + fixes found by the test/acceptance pass:
- FIX ring-buffer: chunks >= capacity skipped bytes now count into the
  monotonic offset (invariant: stream byte k lives at k % capacity) —
  window order was corrupted on unaligned big chunks
- FIX auth: non-numeric cookie expiry no longer bypasses expiration
- FIX protocol: safe-integer validation on ack.bytes / hello.protocol
- FIX @fastify/websocket v11: websocket route must be registered in an
  encapsulated context after plugin load (handler got REST signature)
- FIX flow-control deadlock found by e2e acceptance: client only ACKs
  on data receipt, so pausing with an unACKed residue in (LOW,
  ACK_EVERY] stalled both sides at 0.9 MB. ACK_EVERY now 64 KiB (<=
  LOW invariant, tested) + trailing debounced ACK in the web client
- Web: Vue 3 + Vite + Pinia + Tailwind 4 + vue-i18n (EN/FR) + xterm 6
  (fit + webgl fallback), multiplexed ws-client with reconnect/backoff
  and resync epochs
- Tests: 100 vitest (protocol fuzz, ring edges, auth, pty-manager flow
  control with mocked pty, REST e2e) ; CI Node 22/24 + pack-smoke
- scripts/acceptance-p1.mjs: real daemon + real WS client — boot,
  login, attach, stdin, 10 MB flood w/ ACK (13.7 MB/1.9s, RSS bounded),
  brutal disconnect + replay resync, kill broadcast, SIGTERM drain
2026-06-11 22:29:58 +02:00