Skip to content

Commit 7b7d204

Browse files
mholziClaude Codeclaude
authored
fix: Title & Artist mode hydration + equal reveal footer buttons (v4.1.0-rc7) (#1469)
- Fix "Name the Song" starting as a year-guess game. admin.js hydrateFromStorage copied every saved game-mode flag into adminState EXCEPT titleArtistMode, so the start payload sent title_artist_mode:false and players got the year input. Extracted the settings->adminState mapping into a single shared helper applyStoredGameSettings() (admin/util.js) covering all 5 flags, + unit tests, so the gap can't recur. - Equalise the reveal host buttons: "Invite Players" and "Next Round" now share the same box metrics (260x56); only colour differs. 325 frontend tests pass (5 new). Regenerated admin.min.js (npm run build) + styles.min.css. Co-authored-by: Claude Code <coba@aupairsync.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7b45c11 commit 7b7d204

8 files changed

Lines changed: 120 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to Beatify are documented here. For detailed release notes,
44

55
## [Unreleased]
66

7+
## [4.1.0-rc7] - 2026-06-13
8+
9+
### Fixed
10+
- **"Name the Song" (Title & Artist) mode no longer starts as a year-guess game.** The wizard saved the chosen mode, but the admin home→start hydration path (`admin.js` `hydrateFromStorage`) copied every game-mode flag from saved settings into `adminState` *except* `titleArtistMode`, so the start payload sent `title_artist_mode: false` and players got the year input instead of the title/artist text fields. The settings→`adminState` mapping is now a single shared helper (`applyStoredGameSettings` in `admin/util.js`) covering all five mode flags, with unit tests — so a new mode flag can't be dropped on this path again.
11+
12+
### Changed
13+
- **Reveal host buttons are now equal-sized.** "Invite Players" and "Next Round" share the same box metrics (width + height); only their colour differs (pink primary vs ghost).
14+
715
## [4.1.0-rc6] - 2026-06-13
816

917
### Changed

custom_components/beatify/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
"num2words==0.5.14"
2020
],
2121
"single_config_entry": true,
22-
"version": "4.1.0-rc6"
22+
"version": "4.1.0-rc7"
2323
}

custom_components/beatify/www/css/styles.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15632,6 +15632,23 @@ body.theme-dark .your-result-stats .stat-value {
1563215632
backdrop-filter: blur(20px);
1563315633
border-radius: var(--radius-full);
1563415634
}
15635+
/* Equalise the two footer buttons — same box metrics + equal width, so
15636+
"Spieler einladen" and "Nächste Runde" match (only their colour differs). */
15637+
#reveal-view .reveal-admin-buttons { align-items: stretch; }
15638+
#reveal-view .reveal-admin-buttons .invite-btn,
15639+
#reveal-view .reveal-admin-buttons .next-round-btn {
15640+
flex: 1 1 0;
15641+
min-width: 0;
15642+
max-width: 260px;
15643+
min-height: 56px;
15644+
padding: 12px 20px;
15645+
font-size: var(--font-size-md);
15646+
display: inline-flex;
15647+
align-items: center;
15648+
justify-content: center;
15649+
gap: var(--space-sm);
15650+
text-align: center;
15651+
}
1563515652

1563615653
/* ==========================================================================
1563715654
Reveal standings — "Round-Delta Ledger" (design-shotgun A)

custom_components/beatify/www/css/styles.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

custom_components/beatify/www/js/__tests__/admin-helpers.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
groupPlayersByPlatform,
2525
escapeHtml,
2626
buildRequestRowHtml,
27+
applyStoredGameSettings,
2728
} from '../admin/util.js';
2829

2930
// --- minimal localStorage / sessionStorage stub ----------------------------
@@ -243,3 +244,57 @@ describe('admin/util.js pure helpers — buildRequestRowHtml (#1279 step 2)', ()
243244
expect(html).toContain('request-item-thumbnail-placeholder');
244245
});
245246
});
247+
248+
describe('applyStoredGameSettings — settings → adminState hydration', () => {
249+
// Regression for #1180: the wizard saved titleArtistMode:true but the
250+
// home→start hydration path dropped it, so "name the song" started as a
251+
// year game (players got the year input instead of title/artist fields).
252+
it('hydrates title_artist_mode (the missed flag) from saved settings', () => {
253+
const adminState = { titleArtistModeEnabled: false };
254+
applyStoredGameSettings(adminState, { titleArtistMode: true });
255+
expect(adminState.titleArtistModeEnabled).toBe(true);
256+
});
257+
258+
it('hydrates every game-mode flag from saved settings', () => {
259+
const adminState = {
260+
artistChallengeEnabled: false, movieQuizEnabled: false,
261+
introModeEnabled: false, closestWinsModeEnabled: false,
262+
titleArtistModeEnabled: false,
263+
};
264+
applyStoredGameSettings(adminState, {
265+
artistChallenge: true, movieQuiz: true, introMode: true,
266+
closestWinsMode: true, titleArtistMode: true,
267+
});
268+
expect(adminState).toMatchObject({
269+
artistChallengeEnabled: true, movieQuizEnabled: true,
270+
introModeEnabled: true, closestWinsModeEnabled: true,
271+
titleArtistModeEnabled: true,
272+
});
273+
});
274+
275+
it('hydrates scalar settings (language, duration, difficulty, provider, autoadvance)', () => {
276+
const adminState = {};
277+
applyStoredGameSettings(adminState, {
278+
language: 'de', duration: 45, revealAutoAdvance: 30,
279+
difficulty: 'hard', provider: 'spotify',
280+
});
281+
expect(adminState).toMatchObject({
282+
selectedLanguage: 'de', selectedDuration: 45, revealAutoAdvance: 30,
283+
selectedDifficulty: 'hard', selectedProvider: 'spotify',
284+
});
285+
});
286+
287+
it('leaves flags untouched for absent / wrong-typed keys (partial / legacy payloads)', () => {
288+
const adminState = { titleArtistModeEnabled: true, artistChallengeEnabled: true };
289+
applyStoredGameSettings(adminState, { titleArtistMode: 'yes' /* not boolean */ });
290+
expect(adminState.titleArtistModeEnabled).toBe(true); // unchanged
291+
expect(adminState.artistChallengeEnabled).toBe(true); // absent key, unchanged
292+
});
293+
294+
it('is a no-op for null / non-object input', () => {
295+
const adminState = { titleArtistModeEnabled: false };
296+
applyStoredGameSettings(adminState, null);
297+
applyStoredGameSettings(null, { titleArtistMode: true });
298+
expect(adminState.titleArtistModeEnabled).toBe(false);
299+
});
300+
});

custom_components/beatify/www/js/admin.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
buildRequestRowHtml,
3636
escapeHtml,
3737
acquireWakeLockFirst,
38+
applyStoredGameSettings,
3839
} from './admin/util.js';
3940

4041
// #1279 Schritt 3/6: REST/WS hub layer. The admin WS connection lifecycle +
@@ -340,15 +341,11 @@ document.addEventListener('DOMContentLoaded', async () => {
340341
const raw = localStorage.getItem(STORAGE_GAME_SETTINGS);
341342
if (raw) {
342343
const s = JSON.parse(raw);
343-
if (s.language) adminState.selectedLanguage = s.language;
344-
if (s.duration) adminState.selectedDuration = s.duration;
345-
if (typeof s.revealAutoAdvance === 'number') adminState.revealAutoAdvance = s.revealAutoAdvance;
346-
if (s.difficulty) adminState.selectedDifficulty = s.difficulty;
347-
if (s.provider) adminState.selectedProvider = s.provider;
348-
if (typeof s.artistChallenge === 'boolean') adminState.artistChallengeEnabled = s.artistChallenge;
349-
if (typeof s.movieQuiz === 'boolean') adminState.movieQuizEnabled = s.movieQuiz;
350-
if (typeof s.introMode === 'boolean') adminState.introModeEnabled = s.introMode;
351-
if (typeof s.closestWinsMode === 'boolean') adminState.closestWinsModeEnabled = s.closestWinsMode;
344+
// Single source of truth for the settings→adminState mapping
345+
// (incl. title_artist_mode — its omission here shipped "name
346+
// the song" as a year game, #1180). Playlists stay inline
347+
// below since they need adminState.playlistData.
348+
applyStoredGameSettings(adminState, s);
352349
const wizPaths = Array.isArray(s.selectedPlaylists)
353350
? s.selectedPlaylists.map((p) => (typeof p === 'string' ? p : p.path)).filter(Boolean)
354351
: [];

0 commit comments

Comments
 (0)