fix: comprehensive UUID/username persistence bug fixes (#252)

* fix: comprehensive UUID/username persistence bug fixes

Major fixes for UUID/skin reset issues that caused players to lose cosmetics:

Core fixes:
- Username rename now preserves UUID (atomic rename, not new identity)
- Atomic config writes with backup/recovery system
- Case-insensitive UUID lookup with case-preserving storage
- Pre-launch validation blocks play if no username configured
- Removed saveUsername calls from launch/install flows

UUID Modal fixes:
- Fixed isCurrent badge showing on wrong user
- Added switch identity button to change between saved usernames
- Fixed custom UUID input using unsaved DOM username
- UUID list now refreshes when player name changes
- Enabled copy/paste in custom UUID input field

UI/UX improvements:
- Added translation keys for switch username functionality
- CSS user-select fix for UUID input fields
- Allowed Ctrl+V/C/X/A shortcuts in Electron

Files: config.js, gameLauncher.js, gameManager.js, playerManager.js,
launcher.js, settings.js, main.js, preload.js, style.css, en.json

See UUID_BUGS_FIX_PLAN.md for detailed bug list (18 bugs, 16 fixed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(i18n): add switch username translations to all locales

Added translation keys for username switching functionality:
- notifications.noUsername
- notifications.switchUsernameSuccess
- notifications.switchUsernameFailed
- notifications.playerNameTooLong
- confirm.switchUsernameTitle
- confirm.switchUsernameMessage
- confirm.switchUsernameButton

Languages updated: de-DE, es-ES, fr-FR, id-ID, pl-PL, pt-BR, ru-RU, sv-SE, tr-TR

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: move UUID_BUGS_FIX_PLAN.md to docs folder

* docs: update UUID_BUGS_FIX_PLAN with complete fix details

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex
2026-02-01 04:01:58 +07:00
committed by GitHub
parent 6fbf37422f
commit 62430fe8f0
21 changed files with 1564 additions and 181 deletions

View File

@@ -446,10 +446,27 @@ async function savePlayerName() {
return;
}
await window.electronAPI.saveUsername(playerName);
const result = await window.electronAPI.saveUsername(playerName);
// Check if save was successful
if (result && result.success === false) {
console.error('[Settings] Failed to save username:', result.error);
const errorMsg = window.i18n
? window.i18n.t('notifications.playerNameSaveFailed')
: `Failed to save player name: ${result.error || 'Unknown error'}`;
showNotification(errorMsg, 'error');
return;
}
const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully';
showNotification(successMsg, 'success');
// Refresh UUID display since it may have changed for the new username
await loadCurrentUuid();
// Also refresh the UUID list to update which entry is marked as current
await loadAllUuids();
} catch (error) {
console.error('Error saving player name:', error);
const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name';
@@ -573,11 +590,26 @@ export function getCurrentJavaPath() {
}
/**
* Get current player name from UI input
* Returns null if no name is set (caller must handle this)
* NOTE: launcher.js now loads username directly from backend config
* This function is used for display purposes only
*/
export function getCurrentPlayerName() {
if (settingsPlayerName && settingsPlayerName.value.trim()) {
return settingsPlayerName.value.trim();
}
return 'Player';
// Return null instead of 'Player' - caller must handle missing username
return null;
}
/**
* Get current player name with fallback for display purposes only
* DO NOT use this for launching game - use backend loadUsername() instead
*/
export function getCurrentPlayerNameForDisplay() {
return getCurrentPlayerName() || 'Player';
}
window.openGameLocation = openGameLocation;
@@ -587,6 +619,7 @@ document.addEventListener('DOMContentLoaded', initSettings);
window.SettingsAPI = {
getCurrentJavaPath,
getCurrentPlayerName,
getCurrentPlayerNameForDisplay,
reloadBranch: loadVersionBranch
};
@@ -729,6 +762,9 @@ async function loadAllUuids() {
</div>
<div class="uuid-item-actions">
${mapping.isCurrent ? '<div class="uuid-item-current-badge">Current</div>' : ''}
${!mapping.isCurrent ? `<button class="uuid-item-btn switch" onclick="switchToUsername('${escapeHtml(mapping.username)}')" title="Switch to this identity">
<i class="fas fa-user-check"></i>
</button>` : ''}
<button class="uuid-item-btn copy" onclick="copyUuid('${mapping.uuid}')" title="Copy UUID">
<i class="fas fa-copy"></i>
</button>
@@ -813,7 +849,17 @@ async function setCustomUuid() {
async function performSetCustomUuid(uuid) {
try {
if (window.electronAPI && window.electronAPI.setUuidForUser) {
const username = getCurrentPlayerName();
// IMPORTANT: Use saved username from config, not unsaved DOM input
// This prevents setting UUID for wrong user if username field was edited but not saved
let username = null;
if (window.electronAPI.loadUsername) {
username = await window.electronAPI.loadUsername();
}
if (!username) {
const msg = window.i18n ? window.i18n.t('notifications.noUsername') : 'No username configured. Please save your username first.';
showNotification(msg, 'error');
return;
}
const result = await window.electronAPI.setUuidForUser(username, uuid);
if (result.success) {
@@ -850,6 +896,73 @@ window.copyUuid = async function (uuid) {
}
};
/**
* Switch to a different username/UUID identity
* This changes the active username to use that username's UUID
*/
window.switchToUsername = async function (username) {
try {
const message = window.i18n
? window.i18n.t('confirm.switchUsernameMessage').replace('{username}', username)
: `Switch to username "${username}"? This will change your active player identity.`;
const title = window.i18n ? window.i18n.t('confirm.switchUsernameTitle') : 'Switch Identity';
const confirmBtn = window.i18n ? window.i18n.t('confirm.switchUsernameButton') : 'Switch';
const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel';
showCustomConfirm(
message,
title,
async () => {
await performSwitchToUsername(username);
},
null,
confirmBtn,
cancelBtn
);
} catch (error) {
console.error('Error in switchToUsername:', error);
const msg = window.i18n ? window.i18n.t('notifications.switchUsernameFailed') : 'Failed to switch username';
showNotification(msg, 'error');
}
};
async function performSwitchToUsername(username) {
try {
if (!window.electronAPI || !window.electronAPI.saveUsername) {
throw new Error('API not available');
}
const result = await window.electronAPI.saveUsername(username);
if (result && result.success === false) {
throw new Error(result.error || 'Failed to save username');
}
// Update the username input field
if (settingsPlayerName) {
settingsPlayerName.value = username;
}
// Refresh the current UUID display
await loadCurrentUuid();
// Refresh the UUID list to show new "Current" badge
await loadAllUuids();
const msg = window.i18n
? window.i18n.t('notifications.switchUsernameSuccess').replace('{username}', username)
: `Switched to "${username}" successfully!`;
showNotification(msg, 'success');
} catch (error) {
console.error('Error switching username:', error);
const msg = window.i18n
? window.i18n.t('notifications.switchUsernameFailed')
: `Failed to switch username: ${error.message}`;
showNotification(msg, 'error');
}
}
window.deleteUuid = async function (username) {
try {
const message = window.i18n ? window.i18n.t('confirm.deleteUuidMessage').replace('{username}', username) : `Are you sure you want to delete the UUID for "${username}"? This action cannot be undone.`;