From 9fa4acb3192254a97fd6ffef227b62616ef120db Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Fri, 23 Oct 2026 01:06:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(03-04):=20Telegram=20LocationManager=20deep?= =?UTF-8?q?-link=20=E2=80=94=20LOCATION=20SETTINGS=20CTA=20on=20denied=20c?= =?UTF-8?q?ards=20+=20locationManagerUpdated=20chain=20re-run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/i18n/strings.ts | 3 +++ web/src/main.ts | 32 ++++++++++++++++++++++++++++++-- web/src/ui/cards.ts | 26 +++++++++++++++++--------- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/web/src/i18n/strings.ts b/web/src/i18n/strings.ts index fb7e32a..d2edc8e 100644 --- a/web/src/i18n/strings.ts +++ b/web/src/i18n/strings.ts @@ -23,6 +23,7 @@ export interface ShellStrings { deniedRegister: string; ctaTryAgain: string; ctaAskViaChat: string; + ctaLocationSettings: string; deniedHeadline: string; deniedBody: string; deniedHint: string; @@ -54,6 +55,7 @@ export const strings: Record = { deniedRegister: "GEO // LOCATION", ctaTryAgain: "TRY AGAIN", ctaAskViaChat: "ASK VIA CHAT", + ctaLocationSettings: "LOCATION SETTINGS", deniedHeadline: "Location is switched off", deniedBody: "araharah guides you from where you physically are. Without location there is no route to draw.", deniedHint: "Grant location to Telegram, or send a pin in this chat: attach → Location.", @@ -81,6 +83,7 @@ export const strings: Record = { deniedRegister: "GEO // LOCATION", ctaTryAgain: "CUBA LAGI", ctaAskViaChat: "MINTA VIA CHAT", + ctaLocationSettings: "TETAPAN LOKASI", deniedHeadline: "Lokasi dimatikan", deniedBody: "araharah memandu dari kedudukan sebenar anda. Tanpa lokasi, tiada laluan boleh dilukis.", deniedHint: "Benarkan lokasi untuk Telegram, atau hantar pin dalam chat ini: lampiran → Lokasi.", diff --git a/web/src/main.ts b/web/src/main.ts index f5175cd..da04e3e 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -101,7 +101,14 @@ async function boot(): Promise { const bootCard1 = buildBootCard(locale, 1); const bootCard2 = buildBootCard(locale, 2); const deniedCard = buildDeniedCard(locale, { awaitingPin: false }); - const awaitingCard = buildDeniedCard(locale, { awaitingPin: true }); + const awaitingCard = buildDeniedCard( + locale, + // Debug round 2: the Telegram-only deep-link. LocationManager presence + // is the whole eligibility test — off-Telegram (Desktop/browser) there + // is nothing for openSettings() to open, and navigator.geolocation + // denial is handled by the OS settings hint copy instead. + { awaitingPin: true, withLocationSettings: wa?.LocationManager !== undefined }, + ); const lostCard = buildSignalLostCard(locale); const gantryOf = (card: HTMLElement): HTMLElement => { const el = card.querySelector(".gantry"); @@ -117,7 +124,17 @@ async function boot(): Promise { // is a second buildDeniedCard instance (03-03 stub note) — wiring only the // first left TRY AGAIN / ASK VIA CHAT dead on the awaiting-pin card. const wireDeniedActions = (card: HTMLElement): void => { - const retryButton = card.querySelector(".btn-secondary"); + const settingsButton = card.querySelector('[data-role="open-location-settings"]'); + if (settingsButton instanceof HTMLButtonElement) { + // Debug round 2: deep-links Telegram's pane where the user re-grants + // location for this bot (the "no way to turn on location" report). + settingsButton.addEventListener("click", () => { + const lm = wa?.LocationManager; + if (lm && typeof lm.openSettings === "function") lm.openSettings(); + void runFromSession(); // sheet open OR older client without the API — re-request either way + }); + } + const retryButton = card.querySelector(".card-actions .btn-secondary:not([data-role])"); if (retryButton instanceof HTMLButtonElement) { retryButton.addEventListener("click", () => void runFromSession()); // re-runs from link 1 } @@ -129,6 +146,17 @@ async function boot(): Promise { wireDeniedActions(deniedCard); wireDeniedActions(awaitingCard); + // Debug round 2: after the user grants location from Telegram's sheet + // (opened via openSettings), the client fires locationManagerUpdated — + // re-run the chain immediately instead of waiting for a manual tap. + // The Wire: only live Data events on the wrapper — guard chainInFlight. + if (wa && typeof wa.onEvent === "function" && wa.LocationManager) { + wa.onEvent("locationManagerUpdated", () => { + if (chainInFlight) return; + void runFromSession(); + }); + } + function showCard(card: HTMLElement | null, register: "center" | "bottom" = "center"): void { if (card !== null) { stack.replaceChildren(card); diff --git a/web/src/ui/cards.ts b/web/src/ui/cards.ts index 2b70f97..40c1ca6 100644 --- a/web/src/ui/cards.ts +++ b/web/src/ui/cards.ts @@ -126,15 +126,23 @@ export function buildBootCard(locale: Locale, stage: 1 | 2): HTMLElement { * issue #77) and the retry CTA is .btn-secondary (the RESEARCH Pitfall 4 * inversion, recorded as a callout in the UI-SPEC). With opts.awaitingPin * the body swaps to the chat-fallback waiting copy. */ -export function buildDeniedCard(locale: Locale, opts: { awaitingPin?: boolean } = {}): HTMLElement { - return mountHintedCard( - locale, - "deniedRegister", - "deniedHeadline", - opts.awaitingPin ? "chatFallbackWaiting" : "deniedBody", - "deniedHint", - true, - ); +export function buildDeniedCard( + locale: Locale, + opts: { awaitingPin?: boolean; withLocationSettings?: boolean } = {}, +): HTMLElement { + const card = mountHintedCard(locale, "deniedRegister", "deniedHeadline", opts.awaitingPin ? "chatFallbackWaiting" : "deniedBody", "deniedHint", true); + // Debug-round addition (03-DEVICE-MATRIX round 2): Telegram-only recovery + // path — opens Telegram's own location-access pane via LocationManager. + // Its callback fires locationManagerUpdated; main.ts re-runs the chain. + if (opts.withLocationSettings) { + const el = document.createElement("button"); + el.type = "button"; + el.className = "btn-secondary"; + el.dataset.role = "open-location-settings"; + el.textContent = strings[locale].ctaLocationSettings; + card.querySelector(".card-actions")?.appendChild(el); + } + return card; } /** SYS-labeled position register — the 45s-staleness card (folds into the