- README.md / README.fr.md : bullet dans « What Arboretum does », section dédiée
« VS Code extension » (build/packaging VSIX, install, sign in), ligne monorepo.
- Centre d'aide in-app (help-content.ts) : item « VS Code extension » dans Integrations (EN/FR).
- Site vitrine : 9e carte de features « Native VS Code extension » (feat9, EN/FR + icône).
Nouveau workspace packages/vscode (git-arboretum, privé, non publié sur npm), client REST/WS
réutilisant @arboretum/shared. Auth Authorization: Bearer sur REST et l'upgrade WS (via `ws`) ;
un client Node sans en-tête Origin passe le check Origin strict du serveur.
- Arbres temps réel : Repositories (repos → worktrees → sessions) et Groups, via le WebSocket.
- Terminaux natifs (vscode.Pseudoterminal) pour attacher/observer une session — rendu et
scrollback de VS Code ; décodage UTF-8 streaming + comptabilité ACK dans des modules purs.
- Status bar (compteur waiting) + notifications natives sur passage en waiting, réponses Yes/No
via la commande WS answer.
- Mutations git (create worktree, commit, push, promote), start/kill/hide/resume/fork, session
de groupe ; conscience du workspace (reveal + start/create here).
- Bundle esbuild (format cjs, external vscode) inlinant @arboretum/shared → VSIX autonome.
Logique réutilisable sans import vscode → testée par vitest (19 tests).
- CI : .gitea/workflows/vscode-release.yml package le VSIX sur tag vscode-vX.Y.Z (artefact +
asset de release best-effort). build:vscode hors du build principal (comme le site).
- spikes/s5-vscode/STUDY.md : décision de conception (GO phasé A→D), marquée implémentée.
- Recherche : le watcher débouncé du singleton useWorktreeView est créé dans un effectScope
détaché → ne gèle plus après une navigation ; recherche aussi par label de dépôt ; accès à
la palette depuis le MoreSheet mobile (sidebar/⌘K indisponibles sur mobile).
- Sessions : sessionsForCwd exclut désormais toujours les sessions masquées ; les sessions
externes (discovered) sont masquées par défaut côté UI, avec puce de révélation par carte et
case globale dans le dashboard.
- Terminal : ACK découplé du callback de rendu (armé à la réception + flush au retour de
visibilité) → plus de pause PTY quand l'onglet passe en arrière-plan ; repaint forcé sur
perte de contexte WebGL / resize / retour en avant-plan. Historique élargi : REPLAY_TAIL_BYTES
1 Mo, RING_CAPACITY 4 Mo, scrollback xterm 20k.
- Grille de groupe : bouton « plein écran » sur chaque cellule ouvrant /sessions/:id.
feat1 + showcase worktree-first mentionnent commit/push/promotion sans terminal ; carte worktree du mockup montre les actions Commit/Push (fidèle au WorktreeCard).
- 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.
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.
Le centre d'aide décrivait encore le modèle P5 (une session par repo) ;
l'entrée « cross-repo » dit désormais qu'un groupe lance UNE seule session
Claude couvrant tous ses repos (contexte partagé) et s'ouvre sur son terminal.
Inclut aussi un ajustement de copie de la FAQ du site.
Hauteur de cellule relative au viewport (h-[68dvh] min-h-80
lg:h-[calc((100dvh-9rem)/2)]) au lieu de h-80 fixe : terminaux grands sur PC,
exploitables sur mobile. xterm se re-fit via son ResizeObserver.
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).
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.
- Groupes de travail : une seule session Claude couvrant tous les repos via
--add-dir (au lieu d'une session par repo).
- Sessions démarrables sur la branche principale ou n'importe quel worktree.
- Réponse aux dialogues (options + Deny) plutôt qu'« approuver/refuser ».
- Miroir EN/FR.
Met à jour la copie « Worktree-first » et l'étape « How it works » : on peut
désormais démarrer une session sur sa branche principale ou un worktree isolé.
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.
Gitea Actions ignore .github/workflows/ dès que .gitea/workflows/ existe
(ajouté avec prod.yml). Résultat : ci.yml et release.yml ne se
déclenchaient plus (ni CI sur push, ni publication sur tag). On les
remet dans le répertoire effectivement lu par Gitea.
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.
- .htaccess : désactive Google PageSpeed (mod_pagespeed/ngx_pagespeed) qui renvoyait
un corps HTML vide sur le vhost Plesk (home en 200 mais 0 octet → page blanche),
alors que les assets Vite passaient. Backstop versionné du fix côté serveur.
- CopyButton réutilisable (variantes bar/icon, retour ✓ 1,8 s, bilingue, accessible) :
ajoute « copier » sur la commande de How-it-works, l'URL localhost et la commande du footer.
- URL du dépôt Gitea : johanleroy/git-arboretum → johanleroy/arboretum (centralisée dans
lib/links.ts). Le paquet npm @johanleroy/git-arboretum reste inchangé (c'est son nom).
- Port affiché corrigé : localhost:7777 → 7317 (port réel du daemon, cf. config.ts).
Le commit e7c1a60 a embarqué un package-lock.json antérieur à l'ajout du
workspace @arboretum/site et de la dépendance @fontsource/jetbrains-mono :
`npm ci` (ci.yml) aurait échoué. Régénéré via `npm install --package-lock-only`
(purement additif, aucune autre dépendance touchée).
Première version avec le tarball autonome (shared inliné dans dist/_shared).
Le 1.4.0 du registre a pu être publié sans @arboretum/shared embarqué
(bundleDependencies d'un workspace instable en CI/root) ; 1.4.1 garantit un
paquet installable et démarrable par n'importe quel consommateur.
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.
Le pré-check `npm view` était un signal non fiable contre le registre npm de
Gitea (faux négatif masqué par >/dev/null) : le publish s'exécutait malgré une
version déjà présente et échouait en 409 Conflict, faisant rougir la release
alors que l'artefact était bien publié.
On tente désormais toujours `npm publish` et on traite son 409 « already exists »
— le signal faisant autorité — comme un succès idempotent ; tout autre échec
reste fatal. L'étape est isolée dans la condition d'un `if` pour ne pas
déclencher le `set -e` du shell Actions.
Le SBOM passe en continue-on-error : un bonus supply-chain ne doit jamais faire
échouer une release dont le publish a réussi.
upload-artifact@v4 (@actions/artifact v2+) n'est pas supporté par Gitea Actions.
Le publish skip désormais une version déjà publiée (re-push de tag sans échec).
- constante BUYMEACOFFEE_URL ; item de nav « Offrez-moi un café »
- section support dans les Réglages + i18n EN/FR
- champ `funding` dans le package.json publié ; mentions README EN/FR
Gitea
- lien « Code source » hardcodé (REPO_SOURCE_URL) vers le dépôt, toujours visible
- retrait complet du réglage configurable (api.ts, route+store settings, SettingsView, i18n, help, tests)
Découverte des dépôts
- aucune racine de scan par défaut → pas de scan au premier démarrage (clean install)
- corrige la découverte des dépôts à l'INTÉRIEUR d'une racine qui est elle-même un repo
(depth 0 = conteneur de scan, on descend ; depth > 0 = feuille)
Sécurité « enterprise-deployable »
- en-têtes HTTP durcis (CSP, X-Frame-Options, nosniff, Referrer-Policy, HSTS conditionnel, no-store API), header Server retiré
- permissions DB 0o600 / dossier de données 0o700 ; error handler sanitisé ; garde Content-Type sur les mutations
- chiffrement au repos AES-256-GCM des secrets (server_secret, clé privée VAPID) via SecretBox
- journal d'audit (migration #7) + endpoint /audit-logs + RGPD export/effacement + UI Réglages
- SECURITY.md, docs/ENTERPRISE_DEPLOYMENT.md, sections README EN/FR, SBOM CycloneDX en CI
CI
- pack-smoke packe depuis le contexte du package (cd packages/server) au lieu de « npm pack -w » :
corrige l'embarquement de @arboretum/shared dans le tarball
Purge des données personnelles du dépôt public
- suppression de spikes/s4-discovery/result.json, reformulation du VERDICT
- chemin de test générique, anonymisation des 5 fixtures de dialogues
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).
Le job pack-smoke échouait sur le runner Gitea : « npm pack -w » n'y déclenche
pas le hook prepack (contrairement à « npm publish -w », d'où la 1.2.0 publiée
intacte). vendor-shared ne matérialisait donc pas @arboretum/shared, et npm
retombait sur le symlink workspace (non bundlé) → tarball sans le runtime partagé.
On lance désormais prepack explicitement, on vérifie la matérialisation de
@arboretum/shared/dist/index.js, puis on packe en --ignore-scripts. On nettoie
aussi /tmp/tarballs (runner persistant). Validé en repro locale (npm 10 et 11).
Cookie: routes/auth.ts pose `secure` sur le cookie de session quand la requête
arrive en HTTPS (x-forwarded-proto), sans trustProxy — durcit le cookie derrière
Tailscale Serve sans casser le localhost http.
Install: nouveau cli/install.ts + routeur de sous-commandes dans index.ts
(install/uninstall/status/serve). Service utilisateur systemd (Linux) ou launchd
(macOS), bootstrap du token, --dry-run/--no-enable. Rétrocompat stricte du daemon
par défaut (runDaemon extrait).
Tests: app.e2e (cookie Secure local vs HTTPS) + cli-install (fonctions pures).
203/203 verts, acceptation P1/P4 vertes.
Docs: README.md + README.fr.md (installeur multi-OS, distinction utiliser/cloner,
modèle de sécurité durci).
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)
- ajoute une traduction française complète du README
- lien de langue (English ⇄ Français) en tête des deux fichiers
- corrige une référence résiduelle `npx git-arboretum` → `@johanleroy/git-arboretum`
- 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)
Ajoute un navigateur de dossiers réutilisable sur les deux champs qui
exigeaient un chemin absolu tapé à la main : « Répertoire de travail »
(nouvelle session) et « Chemin du dépôt » (ajout de repo). Un bouton
« Parcourir… » déplie un panneau inline ; le champ texte reste pour
taper/coller un chemin connu. Pour l'ajout de repo, les sous-dossiers
qui sont des dépôts git sont signalés par un badge.
Serveur : nouvel endpoint GET /api/v1/fs/list (authentifié par le hook
preValidation global), ne renvoie que des noms de sous-dossiers (jamais
de contenu de fichier). Valide le chemin via resolve() (clampe à la
racine, neutralise « .. »), masque les dotfiles par défaut (showHidden),
annote les dépôts git (markRepos), tolère les entrées illisibles.
Tests : packages/server/test/fs-routes.test.ts (10 cas — listing, tri,
hidden, markRepos, rejets 400/404, auth 401). Suite : 185/185 verts.
Les tests de dialog-detection lisaient leurs captures dans
spikes/s3-tui/captures/, dossier exclu du suivi git (.gitignore :
spikes/**/captures/ + la règle globale *.log). Verts en local mais
absents au checkout CI → ENOENT sur 9 tests.
Les 5 fixtures réellement utilisées sont déplacées dans
packages/server/test/fixtures/dialogs/ et renommées *.raw (sans .log)
pour échapper aux deux règles d'ignore. Le test pointe désormais ce
dossier. Suite hermétique : 175/175 verts, fixtures versionnées.