electron-builder exige un homepage projet, un author.email et un maintainer
.deb : le build Linux produisait l'AppImage mais échouait sur la cible deb.
Ajoute ces champs au package.json desktop et fixe explicitement le
maintainer dans electron-builder.yml.
Daemon 3.0.0 : marque le cap « IDE IA multi-projet + app de bureau native » (refonte additive, PROTOCOL_VERSION inchangé ; le tarball embarque la nouvelle SPA IDE + le handshake token desktop). Extension VS Code 0.3.0 : deep-link « Open Worktree IDE » vers l'IDE multi-projet, encodage wt-key unifié via @arboretum/shared (CHANGELOG + README à jour). Paquet desktop 0.1.0 (première release).
package.json ET package-lock.json bumpés (npm ci strict). 431 tests, 0 tiret, builds daemon/web/vscode/site verts.
HeroMockup : entrée de nav « IDE » active en tête (comme AppSidebar), badge version v2.0.0 (au lieu de v1.6.0 figé). ShowcaseSection : entrée « IDE » ajoutée à la barre d'onglets mobile (comme MobileTabBar). Clé i18n mIde (EN/FR). build:site vert, 0 tiret.
i18n EN/FR (miroir) : hero, section « workspace » et carte feature 11 recadrés vers l'IDE multi-projet (arbre unique, onglets, dock bas), + clés de la section Download. Nouvelle section DownloadSection.vue (app de bureau : Linux/Windows/macOS, runtime embarqué, auto-update ; boutons vers la page des releases et le build depuis les sources) + entrée de nav « Download ».
Mockup WorkspaceShowcase redessiné sur le nouvel IDE : barre d'activité, arbre unifié multi-projet (api/web + worktree + session), éditeur à onglets, diff, dock terminaux à onglets, barre de statut. build:site vert, 0 tiret.
README.md et README.fr.md (miroir strict, 18 sections) : tagline et « What Arboretum does » recadrés vers l'IDE IA multi-projet (arbre unique, onglets, dock terminaux, panneaux git), nouvelle section « Desktop app » / « Application de bureau » (3 OS, runtime embarqué donc pas de Node à installer côté app, auto-update, SmartScreen/Gatekeeper), « The IDE, workspace & git operations » réécrit, « Requirements » précisé. Pas d'URL d'artefact inventée (page des releases + packages/desktop).
docs/ENTERPRISE_DEPLOYMENT.md : app desktop = client autonome additif (daemon local embarqué), déploiement serveur inchangé. 0 tiret (garde CI).
.gitea/workflows/desktop-release.yml : garde-fou tag==version (packages/desktop), npm ci racine + npm ci desktop (hors workspaces), npm run dist:linux (AppImage+deb), artefacts + attachement des installeurs et de latest-linux.yml (auto-update) à la release Gitea via NPM_TOKEN. Windows/macOS : build manuel sur ces OS (README desktop). N'affecte aucun workflow existant (tag desktop-v* distinct de v* et vscode-v*).
resources/icon.png (source unique, electron-builder dérive tous les formats/tailles par OS). electron-builder.yml : cibles Windows (nsis+portable) et macOS (dmg/zip non signé) déjà présentes. daemon.ts gère SIGTERM puis SIGKILL (Windows sans SIGTERM natif).
README desktop : build par OS (dist:linux/win/mac), ConPTY Windows + SmartScreen (éditeur inconnu), Gatekeeper macOS (clic droit Ouvrir / xattr), limites du cross-build, prérequis git/claude sur le PATH. Les builds Windows/macOS se font sur ces OS.
scripts/prepare-server.mjs : rebuild forcé shared+server (évite la non-idempotence du prepack), npm pack du daemon (tarball autonome), extraction, strip des devDependencies (dont @arboretum/shared inliné, non publié), npm install --omit=dev des deps runtime avec le bon prebuild node-pty (cross via npm_config_platform/arch). scripts/fetch-node.mjs : Node standalone épinglé (22.21.1) vérifié SHA256 -> build/node.
electron-builder.yml : dist/ en ASAR ; build/server et build/node en extraResources (hors ASAR pour les .node) ; cibles Linux (AppImage+deb), Windows (nsis+portable), macOS (dmg/zip non signé) ; publish generic -> release Gitea. Scripts dist:linux/win/mac.
Vérifié : fetch-node (Node bundlé + node:sqlite), prepare-server (structure complète), et la chaîne runtime complète (Node bundlé exécute le daemon empaqueté : handshake + session bash via node-pty OK). Le wrapping electron-builder est une étape machine.
Nouveau paquet packages/desktop (Electron, autonome, HORS des workspaces racine pour ne pas alourdir la CI du daemon). Process principal : lance le daemon en enfant (Node), lit le handshake token sur le fd 3, pose le cookie de session server-to-server (session.fetch) puis charge la SPA sur 127.0.0.1 (aucun écran de login). Fenêtre sûre (contextIsolation, sandbox, nodeIntegration off, preload minimal), instance unique, liens externes vers le navigateur, permissions limitées aux notifications, état de fenêtre persistant, arrêt propre du daemon (SIGTERM puis SIGKILL).
Vérifié ici : tsc contre l'API Electron réelle + bundle esbuild (main/preload). Le run GUI et le packaging se font sur une machine avec affichage (C2+). Racine workspaces passée en liste explicite (5 paquets) pour exclure desktop du npm ci racine.
Dans runDaemon, si ARBORETUM_EMIT_TOKEN_FD est fourni, mint un token 'desktop' et l'écrit en JSON {token,url} sur ce descripteur (pipe stdio privé parent-enfant). Additif et gardé : sans la variable, comportement strictement inchangé. Permettra à l'app Electron d'ouvrir la SPA déjà authentifiée sans écran de login. Vérifié (handshake OK), 431 tests.
IdeShell dégrade sous le breakpoint md en un panneau unique (Explorateur/Éditeur/Terminal/Git) commuté par une barre d'onglets basse, avec un bouton retour vers le dashboard. La redirection / envoie le mobile vers /dashboard (supervision + nav) et le desktop vers /ide.
Garde-fou : pas de '<' dans les commentaires de template Vue (rolldown casse le build alors que vue-tsc passe). 431 tests.
Route / redirige vers /ide ; le dashboard passe à /dashboard (nom inchangé). /workspace/:repoId/:wt rend désormais l'IDE (IdeShell) : au montage il décode la clé, active et déplie le worktree ciblé, ouvre ?file= le cas échéant. WorkspaceView et useWorkspaceLayout retirés.
encode/decodeWtKey déplacés dans @arboretum/shared (source unique) avec décodeur TOLÉRANT (base64url OU chemin percent-encodé), corrigeant la divergence historique du deep-link VS Code. config.ts de l'extension aligné sur encodeWtKey. Tests wt-key (shared) + config (vscode) mis à jour. 431 tests.
components/ide/GitPanel.vue : CommitPanel ciblant le worktree actif (ide.activeContext), chargement des changements + abonnement disque (watchWorktree) ré-abonné au changement de worktree ; sélection d'un fichier ouvre un onglet diff. SessionsPanel : sessions vivantes -> ouverture dans le dock. GroupsPanel : groupes (getters) avec passerelle vers la vue Groupes.
PrimarySidebar aiguille désormais Explorer/Git/Sessions/Groupes vers ces panneaux (fin des placeholders). vue-tsc + build web verts.
components/ide/TerminalDock.vue + TerminalDockTabs.vue : dock à onglets, une TerminalView par session du dock, toutes montées en v-show pour préserver l'attache WS (seule l'active visible). Fermer un onglet démonte réellement la vue -> detach() propre, pas de fuite. DialogPrompt au-dessus du terminal actif en attente. Repli via la barre de statut ou le chevron.
Branché dans IdeShell (section dock redimensionnable, splitter inversé). 427 tests.
components/ide/EditorArea.vue : UNE instance Monaco partagée avec cache de modèles par onglet (préserve contenu, undo et curseur au changement d'onglet ; view states restaurés), thème arboretum-dark, save + conflit STALE_FILE + reload/overwrite, dispose des modèles à la fermeture d'onglet et au démontage. Onglets diff via DiffViewer.
components/ide/EditorTabs.vue : bandeau d'onglets multi-projets, point de modification, fermeture immédiate si propre et garde à deux clics si modifié. Bascule éditeur/diff par onglet. Branché dans IdeShell (centre). 427 tests.
components/ide/ : IdeShell (grille barre d'activité + panneau gauche + zone centrale + dock + barre de statut, splitters persistés, réconciliation des ressources ouvertes contre les données live), ActivityBar (Explorer/Git/Sessions/Groupes + Réglages/Aide), PrimarySidebar, ProjectTree + ProjectTreeNode (arbre unique repos -> worktrees -> sessions, fichiers embarqués via FileTree en mode sans chrome, dépliage paresseux), StatusBar.
Route /ide (layout 'ide'), entrée de nav « IDE », namespace i18n ide (EN/FR). Clics de l'arbre pilotent le store (contexte worktree, ouverture de fichier/terminal). Centre et dock en placeholders (remplis en B4/B5). FileTree gagne un mode embedded. 427 tests.
stores/ide.ts : état de vue de l'IDE (onglets éditeur, dock de sessions, activité, contexte worktree actif, tailles/visibilité des panneaux, expansion de l'arbre), en références uniquement (ids/chemins), persisté par persistedRef via mises à jour immuables. dirty reste volatile.
pruneDeadResources réconcilie au bootstrap (retire onglets/terminaux dont worktree/session n'existent plus). Store pur, sans couplage aux stores de données (résolution côté composants). 11 tests unitaires.
Extrait persistedRef dans lib/persisted-ref.ts (partagé, testable en node). Ajoute useSplitter + components/ide/PanelSplitter.vue (poignée de resize générique par pointer events, largeur/hauteur persistable), généralisant le resize souris de WorkspaceView.
Hisse le chargement initial et les abonnements temps réel dans useRealtimeBootstrap (monté par App.vue) : un seul propriétaire actif sur TOUS les layouts, y compris la vue IDE plein écran. AppShell et WorkspaceView n'en portent plus la responsabilité. Tests unitaires persisted-ref et useSplitter (10).
Introduit une couche sémantique dans style.css (@theme) : surfaces (surface-0..3), bordures, texte (fg/muted/subtle) et accent, aliasant la palette Tailwind (rendu identique, vérifié dans le CSS produit). Ajoute les métriques du chrome IDE (--ide-*) pour la refonte à venir.
Centralise deux sources uniques : lib/terminal-theme.ts (thème + police xterm) et lib/status-tokens.ts (tons de statut, dédupliqués entre BaseBadge et SessionStateBadge). Définit le thème Monaco « arboretum-dark ». Remplace les #09090b épars des composants par bg-surface-0 (ne restent que index.html et les renderers exigeant un hex concret).
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).
Workspace IDE (arbre + Monaco + diff + terminal), moteur git détaillé +
watcher FS temps réel, commit/push avancé, archivage auto des sessions,
sync des réglages en temps réel, services git distants chiffrés + clone.
Section VS Code mise à jour (fetch/pull, lien IDE web, VSIX 0.2.0).
Un terminal caché dans une grille de groupe (ou hors-écran) n'était pas repeint
au retour — `visibilitychange` ne couvre que l'onglet — d'où l'écran noir avec
curseur ; et son ACK ne progressait plus (callback xterm.write throttlé) → le
serveur mettait le PTY en pause → sortie « par à-coups ». Ajoute un
IntersectionObserver (repaint + flushAck ciblé au retour-visible) et expose
Attachment.flushAck (factorisé avec flushAcks).
- Réglages → Claude CLI : override du chemin du binaire `claude` (effet à la
prochaine session, fallback `which claude`), diagnostic de détection, et
override de la racine ~/.claude (effet au redémarrage). Validateurs stricts.
- Push : notifie aussi sur le front busy→idle (session redevenue disponible).
- Renomme l'état affiché idle → « disponible » / « available » (web EN/FR + VS Code) ;
l'enum SessionActivity du protocole reste inchangé.
Un service systemd user / LaunchAgent démarre avec un PATH minimal qui n'inclut
pas ~/.local/bin (où vit le binaire `claude`) → resolveClaudeBin (`which claude`)
échouait avec « Claude Code CLI not found in PATH ». `arboretum install` fige
désormais le PATH de l'environnement d'installation dans l'unit/plist.
Le trigger 'v*' capturait aussi les tags 'vscode-v*' de l'extension : le workflow strippait alors
'v' (→ scode-v0.1.0) et échouait contre la version du daemon. 'v[0-9]*' n'écoute que les tags vX.Y.Z.
- POST /api/v1/projects : crée <root>/<name> (mkdir non récursif, 409 si existant),
git init optionnel, puis lance une session dedans. Anti-traversal sur le nom (core/project.ts).
- Web : NewProjectModal (racine préremplie scanRoots[0], case git init) + bouton dans SessionsListView.
- SessionContextBar : badge du groupe (cliquable) + repos couverts avec leur branche dans l'en-tête
de session ; session simple = repo+branche déduits du cwd.
- 14 tests (unitaires + route).
Le token Gitea NPM_TOKEN (déjà utilisé pour publier le daemon) a les droits repository
suffisants pour l'API release → pas de secret RELEASE_TOKEN dédié à configurer.
- 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.
Les `packages/*/tsconfig.tsbuildinfo` étaient commités alors que `dist/`
est gitignoré. Sur un checkout neuf de la CI, `tsc -b` lit ce tsbuildinfo,
juge `@arboretum/shared` déjà émis et saute la génération de `dist/` →
`TS2307: Cannot find module '@arboretum/shared'` sur tout le serveur
(+ une `TS7006` en cascade). On dé-tracke les tsbuildinfo et on les ignore.
- 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.
README et CLAUDE.md reflètent désormais la réalité : découverte/reprise,
worktrees multi-repo, états fins et dashboard sont livrés et testés. La
prochaine phase est P4 (supervision mobile : answer + Web Push + PWA).
Les badges busy/waiting/idle des sessions affichées dans les cartes worktree
sont désormais résolus depuis le store des sessions (mis à jour en temps réel),
au lieu de l'instantané embarqué dans le worktree — pour que « quelle session
attend une réponse » reste live sans attendre un worktree_update.