fix(web): repeindre les terminaux au retour-visible + flush ACK ciblé
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled

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).
This commit is contained in:
2026-06-24 10:15:29 +02:00
parent b5236b41c8
commit 0f126cf911
3 changed files with 91 additions and 6 deletions

View File

@@ -100,6 +100,12 @@ export class Attachment {
this.pending = null;
this.client.releaseAttachment(this);
}
/** Flush ACK ciblé : relance immédiatement le flux si le serveur avait mis ce PTY en pause
* (ex. retour-visible de la cellule, dont le rendu était gelé hors-écran). N'ACK que du traité. */
flushAck(): void {
this.client.flushAck(this);
}
}
export class WsClient {
@@ -357,15 +363,25 @@ export class WsClient {
/** ACK immédiat du reliquat traité pour tous les terminaux ouverts — au retour en avant-plan,
* pour rattraper sans attendre le débounce un serveur éventuellement en pause. */
private flushAcks(): void {
for (const att of this.attachments) {
if (att.closed || att.channel < 0) continue;
if (att.processedBytes > att.lastAckBytes) {
att.lastAckBytes = att.processedBytes;
this.sendControl({ type: 'ack', channel: att.channel, bytes: att.processedBytes });
}
for (const att of this.attachments) this.flushAttachmentAck(att);
}
/** ACK immédiat du reliquat traité d'UN attachment — n'ACK que du réellement traité (invariant
* préservé). Factorisé : utilisé par flushAcks() et par le flush ciblé flushAck(). */
private flushAttachmentAck(att: Attachment): void {
if (att.closed || att.channel < 0) return;
if (att.processedBytes > att.lastAckBytes) {
att.lastAckBytes = att.processedBytes;
this.sendControl({ type: 'ack', channel: att.channel, bytes: att.processedBytes });
}
}
/** Flush ACK ciblé d'un seul attachment : relance un PTY mis en pause par le serveur quand la
* cellule était cachée (cf. Attachment.flushAck, appelé au retour-visible). */
flushAck(att: Attachment): void {
this.flushAttachmentAck(att);
}
private handleServerMessage(msg: ServerMessage): void {
switch (msg.type) {
case 'hello_ok': {