feat(vscode): extension VS Code native (intégration native, pas un webview)

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.
This commit is contained in:
2026-06-23 17:43:03 +02:00
parent 663ae7ace1
commit cf7eb05aca
35 changed files with 6172 additions and 4 deletions

131
spikes/s5-vscode/STUDY.md Normal file
View File

@@ -0,0 +1,131 @@
# Spike S5 — Extension VSCode (intégration native) — ÉTUDE : ✅ GO (phasé)
> **✅ IMPLÉMENTÉ** dans `packages/vscode` (`git-arboretum`, privé) — phases A→D livrées : connexion +
> auth SecretStorage, arbres temps réel (Repos/Groups), terminaux natifs `Pseudoterminal`, status bar +
> notifications `waiting` (commande `answer`), mutations git, conscience du workspace. Bundle esbuild
> (shared inliné), packagé en VSIX (`npm run build:vscode` puis `vsce package --no-dependencies`).
> CI : `.gitea/workflows/vscode-release.yml` sur tag `vscode-vX.Y.Z`. L'étude ci-dessous reste la
> justification de conception.
Étude de conception (pas de mesure Go/No-Go : c'est un design + plan d'implémentation phasé). Objectif
validé avec l'utilisateur : **une vraie intégration native dans VSCode, pas un simple webview** — une
connexion réelle avec ce qui se passe dans l'éditeur (terminaux natifs, arbre, statut, notifications,
conscience du workspace).
## Contexte & contrainte
Arboretum est déjà un daemon qui sert une SPA et expose :
- une **API REST** (`/api/v1/...`) : repos, worktrees (+ mutations git commit/push/promote), sessions,
groupes, auth ;
- un **protocole WebSocket** multiplexé (`@arboretum/shared``protocol.ts`) : attach/stdin/resize,
frames binaires `[type u8][channel u32le][payload]`, flow-control par watermarks (`FLOW`), events
temps réel `sessions`/`worktrees`/`groups`, commande `answer` (dialogues), `hello`/`hello_ok`.
Un plugin n'a donc **rien à réimplémenter côté logique** : il est un **client** de cette API. La question
n'est pas « comment afficher le dashboard » mais « comment exposer Arboretum avec les primitives natives
de VSCode ». Le webview (iframe du dashboard) reste un *fallback* optionnel ; ce n'est pas le cœur.
## Pourquoi pas « juste un webview »
- Un webview = iframe vers `http://127.0.0.1:7317` : lourd, isolé (CSP stricte, `acquireVsCodeApi`
indisponible dans l'iframe), pas d'intégration avec les terminaux/arbres/statut de VSCode, auth à
re-bricoler (cookie/token dans une iframe cross-origin). On retrouve les bugs xterm-in-webview
(rendu, scrollback) qu'on cherche justement à éviter.
- Une intégration native réutilise le **rendu terminal, le scrollback, le copier/coller, les liens** de
VSCode (gratuitement robustes), et place Arboretum là où l'utilisateur travaille déjà.
## Architecture proposée — `packages/vscode`
Nouveau workspace privé `@arboretum/vscode`, extension TypeScript, packagée en **VSIX** via `vsce`
(build `esbuild`). Le `@arboretum/shared` est résolu en dev par le symlink workspace et **inliné** au
packaging (même principe que `scripts/inline-shared.mjs` côté serveur) → VSIX autonome.
### Connexion au daemon
- Réglages : `arboretum.url` (défaut `http://127.0.0.1:7317`), `arboretum.token`.
- **Auth** : commande `Arboretum: Sign in` qui demande le token et le stocke dans **VS Code
SecretStorage** (les tokens daemon sont stockés *hashés* et le bootstrap ne s'affiche qu'une fois →
impossible de « lire » le token existant ; on le saisit une fois). Option avancée : commande qui
**spawn** le daemon (`npx @johanleroy/git-arboretum`) et capture le bootstrap token au premier
démarrage.
- Le check Origin strict du daemon ne s'applique qu'aux navigateurs (en-tête `Origin`) : un client
Node/extension n'envoie pas d'`Origin` → OK ; l'auth par token (en-tête/cookie) reste requise.
### Réutilisation du protocole (le « vrai » lien)
Importer `@arboretum/shared` : `decodeBinaryFrame`/encodage, `BINARY_FRAME`, `FLOW`, `PROTOCOL_VERSION`,
types de messages et `SessionSummary`/`WorktreeSummary`/`GroupSummary`. Le pont terminal et la liste
restent **synchronisés** avec le serveur par construction.
### Surfaces natives
1. **Activity Bar + TreeView(s)** (`vscode.window.createTreeView`) : arbre **Repos → Worktrees →
Sessions**, avec badges d'état fins (`activity` busy/waiting/idle, `dialog`) déjà portés par
`SessionSummary`. Une vue **Groupes** en parallèle. Rafraîchi en temps réel par les events WS
(`onDidChangeTreeData`).
2. **Terminaux VSCode natifs via `Pseudoterminal`** *(pièce maîtresse)* :
`vscode.window.createTerminal({ name, pty })` où le `Pseudoterminal` ponte la session Arboretum :
- `open()``wsClient.attach(sessionId)` ;
- OUTPUT WS (frames binaires) → `writeEmitter.fire(text)` (`onDidWrite`) ;
- `handleInput(data)` → WS `stdin` ;
- `setDimensions({columns, rows})` → WS `resize` ;
- `close()``detach`.
On réutilise le **renderer + scrollback natifs de VSCode** : règle au passage, côté VSCode, les
problèmes de rendu et d'historique de la SPA. Flow-control : réutiliser `FLOW`/les ACK du protocole
(envoyer un ACK des octets écrits) pour ne pas saturer.
3. **Status bar** (`createStatusBarItem`) : compteur des sessions `waiting` (à traiter) ; clic → focus
de l'arbre / quick-pick des sessions en attente.
4. **Notifications natives** : sur transition d'une session vers `waiting` (event WS), `showInformation
Message` avec actions (« Ouvrir le terminal », « Oui »/« Non » → commande WS `answer`). Doublonne
utilement le Web Push : ici, natif dans l'éditeur.
5. **Conscience du workspace** : mapper `workspace.workspaceFolders` → repo/worktree Arboretum (par
chemin). Actions contextuelles : « Démarrer une session Claude ici », « Créer un worktree pour ce
repo », et les mutations git existantes (commit/push/promote via REST). Met aussi en évidence dans
l'arbre le worktree correspondant au dossier ouvert.
6. **Commandes** (`contributes.commands` + palette VSCode) : sign in, ouvrir le dashboard (webview
*optionnel*), rafraîchir, attacher/observer une session, créer worktree, commit/push/promote,
start/hide session, répondre à un dialogue.
7. **Webview** : seulement en *option* (vue dashboard complète pour qui la veut). UX primaire = native.
## Découpage phasé (livrable séparé — non implémenté dans ce lot)
- **A — Lecture & connexion** : auth (SecretStorage) + client REST/WS + TreeView lecture seule
(repos/worktrees/sessions/groupes) en temps réel. *Valeur immédiate, risque faible.*
- **B — Terminaux natifs** : `Pseudoterminal` attach/observe via pont WS réutilisant `@arboretum/shared`
(flow-control inclus). *Le cœur de la valeur ; à faire juste après A.*
- **C — Actions & supervision** : mutations (créer worktree, commit/push/promote, start/hide), status
bar `waiting`, notifications + commande `answer`.
- **D — Workspace-aware** : mapping dossier ↔ repo/worktree + actions contextuelles.
## Intégration monorepo & packaging
- `packages/vscode/` : `package.json` d'extension (`engines.vscode`, `activationEvents`,
`contributes.{commands,views,viewsContainers,configuration}`), build `esbuild` (bundle Node), test via
`@vscode/test-electron` (optionnel). Publication : VSIX (`vsce package`) — le registre privé Gitea ne
sert pas d'extensions VSCode, donc distribution par VSIX (et/ou Open VSX) plutôt que le Marketplace si
l'on veut rester privé.
- Le `tsconfig` reste hors du `tsc -b` server/shared actuel (build séparé) pour ne pas alourdir le
typecheck CI existant.
## Risques / questions ouvertes
- **Auth bootstrap** : saisie manuelle du token vs spawn+capture. MVP = saisie manuelle (SecretStorage).
- **Flow-control côté extension** : reprendre `FLOW` (ACK par octets) pour les terminaux natifs ; un
`Pseudoterminal` n'expose pas de back-pressure → ACK « ce qui a été émis dans `onDidWrite` ».
- **Remote/SSH/Codespaces** : si l'on veut le webview fallback, prévoir `vscode.env.asExternalUri` +
`portMapping`. Les surfaces natives (REST/WS via `arboretum.url`) restent valides tant que le daemon
est joignable depuis l'hôte de l'extension.
- **Multi-fenêtres VSCode** : une connexion WS par fenêtre (acceptable, outil mono-utilisateur).
## Verdict
**GO** sur une extension native `packages/vscode`, en commençant par **A → B** (TreeView temps réel +
terminaux natifs `Pseudoterminal`), qui apportent l'essentiel de la valeur et réutilisent directement
l'API REST + le protocole WS de `@arboretum/shared`. Le webview n'est qu'un fallback optionnel.
Références API : Webview, Tree View, Pseudoterminal/`createTerminal`, StatusBarItem, SecretStorage,
`asExternalUri` (docs officielles VSCode).