Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Capture the Three.js group once in `VrmAvatar` so effect cleanup detaches from the same container the model was attached to (avoids orphan scenes on avatar swap). (#34)
- Drop unused `audioFile` prop from `VoicePanel` (file input is uncontrolled). (#34)
- `npm run dev:desktop` now passes `concurrently -k` so closing the Electron window (or killing Vite) tears down both processes and frees port 5173. (#37, #38)
- Custom environment library blob URLs are revoked only after the replacement list is in state, so the stage/picker never briefly reference dead `blob:` URLs while a Directories folder switch loads. (#39, #40)

## [0.5.0] — 2026-08-06

Expand Down
16 changes: 14 additions & 2 deletions avatar/src/components/AvatarStage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -362,27 +362,37 @@ export function AvatarStage() {
let cancelled = false;

async function refreshEnvLibrary() {
revokeEnvironmentBlobUrls(libraryEnvironmentsRef.current);
// Revoking before the await left the stage and the picker pointing at
// urls that no longer resolve for as long as the replacement took to
// load. Already-decoded images survive revocation, so nothing changed on
// screen — but a remount in that window loaded nothing. Hold the old list
// and drop it only once its replacement is in state (#39).
const previous = libraryEnvironmentsRef.current;
const useLibrary =
desktopMode &&
directories.environments.mode === 'custom' &&
Boolean(directories.environments.path);

if (!useLibrary) {
setLibraryCustomEnvironments([]);
if (!cancelled) setLibraryEnvironments([]);
if (!cancelled) {
setLibraryEnvironments([]);
revokeEnvironmentBlobUrls(previous);
}
return;
}

const { environments: next, error } = await loadLibraryEnvironments(
directories.environments.path,
);
if (cancelled) {
// A newer run captured the same `previous` and owns retiring it.
revokeEnvironmentBlobUrls(next);
return;
}
setLibraryEnvironments(next);
setLibraryCustomEnvironments(next);
revokeEnvironmentBlobUrls(previous);
if (error) {
setDirectoriesNotice(error);
setDirectories((prev) => ({
Expand All @@ -391,6 +401,7 @@ export function AvatarStage() {
}));
setLibraryEnvironments([]);
setLibraryCustomEnvironments([]);
revokeEnvironmentBlobUrls(next);
setSelectedBg((prev) =>
prev.type === 'env' && String(prev.id).startsWith('lib-env-')
? { type: 'color', value: defaultColor }
Expand All @@ -408,6 +419,7 @@ export function AvatarStage() {
}));
setLibraryEnvironments([]);
setLibraryCustomEnvironments([]);
revokeEnvironmentBlobUrls(next);
setSelectedBg((prev) =>
prev.type === 'env' && String(prev.id).startsWith('lib-env-')
? { type: 'color', value: defaultColor }
Expand Down