Extend speech bubble duration

This commit is contained in:
Alvin Unreal 2026-05-14 16:36:40 +02:00
parent fec746d62f
commit 5372a5c513
4 changed files with 18 additions and 6 deletions

View file

@ -4,7 +4,7 @@ import { getAppStateSnapshot } from "./app-state.js";
import { defaultPetWindowSize, getDefaultPetInitialPosition } from "./display.js";
import { debug, info } from "./logger.js";
import { transientDisplayMs, type OpenPetsReaction } from "./local-ipc-protocol.js";
import { clearTransientReaction, createAgentPetWindow, getTransientReactionAnimationMs, loadExplicitPetContent, mergePetTransientDisplay, setPetReactionState, type PetStatusBadgeReaction, type PetTransientDisplay } from "./pet-window.js";
import { clearTransientReaction, createAgentPetWindow, getTransientDisplayDurationMs, getTransientReactionAnimationMs, loadExplicitPetContent, mergePetTransientDisplay, setPetReactionState, type PetStatusBadgeReaction, type PetTransientDisplay } from "./pet-window.js";
const agentPetWindows = new Map<string, BrowserWindow>();
const transientDisplays = new Map<string, PetTransientDisplay>();
@ -129,7 +129,8 @@ function setAgentDisplay(petId: string, display: PetTransientDisplay): void {
if (existingAnimationTimer) clearTimeout(existingAnimationTimer);
const animationMs = getTransientReactionAnimationMs(preparedDisplay);
if (animationMs !== null && animationMs < transientDisplayMs) {
const displayDurationMs = getTransientDisplayDurationMs(preparedDisplay);
if (animationMs !== null && animationMs < displayDurationMs) {
const animationTimer = setTimeout(() => {
const current = transientDisplays.get(petId);
if (!current) return;
@ -150,7 +151,7 @@ function setAgentDisplay(petId: string, display: PetTransientDisplay): void {
transientAnimationTimers.delete(petId);
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) void loadExplicitPetContent(window, petId, null, statusBadges.get(petId) ?? null);
}, transientDisplayMs);
}, displayDurationMs);
transientTimers.set(petId, timer);
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) void loadExplicitPetContent(window, petId, preparedDisplay, statusBadges.get(petId) ?? null);

View file

@ -105,6 +105,9 @@ assert.match(reactionMessagesSource, /satisfies Record<OpenPetsReaction, readonl
assert.match(petWindowSource, /pickReactionMessage\(display\.reaction\)/, "reaction-only bubbles must render randomized messages instead of raw lowercase reaction ids.");
assert.match(petWindowSource, /function preparePetTransientDisplay/, "reaction-only bubbles must prepare a stable random message before rerenders.");
assert.match(petWindowSource, /function mergePetTransientDisplay/, "reaction-only events must not replace an active explicit message bubble.");
assert.match(petWindowSource, /function getTransientDisplayDurationMs[\s\S]*?12_000[\s\S]*?message\.length \* 70/, "speech bubbles must stay visible longer for longer messages without becoming permanent.");
assert.match(defaultPetControllerSource, /getTransientDisplayDurationMs\(transientDisplay\)/, "default pet speech bubble timeout must be length-aware.");
assert.match(agentPetControllerSourceForLogging, /getTransientDisplayDurationMs\(preparedDisplay\)/, "agent pet speech bubble timeout must be length-aware.");
assert.match(petWindowSource, /function getTransientReactionAnimationMs/, "finite reaction animations must expose their own shorter lifetime.");
assert.match(petWindowSource, /function clearTransientReaction/, "finite reaction animations must be clearable while the bubble remains visible.");
assert.match(petWindowSource, /webContents\.send\("openpets:pet-reaction-state"/, "finite reaction animations must clear sprite state without reloading the bubble.");

View file

@ -4,7 +4,7 @@ import { getAppStateSnapshot, getDefaultPetPosition, resetDefaultPetPosition, se
import { defaultPetWindowSize, getDefaultPetInitialPosition } from "./display.js";
import { debug, info } from "./logger.js";
import { transientDisplayMs, type OpenPetsReaction } from "./local-ipc-protocol.js";
import { clearTransientReaction, createDefaultPetWindow, getSafeDefaultPetPosition, getTransientReactionAnimationMs, loadDefaultPetContent, mergePetTransientDisplay, readWindowPosition, setPetReactionState, type PetStatusBadgeReaction, type PetTransientDisplay } from "./pet-window.js";
import { clearTransientReaction, createDefaultPetWindow, getSafeDefaultPetPosition, getTransientDisplayDurationMs, getTransientReactionAnimationMs, loadDefaultPetContent, mergePetTransientDisplay, readWindowPosition, setPetReactionState, type PetStatusBadgeReaction, type PetTransientDisplay } from "./pet-window.js";
let defaultPetWindow: BrowserWindow | null = null;
let paused = false;
@ -149,7 +149,8 @@ function setTransientDisplay(display: PetTransientDisplay): void {
}
const animationMs = getTransientReactionAnimationMs(transientDisplay);
if (animationMs !== null && animationMs < transientDisplayMs) {
const displayDurationMs = getTransientDisplayDurationMs(transientDisplay);
if (animationMs !== null && animationMs < displayDurationMs) {
transientAnimationTimeout = setTimeout(() => {
if (!transientDisplay) return;
transientDisplay = clearTransientReaction(transientDisplay);
@ -166,7 +167,7 @@ function setTransientDisplay(display: PetTransientDisplay): void {
transientAnimationTimeout = null;
}
refreshDefaultPetContent();
}, transientDisplayMs);
}, displayDurationMs);
refreshDefaultPetContent();
}

View file

@ -359,6 +359,13 @@ export function getTransientReactionAnimationMs(display: PetTransientDisplay): n
return typeof iterations === "number" ? row.durationMs * iterations : null;
}
export function getTransientDisplayDurationMs(display: PetTransientDisplay): number {
const baseMs = display.reaction === "success" || display.reaction === "error" ? 5_000 : 4_000;
const message = display.message ?? display.reactionMessage;
if (!message) return baseMs;
return Math.min(12_000, Math.max(baseMs, message.length * 70));
}
export function clearTransientReaction(display: PetTransientDisplay): PetTransientDisplay {
if (!display.reaction) return display;
return { ...display, reaction: undefined };