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
85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
import { computed, onScopeDispose, watch } from 'vue';
|
|
import { useIdeStore } from '../stores/ide';
|
|
import { useWorktreesStore } from '../stores/worktrees';
|
|
import { wsClient } from '../lib/ws-client';
|
|
|
|
/**
|
|
* Propriétaire UNIQUE des abonnements `watch` de worktrees (message WS ciblé P7). Monté dans App.vue,
|
|
* donc jamais démonté : avant, seul `GitPanel` posait un `watch`, et il partait dès qu'on quittait
|
|
* l'onglet Git · plus aucun worktree n'était alors observé et les compteurs de l'arbre gelaient.
|
|
*
|
|
* On observe ce que l'utilisateur regarde vraiment : le worktree actif et ceux des dépôts dépliés dans
|
|
* l'arbre. Le coût suit donc l'attention, et non le nombre de dépôts enregistrés. Les worktrees
|
|
* portant une session vivante sont, eux, épinglés côté serveur (`syncSessionPin`) : inutile de les
|
|
* demander ici.
|
|
*
|
|
* Chaque `worktree_changes` reçu incrémente le jeton d'invalidation du store (`noteChange`), que les
|
|
* vues à contenu paresseux (arbre de fichiers, diff, historique) observent pour se recharger.
|
|
*/
|
|
|
|
/**
|
|
* Plafond d'abonnements simultanés. Le pool de watchers du serveur est borné (LRU) mais n'évince
|
|
* jamais une entrée explicitement demandée : c'est donc au client de ne pas en réclamer trop. 40
|
|
* couvre très largement un travail réel (le worktree actif plus quelques dépôts dépliés).
|
|
*/
|
|
export const MAX_WATCHED = 40;
|
|
|
|
export function useWatchedWorktrees(): void {
|
|
const ide = useIdeStore();
|
|
const worktrees = useWorktreesStore();
|
|
|
|
/** Abonnements en cours, clé `repoId\0path` → fonction de désabonnement. */
|
|
const active = new Map<string, () => void>();
|
|
|
|
const wanted = computed<Array<{ repoId: string; path: string }>>(() => {
|
|
const out: Array<{ repoId: string; path: string }> = [];
|
|
const seen = new Set<string>();
|
|
const add = (repoId: string, path: string): void => {
|
|
const key = `${repoId}\0${path}`;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
out.push({ repoId, path });
|
|
};
|
|
// Le worktree actif d'abord : il doit survivre au plafond.
|
|
const ctx = ide.activeContext;
|
|
if (ctx) add(ctx.repoId, ctx.wtPath);
|
|
// Worktrees dont l'arbre de fichiers est déplié : ils peuvent l'être depuis le panneau Groupes,
|
|
// sans que leur dépôt soit déplié dans l'Explorateur. Sans eux, cet arbre-là ne recevrait aucun
|
|
// `worktree_changes` et resterait figé sur son premier chargement.
|
|
for (const path of ide.expandedWtPaths) {
|
|
const wt = worktrees.worktrees.find((w) => w.path === path);
|
|
if (wt) add(wt.repoId, wt.path);
|
|
}
|
|
for (const repoId of ide.expandedRepoIds) {
|
|
for (const wt of worktrees.worktreesForRepo(repoId)) add(wt.repoId, wt.path);
|
|
}
|
|
return out.slice(0, MAX_WATCHED);
|
|
});
|
|
|
|
watch(
|
|
wanted,
|
|
(list) => {
|
|
const keys = new Set(list.map((w) => `${w.repoId}\0${w.path}`));
|
|
for (const [key, off] of active) {
|
|
if (keys.has(key)) continue;
|
|
off();
|
|
active.delete(key);
|
|
}
|
|
for (const w of list) {
|
|
const key = `${w.repoId}\0${w.path}`;
|
|
if (active.has(key)) continue;
|
|
active.set(
|
|
key,
|
|
wsClient.watchWorktree(w.repoId, w.path, (e) => worktrees.noteChange(e.repoId, e.path)),
|
|
);
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
onScopeDispose(() => {
|
|
for (const off of active.values()) off();
|
|
active.clear();
|
|
});
|
|
}
|