feat(ui): first-run onboarding coachmarks
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
A one-time coachmark overlay walks a new player through the lobby and their first game board: a light dimmed layer draws one tail-pointed hint bubble at a time, advancing on a tap anywhere and removing itself for good after the last hint. Two independent series (lobby: settings/stats/new game; game: header/pass-exchange/hints/shuffle/rack), gated by a per-device persisted flag and marked done only after the last hint, so an interrupted run replays from the start. A deep-link into Settings -> Friends still triggers the lobby series on the first trip back to the lobby. Targets carry a data-coach attribute, so one positioning engine anchors the bubble in both portrait and landscape, re-measuring each frame until the geometry settles (route slide, hidden-banner reflow, fonts). The promo banner hides while the overlay is up (app.coachActive); a hidden DebugPanel "Reset visited" control replays the walk-through. Off by default in the mock build so the Playwright smoke is unaffected; ?coach forces it on for the dedicated e2e. Pure geometry (step lists, nextVisibleStep, placeBubble) in lib/coachmark.ts (unit-tested); Coachmark.svelte renders. Docs: FUNCTIONAL(+ru) onboarding story, UI_DESIGN coachmark section.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { expect, test, type Page } from './fixtures';
|
||||
|
||||
// The first-run onboarding coachmarks. `?coach` forces the overlay on in the mock build (where it
|
||||
// is otherwise off so it cannot eat the smoke's taps) and bypasses the persisted "seen" flag, so
|
||||
// the series replay deterministically here. A tap anywhere on the overlay advances; after the last
|
||||
// hint the overlay removes itself.
|
||||
|
||||
// Tap the overlay near a corner (clear of the bubble) to advance to the next hint.
|
||||
async function advance(page: Page): Promise<void> {
|
||||
await page.locator('[data-coach-overlay]').click({ position: { x: 5, y: 5 } });
|
||||
}
|
||||
|
||||
// Drive both series end to end: guest login -> lobby coachmarks (3) -> open the seeded game ->
|
||||
// game coachmarks (5) -> gone. Shared by the portrait and landscape projects/cases.
|
||||
async function runOnboarding(page: Page): Promise<void> {
|
||||
const overlay = page.locator('[data-coach-overlay]');
|
||||
|
||||
await page.goto('/?coach=1');
|
||||
await page.getByRole('button', { name: /guest/i }).click();
|
||||
|
||||
// Lobby series: settings -> stats -> new game.
|
||||
await expect(overlay).toBeVisible({ timeout: 10000 });
|
||||
await advance(page);
|
||||
await advance(page);
|
||||
await advance(page);
|
||||
await expect(overlay).toBeHidden();
|
||||
|
||||
// The lobby is interactive again: open the seeded active game.
|
||||
await page.getByRole('button', { name: /Ann/ }).click();
|
||||
await expect(page.locator('[data-cell]').first()).toBeVisible();
|
||||
|
||||
// Game series: header -> pass/exchange -> hints -> shuffle -> rack.
|
||||
await expect(overlay).toBeVisible({ timeout: 10000 });
|
||||
for (let i = 0; i < 5; i++) await advance(page);
|
||||
await expect(overlay).toBeHidden();
|
||||
}
|
||||
|
||||
test('onboarding walks the lobby then the first game (portrait)', async ({ page }) => {
|
||||
await runOnboarding(page);
|
||||
});
|
||||
|
||||
test.describe('landscape', () => {
|
||||
test.use({ viewport: { width: 1100, height: 640 } });
|
||||
|
||||
test('onboarding anchors and advances in the wide layout', async ({ page }) => {
|
||||
await runOnboarding(page);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user