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
This commit is contained in:
2026-08-04 16:23:33 +02:00
parent 9624270d9b
commit bde5358ea8
21 changed files with 599 additions and 45 deletions
+35 -1
View File
@@ -64,6 +64,22 @@ const SECURITY_HEADERS: Record<string, string> = {
].join('; '),
};
// Politique de cache du statique. Piège à connaître : `npm pack` normalise le mtime de TOUS les
// fichiers du tarball à une date constante (1985-10-26). L'etag faible de @fastify/static étant
// dérivé de taille+mtime, deux versions différentes d'un même fichier non haché produisent le
// MÊME etag dès que leur taille coïncide : le client reçoit un 304 et garde indéfiniment
// l'ancienne copie. Vécu en production sur index.html à la mise à jour de l'app desktop : l'index
// obsolète référençait des `/assets/<hash>.js` disparus, le fallback SPA répondait du text/html
// pour ces modules, et la page restait noire.
// Conséquence : seuls les fichiers dont le NOM porte un hash de contenu (/assets/) sont
// cachables ; tout le reste (index.html, sw.js, theme-boot.js, manifest, icônes) part en
// no-store, la revalidation par etag n'étant pas fiable ici.
export function cacheControlFor(pathname: string): string {
return pathname.startsWith('/assets/')
? 'public, max-age=31536000, immutable'
: 'no-store';
}
declare module 'fastify' {
interface FastifyRequest {
authContext: AuthContext | null;
@@ -220,7 +236,25 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
// SPA buildée embarquée dans le paquet npm (public/) : absente en dev (vite dev sert le front)
const publicDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'public');
if (existsSync(publicDir)) {
void app.register(fastifyStatic, { root: publicDir, wildcard: false });
void app.register(fastifyStatic, {
root: publicDir,
wildcard: false,
// Validation conditionnelle désactivée : l'etag faible et le Last-Modified dérivent du mtime,
// que `npm pack` fige (cf. cacheControlFor). Les laisser actifs ferait répondre 304 aux
// clients qui détiennent encore un index.html périmé d'une version antérieure : ils y
// resteraient bloqués. Sans etag, ils reçoivent un 200 et se réparent d'eux-mêmes. Le coût
// est nul pour /assets (noms hachés, servis immutable) et négligeable ailleurs.
etag: false,
lastModified: false,
// Indispensable : sinon le plugin écrit son propre `cache-control: public, max-age=0`
// APRÈS setHeaders et écrase le no-store ci-dessous.
cacheControl: false,
// `setHeaders` s'applique aussi aux `reply.sendFile` du fallback SPA ci-dessous.
setHeaders(res, path) {
const rel = path.slice(publicDir.length).replace(/\\/g, '/');
res.setHeader('Cache-Control', cacheControlFor(rel));
},
});
app.setNotFoundHandler((req, reply) => {
if (req.url.startsWith('/api/') || req.url.startsWith('/ws')) {
return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'Route not found' } });