Round-6 follow-up: UX polish + client-IP fix #26

Merged
developer merged 8 commits from feature/ux-polish-ipfix into development 2026-06-08 21:40:13 +00:00
6 changed files with 46 additions and 26 deletions
Showing only changes of commit 7e34897d6d - Show all commits
+3
View File
@@ -44,6 +44,9 @@
/* Height Telegram's native nav overlays at the top in fullscreen; set from the SDK's /* Height Telegram's native nav overlays at the top in fullscreen; set from the SDK's
content-safe-area inset (Stage 17), 0 elsewhere. */ content-safe-area inset (Stage 17), 0 elsewhere. */
--tg-content-top: 0px; --tg-content-top: 0px;
/* Telegram device safe-area top (the notch); TG's own nav controls sit between it and
--tg-content-top, so the in-app header aligns to that band (Stage 17), 0 elsewhere. */
--tg-safe-top: 0px;
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
"Noto Sans", "Liberation Sans", sans-serif; "Noto Sans", "Liberation Sans", sans-serif;
--shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06); --shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 6px 16px rgba(0, 0, 0, 0.06);
+8 -7
View File
@@ -89,17 +89,18 @@
transform: rotate(45deg); transform: rotate(45deg);
margin-left: 3px; margin-left: 3px;
} }
/* Telegram fullscreen: TG's native nav overlays a band of height --tg-content-top at the top /* Telegram fullscreen: TG's native nav occupies the band between the device notch
of the viewport. Pull our title + menu up into the BOTTOM of that band and centre them as a (--tg-safe-top) and --tg-content-top. Our header spans that full band (so the layout below
pair (hamburger right of the title) so they line up with Telegram's own nav controls rather is unchanged) and centres the title + menu as a pair (hamburger right of the title) within
than floating above them (Stage 17). */ it, BELOW the notch — lining them up vertically with Telegram's own back/menu controls,
which sit in the band's corners (Stage 17). */
:global(html.tg-fullscreen) .bar { :global(html.tg-fullscreen) .bar {
min-height: var(--tg-content-top); min-height: var(--tg-content-top);
box-sizing: border-box; box-sizing: border-box;
align-items: flex-end; align-items: center;
justify-content: center; justify-content: center;
padding-top: 0; padding-top: var(--tg-safe-top);
padding-bottom: 6px; padding-bottom: 0;
} }
:global(html.tg-fullscreen) .spacer { :global(html.tg-fullscreen) .spacer {
display: none; display: none;
+20 -18
View File
@@ -3,7 +3,6 @@
import Header from './Header.svelte'; import Header from './Header.svelte';
import AdBanner from './AdBanner.svelte'; import AdBanner from './AdBanner.svelte';
import { navigate } from '../lib/router.svelte'; import { navigate } from '../lib/router.svelte';
import { insideTelegram } from '../lib/telegram';
// The app-shell layout (all screens): the nav bar grows; the ad strip, content and // The app-shell layout (all screens): the nav bar grows; the ad strip, content and
// optional tab bar pin to the bottom (ad directly above the content). Pass `scroll` // optional tab bar pin to the bottom (ad directly above the content). Pass `scroll`
@@ -37,25 +36,28 @@
const SHOW_AD_BANNER = false; const SHOW_AD_BANNER = false;
// Edge-swipe back (Stage 17): a left-edge rightward drag returns to `back`, the standard // Edge-swipe back (Stage 17): a left-edge rightward drag returns to `back`, the standard
// mobile gesture. Armed only from the very left edge (<=24px) so it never competes with the // mobile gesture. Listened at the window in the CAPTURE phase so the board's own pointer
// board's own horizontal gestures; touch/pen only. Skipped inside Telegram, whose native // handlers (which capture/stop the event) can never swallow it; armed only from the very
// back button + swipe already cover this (and would otherwise double up). // left edge (<=24px), touch/pen only, so it never competes with the board's gestures.
function onEdgeDown(e: PointerEvent): void { $effect(() => {
if (!back || e.pointerType === 'mouse' || insideTelegram() || e.clientX > 24) return; function onDown(e: PointerEvent) {
const x0 = e.clientX; if (!back || e.pointerType === 'mouse' || e.clientX > 24) return;
const y0 = e.clientY; const x0 = e.clientX;
const onUp = (ev: PointerEvent) => { const y0 = e.clientY;
window.removeEventListener('pointerup', onUp); const onUp = (ev: PointerEvent) => {
const dx = ev.clientX - x0; window.removeEventListener('pointerup', onUp, true);
const dy = ev.clientY - y0; const dx = ev.clientX - x0;
if (back && dx > 64 && Math.abs(dx) > Math.abs(dy) * 1.4) navigate(back); const dy = ev.clientY - y0;
}; if (back && dx > 64 && Math.abs(dx) > Math.abs(dy) * 1.4) navigate(back);
window.addEventListener('pointerup', onUp); };
} window.addEventListener('pointerup', onUp, true);
}
window.addEventListener('pointerdown', onDown, true);
return () => window.removeEventListener('pointerdown', onDown, true);
});
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <div class="screen">
<div class="screen" onpointerdown={onEdgeDown}>
<Header {title} {back} {menu} grow={growNav} /> <Header {title} {back} {menu} grow={growNav} />
{#if SHOW_AD_BANNER}<AdBanner />{/if} {#if SHOW_AD_BANNER}<AdBanner />{/if}
<main class="content" class:scroll class:fill={!growNav} class:column>{@render children?.()}</main> <main class="content" class:scroll class:fill={!growNav} class:column>{@render children?.()}</main>
+1 -1
View File
@@ -364,7 +364,7 @@
zoomed = true; zoomed = true;
telegramHaptic('light'); telegramHaptic('light');
} }
}, 1000) }, 700)
: null; : null;
} }
} }
+3
View File
@@ -14,6 +14,7 @@ import {
onTelegramPath, onTelegramPath,
telegramColorScheme, telegramColorScheme,
telegramContentSafeAreaTop, telegramContentSafeAreaTop,
telegramSafeAreaTop,
telegramDisableVerticalSwipes, telegramDisableVerticalSwipes,
telegramHaptic, telegramHaptic,
telegramLaunch, telegramLaunch,
@@ -238,6 +239,7 @@ function syncTelegramSafeArea(): void {
if (typeof document === 'undefined') return; if (typeof document === 'undefined') return;
const top = telegramContentSafeAreaTop(); const top = telegramContentSafeAreaTop();
document.documentElement.style.setProperty('--tg-content-top', `${top}px`); document.documentElement.style.setProperty('--tg-content-top', `${top}px`);
document.documentElement.style.setProperty('--tg-safe-top', `${telegramSafeAreaTop()}px`);
document.documentElement.classList.toggle('tg-fullscreen', top > 0); document.documentElement.classList.toggle('tg-fullscreen', top > 0);
} }
@@ -279,6 +281,7 @@ export async function bootstrap(): Promise<void> {
syncTelegramChrome(); syncTelegramChrome();
syncTelegramSafeArea(); syncTelegramSafeArea();
telegramOnEvent('contentSafeAreaChanged', syncTelegramSafeArea); telegramOnEvent('contentSafeAreaChanged', syncTelegramSafeArea);
telegramOnEvent('safeAreaChanged', syncTelegramSafeArea);
telegramOnEvent('fullscreenChanged', syncTelegramSafeArea); telegramOnEvent('fullscreenChanged', syncTelegramSafeArea);
telegramDisableVerticalSwipes(); telegramDisableVerticalSwipes();
try { try {
+11
View File
@@ -11,6 +11,7 @@ interface TelegramWebApp {
themeParams?: TelegramThemeParams; themeParams?: TelegramThemeParams;
colorScheme?: 'light' | 'dark'; colorScheme?: 'light' | 'dark';
isFullscreen?: boolean; isFullscreen?: boolean;
safeAreaInset?: { top: number; bottom: number; left: number; right: number };
contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number }; contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number };
ready?: () => void; ready?: () => void;
expand?: () => void; expand?: () => void;
@@ -110,6 +111,16 @@ export function telegramContentSafeAreaTop(): number {
return webApp()?.contentSafeAreaInset?.top ?? 0; return webApp()?.contentSafeAreaInset?.top ?? 0;
} }
/**
* telegramSafeAreaTop returns the device safe-area top inset (px) — the notch / status bar
* (Bot API 8.0). Telegram's own nav controls sit in the band between it and
* telegramContentSafeAreaTop, so aligning our header to that band lines it up with them. 0
* outside Telegram or on older clients.
*/
export function telegramSafeAreaTop(): number {
return webApp()?.safeAreaInset?.top ?? 0;
}
/** /**
* telegramDisableVerticalSwipes turns off Telegram's swipe-down-to-minimise gesture so * telegramDisableVerticalSwipes turns off Telegram's swipe-down-to-minimise gesture so
* it does not fight tile drag-and-drop or the board's vertical scroll. * it does not fight tile drag-and-drop or the board's vertical scroll.