Fix agent pet scale rendering

This commit is contained in:
Alvin Unreal 2026-05-16 16:13:57 +02:00
parent c72c8a423b
commit 5ae609429c
3 changed files with 25 additions and 10 deletions

View file

@ -1,6 +1,6 @@
import { BrowserWindow } from "electron";
import { getAppStateSnapshot } from "./app-state.js";
import { getAppStateSnapshot, type PetScaleValue } 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";
@ -84,11 +84,12 @@ export function closeAllAgentPets(): void {
export function refreshAgentPetContent(): void {
debug("pet.agent", "refresh all content", { activeWindows: agentPetWindows.size, petIds: [...agentPetWindows.keys()] });
const scale = getPreferredPetScale();
for (const [petId, window] of agentPetWindows.entries()) {
if (!window.isDestroyed()) {
const display = transientDisplays.get(petId) ?? null;
const badge = statusBadges.get(petId) ?? null;
void loadExplicitPetContent(window, petId, display, badge, getCurrentDismissToken(petId, display, badge));
void loadExplicitPetContent(window, petId, display, badge, getCurrentDismissToken(petId, display, badge), scale);
}
}
}
@ -103,7 +104,7 @@ function handleBubbleDismissed(petId: string, dismissToken: string): void {
clearAgentDisplay(petId);
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) {
void loadExplicitPetContent(window, petId, null, null);
void loadExplicitPetContent(window, petId, null, null, undefined, getPreferredPetScale());
}
}
@ -114,7 +115,9 @@ function getOrCreateAgentPetWindow(petId: string): BrowserWindow {
return existing;
}
const pet = getAppStateSnapshot().pets.installed.find((candidate) => candidate.id === petId);
const state = getAppStateSnapshot();
const scale = state.preferences.petScale as PetScaleValue;
const pet = state.pets.installed.find((candidate) => candidate.id === petId);
if (!pet) throw new Error(`Installed pet is unavailable: ${petId}`);
const offset = agentPetWindows.size + 1;
const initial = getDefaultPetInitialPosition(defaultPetWindowSize);
@ -123,6 +126,7 @@ function getOrCreateAgentPetWindow(petId: string): BrowserWindow {
const window = createAgentPetWindow({
petId,
displayName: pet.displayName,
scale,
position: { x: initial.x - offset * 36, y: initial.y - offset * 24 },
display,
badge,
@ -177,12 +181,12 @@ function setAgentDisplay(petId: string, display: PetTransientDisplay): void {
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) {
const badge = statusBadges.get(petId) ?? null;
void loadExplicitPetContent(window, petId, null, badge, getCurrentDismissToken(petId, null, badge));
void loadExplicitPetContent(window, petId, null, badge, getCurrentDismissToken(petId, null, badge), getPreferredPetScale());
}
}, displayDurationMs);
transientTimers.set(petId, timer);
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) void loadExplicitPetContent(window, petId, preparedDisplay, statusBadges.get(petId) ?? null, preparedDisplay.dismissToken);
if (window && !window.isDestroyed()) void loadExplicitPetContent(window, petId, preparedDisplay, statusBadges.get(petId) ?? null, preparedDisplay.dismissToken, getPreferredPetScale());
}
function clearAgentDisplay(petId: string): void {
@ -226,7 +230,7 @@ function setStatusBadge(petId: string, reaction: OpenPetsReaction): void {
const window = agentPetWindows.get(petId);
if (window && !window.isDestroyed()) {
const display = transientDisplays.get(petId) ?? null;
void loadExplicitPetContent(window, petId, display, null, getCurrentDismissToken(petId, display, null));
void loadExplicitPetContent(window, petId, display, null, getCurrentDismissToken(petId, display, null), getPreferredPetScale());
}
}, isBusyStatusBadgeReaction(reaction) ? busyStatusBadgeMs : transientDisplayMs);
statusBadgeTimers.set(petId, timer);
@ -247,3 +251,7 @@ function isBusyStatusBadgeReaction(reaction: OpenPetsReaction): boolean {
function getCurrentDismissToken(petId: string, display: PetTransientDisplay | null, badge: PetStatusBadgeReaction | null): string | undefined {
return display?.dismissToken ?? (badge ? String(displayGenerations.get(petId) ?? 0) : undefined);
}
function getPreferredPetScale(): PetScaleValue {
return getAppStateSnapshot().preferences.petScale as PetScaleValue;
}

View file

@ -213,6 +213,11 @@ assert.match(petWindowSource, /html\[data-motion-state=\"\$\{motion\}\"\] \$\{se
assert.match(petWindowSource, /\.sprite, \.installed-sprite, \.bubble/, "reduced-motion CSS must include built-in and installed sprites.");
assert.match(petWindowSource, /function createAgentPetWindow[\s\S]*?installMotionStatePublisher\(window\)/, "agent pet windows must publish motion state so dragged non-default pets run.");
assert.match(petWindowSource, /loadExplicitPetContent[\s\S]*?state\.preferences\.petScale/, "explicit agent pet windows must use the saved pet scale preference.");
assert.match(petWindowSource, /interface AgentPetWindowOptions[\s\S]*?readonly scale: PetScaleValue/, "new agent pet windows must receive the current pet scale explicitly for their first render.");
assert.match(petWindowSource, /loadExplicitPetContent\(window, options\.petId, options\.display, options\.badge, dismissToken, options\.scale\)/, "agent pet first render must not fall back to the medium default scale.");
assert.match(agentPetControllerSourceForLogging, /function getPreferredPetScale\(\): PetScaleValue/, "agent pet reloads must share one explicit saved scale helper.");
assert.match(agentPetControllerSourceForLogging, /loadExplicitPetContent\(window, petId, display, badge, getCurrentDismissToken\(petId, display, badge\), scale\)/, "agent pet refreshes must pass the saved pet scale explicitly.");
assert.match(agentPetControllerSourceForLogging, /loadExplicitPetContent\(window, petId, preparedDisplay, statusBadges\.get\(petId\) \?\? null, preparedDisplay\.dismissToken, getPreferredPetScale\(\)\)/, "agent pet transient updates must pass the saved pet scale explicitly.");
assert.match(mappingDoc, /\| 3 \| `waving` \| `waving`, Claude `Notification`\. \|/, "mapping docs must describe waving animation row and notification mapping.");
assert.match(mappingDoc, /reaction-animation-mapping\.ts/, "mapping docs must reference the shared reaction animation mapping source of truth.");
assert.match(mappingDoc, /overrid/i, "mapping docs must mention that reaction animation defaults can be overridden in Settings.");

View file

@ -25,6 +25,7 @@ export interface DefaultPetWindowOptions {
export interface AgentPetWindowOptions {
readonly petId: string;
readonly displayName: string;
readonly scale: PetScaleValue;
readonly position: Point;
readonly display: PetTransientDisplay | null;
readonly badge: PetStatusBadgeReaction | null;
@ -86,7 +87,7 @@ export function createAgentPetWindow(options: AgentPetWindowOptions, dismissToke
installMousePassthroughAndDrag(window, options.onBubbleDismissed);
installMotionStatePublisher(window);
installPetContextMenu(window, { label: "Close pet", click: options.onCloseRequested });
void loadExplicitPetContent(window, options.petId, options.display, options.badge, dismissToken);
void loadExplicitPetContent(window, options.petId, options.display, options.badge, dismissToken, options.scale);
return window;
}
@ -378,7 +379,7 @@ export async function loadDefaultPetContent(window: BrowserWindow, paused: boole
});
}
export async function loadExplicitPetContent(window: BrowserWindow, petId: string, display: PetTransientDisplay | null = null, badge: PetStatusBadgeReaction | null = null, dismissToken?: string): Promise<void> {
export async function loadExplicitPetContent(window: BrowserWindow, petId: string, display: PetTransientDisplay | null = null, badge: PetStatusBadgeReaction | null = null, dismissToken?: string, scaleOverride?: PetScaleValue): Promise<void> {
const sequence = allocateWindowLoadSequence(window);
try {
const state = getAppStateSnapshot();
@ -387,7 +388,8 @@ export async function loadExplicitPetContent(window: BrowserWindow, petId: strin
throw new Error(`Cannot render explicit pet: ${petId}`);
}
debug("pet.window", "explicit content render begin", { windowId: window.id, sequence, petId, displayName: pet.displayName, hasDisplay: Boolean(display), reaction: display?.reaction, hasMessage: Boolean(display?.message), badge });
const render = await createInstalledPetRender(pet.id, pet.displayName, false, display, state.preferences.petScale as PetScaleValue, badge, `explicit:${pet.id}`, dismissToken);
const scale = scaleOverride ?? state.preferences.petScale as PetScaleValue;
const render = await createInstalledPetRender(pet.id, pet.displayName, false, display, scale, badge, `explicit:${pet.id}`, dismissToken);
if (tryUpdateLoadedPetContent(window, render, `explicit-${pet.id}`, sequence)) return;
await loadPetHtmlFile(window, render.html, `explicit-${pet.id}`, sequence);
petWindowRenderCache.set(window, render.cacheKey);