Skip to content

feat: Smart App Banner deep-linking + universal links (iOS) and App Links (Android) for app.kilo.ai - #4783

Merged
iscekic merged 13 commits into
mainfrom
feat/web-smart-app-banner
Jul 27, 2026
Merged

feat: Smart App Banner deep-linking + universal links (iOS) and App Links (Android) for app.kilo.ai#4783
iscekic merged 13 commits into
mainfrom
feat/web-smart-app-banner

Conversation

@iscekic

@iscekic iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Batch O — Smart App Banner app-argument + Universal Links (iOS) + App Links (Android)

Widens the batch-L Smart App Banner work into full deep linking: tapping an app.kilo.ai link
on a phone opens the Kilo app on the matching screen instead of Safari/Chrome, and the banner's
OPEN button deep-links into the current page's app screen.

What ships

Commit Slice
6c729fe51 feat(app-shared): add universal link route map — single source of truth
12c08e213 feat(web): serve app site association and asset links for deep linking
4e9d5d986 feat(web): deep-link the smart app banner open button (app-argument)
14742fa41 feat(mobile): associate app.kilo.ai domain for universal and app links
980d09025 feat(mobile): route incoming web links to matching app screens

One route-map artifact in @kilocode/app-shared/universal-links generates every consumer: the
committed AASA file, assetlinks.json, the Android intentFilters, and the app's runtime URL
translator. Drift between the committed JSON files and the map is CI-caught
(apps/web/src/lib/app-site-association.test.ts); drift between the Android config literals and
the map is CI-caught (apps/mobile/src/lib/universal-link-paths.test.ts).

The route map (11 rows)

web path (app.kilo.ai) app screen
/profile Profile tab
/claw KiloClaw tab
/cloud/sessions Agents tab (cloud sessions)
/security-agent · /security-agent/findings Security Agent (personal scope)
/code-reviews · /code-reviews/* Code reviewer config (personal) · review detail
/organizations/*/security-agent (+ /findings) Security Agent (org scope)
/organizations/*/code-reviews (+ /*) Code reviewer config (org) · review detail

/code-reviews/review-md and its org twin are excluded (the review.md guide is not a
review). Auth/payment/IDE-handoff flows (/users/sign_in, /auth/*, /payments/*,
/device-auth, /sign-in-to-editor, …), /admin/*, static pages, public share pages
(/s/*, /share/*), and unmappable detail routes are deliberately not mapped — hijacking
those would break browser-based flows or land on screens that don't exist. The runtime is
synchronous redirectSystemPath (+native-intent): cold starts stash the target in a pending
slot (consumed after the auth/consent gates clear, sharing the push-notification channel —
the slot moved to src/lib/deep-link-launch.ts, with source-tag precedence so a universal
link always wins over a possibly-stale notification response); warm opens navigate directly.
Android cold start additionally reads the launch URL synchronously via getLinkingURL(),
closing expo-router's 150ms getInitialURL() race.

Decisions (recorded)

  • D1 — kilo.ai ships no association file. Its account surfaces 307 to app.kilo.ai, its
    app-sounding paths (/claw, /code-reviews) 301 to marketing pages by design, and nothing
    else has an app screen. An empty details list is dead config with a real per-install cost.
    Reasoning recorded in Kilo-Org/kilocode-landing#1798's description. If a kilo.ai path ever
    gains an app screen, adding the file is a ~10-line PR.
  • D2 — app-argument on exactly four surfaces (/profile, /code-reviews,
    /security-agent/findings, /cloud/sessions) via smartAppBannerItunes(path), which throws
    at build time on any path the app can't resolve. /claw and /security-agent are excluded
    (their only metadata hosts wrap unmapped children); dynamic detail routes stay appId-only.
  • D3 — only paths the app genuinely renders are mapped (see exclusions above).

Native-build note (not OTA-updatable)

associatedDomains, the intent filters, and the iOS entitlement are native changes: they take
effect in the next native build, not via OTA. Manual provisioning (stacked on #4779's
steps): enable the Associated Domains capability on the App ID; EAS regenerates the
provisioning profile on the next remote-credentials build.

Composition note for #4779 (share target)

apps/mobile/src/app/+native-intent.tsx is created by both PRs. Whichever lands second
composes both concerns in one redirectSystemPath, share-intent check first (a share URL
must return early before web-path mapping), per the comment in the file.

Verification

Automated (green now):

  • packages/app-shared: 258 tests — every row, every exclusion, wildcard single-segment
    semantics, trailing slashes, kiloapp:// forms, foreign hosts, garbage.
  • apps/web: 17 tests — committed AASA/assetlinks vs the route map (drift guard), banner
    helper both ways + unmapped-throws guard.
  • apps/mobile: 1894 tests — handler cold/warm/falsy/passthrough invariants, pending-slot
    precedence (both write orders), synchronous launch capture, the intent-filter-literal drift
    test; existing notification suites unweakened.
  • npx expo config --type public shows applinks:app.kilo.ai + all 11 pathPatterns;
    expo prebuild emits applinks:app.kilo.ai in the iOS entitlements and the
    autoVerify intent-filter with all 11 paths in AndroidManifest.xml.
  • Checks: pnpm test && pnpm typecheck && pnpm lint && pnpm check:unused (apps/mobile);
    pnpm test -- <new suites> && pnpm typecheck (apps/web); pnpm test && pnpm typecheck
    (packages/app-shared); oxfmt clean.

On-device (this PR's verifiable scope): app-side resolution via the custom scheme on the
iOS simulator (simctl openurl "kiloapp:///<path>" — HTTPS simctl openurl is NOT a universal
link without a CDN-verified AASA and must not be used as an oracle) and
adb shell am start -W -a android.intent.action.VIEW -d "<url>" com.kilocode.kiloapp on the
emulator. Sequences: signed-in warm, signed-in cold, signed-out cold (target survives login),
consent cold; tab-root rows and deep rows verified separately; an unmapped URL resolves to
null with no navigation. Results (all PASS):

iOS simulator (dev client):

  • Warm, signed in — full matrix: /profile, /claw, /cloud/sessions (tab roots, verified
    separately), /security-agent/findings, /code-reviews/rev_e2e_1 (bogus id → the screen's
    own query-error not-found), /organizations/o1/security-agent.
  • Cold, signed in: kiloapp:///profile → Profile tab; kiloapp:///security-agent/findings
    Findings.
  • Delivery-form pin: both kiloapp:///profile and kiloapp://profile resolve (parser
    normalises both forms).
  • Signed-out cold: deep link → login gate → OTP sign-in → Findings (pending slot consumed
    after the gate). Harness note: the scripted login helper wipes the slot (its preflight
    re-opens the dev client with a different Metro URL string, reloading the JS process) — proven
    with a manual OTP that skips the preflight.
  • Consent-required cold: fresh user → deep link → login → OTP → "Accept and continue" →
    Findings.
  • Unmapped (/admin, /code-reviews/review-md): matcher returns null, no navigation
    (expo-router +not-found) — the documented accepted equivalence.

Android emulator (dev client, am start VIEW intents on https://app.kilo.ai URLs):

  • Cold, signed in: /profile → Profile tab; /security-agent/findings → Findings — the
    synchronous getLinkingURL() capture survives Android cold start (plan risk R7 retired).
  • Warm: /cloud/sessions → Agents tab.
  • Unmapped: /admin → no mapped screen (+not-found), accepted as documented.
  • pm get-app-links lists app.kilo.ai as unverified — expected locally; Google's verifier
    is post-deploy and Play builds additionally need manual step 4 below.

Deferred to post-merge/post-release (cannot be verified locally — Apple/Google must fetch
the association files from the deployed domain):

  1. Deploy app.kilo.ai; curl -sI both /.well-known/apple-app-site-association (200,
    content-type: application/json, no redirect) and /.well-known/assetlinks.json.
  2. Validate with Apple's AASA validator and Google's Digital Asset Links API.
  3. Wait for Apple's CDN to pick up the AASA (up to ~24h; it is served from Apple's cache).
  4. Append the Google Play App Signing SHA-256 (Play Console → Test and release → App
    integrity → App signing key certificate) to sha256_cert_fingerprints in
    apps/web/public/.well-known/assetlinks.json and redeploy. The shipped fingerprint is the
    verified EAS upload certificate (two independent sources agree), which covers EAS
    internal-distribution and dev-client builds. Until the Play fingerprint is appended, App
    Links will not verify for Play-store-installed builds
    — the single manual input this
    batch needs.
  5. Ship the next native build (entitlement + intent filters are native).
  6. Test a real link tap on TestFlight and an internal-track Android build.

Non-goals (documented)

  • Any kilo.ai association file (D1). Deferred (install-then-route) deep links. Custom in-app
    link previews. An Android Smart App Banner (Safari feature; no Android analogue).
    webcredentials. Mapping detail routes whose app screen needs an id the web URL lacks.
  • Deep links surviving a warm open while a root gate is showing (auth/consent/force-update):
    target is lost — identical to pre-feature behaviour, not a regression.
  • Deep links surviving a mandatory force-update: structurally impossible (clearing the gate
    means a new binary, hence a new JS process).
  • staging-app.kilo.ai association: app-argument correctly points at the staging origin via
    APP_URL, but staging is not in associatedDomains, so the deep link is simply not honoured
    there.

Accepted residuals

  • The AASA components * is an Apple glob that crosses / while the app matcher is
    single-segment — a deliberate, analysed superset: every URL in the gap is already a web 404,
    so the app landing on its own +not-found is an acceptable equivalence (no browser-handoff
    fallback built).
  • The AASA Content-Type header cannot be unit-tested (Next runtime behaviour); it is covered
    by the post-deploy curl -I step above.
  • captureLaunchDeepLink() is not try/caught at module scope: its throw surface is nil
    (bundled native module, pure never-throwing parser, trivial setter), so the
    never-brick-launch guarantee is preserved.

@iscekic iscekic self-assigned this Jul 26, 2026
@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review — posted because the automated reviewer posts no findings on this repository's PRs; this is not a Kilobot review. A fresh mobile-reviewer agent (kilo/moonshotai/kimi-k3, high reasoning) reviewed the full diff git diff origin/main...HEAD at head 97b0420ed against the PR's acceptance criteria. Its verdict, verbatim:

No findings.

Residual risks (non-blocking):

  • Typecheck of the final head against Next 16's Metadata type was accepted as given per the brief; if CI on 97b0420ed has not run pnpm typecheck, that is the one mechanical check still worth confirming.
  • The tag renders on all deployments including staging (deliberate, no environment-conditional rendering per the plan); iOS Safari on staging will point to the production App Store listing.

Context from the reviewer's notes: it independently confirmed the new line is the only itunes/apple-itunes-app declaration in apps/web/src, that no apps/web/AGENTS.md invariant is violated, and that four-state review is structurally not applicable (Apple renders the banner natively; no app-controlled UI exists). On the first residual risk: pnpm typecheck was run locally on the exact head (exit 0) and the CI typecheck job is running on the same head, so the mechanical check is covered. No actionable findings; nothing to repair.

@iscekic iscekic changed the title feat(web): add Apple Smart App Banner to app.kilo.ai feat: Smart App Banner deep-linking + universal links (iOS) and App Links (Android) for app.kilo.ai Jul 26, 2026
Comment thread packages/app-shared/src/universal-links/routes.ts Outdated
Comment thread apps/mobile/src/lib/deep-link-launch.ts
Comment thread apps/mobile/src/app/_layout.tsx
@kilo-code-bot

kilo-code-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the last review (037353d78) is a mechanical test-file split — moving the applyAgentPushOptimistic/rollbackAgentPushOptimistic test blocks verbatim from agent-push-preference.test.ts into a new agent-push-preference.optimistic.test.ts to stay under a max-lines cap — with no logic changes and no risk to the deep-linking feature this PR ships.

Files Reviewed (2 files changed since last review)
  • apps/mobile/src/lib/hooks/agent-push-preference.optimistic.test.ts - 0 issues (new file; content is a byte-for-byte relocation of existing test blocks, no assertions changed)
  • apps/mobile/src/lib/hooks/agent-push-preference.test.ts - 0 issues (only the relocated blocks removed; remaining tests unchanged)

Note: All other files that appeared in the raw git diff since the previous review commit (app.config.ts, +native-intent.tsx, _layout.tsx, plus dozens of unrelated files) came in via a Merge origin/main commit pulling in unrelated PRs (#4779, #4772, #4771), not from new work on this branch; none of this PR's own universal-links/deep-linking files (routes.ts, deep-link-launch.ts, deep-link-handler.ts, universal-link-paths.js, AASA/assetlinks files) changed since the last review. The previously reported findings on routes.ts (cross-contamination), deep-link-launch.ts:69 (unguarded launch capture), and _layout.tsx:346 (stale re-navigation) remain as already-triaged/resolved threads on this PR and are not re-evaluated here.

Previous Review Summaries (5 snapshots, latest commit 9202b5f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 9202b5f)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit (9202b5fea) rewrites substituteCaptures as a single left-to-right pass that never rescans inserted capture text, fixing the previously reported cross-contamination between two-wildcard route captures; no new issues were found in the two changed files.

Files Reviewed (2 files changed since last review)
  • packages/app-shared/src/universal-links/routes.ts - 0 issues (previously reported WARNING on sequential replaceAll cross-contamination is fixed: substituteCaptures now walks the template once with a cursor, inserting each capture verbatim without re-scanning it, so a captured segment containing a literal <n> token can no longer be substituted again in a later pass)
  • packages/app-shared/src/universal-links/routes.test.ts - 0 issues (new test explicitly covers a capture containing a literal <2>/<1> token staying verbatim across both capture positions)

Note: The previously reported CRITICAL in apps/mobile/src/lib/deep-link-launch.ts:69 and WARNING in apps/mobile/src/app/_layout.tsx:246 are in files unchanged by this commit; both threads were already triaged/resolved by the author in prior replies and are not re-evaluated here.

Previous review (commit 4ac8256)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The highest-risk issue remains an unguarded native-bridge call in captureLaunchDeepLink() that can crash app launch; this incremental commit fixed the _layout.tsx stale-restash race (by skipping cold-path restash once the launch capture already handled the link) but only partly fixed substituteCaptures — it now inserts captures literally (no $-pattern interpretation) yet still cross-contaminates two-wildcard routes via sequential replaceAll.

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/lib/deep-link-launch.ts 69 captureLaunchDeepLink() has no try/catch and is called unguarded at module scope; a native-module failure in readLaunchUrl() can crash app launch

WARNING

File Line Issue
packages/app-shared/src/universal-links/routes.ts 258 substituteCaptures now inserts captures literally (fixed $-pattern interpretation) but still uses sequential replaceAll per capture index, so a captured segment containing a literal <n> token can still cross-contaminate a later substitution on two-wildcard routes
Files Reviewed (5 files changed since last review)
  • apps/mobile/src/lib/deep-link-handler.ts - 0 issues (fixed: redirectSystemPath's cold path now skips setPendingDeepLink when wasLaunchLinkHandled() is true, closing the previously reported stale-restash race with the launch-capture write)
  • apps/mobile/src/lib/deep-link-handler.test.ts - 0 issues (new tests cover the dedup skip and the found-nothing fallback)
  • apps/mobile/src/lib/deep-link-launch.ts - 1 issue (new wasLaunchLinkHandled() export is correct; previously reported CRITICAL on captureLaunchDeepLink() at line 69 is unchanged and still applies)
  • packages/app-shared/src/universal-links/routes.ts - 1 issue (substituteCaptures switched to a function replacer, fixing $&/$`/$' interpretation; the previously reported cross-contamination WARNING is unchanged in substance and now re-anchored to line 258)
  • packages/app-shared/src/universal-links/routes.test.ts - 0 issues (new test covers $-pattern literalness only, not the still-open cross-contamination case)

Fix these issues in Kilo Cloud

Previous review (commit 13ac1c1)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The highest-risk issue remains an unguarded native-bridge call in captureLaunchDeepLink() that can crash app launch; this incremental commit only refactored indexed-access to .entries() in routes.ts's matcher and did not change the substitution logic, so the cross-contamination WARNING still applies (re-anchored to its new line).

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/lib/deep-link-launch.ts 69 captureLaunchDeepLink() has no try/catch and is called unguarded at module scope; a native-module failure in readLaunchUrl() can crash app launch

WARNING

File Line Issue
packages/app-shared/src/universal-links/routes.ts 255 Sequential replaceAll substitution in substituteCaptures can cross-contaminate captures on two-wildcard routes if a captured segment contains a literal <n> token
apps/mobile/src/app/_layout.tsx 246 Possible stale re-navigation if expo-router's own async cold-path write races with this effect's consumption of the pending deep-link slot
Files Reviewed (1 file changed since last review)
  • packages/app-shared/src/universal-links/routes.ts - 0 new issues (converted indexed loops in matchPattern/substituteCaptures to .entries() with an added unreachable-guard on undefined path segments; behavior unchanged, previously reported WARNING now at line 255 still applies)

Fix these issues in Kilo Cloud

Previous review (commit 9383120)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The highest-risk issue remains an unguarded native-bridge call in captureLaunchDeepLink() that can crash app launch, contradicting the PR's own "never-brick-launch" invariant; this incremental commit only removed non-null assertions in routes.ts and did not touch these findings.

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/lib/deep-link-launch.ts 69 captureLaunchDeepLink() has no try/catch and is called unguarded at module scope; a native-module failure in readLaunchUrl() can crash app launch

WARNING

File Line Issue
packages/app-shared/src/universal-links/routes.ts 252 Sequential replaceAll substitution in substituteCaptures can cross-contaminate captures on two-wildcard routes if a captured segment contains a literal <n> token
apps/mobile/src/app/_layout.tsx 246 Possible stale re-navigation if expo-router's own async cold-path write races with this effect's consumption of the pending deep-link slot
Files Reviewed (1 file changed since last review)
  • packages/app-shared/src/universal-links/routes.ts - 0 new issues (removed unnecessary non-null assertions on lines 218-219, 252; behavior unchanged, previously reported WARNING on line 252 still applies)

Fix these issues in Kilo Cloud

Previous review (commit 980d090)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The highest-risk issue is an unguarded native-bridge call in captureLaunchDeepLink() that can crash app launch, directly contradicting the PR's own "never-brick-launch" invariant.

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/mobile/src/lib/deep-link-launch.ts 69 captureLaunchDeepLink() has no try/catch and is called unguarded at module scope; a native-module failure in readLaunchUrl() can crash app launch

WARNING

File Line Issue
packages/app-shared/src/universal-links/routes.ts 252 Sequential replaceAll substitution in substituteCaptures can cross-contaminate captures on two-wildcard routes if a captured segment contains a literal <n> token
apps/mobile/src/app/_layout.tsx 246 Possible stale re-navigation if expo-router's own async cold-path write races with this effect's consumption of the pending deep-link slot
Files Reviewed (28 files)
  • apps/mobile/app.config.ts - 0 issues
  • apps/mobile/src/app/+native-intent.tsx - 0 issues
  • apps/mobile/src/app/_layout.tsx - 1 issue
  • apps/mobile/src/lib/deep-link-handler.test.ts - 0 issues
  • apps/mobile/src/lib/deep-link-handler.ts - 0 issues
  • apps/mobile/src/lib/deep-link-launch.test.ts - 0 issues
  • apps/mobile/src/lib/deep-link-launch.ts - 1 issue
  • apps/mobile/src/lib/notifications.ts - 0 issues
  • apps/mobile/src/lib/pending-navigation.test.ts - 0 issues
  • apps/mobile/src/lib/pending-navigation.ts - 0 issues
  • apps/mobile/src/lib/universal-link-paths.js - 0 issues
  • apps/mobile/src/lib/universal-link-paths.test.ts - 0 issues
  • apps/web/jest.config.ts - 0 issues
  • apps/web/next.config.mjs - 0 issues
  • apps/web/public/.well-known/apple-app-site-association - 0 issues
  • apps/web/public/.well-known/assetlinks.json - 0 issues
  • apps/web/src/app/(app)/cloud/sessions/layout.tsx - 0 issues
  • apps/web/src/app/(app)/code-reviews/page.tsx - 0 issues
  • apps/web/src/app/(app)/profile/page.tsx - 0 issues
  • apps/web/src/app/(app)/security-agent/findings/page.tsx - 0 issues
  • apps/web/src/app/layout.tsx - 0 issues
  • apps/web/src/lib/app-site-association.test.ts - 0 issues
  • apps/web/src/lib/smart-app-banner.test.ts - 0 issues
  • apps/web/src/lib/smart-app-banner.ts - 0 issues
  • packages/app-shared/package.json - 0 issues
  • packages/app-shared/src/universal-links/index.ts - 0 issues
  • packages/app-shared/src/universal-links/routes.test.ts - 0 issues
  • packages/app-shared/src/universal-links/routes.ts - 1 issue

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 40 · Output: 9.1K · Cached: 1.1M

Review guidance: REVIEW.md from base branch main

@iscekic

iscekic commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Standin review comment — PR #4783 @ 980d090

(bot) Standin review of record (Kilobot replacement, per workflow override) — fresh
mobile-reviewer (kimi-k3, high) over the full PR diff origin/main...980d09025.

Verdict: 1 finding (medium) — triaged below; not actionable on this PR

Finding (medium) — apps/web/public/.well-known/assetlinks.json:8: the file lists only
the EAS upload-certificate SHA-256, but Play-installed builds are re-signed by Google Play
App Signing, so Android App Links verification fails on Play-store installs; the reviewer asks
for the Play app-signing fingerprint to be added before merge.

Triage: rejected as an actionable PR remark — the finding restates a deliberate, documented
plan decision, and its required artifact is not obtainable from this environment.

  1. This exact gap was analysed and decided at plan time (risk R4, "ship wired, do not guess"):
    assetlinks.json ships the verified EAS upload certificate — two independent sources
    agree on it (EAS GraphQL androidAppCredentials for project
    2cf05e39-90b5-48a5-a8a5-e0b3423cf3f4, and the byte-identical value in the human-authored
    April prior-art commit 9467096c4). That certificate is what signs every EAS build
    (development/preview/production upload), so EAS internal-distribution and dev-client builds
    verify today.
  2. The Play App Signing fingerprint exists only in Play Console (Test and release → App
    integrity). It is not discoverable from any local evidence: no Play service-account
    credential is present in this environment, and the one EAS holds is a secret we must not
    extract (and it would not carry App Integrity read scope anyway). The reviewer’s "add it
    before merge" is therefore not executable here — it is a human step, not a code change.
  3. The PR description already records exactly this as manual post-deploy step 4 with the
    same warning the finding asks for ("Until the Play fingerprint is appended, App Links will
    not verify for Play-store-installed builds — the single manual input this batch needs").
    Appending the fingerprint is one string in an array — no code change — and blocking the
    merge on it buys nothing: the association file is additive and inert until the domain
    association verifies.
  4. On non-verified installs the failure mode is the system disambiguation dialog, not a crash
    or silent misroute; nothing in this PR makes any existing flow worse.

No code change. The finding stays open as the recorded manual step, which the human approver
can expedite post-merge.

Residual risks noted by the reviewer (accepted, not findings)

  • Local web typecheck signal was partially blocked by a torn generated file
    (apps/web/.next/dev/types/routes.d.ts from a running dev server; gitignored). CI typecheck
    on the clean checkout passed; zero errors in PR source.
  • OS-delivery edges (AASA fetch semantics, Play-store autoVerify, cold getLinkingURL()
    timing) are device/manual-verification surfaces by design; the E2E run covers the local
    oracles.
  • Android intent filters cannot express exclusions, so …/code-reviews/review-md taps open the
    app to its normal bootstrap screen instead of Safari (iOS excludes it via AASA). Documented
    trade-off.

Checks the reviewer ran

app-shared 258 tests, mobile 1894 tests, web targeted 17 tests — green; mobile
typecheck/lint/check:unused/format — clean; expo-router 55.0.16 / expo-linking 55.0.16
contracts re-verified against installed sources.

Comment thread packages/app-shared/src/universal-links/routes.ts Outdated
Comment thread packages/app-shared/src/universal-links/routes.ts Outdated
@iscekic

iscekic commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review round 3 (head 13ac1c1, kilo/moonshotai/kimi-k3 high, full diff vs origin/main) returned one low finding, now fixed:

  • low — substituteCaptures interprets $ substitution patterns in captured path segments (packages/app-shared/src/universal-links/routes.ts): String.prototype.replaceAll runs ECMA-262 GetSubstitution on the replacement string, so $&, $`, $' inside a URL-captured segment were interpreted instead of inserted literally. Unlike the literal-<2> scenario (unreachable — delivery layers percent-encode), $ survives URLs verbatim, making the contamination reachable. Fixed in c6a1a0d (function replacement bypasses GetSubstitution; regression tests pin $&, $`, $' captures).

The same review verified: all mapped target routes exist; no middleware/rewrite interferes with /.well-known serving; exactly four banner surfaces plus site-wide root, all server components; notification slot moved (not duplicated) with identical consume behavior; lint-repair matcher index-free with genuinely unreachable guard and unchanged semantics; 258 + 1894 + 17 tests green.

Separately, the four Kilobot threads were triaged in-thread: the stale-restash warning was valid and is fixed in 4ac8256; the unguarded-captureLaunchDeepLink critical was rejected as the documented accepted residual; both replaceAll warnings resolved via c6a1a0d. A fresh standin round 4 is running on the new head 4ac8256.

@iscekic

iscekic commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review round 4 (head 4ac82560f, kilo/moonshotai/kimi-k3 high, full diff vs origin/main) returned one low finding, now fixed:

  • low — substituteCaptures rescanned inserted capture text (packages/app-shared/src/universal-links/routes.ts): sequential per-capture replaceAll passes re-substituted a literal <n> contained in an earlier capture (e.g. org segment <2> → both positions replaced). Reachability is narrow — https delivery percent-encodes angle brackets, so only crafted kiloapp:// URLs or explicit intents trigger it — and impact is a wrong-but-benign navigation target. Fixed in 9202b5fea: substitution is now a single pass over the template, with regression tests pinning captures containing <2> and <1> (260 app-shared tests pass).

The reviewer verified all prior fixes (round-3 GetSubstitution $-class, launch-capture dedup, index-free matcher) against their regression tests and re-checked the vendored expo-router 55.0.16 / expo-linking contracts; identifiers, AASA ordering, and banner metadata surfaces all cross-checked clean. A fresh standin round 5 is running on the fixed head.

@iscekic

iscekic commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

(bot) Standin review round 5 (head 9202b5fea, kilo/moonshotai/kimi-k3 high, full diff vs origin/main): No findings.

The reviewer traced the round-4 single-pass substituteCaptures exhaustively — capture text lands only in the parts array and is structurally never rescanned at any position; invalid/unknown tokens (<>, <x>, <0>, <3> on two-capture rows, unterminated <, non-canonical numerics) stay verbatim without throwing; both regression pins verified. It also re-verified the launch-capture dedup guard (latch cannot suppress a needed stash; notification precedence unaffected), the lint repairs, notification-slot move, runtime invariants (synchronous, falsy-on-handled, no cold navigate), AASA ordering, and bundle-ID consistency. Checks re-run by the reviewer: app-shared 260 tests, apps/mobile 1896 tests, apps/web new suites 17 tests, typecheck in all three packages, lint/check:unused/format:check in apps/mobile — all clean.

This concludes the review loop: CI is fully green on 9202b5fea and the device E2E matrix (results in the PR description) passed on both platforms, including the consent-required cold-open path and a post-fix cold-start insurance re-run.

@iscekic
iscekic enabled auto-merge (squash) July 27, 2026 09:12
@iscekic
iscekic merged commit 1ee4b1e into main Jul 27, 2026
65 checks passed
@iscekic
iscekic deleted the feat/web-smart-app-banner branch July 27, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants