Review fixes: swipe (capture-phase, enabled in TG), TG header aligns to the nav band, DnD zoom delay 1s->0.7s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s

- Edge-swipe back now listens at the window in the CAPTURE phase (the board's
  pointer handlers can't swallow it) and is no longer skipped inside Telegram
  (where the owner tests it).
- TG-fullscreen header: expose the device safe-area top (--tg-safe-top) and
  centre the title + menu pair within Telegram's nav band ([safe-top,
  content-top]) below the notch, keeping the band's height — lining up with
  Telegram's own controls.
- DnD auto-zoom-on-hover delay reduced 1000ms -> 700ms.

(Client-IP: diagnosed as the owner's home-router SNAT — the host caddy already
receives 192.168.0.1 with no XFF, so the real IP is lost upstream of our stack;
correct in prod. No code change.)
This commit is contained in:
Ilia Denisov
2026-06-08 22:01:43 +02:00
parent 645df52c0b
commit 7e34897d6d
6 changed files with 46 additions and 26 deletions
+3
View File
@@ -44,6 +44,9 @@
/* Height Telegram's native nav overlays at the top in fullscreen; set from the SDK's
content-safe-area inset (Stage 17), 0 elsewhere. */
--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,
"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);
+8 -7
View File
@@ -89,17 +89,18 @@
transform: rotate(45deg);
margin-left: 3px;
}
/* Telegram fullscreen: TG's native nav overlays a band of height --tg-content-top at the top
of the viewport. Pull our title + menu up into the BOTTOM of that band and centre them as a
pair (hamburger right of the title) so they line up with Telegram's own nav controls rather
than floating above them (Stage 17). */
/* Telegram fullscreen: TG's native nav occupies the band between the device notch
(--tg-safe-top) and --tg-content-top. Our header spans that full band (so the layout below
is unchanged) and centres the title + menu as a pair (hamburger right of the title) within
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 {
min-height: var(--tg-content-top);
box-sizing: border-box;
align-items: flex-end;
align-items: center;
justify-content: center;
padding-top: 0;
padding-bottom: 6px;
padding-top: var(--tg-safe-top);
padding-bottom: 0;
}
:global(html.tg-fullscreen) .spacer {
display: none;
+20 -18
View File
@@ -3,7 +3,6 @@
import Header from './Header.svelte';
import AdBanner from './AdBanner.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
// optional tab bar pin to the bottom (ad directly above the content). Pass `scroll`
@@ -37,25 +36,28 @@
const SHOW_AD_BANNER = false;
// 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
// board's own horizontal gestures; touch/pen only. Skipped inside Telegram, whose native
// back button + swipe already cover this (and would otherwise double up).
function onEdgeDown(e: PointerEvent): void {
if (!back || e.pointerType === 'mouse' || insideTelegram() || e.clientX > 24) return;
const x0 = e.clientX;
const y0 = e.clientY;
const onUp = (ev: PointerEvent) => {
window.removeEventListener('pointerup', onUp);
const dx = ev.clientX - x0;
const dy = ev.clientY - y0;
if (back && dx > 64 && Math.abs(dx) > Math.abs(dy) * 1.4) navigate(back);
};
window.addEventListener('pointerup', onUp);
}
// mobile gesture. Listened at the window in the CAPTURE phase so the board's own pointer
// handlers (which capture/stop the event) can never swallow it; armed only from the very
// left edge (<=24px), touch/pen only, so it never competes with the board's gestures.
$effect(() => {
function onDown(e: PointerEvent) {
if (!back || e.pointerType === 'mouse' || e.clientX > 24) return;
const x0 = e.clientX;
const y0 = e.clientY;
const onUp = (ev: PointerEvent) => {
window.removeEventListener('pointerup', onUp, true);
const dx = ev.clientX - x0;
const dy = ev.clientY - y0;
if (back && dx > 64 && Math.abs(dx) > Math.abs(dy) * 1.4) navigate(back);
};
window.addEventListener('pointerup', onUp, true);
}
window.addEventListener('pointerdown', onDown, true);
return () => window.removeEventListener('pointerdown', onDown, true);
});
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="screen" onpointerdown={onEdgeDown}>
<div class="screen">
<Header {title} {back} {menu} grow={growNav} />
{#if SHOW_AD_BANNER}<AdBanner />{/if}
<main class="content" class:scroll class:fill={!growNav} class:column>{@render children?.()}</main>
+1 -1
View File
@@ -364,7 +364,7 @@
zoomed = true;
telegramHaptic('light');
}
}, 1000)
}, 700)
: null;
}
}
+3
View File
@@ -14,6 +14,7 @@ import {
onTelegramPath,
telegramColorScheme,
telegramContentSafeAreaTop,
telegramSafeAreaTop,
telegramDisableVerticalSwipes,
telegramHaptic,
telegramLaunch,
@@ -238,6 +239,7 @@ function syncTelegramSafeArea(): void {
if (typeof document === 'undefined') return;
const top = telegramContentSafeAreaTop();
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);
}
@@ -279,6 +281,7 @@ export async function bootstrap(): Promise<void> {
syncTelegramChrome();
syncTelegramSafeArea();
telegramOnEvent('contentSafeAreaChanged', syncTelegramSafeArea);
telegramOnEvent('safeAreaChanged', syncTelegramSafeArea);
telegramOnEvent('fullscreenChanged', syncTelegramSafeArea);
telegramDisableVerticalSwipes();
try {
+11
View File
@@ -11,6 +11,7 @@ interface TelegramWebApp {
themeParams?: TelegramThemeParams;
colorScheme?: 'light' | 'dark';
isFullscreen?: boolean;
safeAreaInset?: { top: number; bottom: number; left: number; right: number };
contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number };
ready?: () => void;
expand?: () => void;
@@ -110,6 +111,16 @@ export function telegramContentSafeAreaTop(): number {
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
* it does not fight tile drag-and-drop or the board's vertical scroll.