15 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-destination-search-kl-gazetteer | 04 | web |
|
|
|
|
|
|
|
|
Phase 4 Plan 4: Webview Deep-Link Hand-off + Device Gate Summary
One-liner: the webview half of ONBD-02 — a two-carrier destination reader (?dest= URL
query first, signed initData.start_param second, one [A-Za-z0-9_-]{1,64} validation,
dest_ prefix stripped) resolving against the webview's own build-time copy of the shared
gazetteer into a memory-only holder, rendering the destination card (never a blank map,
never a fabricated coordinate) — plus ONBD-03's minimal Telegram-native home-screen hooks
behind a Bot API 8.0 guard with the iOS "unknown" treated as indeterminate, the BA7/BA8
drift gates proving the shipped bundle carries all 30 ids and zero crypto, the 8/8 capture
including the destination-preload leg, and the deployed origin with its blocking
six-leg device gate recorded honest-UNVERIFIED.
Performance
- Duration: ~14 min
- Started: 2026-10-23T17:31:23Z
- Completed: 2026-10-23T17:45Z (automated half; device gate pending user)
- Tasks: 3/3 automated + the blocking checkpoint escalated
- Files: 16 (6 created, 10 modified)
Accomplishments
- The deep-link hand-off is complete end-to-end in code and live on the deployed Worker:
/cari→ venue card → OPEN (?dest=<slug>) → the webview resolves the id against its own gazetteer copy → the DESTINATION card renders (verified by the capture leg'sstate=resolved+ "Suria KLCC" DOM assertion). No crypto anywhere on the path. - ONBD-03's minimal hooks are live:
wireHomeScreenguards on BOTH halves (method presence +isVersionAtLeast("8.0")), hides on "added", shows the neutral prompt on "unknown"/"missed", nothing on "unsupported", catches refused installs, and hides onhomeScreenAdded— with NO browser install APIs (HS9). - The honesty laws are mechanical: DP6 rejects traversal/XSS/empty/200-char ids on BOTH carriers; DP8/D-11 pin the honest null; BA7 proves the shipped bundle carries every committed gazetteer id (the bot/web parity gate); BA8 proves no deep-link crypto ships.
- Deploy
31138f79is live with all canaries green after re-arming Telegram-side registrations (menu button web_app, help/privacy/cari command menus).
Task Commits
| task | commit | message |
|---|---|---|
| 1 RED | 3e3d191 |
test(04-04): RED — the ONBD-02 destination-preload gate suite (DP1-DP12) |
| 1 GREEN | 4f95841 |
feat(04-04): the destination resolver — two carriers, one validation, memory only |
| 2 RED | 6e421e6 |
test(04-04): RED — the ONBD-03 home-screen gate suite (HS1-HS9) |
| 2 GREEN | c2d78b0 |
feat(04-04): ONBD-03 home-screen hooks + the boot wiring + the ?dest= harness profile |
| 3 | 747eeb7 |
test(04-04): the BA7/BA8 drift gates + the search-tree GEO3 pin + the destination-preload capture leg |
| 4 | 7b02c7d |
docs(04-04): the blocking ONBD-02/ONBD-03 device gate — deployed + canaries green, six legs UNVERIFIED pending a real device |
Verification Results
npm test -w web— 57 passed, 0 failed (DP1–DP12, HS1–HS9, BA1–BA8, GEO3 incl. the new explicitweb/src/searchpin, COPY gates over the 5 new strings, all prior suites)npm test -w bot— 65 passed, 0 failed (unaffected; the deep-link contract's bot half)npm run search:corpus— threshold PASS: top-3 68/68 (100%), negatives refused 7/7, margin 0.643 < 0.72 (D-14 close holds)npm run web:typecheck/search:typecheck— greennpm run build -w theme && npm run build -w web— green; BA7 RED against the stale pre-gazetteer bundle, GREEN after (the RED evidence for task 3)npm run capture -w web— CAPTURE OK — 8/8 non-blank captures, incl.shell-dest-preload.png(34,499 B) whose leg assertedARAHARAH-DEST state=resolvedAND the rendered card text "Suria KLCC"; GEO-03 runtime probe PASSED (storage empty)- Live-origin canaries:
getWebhookInfo.urlends/telegram/webhook(last_error is the STALE 2026-10-22 phase-3-era 500, pending 0); menu buttonweb_app→ origin ✓; commands default+ms+all_private_chats listhelp/privacy/cari✓; unsigned webhook POST → 401 ✓; the livemain-*.jschunk carries 30/30 gazetteer ids, zero crypto tokens, and the new copy ✓ MINI_APP_URL== deployed origin (binding shown in the deploy log); secrets never echoed
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] The dest capture leg's DOM check raced the boot chain
- Found during: task 3 (design review of the wait mode, pre-commit)
- Issue: the plan's
waitMode: 'dest'snippet sampledpage.textContent('body')ONCE, immediately after the ARAHARAH-DEST race. The signal fires at boot stage 13, but the destination card mounts only when the chain reaches LIVE (the boot stages replace the card stack transiently) — a one-shot sample could read the body before the card existed. - Fix: kept the plan's
state=resolvedassertion, then waited for the card text withpage.waitForFunction(...'Suria KLCC', { timeout: 15000, polling: 250 })instead of a single sample. Same assertion, race-free. Capture passed 8/8. - Files modified: web/scripts/capture.mjs
2. [Rule 3 - Blocking] Live-bot registrations were un-armed (menu button + command menu)
- Found during: task 4 (deploy canaries —
getChatMenuButtonreturned the defaultcommandstype,getMyCommandsempty) - Issue: Telegram-side bot registrations from earlier phases did not survive on the live bot; the plan requires the menu button web_app armed and the command list verified post-deploy.
- Fix: re-ran the repo's own one-time registration scripts (
node bot/scripts/set-menu-button.mjs,node bot/scripts/register-commands.mjs); re-verified viagetChatMenuButton/getMyCommands. No source changes. - Files modified: none (Telegram-side state)
3. [Planner-note] Capture-leg comment numbering
- The plan's leg snippet was commented "/* 8 —" while instructing placement after leg 6;
inserted as leg 7 with gallery-compare renumbered 8 so execution order reads correctly.
Cosmetic only; the
expectedarray and the 8/8 line match the plan verbatim.
Total deviations: 2 auto-fixed (1 × Rule 1, 1 × Rule 3) + 1 cosmetic note Impact on plan: none on scope; both fixes were required for the plan's own gates/ canaries to hold.
Auth Gates
None — no secrets handled beyond reading the local gitignored bot/.dev.vars for deploy
canary Bot API calls; the token was never echoed (only concatenated into API URLs), and
MINI_APP_URL is a public var by design.
Known Stubs
strings.*.homeScreenAlreadyAdded(web/src/i18n/strings.ts) — defined + copy-gated in both locales but with NO render site: the plan's own behaviour law makes status "added" HIDE the affordance (do not nag), so no surface can honestly display "Already on your home screen" in this phase. Reserved for a future surface that must SAY "already added" (e.g. a settings view); the string lives in the strings module so copy stays centralized.
No other stubs — every export is wired to real behavior; no TODO/FIXME/placeholder in the new tree.
Threat Flags
None — the built surface is exactly the plan's threat model (T-04-32…T-04-42), every mitigation landed WITH a gate: charset validation before any lookup (DP6, T-04-32), the signed-string-only initData parse with start_param as a pure lookup key (DP2/DP10, T-04-33), the bounded prank consequence + honest null (DP8, T-04-34), memory-only destination (DP9/DP12 + GEO3 search-tree pin + runtime probe, T-04-35), the both-halves capability guard with caught refusals (HS1/HS2/HS7, T-04-36), the never-fail status handling (HS4/HS5, T-04-37), the browser-install-API ban (HS9, T-04-38), the id-parity drift gate (BA7, T-04-39), the no-crypto bundle gate (BA8 + DP10, T-04-40), the harness DCE proof (BA5/BA6 + capture's inverse assertion, T-04-41), and the anti-fake capture race (state=resolved + DOM text, T-04-42).
TDD Gate Compliance
PASSED — tasks 1 and 2 each carry a test(...) RED commit verified failing (suite-load
failures: 33+1 and 45+1) followed by a feat(...) GREEN commit:
3e3d191→4f95841, 6e421e6→c2d78b0. Task 3 is a gates-and-tooling task (no production
source): its RED (BA7 failing against the stale pre-gazetteer web/dist, 56+1) and its
GREEN (BA1–BA8 green after the rebuild — the build output is gitignored, so no source
commit exists for the GREEN half) are recorded here and in the execution log; the git
sequence is therefore test → feat → feat → test → test → docs at plan level.
Device Gate (task 4 — BLOCKING CHECKPOINT)
.planning/phases/04-destination-search-kl-gazetteer/04-DEVICE-GATE.md records the
deploy (version 31138f79), the canary table, the origin prerequisite, and SIX physical
legs — ONBD-02 on Android / iOS / Desktop, the ONBD-03 home-screen add + launch, the
unknown-id honesty check, and the /cari Kuching honest refusal — every leg
UNVERIFIED: no physical device is attached to this environment and none was faked.
ORCHESTRATOR ACTION REQUIRED: per this wave's split of ownership, .planning/STATE.md
and .planning/ROADMAP.md were deliberately NOT touched. The device-gate blocker
("ONBD-02/ONBD-03 device legs UNVERIFIED — needs a real Android/iOS/Desktop; phase held
open") must be recorded under STATE.md "Blockers/Concerns" by the orchestrator, and
ONBD-02/ONBD-03 must NOT be marked complete in REQUIREMENTS.md until the legs pass (the
plan's resume signal: "device gate passed").
User Setup Required
Walk the six legs in 04-DEVICE-GATE.md on a real device (the bot is LIVE at
https://araharah-bot.araharah-bot.workers.dev — /cari KLCC → OPEN). Also visually
confirm @BotFather's Mini App URL equals that origin (the Bot API cannot read the
BotFather-side value; the bot-side menu button + MINI_APP_URL are confirmed equal).
Next Phase Readiness
- Phase 5 can consume
getDestination()— the boot already seeds the memory-only holder from either carrier; the origin/destination resolvers plug into the SearchSource seam. - The carrier-swap lever stands ready if device legs 1–3 fail:
miniAppUrlFor(origin, id)inbot/src/bot/commands/cari.tsis the one place the deep link is built. /pergi_keremains registered nowhere (D-01 reservation); the corpus gate and all suite gates are green for the phase-exit verifier.
Self-Check: PASSED
- All 6 created files exist on disk (verified below); all 10 modified files committed.
- All 6 task commits present in
git log(3e3d191,4f95841,6e421e6,c2d78b0,747eeb7,7b02c7d); zero file deletions in any (git diff --diff-filter=D= 0 after each). - Suites re-run at close: web 57/57, bot 65/65, corpus threshold PASS; capture 8/8; deploy live with canaries green.
Checkpoint resolution (orchestrator, 2026-10-24)
Device gate PASSED (user verdict): legs 1–4 + 6 PASS on real hardware; leg 5 user-accepted after the BotFather Main Mini App fix (automated DP coverage stands). Post-gate rounds shipped: map tilt + extrusion, /start logo, command-scope shadow law. Known-open cosmetic: client command menu (Telegram non-default setMyCommands defect; diagnosis in 04-DEVICE-GATE.md). Verification: 04-VERIFICATION.md — phase 4 PASSED.