feat(04-04): the destination resolver — two carriers, one validation, memory only

- resolve-destination.ts: reads ?dest= (URL, primary — the inline
  web_app-button carrier) then initData's start_param (the signed
  string, secondary — the direct-link/home-screen carrier), ONE
  slug validation, dest_ prefix stripped; resolves against the
  webview's own build-time @araharah/search copy; honest null for
  unknown ids; module-scope memory-only holder (GEO-03/D-07)
- destination-card.ts: the confirmation + honest-unavailable cards,
  createElement/textContent only, existing theme classes (T-04-32)
- webapp.ts: TelegramWebAppLike gains startParam, addToHomeScreen?,
  checkHomeScreenStatus? + HomeScreenStatus (Bot API 8.0+ members)
- telegram-mock.ts: satisfies the extended contract in the same
  change (startParam initData wiring, homeScreenStatus, __homeScreenAdded)
- strings.ts: 5 new copy fields in en+ms; web depends on @araharah/search
- DP1-DP12 green (45/45 web suite), web:typecheck green
This commit is contained in:
araharah-executor
2026-10-24 01:33:56 +08:00
parent 3e3d1917e6
commit 4f958411f3
7 changed files with 269 additions and 8 deletions
+75
View File
@@ -0,0 +1,75 @@
/*
* destination-card.ts — the ONBD-02 destination surface (D-11).
* -------------------------------------------------------------
* The webview's visible confirmation of a PRELOADED destination: the entry's
* canonical NAME and its curated ADDRESS (the address VERBATIM from the
* gazetteer — never the normalized form; matching normalizes, display does
* not, MS 2039 display rule / RESEARCH Pitfall 3). The honest counterpart
* renders when an id ARRIVED but does not resolve: "destination unavailable"
* — never a blank map (Phase 3 D-09 anti-blank invariant), never a
* fabricated coordinate (T-04-34).
*
* Built with createElement + textContent ONLY — an id-derived string never
* reaches innerHTML (T-04-32). Composed from the theme's existing component
* classes (.card / .card-header / .card-ref / .text-headline-md /
* .text-body-md / .text-body-sm) exactly as web/src/ui/cards.ts does — no
* new CSS, no off-token styling, no new screen: this is the Phase 3 card
* stack's existing surface carrying a different card. Every string comes
* from the strings module (COPY5 law).
*/
import type { GazetteerEntry } from "@araharah/search/src/types.ts";
import { strings, type Locale } from "../i18n/strings.ts";
/** The destination confirmation card: register line, name headline, the
* curated address verbatim. */
export function buildDestinationCard(locale: Locale, entry: GazetteerEntry): HTMLElement {
const card = document.createElement("div");
card.className = "card";
const header = document.createElement("div");
header.className = "card-header";
const ref = document.createElement("span");
ref.className = "card-ref";
ref.textContent = strings[locale].destinationHeadline;
header.appendChild(ref);
const headline = document.createElement("h2");
headline.className = "text-headline-md";
headline.textContent = entry.name;
const address = document.createElement("p");
address.className = "text-body-sm";
address.textContent = entry.address ?? "";
card.appendChild(header);
card.appendChild(headline);
card.appendChild(address);
return card;
}
/** The honest unavailable card: an id arrived and resolved to nothing.
* The boot continues into the normal staged boot around it. */
export function buildDestinationUnavailableCard(locale: Locale): HTMLElement {
const card = document.createElement("div");
card.className = "card";
const header = document.createElement("div");
header.className = "card-header";
const ref = document.createElement("span");
ref.className = "card-ref";
ref.textContent = strings[locale].destinationHeadline;
header.appendChild(ref);
const headline = document.createElement("h2");
headline.className = "text-headline-md";
headline.textContent = strings[locale].destinationUnavailableHeadline;
const body = document.createElement("p");
body.className = "text-body-md";
body.textContent = strings[locale].destinationUnavailableBody;
card.appendChild(header);
card.appendChild(headline);
card.appendChild(body);
return card;
}