feat(p11): temps réel complet (watcher checkout principal + topic settings)

P11-A — branche du checkout principal modifiée hors Arboretum :
- fs-watcher: pin « permanent » non évinçable (pinRepo/unpinRepo, repoPins dans evictIfNeeded)
- worktree-manager: armMainCheckoutWatchers() + arm/désarm sur addRepo/découverte/removeRepo/hidden
- index.ts: armMainCheckoutWatchers() dans runDaemon → git checkout CLI sur le principal → worktree_update <500ms sans watch client

P11-B — réglages en temps réel :
- protocole additif: type SettingsBroadcast (source unique, réutilisé par SettingsResponse), topic 'settings', message settings_update (validés parseClientMessage + gateway)
- core/settings-bus.ts (EventEmitter) ; routes/settings émet après PATCH ; gateway relaie aux abonnés 'settings'
- web: ws-client subscribeSettings + routage ; store settings applySettings/startRealtime ; AppShell abonne globalement ; SettingsView re-sync des drafts scalaires sans écraser une saisie en cours
- tests: protocol (topic settings) + fs-watcher (pinRepo non évincé) ; acceptance-p11.mjs (checkout principal <500ms + settings_update)
This commit is contained in:
2026-06-27 14:08:44 +02:00
parent 92670a796a
commit e8d10b7ec0
16 changed files with 304 additions and 38 deletions

View File

@@ -11,6 +11,7 @@ import { AuthService, LoginRateLimiter, type AuthContext } from './auth/service.
import { PtyManager } from './core/pty-manager.js';
import { DiscoveryService } from './core/discovery-service.js';
import { SessionArchiveService } from './core/session-archive.js';
import { SettingsBus } from './core/settings-bus.js';
import { WorktreeManager } from './core/worktree-manager.js';
import { RepoDiscoveryService } from './core/repo-discovery.js';
import { GroupManager } from './core/group-manager.js';
@@ -80,6 +81,7 @@ export interface AppBundle {
groups: GroupManager;
push: PushService;
fsWatcher: FsWatcherService;
settingsBus: SettingsBus;
}
export function buildApp(config: Config, db: Db, serverVersion: string): AppBundle {
@@ -105,6 +107,8 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
// Démarré dans runDaemon() (jamais ici) → le scan ne tourne pas pendant les tests qui appellent buildApp.
const repoDiscovery = new RepoDiscoveryService(db, worktrees);
const groups = new GroupManager(db);
// P11 — bus de diffusion des réglages : PATCH /settings émet, la gateway relaie au topic 'settings'.
const settingsBus = new SettingsBus();
// En-têtes de sécurité sur toute réponse + no-store sur les réponses sensibles (API/WS).
// onSend DOIT retourner le payload (sinon Fastify vide la réponse).
@@ -189,14 +193,14 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
registerGitRoutes(app, worktrees, db);
registerFileRoutes(app, worktrees, db);
registerPushRoutes(app, push, db);
registerSettingsRoutes(app, db, config, serverVersion, push);
registerSettingsRoutes(app, db, config, serverVersion, push, settingsBus);
registerFsRoutes(app);
registerAuditRoutes(app, db);
registerDataRoutes(app, db, auth);
// La route websocket doit être déclarée APRÈS le chargement du plugin (contexte
// encapsulé) — sinon le handler reçoit la signature REST (request, reply).
void app.register(async (scoped) => {
registerWsGateway(scoped, manager, discovery, sessionArchive, worktrees, groups, serverVersion);
registerWsGateway(scoped, manager, discovery, sessionArchive, worktrees, groups, settingsBus, serverVersion);
});
// SPA buildée embarquée dans le paquet npm (public/) — absente en dev (vite dev sert le front)
@@ -211,5 +215,5 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
});
}
return { app, auth, manager, discovery, sessionArchive, repoDiscovery, worktrees, groups, push, fsWatcher };
return { app, auth, manager, discovery, sessionArchive, repoDiscovery, worktrees, groups, push, fsWatcher, settingsBus };
}