feat(ui): batch of UI polish tweaks
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 52s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 52s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
- admin dashboard "Users" count excludes robots (CountUsers, humans only) - lobby finished-game delete reveal: background matches header/tab-bar (--bg-elev) - mobile: swipe up on the zoom-out board (no staged tiles) shuffles the rack - lobby: status icons -25%, game-row top/bottom padding halved - toast: info tier drifts up ~a tab-bar height while fading over 2s and dismisses on tap; error tier unchanged - game: confirm-move button stays pinned at the right edge; rack tiles shrink to fit so a full rack never overflows onto it - telegram: a successful invite-link redeem shows a welcome window pointing at the bot - settings: remove the grid-lines toggle; the board is always a gapless checkerboard - settings/comms hubs: the selected tab highlight wraps icon + label as a pill Docs: UI_DESIGN (toast, board surface, selected tab), PLAN; e2e updated for the removed grid-lines toggle.
This commit is contained in:
+29
-12
@@ -56,8 +56,6 @@ export const app = $state<{
|
||||
locale: Locale;
|
||||
reduceMotion: boolean;
|
||||
boardLabels: BoardLabelMode;
|
||||
/** Draw grid lines between board cells; off (default) is a gapless checkerboard. */
|
||||
boardLines: boolean;
|
||||
localeLocked: boolean;
|
||||
/** Pending incoming friend requests, for the lobby ⚙️ badge and the Settings Friends tab. */
|
||||
notifications: number;
|
||||
@@ -72,6 +70,10 @@ export const app = $state<{
|
||||
* code is already used/expired, so the visitor lands in the lobby with a gentle pointer to
|
||||
* the bot instead of a scary error on the Friends screen. */
|
||||
staleInvite: boolean;
|
||||
/** Whether to show the Telegram "welcome" window: set when a deep-link friend code is
|
||||
* redeemed successfully inside Telegram, greeting the arriving player and pointing them at
|
||||
* the bot for the most convenient way to play. */
|
||||
welcomeRedeem: boolean;
|
||||
/** Monotonic counter bumped when the app returns to the foreground without the live stream
|
||||
* having dropped. An open game watches it to refetch once, recovering an in-game event shed
|
||||
* from a full hub buffer while suspended (the stream-drop case is covered by streamAlive). */
|
||||
@@ -88,12 +90,12 @@ export const app = $state<{
|
||||
locale: 'en',
|
||||
reduceMotion: false,
|
||||
boardLabels: 'beginner',
|
||||
boardLines: false,
|
||||
localeLocked: false,
|
||||
notifications: 0,
|
||||
chatUnread: {},
|
||||
feedbackReplyUnread: false,
|
||||
staleInvite: false,
|
||||
welcomeRedeem: false,
|
||||
resync: 0,
|
||||
});
|
||||
|
||||
@@ -143,7 +145,18 @@ function goForeground(): void {
|
||||
export function showToast(text: string, kind: Toast['kind'] = 'info'): void {
|
||||
app.toast = { kind, text, seq: ++toastSeq };
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => (app.toast = null), 4000);
|
||||
// An info bubble lives exactly as long as its rise-and-fade animation (2s); an error
|
||||
// dwells longer (4s) so it is not missed.
|
||||
toastTimer = setTimeout(() => (app.toast = null), kind === 'error' ? 4000 : 2000);
|
||||
}
|
||||
|
||||
/** dismissToast hides the current toast at once — a tap on the bubble dismisses it. */
|
||||
export function dismissToast(): void {
|
||||
if (toastTimer) {
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = null;
|
||||
}
|
||||
app.toast = null;
|
||||
}
|
||||
|
||||
/** dismissStaleInvite hides the outdated-invite-link notice (the user acknowledged it). */
|
||||
@@ -151,6 +164,11 @@ export function dismissStaleInvite(): void {
|
||||
app.staleInvite = false;
|
||||
}
|
||||
|
||||
/** dismissWelcomeRedeem hides the Telegram welcome window (the user acknowledged it). */
|
||||
export function dismissWelcomeRedeem(): void {
|
||||
app.welcomeRedeem = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* seedChatUnread sets a game's unread flag from an authoritative per-viewer REST view (the lobby
|
||||
* list, a game's state, or a move result). The live-event GameView omits the flag, so the live
|
||||
@@ -476,7 +494,6 @@ export async function bootstrap(): Promise<void> {
|
||||
app.theme = prefs.theme ?? 'auto';
|
||||
app.reduceMotion = prefs.reduceMotion ?? false;
|
||||
app.boardLabels = prefs.boardLabels ?? 'beginner';
|
||||
app.boardLines = prefs.boardLines ?? false;
|
||||
applyTheme(app.theme);
|
||||
applyReduceMotion(app.reduceMotion);
|
||||
if (prefs.locale) {
|
||||
@@ -560,7 +577,13 @@ async function routeStartParam(param: string): Promise<void> {
|
||||
try {
|
||||
const friend = await gateway.friendCodeRedeem(link.code);
|
||||
navigate('/friends');
|
||||
showToast(t('friends.added', { name: friend.displayName }));
|
||||
// Inside Telegram a successful invite-link redeem welcomes the arriving player and
|
||||
// points them at the bot (its native convenience); elsewhere a quiet toast suffices.
|
||||
if (insideTelegram()) {
|
||||
app.welcomeRedeem = true;
|
||||
} else {
|
||||
showToast(t('friends.added', { name: friend.displayName }));
|
||||
}
|
||||
void refreshNotifications();
|
||||
} catch (err) {
|
||||
const code = err instanceof GatewayError ? err.code : '';
|
||||
@@ -633,7 +656,6 @@ function persistPrefs(): void {
|
||||
locale: app.locale,
|
||||
reduceMotion: app.reduceMotion,
|
||||
boardLabels: app.boardLabels,
|
||||
boardLines: app.boardLines,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,11 +708,6 @@ export function setBoardLabels(mode: BoardLabelMode): void {
|
||||
persistPrefs();
|
||||
}
|
||||
|
||||
export function setBoardLines(on: boolean): void {
|
||||
app.boardLines = on;
|
||||
persistPrefs();
|
||||
}
|
||||
|
||||
// Background/foreground lifecycle: silence the reconnect banner during a suspend and
|
||||
// reconnect quietly on return (and refresh the lobby badge for any push missed while
|
||||
// hidden, §10). Several signals cover the platforms: the page Visibility API, the
|
||||
|
||||
@@ -163,7 +163,6 @@ export const en = {
|
||||
'settings.labelsBeginner': 'Beginner',
|
||||
'settings.labelsClassic': 'Classic',
|
||||
'settings.labelsNone': 'None',
|
||||
'settings.boardLines': 'Grid lines',
|
||||
'settings.reduceMotion': 'Reduce motion',
|
||||
|
||||
'about.title': 'About',
|
||||
@@ -241,6 +240,8 @@ export const en = {
|
||||
'friends.selfInvite': "Hopefully you've been friends with yourself for a while ☺️",
|
||||
'friends.staleInviteTitle': 'Link expired',
|
||||
'friends.staleInvite': 'You opened the game from an outdated link. Open the bot {bot} to play and get notifications.',
|
||||
'friends.welcomeRedeemTitle': 'Welcome!',
|
||||
'friends.welcomeRedeem': 'Welcome, {name}!\n\nUse {bot} to join games the most convenient way.',
|
||||
'friends.added': 'Added {name}.',
|
||||
'friends.blockedList': 'Blocked players',
|
||||
'friends.unblock': 'Unblock',
|
||||
|
||||
@@ -164,7 +164,6 @@ export const ru: Record<MessageKey, string> = {
|
||||
'settings.labelsBeginner': 'Новичок',
|
||||
'settings.labelsClassic': 'Классика',
|
||||
'settings.labelsNone': 'Без текста',
|
||||
'settings.boardLines': 'Линии сетки',
|
||||
'settings.reduceMotion': 'Меньше анимаций',
|
||||
|
||||
'about.title': 'О программе',
|
||||
@@ -242,6 +241,8 @@ export const ru: Record<MessageKey, string> = {
|
||||
'friends.selfInvite': 'Надеюсь, что с собой Вы уже давно дружите ☺️',
|
||||
'friends.staleInviteTitle': 'Ссылка устарела',
|
||||
'friends.staleInvite': 'Вы открыли игру по устаревшей ссылке. Откройте бота {bot}, чтобы играть и получать уведомления.',
|
||||
'friends.welcomeRedeemTitle': 'Добро пожаловать!',
|
||||
'friends.welcomeRedeem': 'Добро пожаловать, {name}!\n\nВоспользуйтесь {bot}, чтобы заходить в игру наиболее удобным способом.',
|
||||
'friends.added': 'Добавлен(а) {name}.',
|
||||
'friends.blockedList': 'Заблокированные',
|
||||
'friends.unblock': 'Разблокировать',
|
||||
|
||||
@@ -124,8 +124,6 @@ export interface Prefs {
|
||||
locale: Locale;
|
||||
reduceMotion: boolean;
|
||||
boardLabels: BoardLabelMode;
|
||||
/** Draw the 1px grid lines between cells; off (default) shows a gapless checkerboard. */
|
||||
boardLines: boolean;
|
||||
}
|
||||
|
||||
export async function loadPrefs(): Promise<Partial<Prefs>> {
|
||||
|
||||
Reference in New Issue
Block a user