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
11 changes: 11 additions & 0 deletions custom_components/beatify/www/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -10689,6 +10689,9 @@ body.home-mode .home-view {
/* Sticky CTA bar at the bottom of the home card */
.home-cta-bar {
display: flex;
/* #2365: lets the guard on `.beatify-banner` actually take effect, and on
a narrow phone lets the buttons themselves wrap rather than squeeze. */
flex-wrap: wrap;
gap: var(--space-sm);
padding: var(--space-md) var(--space-lg);
background: rgba(0, 0, 0, 0.3);
Expand Down Expand Up @@ -16685,6 +16688,14 @@ body.theme-dark .your-result-stats .stat-value {
/* --- Inline Panel-Banner (Variant C): docked above the primary action --- */
.beatify-banner {
display: flex;
/* #2365: a guard, not a layout. The banner is a block that fills the width
of whatever it sits above; it is never meant to share a line. Should a
future call site anchor it inside a flex row again, this makes it claim
its own line there instead of being shrunk to one character per line.
Outside a flex parent `flex-basis` has no effect, so this costs nothing
in the normal case. Paired with `flex-wrap` on `.home-cta-bar` — a basis
of 100% only wins a line of its own if the row is allowed to wrap. */
flex-basis: 100%;
align-items: flex-start;
gap: var(--space-sm);
margin-bottom: var(--space-md);
Expand Down
2 changes: 1 addition & 1 deletion custom_components/beatify/www/css/styles.min.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions custom_components/beatify/www/css/styles.min.css.map

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* #2365 — the start-failure banner must dock above the footer, not inside it.
*
* Reported from a phone on v4.3.1-rc1: pressing Start with no players joined
* rendered "Start nicht möglich" as a narrow column between the two footer
* buttons, wrapping mid-word — `beitrete` / `n`, `scanne` / `n`.
*
* The banner was fine. The anchor was wrong. `showBanner` inserts its node
* *before the anchor, inside the anchor's parent*, so anchoring on the Start
* button placed the banner **into** `.home-cta-bar` — a flex row in which
* `.home-cta-start` takes the free space via `flex: 1`, leaving the banner at
* its min-content width.
*
* The bar for these tests: would they have failed on the evening of the
* report? The first one would.
*/
import { describe, it, expect } from 'vitest';
import { bannerAnchorFor } from '../admin/util.js';

/** Minimal stand-in for the Start button; `closest` is the only call made. */
function startButton(barOrNull) {
return {
id: 'home-start-game',
closest(selector) {
return selector === '.home-cta-bar' ? barOrNull : null;
},
};
}

describe('#2365 bannerAnchorFor', () => {
it('returns the footer row, not the button inside it', () => {
const bar = { className: 'home-cta-bar' };
const btn = startButton(bar);
// The regression: this used to be the button, which put the banner in
// the flex row and squeezed it to one character per line.
expect(bannerAnchorFor(btn)).toBe(bar);
expect(bannerAnchorFor(btn)).not.toBe(btn);
});

it('falls back to the button when the markup has no footer row', () => {
// A banner in the wrong box still beats no banner at all.
const btn = startButton(null);
expect(bannerAnchorFor(btn)).toBe(btn);
});

it('falls back to the button when the element has no closest()', () => {
// Older test doubles and very old engines; must not throw.
const btn = { id: 'home-start-game' };
expect(bannerAnchorFor(btn)).toBe(btn);
});

it('returns null when there is no button', () => {
// showSetupError reads this to decide on the toast fallback, so the
// null must survive rather than become a truthy stand-in.
expect(bannerAnchorFor(null)).toBe(null);
expect(bannerAnchorFor(undefined)).toBe(null);
});
});
4 changes: 3 additions & 1 deletion custom_components/beatify/www/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
_setAdminToken,
_adminHeaders,
groupPlayersByPlatform,
REQUEST_STATUS_LABELS,

Check warning on line 47 in custom_components/beatify/www/js/admin.js

View workflow job for this annotation

GitHub Actions / Frontend build check

'REQUEST_STATUS_LABELS' is defined but never used
buildRequestRowHtml,
escapeHtml,
errorHeadlineAndDetail,
Expand All @@ -54,6 +54,7 @@
adminHasVisibleView,
createRenderCoalescer,
adminStateEqual,
bannerAnchorFor,
} from './admin/util.js';

// #1279 Schritt 3/6: REST/WS hub layer. The admin WS connection lifecycle +
Expand All @@ -79,7 +80,7 @@
renderAdminSubmissionDots,
renderAdminLeaderboard,
renderAdminResultCards,
renderAdminChallengeOptions,

Check warning on line 83 in custom_components/beatify/www/js/admin.js

View workflow job for this annotation

GitHub Actions / Frontend build check

'renderAdminChallengeOptions' is defined but never used
_providerDisplayName,
} from './admin/sections/render-helpers.js';

Expand Down Expand Up @@ -1780,7 +1781,8 @@
* rendered as a button in the banner (#2269).
*/
function showSetupError(message, action, detail) {
var anchor = document.getElementById('home-start-game');
// #2365: dock above the footer row, not inside it — see bannerAnchorFor.
var anchor = bannerAnchorFor(document.getElementById('home-start-game'));
if (!anchor) {
// No start button in view (e.g. mid-game) — fall back to a toast. The
// toast has no second slot, so the detail is appended inline (#2294).
Expand Down
Loading
Loading