Skip to content

POC: Capacitor.js native Android app#1766

Draft
Hugo0 wants to merge 1 commit intomainfrom
native-poc
Draft

POC: Capacitor.js native Android app#1766
Hugo0 wants to merge 1 commit intomainfrom
native-poc

Conversation

@Hugo0
Copy link
Contributor

@Hugo0 Hugo0 commented Mar 19, 2026

What this is

Proof-of-concept for wrapping the Peanut Next.js app as a native Android app using Capacitor.js 8. The primary goal is testing whether passkeys (WebAuthn) work inside a Capacitor WebView — since passkeys are our auth mechanism and the biggest unknown for going native.

This is a POC/demo branch — not intended for merge as-is.

Architecture overview

Next.js app (static export) → Capacitor WebView → Android APK

The approach uses output: 'export' to produce a fully static build, then Capacitor serves it inside an Android WebView. During development, the WebView points at an ngrok URL for live testing with HTTPS (required for passkey rpId matching).

What's included

Capacitor setup

  • capacitor.config.ts — App config (com.peanut.app), points WebView at ngrok URL for dev
  • android/ — Full Android project scaffold (Gradle, manifest, splash screens, icons)
  • package.json — New scripts: build:native, cap:sync, cap:open:android, cap:run:android
  • Dependencies: @capacitor/core, @capacitor/android, @capacitor/cli, capacitor-webauthn

Static export build pipeline

  • next.config.native.js — Separate Next.js config for native builds with output: 'export', unoptimized images, relative asset paths, NEXT_PUBLIC_IS_NATIVE_BUILD env flag
  • scripts/native-build.js — Build orchestrator that:
    1. Temporarily disables server-only features (API routes, sitemap, robots, manifest)
    2. Wraps client-side dynamic [param] routes with generateStaticParams() stubs
    3. Runs next build with static export
    4. Restores all files to original state (cleanup on success or failure)

Native POC test page (/native-poc)

  • src/app/(mobile-ui)/native-poc/page.tsx (848 lines) — Comprehensive diagnostic page that tests:
    • Environment detection: WebAuthn API presence, secure context, Capacitor detection, platform authenticator availability
    • Passkey registration: Creates a passkey via navigator.credentials.create() with com.peanut.app rpId
    • Passkey signing: Tests credential retrieval and signing via navigator.credentials.get()
    • ZeroDev integration: Full end-to-end test — creates a ZeroDev kernel account from the passkey, builds a UserOp, and sends a test transaction (0 ETH on Arbitrum)
    • All steps show detailed logs/diagnostics for debugging WebView quirks

Passkey support utilities

  • src/hooks/usePasskeySupport.ts — Hook for checking WebAuthn + conditional mediation support (has a POC bypass that always returns true)
  • src/utils/passkeyPreflight.ts — Detailed preflight checks: HTTPS, Chrome version, Android version, UVPA, conditional mediation, device model extraction (also has POC bypass)

Android Digital Asset Links

  • public/.well-known/assetlinks.json — Updated with app signing certificate fingerprint for passkey rpId association

Minor changes

  • Small tweaks to next.config.js, route constants, home page, setup page to accommodate native context detection

How to run

# 1. Build static export
pnpm build:native

# 2. Sync with Android project
pnpm cap:sync

# 3. Open in Android Studio
pnpm cap:open:android

# 4. Run on device/emulator (or use Android Studio)
pnpm cap:run:android

For passkey testing, the WebView needs HTTPS with a domain that matches assetlinks.json. The config currently uses an ngrok tunnel for this — update the URL in capacitor.config.ts to your own ngrok instance.

Key findings / notes

  • Capacitor 8 WebView does support WebAuthn, but requires proper Digital Asset Links setup for the rpId
  • The capacitor-webauthn plugin provides native passkey bridge as fallback
  • Static export requires disabling all server-side features (API routes, SSR, dynamic routes) — the build script handles this automatically
  • The POC page (/native-poc) has step-by-step diagnostics to debug any passkey/signing issues on real devices

Known TODOs (not for this PR)

  • Remove hardcoded ngrok URL from capacitor.config.ts
  • Remove POC bypass in usePasskeySupport.ts and passkeyPreflight.ts
  • iOS project setup
  • App Store / Play Store signing config
  • Deep link handling
  • Push notifications via Capacitor plugin

@vercel
Copy link

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Error Error Mar 19, 2026 10:07am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

This pull request introduces comprehensive Android support via Capacitor, adds a native build orchestration script, integrates WebAuthn functionality via capacitor-webauthn, and creates a "Native PoC" page demonstrating end-to-end passkey workflows with ZeroDev account abstraction. It also bypasses passkey support detection checks across the codebase.

Changes

Cohort / File(s) Summary
Android Project Configuration
android/build.gradle, android/gradle.properties, android/settings.gradle, android/variables.gradle, android/capacitor.settings.gradle, android/gradle/wrapper/gradle-wrapper.properties
Gradle build system setup with SDK versions, dependency repositories, AndroidX support, heap memory configuration, and Capacitor module inclusion via pnpm node_modules paths.
Android Gradle Wrapper Scripts
android/gradlew, android/gradlew.bat
POSIX and Windows shell/batch scripts to bootstrap Gradle wrapper with Java detection, environment handling, and platform-specific path resolution.
Android App Gradle & Build
android/app/build.gradle, android/app/capacitor.build.gradle, android/app/proguard-rules.pro
App-level Gradle configuration with Android plugin, SDK settings, Capacitor dependencies, Google Services conditional integration, and scaffolded ProGuard rules.
Android App Ignore Rules
android/.gitignore, android/app/.gitignore
Git ignore patterns for Android build artifacts, Gradle cache, IDE metadata, and selective Capacitor asset exclusions.
Android App Manifest & Entry Point
android/app/src/main/AndroidManifest.xml, android/app/src/main/java/com/peanut/app/MainActivity.java
App manifest declaring MainActivity with LAUNCHER intent filter, FileProvider configuration, and INTERNET permission; MainActivity extending Capacitor's BridgeActivity.
Android App Layouts & UI Resources
android/app/src/main/res/layout/activity_main.xml, android/app/src/main/res/drawable/ic_launcher_background.xml, android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml, android/app/src/main/res/mipmap-anydpi-v26/ic_launcher*.xml, android/app/src/main/res/values/strings.xml, android/app/src/main/res/values/styles.xml, android/app/src/main/res/values/ic_launcher_background.xml, android/app/src/main/res/xml/file_paths.xml
WebView-based layout, adaptive/legacy launcher icons, color and string resources, AppTheme styling, and FileProvider URI configuration.
Android Test Files
android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java, android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
JUnit unit test and AndroidJUnit4 instrumented test with package name and arithmetic assertions.
Capacitor & Native Configuration
capacitor.config.ts
Capacitor app configuration with ngrok development server URL, Android mixed-content allowance, web debugging, and app identity settings.
Next.js Native Build
next.config.native.js
Static export configuration for Capacitor builds with Git commit hash injection, bundle analyzer setup, OpenTelemetry instrumentation warning suppression, and package import optimization.
Next.js Standard Config Updates
next.config.js
Conditional Next.js configuration for native builds: output mode, image optimization, asset prefix, and trailing slash adjustments; removed assetLinks rewrite for native PoC path.
Native Build Orchestration Script
scripts/native-build.js
Node.js script disabling server-only routes/API endpoints, wrapping client-component pages with static generation wrappers, and invoking the native build process with environment isolation and cleanup.
Package & Asset Configuration
package.json, public/.well-known/assetlinks.json
Added npm scripts and Capacitor/WebAuthn dependencies; replaced web-based assetlinks with Android app identifiers and certificate fingerprints.
Native PoC Page
src/app/(mobile-ui)/native-poc/page.tsx
Comprehensive WebAuthn and ZeroDev integration page: runtime diagnostics, browser/native credential creation, passkey registration with RP ID override, message signing via native plugin with DER-to-signature conversion, and sponsored ERC-4337 user operations on Arbitrum with paymaster.
Passkey Support Detection Bypass
src/hooks/usePasskeySupport.ts, src/utils/passkeyPreflight.ts, src/app/(setup)/setup/page.tsx
Hardcoded returns from passkey capability checks to always report isSupported: true, bypassing runtime WebAuthn/secure-context/conditional-mediation validation.
Route & Component Updates
src/constants/routes.ts, src/app/page.tsx, src/app/(mobile-ui)/card/page.tsx, src/app/lp/card/CardLandingPage.tsx, src/components/Home/HomePerkClaimSection.tsx
Public route regex extended for native-poc, added landing-page link to PoC, wired onContinue prop for card flow, passed heading prop to FAQ panel, and updated WebSocket username handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'POC: Capacitor.js native Android app' accurately summarizes the main change—adding a proof-of-concept Capacitor Android integration.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing Capacitor setup, static export pipeline, native POC test page, and implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch native-poc
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch native-poc
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Home/HomePerkClaimSection.tsx (1)

52-66: ⚠️ Potential issue | 🟡 Minor

Resolve the Prettier failure before merge readiness.

CI reports a formatting warning; please run the formatter on this file/change so the pipeline is clean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Home/HomePerkClaimSection.tsx` around lines 52 - 66, The file
fails Prettier formatting; run your project formatter (e.g., prettier --write)
on HomePerkClaimSection.tsx or reformat the useWebSocket call and its callback
so it matches project style (ensure consistent indentation, spacing and trailing
commas) around useWebSocket, useCallback, queryClient.invalidateQueries,
setArrivalSource and setArrivalPhase; then re-run CI to confirm the Prettier
warning is resolved.
🧹 Nitpick comments (6)
android/app/src/main/res/layout/activity_main.xml (1)

1-12: Layout may be unused by Capacitor's BridgeActivity.

Capacitor's BridgeActivity manages its own WebView programmatically and does not use this layout file. This is harmless boilerplate from Android Studio scaffolding, but can be removed to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/res/layout/activity_main.xml` around lines 1 - 12, The
activity_main.xml layout includes a WebView that is likely unused because
Capacitor's BridgeActivity manages its WebView programmatically; remove or
archive this boilerplate layout to avoid confusion—specifically delete or move
android/app/src/main/res/layout/activity_main.xml (the <WebView> entry and
surrounding CoordinatorLayout) or add a comment indicating it is unused, and
ensure no references to activity_main.xml remain in AndroidManifest or
MainActivity so BridgeActivity continues to supply the WebView.
public/.well-known/assetlinks.json (1)

1-10: Debug signing fingerprint - must be replaced for production.

The sha256_cert_fingerprints value appears to be a development/debug signing key fingerprint. For production releases, this must be updated to match the release keystore fingerprint, otherwise passkey association will fail on production builds.

Consider adding a comment or documentation noting this requirement, or automate fingerprint injection during build pipelines.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/.well-known/assetlinks.json` around lines 1 - 10, The assetlinks.json
currently contains a debug signing fingerprint in the sha256_cert_fingerprints
array which must be replaced for production; update the value in
public/.well-known/assetlinks.json so the "sha256_cert_fingerprints" entry under
the "target" for package_name "com.peanut.app" uses the release keystore SHA-256
fingerprint (or modify your build to inject the correct fingerprint), and
optionally add a brief comment or README note near assetlinks.json indicating
fingerprints must be replaced/automatically injected for production releases.
src/app/page.tsx (1)

197-204: Temporary debug link on production landing page.

The comment correctly marks this as temporary, but since it's on the main landing page, consider gating it behind an environment check to prevent accidental production deployment:

🛡️ Proposed environment-gated rendering
-            {/* TEMPORARY: Native PoC Link - Remove after testing */}
-            <Link
-                href="/native-poc"
-                className="fixed top-4 right-4 z-50 rounded-lg border-2 border-dashed border-purple-400 bg-purple-100 px-4 py-2 text-sm font-bold text-purple-700 shadow-lg"
-            >
-                🧪 Native PoC
-            </Link>
+            {/* TEMPORARY: Native PoC Link - Remove after testing */}
+            {process.env.NODE_ENV === 'development' && (
+                <Link
+                    href="/native-poc"
+                    className="fixed top-4 right-4 z-50 rounded-lg border-2 border-dashed border-purple-400 bg-purple-100 px-4 py-2 text-sm font-bold text-purple-700 shadow-lg"
+                >
+                    🧪 Native PoC
+                </Link>
+            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/page.tsx` around lines 197 - 204, The temporary Native PoC Link JSX
in page.tsx is rendered unconditionally on the landing page; wrap the <Link ...>
block in an environment check so it only renders in non-production or when an
explicit opt-in flag is set (e.g., process.env.NODE_ENV !== 'production' or
process.env.NEXT_PUBLIC_ENABLE_NATIVE_POC === 'true'). Update the conditional
where the Link component appears to gate rendering, and ensure you use a
NEXT_PUBLIC_ prefixed env var if the check runs client-side so the variable is
available in the browser; default to not rendering when the env var is missing.
android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java (1)

1-17: Drop or rename the template test scaffold.

This is still the stock Capacitor sample (com.getcapacitor.myapp + 2 + 2 == 4), so it doesn't protect the new Android integration and will just add noise once real tests land.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java` around
lines 1 - 17, The current unit test is the default Capacitor template (package
com.getcapacitor.myapp, class ExampleUnitTest, method addition_isCorrect) and
should be removed or replaced; either delete this test file entirely or rename
ExampleUnitTest and replace addition_isCorrect with meaningful Android
integration/unit tests relevant to our app (or add a TODO and a real test
scaffolding) so the placeholder 2+2==4 test does not clutter CI or mask
regressions.
scripts/native-build.js (1)

194-224: Trap interruptions before mutating the worktree in place.

If this process is interrupted after disableItems() or wrapClientComponents(), the repo is left with renamed *.disabled / _ClientPage.tsx files. Consider trapping SIGINT / SIGTERM or doing the rewrite in a temp copy if this script is going to be reused beyond the demo branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/native-build.js` around lines 194 - 224, The script mutates the
worktree in-place via disableItems() and wrapClientComponents() without handling
interruptions; trap SIGINT and SIGTERM in main so that on signal you call
restoreModifiedFiles() and restoreItems() (and set process.exitCode) before
exiting, or alternatively perform the file rewrites in a temporary copy and run
the build there and always call restoreModifiedFiles()/restoreItems() in a
finally/cleanup handler; update main to register signal handlers that reference
disableItems/wrapClientComponents/restoreModifiedFiles/restoreItems (and remove
handlers after completion) to ensure the repo is restored on interrupt.
src/app/(mobile-ui)/native-poc/page.tsx (1)

719-722: Preserve multiline debug output.

Most testResult values contain \n, but this container collapses whitespace, so the logs render as one long line. whitespace-pre-wrap would make the PoC output readable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/native-poc/page.tsx around lines 719 - 722, The debug
output container for testResult collapses newlines; update the JSX element that
renders testResult (the div that reads {testResult && (...)} and uses classes
including rounded p-2 text-sm break-all) to preserve multiline output by adding
the whitespace-pre-wrap utility (e.g., include "whitespace-pre-wrap" in the
className list alongside or instead of break-all) so \n in testResult renders as
line breaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java`:
- Line 24: The test ExampleInstrumentedTest contains a stale package assertion
using appContext.getPackageName(); update the expected literal from
"com.getcapacitor.app" to the actual app id "com.peanut.app" so the assertion in
the test method (in ExampleInstrumentedTest) reflects the new package name used
by the app.

In `@android/app/src/main/AndroidManifest.xml`:
- Line 5: Change the Android manifest's backup setting for the passkey-enabled
WebView wrapper by setting the android:allowBackup attribute to false (replace
android:allowBackup="true" with android:allowBackup="false") in the
AndroidManifest so app data is not included in device/cloud backups;
alternatively, add explicit fullBackupContent or dataExtractionRules that
exclude authentication state if you prefer to keep allowBackup true.

In `@android/app/src/main/res/xml/file_paths.xml`:
- Around line 2-5: The FileProvider paths in file_paths.xml are too permissive
(external-path name="my_images" path="." and cache-path name="my_cache_images"
path=".")—restrict them to app-specific subdirectories instead: create and use a
dedicated external media folder (e.g., Pictures/YourApp or
Android/data/<package>/files/Pictures) for my_images and a dedicated cache
subfolder for my_cache_images, then update the path attributes to those specific
relative paths and ensure your app creates/uses those directories when sharing
files; this limits FileProvider URIs to only the files your app actually needs
to expose.

In `@capacitor.config.ts`:
- Around line 14-17: The Android webview debug and mixed-content settings are
enabled (android.allowMixedContent and android.webContentsDebuggingEnabled)
which must be disabled for production; update the configuration to set these
flags false by default and make them conditional on a development flag (e.g.,
process.env.NODE_ENV === "development" or an env var like DEBUG_WEBVIEW) so they
remain true only in dev builds, or ensure release builds explicitly set
android.allowMixedContent = false and android.webContentsDebuggingEnabled =
false (adjust the exported capacitor config or initialization logic that builds
the android config object to apply this conditional).
- Around line 9-12: The hardcoded ngrok URL in capacitor.config.ts (server.url)
will break for other developers; change server.url to read from an environment
variable (e.g., process.env.CAPACITOR_SERVER_URL) with a sensible fallback (or
omit the url entirely for local dev), and update the project README to document
how to set CAPACITOR_SERVER_URL for development. Ensure the code that reads
server.url handles missing/empty env values gracefully so other devs aren’t
forced to use the original ngrok address.

In `@next.config.js`:
- Around line 39-43: The native export block (isNativeBuild) currently sets
output: 'export' but leaves server-only routing functions active; update the
config to guard rewrites(), redirects(), and headers() so they are only included
when !isNativeBuild (or move all server-routing logic into a dedicated native
config file), e.g., wrap the rewrites, redirects, and headers blocks behind the
same isNativeBuild check that sets output: 'export' so these functions are
skipped in native/export mode.

In `@next.config.native.js`:
- Around line 1-73: Delete the unused next.config.native.js file (it’s dead
code) and ensure the native build configuration remains only in next.config.js
which already checks NATIVE_BUILD/environment and applies the native options
(e.g., output: 'export', trailingSlash: true, images.unoptimized). Remove any
duplicate configuration in the repo so native-specific settings live solely in
next.config.js and do not conflict with or duplicate symbols found in
next.config.native.js (like transpilePackages,
experimental.optimizePackageImports, or webpack ignoreWarnings).

In `@scripts/native-build.js`:
- Around line 208-212: The execSync invocation in scripts/native-build.js
currently prefixes the command with the shell-specific token
"NATIVE_BUILD=true", which breaks on Windows; remove that prefix so the command
string is just "next build --webpack" and rely on the env object already passing
NATIVE_BUILD: 'true' (update the call where execSync('NATIVE_BUILD=true next
build --webpack', { ... }) is used), ensuring stdio/cwd/env remain unchanged so
the build runs cross-platform.

In `@src/app/`(mobile-ui)/native-poc/page.tsx:
- Around line 595-689: The testZeroDevTransaction handler currently always
constructs a real kernelClient that uses BUNDLER_URL and PAYMASTER_URL (via
createZeroDevPaymasterClient) and can spend sponsor budget; add an explicit
non-production guard (e.g., check a feature flag like
NEXT_PUBLIC_ENABLE_SPONSORED_ARBITRUM or a runtime boolean) at the top of
testZeroDevTransaction and abort early (setError and return) when the flag is
false, or alternatively fall back to a non-sponsored flow by constructing
kernelClient without the paymaster option; specifically gate the code paths that
reference BUNDLER_URL, PAYMASTER_URL, createZeroDevPaymasterClient, and the
kernelClient creation so sponsored transactions cannot run unless the explicit
flag is enabled.
- Around line 323-326: Replace the manual atob/replace decoding for
clientDataJSON with the existing base64URLToBytes utility to handle base64url
padding and decoding consistently: call base64URLToBytes on
cred.response.clientDataJSON and then decode the resulting Uint8Array to a
string (e.g., via new TextDecoder().decode(...)) wherever clientDataJSON is
used; keep using uint8ArrayToHexString for authenticatorData as before and
ensure you reference base64URLToBytes and clientDataJSON in the updated code.

In `@src/constants/routes.ts`:
- Line 71: The PUBLIC_ROUTES_REGEX includes "/native-poc" but the canonical
route arrays do not, causing isReservedRoute('/native-poc') to return false;
update the route constants so the slug is present in the same canonical lists —
add "native-poc" to DEDICATED_ROUTES and RESERVED_ROUTES (or move it into the
dev-only list if it should be internal) in src/constants/routes.ts so
PUBLIC_ROUTES_REGEX and the array-based checks (e.g., DEDICATED_ROUTES,
RESERVED_ROUTES, isReservedRoute) remain consistent.

In `@src/hooks/usePasskeySupport.ts`:
- Around line 33-38: In usePasskeySupport, the temporary bypass currently
unconditionally sets setBrowserSupported(true),
setConditionalMediationSupported(true), setIsSupported(true) and returns; change
this so the bypass only runs when the native build flag is present (e.g. check
the project’s native indicator like isNativeBuild / process.env.NATIVE_BUILD /
__ANDROID_POC__), and otherwise fall through to the existing web/HTTPS detection
logic; ensure the guarded branch references the same setters
(setBrowserSupported, setConditionalMediationSupported, setIsSupported) and
retains the early return only when the native flag is true.

In `@src/utils/passkeyPreflight.ts`:
- Around line 31-41: The temporary unconditional bypass in passkeyPreflight.ts
is returning isSupported=true and hardcoding diagnostics.isAndroid=true for all
builds; change it to only short-circuit when NEXT_PUBLIC_IS_NATIVE_BUILD (or a
dedicated debug flag) is truthy, otherwise run the real diagnostics path and
return the actual diagnostics/warning values; specifically wrap the current
early return so it only executes when the env flag is set, ensure diagnostics,
isSupported and warning values are preserved/derived from the existing checks
(e.g., the diagnostics object and any navigator.credentials checks) for
non-native builds.

---

Outside diff comments:
In `@src/components/Home/HomePerkClaimSection.tsx`:
- Around line 52-66: The file fails Prettier formatting; run your project
formatter (e.g., prettier --write) on HomePerkClaimSection.tsx or reformat the
useWebSocket call and its callback so it matches project style (ensure
consistent indentation, spacing and trailing commas) around useWebSocket,
useCallback, queryClient.invalidateQueries, setArrivalSource and
setArrivalPhase; then re-run CI to confirm the Prettier warning is resolved.

---

Nitpick comments:
In `@android/app/src/main/res/layout/activity_main.xml`:
- Around line 1-12: The activity_main.xml layout includes a WebView that is
likely unused because Capacitor's BridgeActivity manages its WebView
programmatically; remove or archive this boilerplate layout to avoid
confusion—specifically delete or move
android/app/src/main/res/layout/activity_main.xml (the <WebView> entry and
surrounding CoordinatorLayout) or add a comment indicating it is unused, and
ensure no references to activity_main.xml remain in AndroidManifest or
MainActivity so BridgeActivity continues to supply the WebView.

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java`:
- Around line 1-17: The current unit test is the default Capacitor template
(package com.getcapacitor.myapp, class ExampleUnitTest, method
addition_isCorrect) and should be removed or replaced; either delete this test
file entirely or rename ExampleUnitTest and replace addition_isCorrect with
meaningful Android integration/unit tests relevant to our app (or add a TODO and
a real test scaffolding) so the placeholder 2+2==4 test does not clutter CI or
mask regressions.

In `@public/.well-known/assetlinks.json`:
- Around line 1-10: The assetlinks.json currently contains a debug signing
fingerprint in the sha256_cert_fingerprints array which must be replaced for
production; update the value in public/.well-known/assetlinks.json so the
"sha256_cert_fingerprints" entry under the "target" for package_name
"com.peanut.app" uses the release keystore SHA-256 fingerprint (or modify your
build to inject the correct fingerprint), and optionally add a brief comment or
README note near assetlinks.json indicating fingerprints must be
replaced/automatically injected for production releases.

In `@scripts/native-build.js`:
- Around line 194-224: The script mutates the worktree in-place via
disableItems() and wrapClientComponents() without handling interruptions; trap
SIGINT and SIGTERM in main so that on signal you call restoreModifiedFiles() and
restoreItems() (and set process.exitCode) before exiting, or alternatively
perform the file rewrites in a temporary copy and run the build there and always
call restoreModifiedFiles()/restoreItems() in a finally/cleanup handler; update
main to register signal handlers that reference
disableItems/wrapClientComponents/restoreModifiedFiles/restoreItems (and remove
handlers after completion) to ensure the repo is restored on interrupt.

In `@src/app/`(mobile-ui)/native-poc/page.tsx:
- Around line 719-722: The debug output container for testResult collapses
newlines; update the JSX element that renders testResult (the div that reads
{testResult && (...)} and uses classes including rounded p-2 text-sm break-all)
to preserve multiline output by adding the whitespace-pre-wrap utility (e.g.,
include "whitespace-pre-wrap" in the className list alongside or instead of
break-all) so \n in testResult renders as line breaks.

In `@src/app/page.tsx`:
- Around line 197-204: The temporary Native PoC Link JSX in page.tsx is rendered
unconditionally on the landing page; wrap the <Link ...> block in an environment
check so it only renders in non-production or when an explicit opt-in flag is
set (e.g., process.env.NODE_ENV !== 'production' or
process.env.NEXT_PUBLIC_ENABLE_NATIVE_POC === 'true'). Update the conditional
where the Link component appears to gate rendering, and ensure you use a
NEXT_PUBLIC_ prefixed env var if the check runs client-side so the variable is
available in the browser; default to not rendering when the env var is missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 140b92ad-832b-40d4-8d0c-e0c5283abb27

📥 Commits

Reviewing files that changed from the base of the PR and between 98d32fb and d64700a.

⛔ Files ignored due to path filters (28)
  • android/app/src/main/res/drawable-land-hdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-mdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xxxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-hdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-mdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xxxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable/splash.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (41)
  • android/.gitignore
  • android/app/.gitignore
  • android/app/build.gradle
  • android/app/capacitor.build.gradle
  • android/app/proguard-rules.pro
  • android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/com/peanut/app/MainActivity.java
  • android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  • android/app/src/main/res/drawable/ic_launcher_background.xml
  • android/app/src/main/res/layout/activity_main.xml
  • android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  • android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  • android/app/src/main/res/values/ic_launcher_background.xml
  • android/app/src/main/res/values/strings.xml
  • android/app/src/main/res/values/styles.xml
  • android/app/src/main/res/xml/file_paths.xml
  • android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
  • android/build.gradle
  • android/capacitor.settings.gradle
  • android/gradle.properties
  • android/gradle/wrapper/gradle-wrapper.properties
  • android/gradlew
  • android/gradlew.bat
  • android/settings.gradle
  • android/variables.gradle
  • capacitor.config.ts
  • next.config.js
  • next.config.native.js
  • package.json
  • public/.well-known/assetlinks.json
  • scripts/native-build.js
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/native-poc/page.tsx
  • src/app/(setup)/setup/page.tsx
  • src/app/lp/card/CardLandingPage.tsx
  • src/app/page.tsx
  • src/components/Home/HomePerkClaimSection.tsx
  • src/constants/routes.ts
  • src/hooks/usePasskeySupport.ts
  • src/utils/passkeyPreflight.ts

// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

assertEquals("com.getcapacitor.app", appContext.getPackageName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the stale template package assertion.

This test still expects the default Capacitor package id, so it will fail once instrumentation runs against the new app id (com.peanut.app in this PR).

✅ Suggested assertion update
-        assertEquals("com.getcapacitor.app", appContext.getPackageName());
+        assertEquals("com.peanut.app", appContext.getPackageName());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertEquals("com.getcapacitor.app", appContext.getPackageName());
assertEquals("com.peanut.app", appContext.getPackageName());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java`
at line 24, The test ExampleInstrumentedTest contains a stale package assertion
using appContext.getPackageName(); update the expected literal from
"com.getcapacitor.app" to the actual app id "com.peanut.app" so the assertion in
the test method (in ExampleInstrumentedTest) reflects the new package name used
by the app.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disable Android backup for the passkey-enabled WebView wrapper.

Leaving android:allowBackup="true" means WebView-backed app data can be copied into device/cloud backups unless you add explicit exclusion rules. For this kind of auth-enabled container, that is a risky default. Flip this to false for the POC, or add fullBackupContent / dataExtractionRules that exclude auth state before this ships.

🔒 Safer default
-        android:allowBackup="true"
+        android:allowBackup="false"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/AndroidManifest.xml` at line 5, Change the Android
manifest's backup setting for the passkey-enabled WebView wrapper by setting the
android:allowBackup attribute to false (replace android:allowBackup="true" with
android:allowBackup="false") in the AndroidManifest so app data is not included
in device/cloud backups; alternatively, add explicit fullBackupContent or
dataExtractionRules that exclude authentication state if you prefer to keep
allowBackup true.

Comment on lines +2 to +5
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths> No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Overly permissive FileProvider paths.

Using path="." exposes the entire root of external storage and cache directories via FileProvider URIs. This is a security concern — any code with access to these URIs could read/write any file in those directories.

For POC this may be acceptable, but for production, scope paths to specific subdirectories the app actually needs to share.

🛡️ Recommended fix to scope paths
 <?xml version="1.0" encoding="utf-8"?>
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
-    <external-path name="my_images" path="." />
-    <cache-path name="my_cache_images" path="." />
+    <external-path name="my_images" path="Pictures/" />
+    <cache-path name="my_cache_images" path="images/" />
 </paths>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Pictures/" />
<cache-path name="my_cache_images" path="images/" />
</paths>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/res/xml/file_paths.xml` around lines 2 - 5, The
FileProvider paths in file_paths.xml are too permissive (external-path
name="my_images" path="." and cache-path name="my_cache_images"
path=".")—restrict them to app-specific subdirectories instead: create and use a
dedicated external media folder (e.g., Pictures/YourApp or
Android/data/<package>/files/Pictures) for my_images and a dedicated cache
subfolder for my_cache_images, then update the path attributes to those specific
relative paths and ensure your app creates/uses those directories when sharing
files; this limits FileProvider URIs to only the files your app actually needs
to expose.

Comment on lines +9 to +12
server: {
url: 'https://3ff1-146-70-189-102.ngrok-free.app',
cleartext: false,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded ngrok URL will break for other developers.

This URL is specific to one developer's ngrok session and will not work for anyone else. Consider using an environment variable or documenting that developers need to update this URL.

💡 Suggested approach using environment variable
+// Set CAPACITOR_DEV_URL env var to your ngrok URL for passkey testing
 const config: CapacitorConfig = {
   appId: 'com.peanut.app',
   appName: 'Peanut',
   webDir: 'out',

   // Load from ngrok for passkey testing (needs HTTPS + assetlinks)
   server: {
-    url: 'https://3ff1-146-70-189-102.ngrok-free.app',
+    url: process.env.CAPACITOR_DEV_URL || 'https://your-ngrok-url.ngrok-free.app',
     cleartext: false,
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@capacitor.config.ts` around lines 9 - 12, The hardcoded ngrok URL in
capacitor.config.ts (server.url) will break for other developers; change
server.url to read from an environment variable (e.g.,
process.env.CAPACITOR_SERVER_URL) with a sensible fallback (or omit the url
entirely for local dev), and update the project README to document how to set
CAPACITOR_SERVER_URL for development. Ensure the code that reads server.url
handles missing/empty env values gracefully so other devs aren’t forced to use
the original ngrok address.

Comment on lines +14 to +17
android: {
allowMixedContent: true,
webContentsDebuggingEnabled: true,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Debug settings enabled — do not ship to production.webContentsDebuggingEnabled: true allows inspection and modification of WebView state via Chrome DevTools. Security guidelines recommend that "the debugging of web contents option is switched off in release builds." "This is a security liability and should not be enabled in production builds of apps."

Similarly, allowMixedContent: true permits loading HTTP resources in an HTTPS context, weakening transport security.

These settings are appropriate for development/POC but must be disabled or made conditional before any production release.

🛡️ Recommended: Make debug settings conditional
 android: {
-    allowMixedContent: true,
-    webContentsDebuggingEnabled: true,
+    // WARNING: These settings are for development only
+    // Set to false or remove for production builds
+    allowMixedContent: process.env.NODE_ENV !== 'production',
+    webContentsDebuggingEnabled: process.env.NODE_ENV !== 'production',
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android: {
allowMixedContent: true,
webContentsDebuggingEnabled: true,
},
android: {
allowMixedContent: process.env.NODE_ENV === 'development',
webContentsDebuggingEnabled: process.env.NODE_ENV === 'development',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@capacitor.config.ts` around lines 14 - 17, The Android webview debug and
mixed-content settings are enabled (android.allowMixedContent and
android.webContentsDebuggingEnabled) which must be disabled for production;
update the configuration to set these flags false by default and make them
conditional on a development flag (e.g., process.env.NODE_ENV === "development"
or an env var like DEBUG_WEBVIEW) so they remain true only in dev builds, or
ensure release builds explicitly set android.allowMixedContent = false and
android.webContentsDebuggingEnabled = false (adjust the exported capacitor
config or initialization logic that builds the android config object to apply
this conditional).

Comment on lines +323 to +326
const authenticatorData = base64URLToBytes(cred.response.authenticatorData)
const authenticatorDataHex = uint8ArrayToHexString(authenticatorData)

const clientDataJSON = atob(cred.response.clientDataJSON.replace(/-/g, '+').replace(/_/g, '/'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the existing base64URLToBytes utility for consistent and correct decoding.

Line 323 correctly uses base64URLToBytes() for authenticatorData, but line 326 manually decodes clientDataJSON with atob() and character replacement. This is both inconsistent and fragile—WebAuthn responses commonly omit base64url padding, and atob() after simple character replacement may throw on those valid inputs. The file already defines base64URLToBytes (lines 65–73) which properly handles padding, so apply the same pattern here.

Suggested fix
-            const clientDataJSON = atob(cred.response.clientDataJSON.replace(/-/g, '+').replace(/_/g, '/'))
+            const clientDataJSON = new TextDecoder().decode(base64URLToBytes(cred.response.clientDataJSON))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const authenticatorData = base64URLToBytes(cred.response.authenticatorData)
const authenticatorDataHex = uint8ArrayToHexString(authenticatorData)
const clientDataJSON = atob(cred.response.clientDataJSON.replace(/-/g, '+').replace(/_/g, '/'))
const authenticatorData = base64URLToBytes(cred.response.authenticatorData)
const authenticatorDataHex = uint8ArrayToHexString(authenticatorData)
const clientDataJSON = new TextDecoder().decode(base64URLToBytes(cred.response.clientDataJSON))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/native-poc/page.tsx around lines 323 - 326, Replace the
manual atob/replace decoding for clientDataJSON with the existing
base64URLToBytes utility to handle base64url padding and decoding consistently:
call base64URLToBytes on cred.response.clientDataJSON and then decode the
resulting Uint8Array to a string (e.g., via new TextDecoder().decode(...))
wherever clientDataJSON is used; keep using uint8ArrayToHexString for
authenticatorData as before and ensure you reference base64URLToBytes and
clientDataJSON in the updated code.

Comment on lines +595 to +689
// Test actual ZeroDev transaction - sends 0 ETH to self
const testZeroDevTransaction = async () => {
if (!webAuthnKey) {
setError('No WebAuthnKey available. Create a passkey first!')
return
}

setIsLoading(true)
setError(null)
setTestResult(null)

try {
setTestResult('Creating ZeroDev kernel account...')

// Create public client for Arbitrum
const publicClient = createPublicClient({
chain: arbitrum,
transport: http(),
})

// Create passkey validator from our WebAuthnKey
const passkeyValidator = await toPasskeyValidator(publicClient, {
webAuthnKey,
entryPoint: USER_OP_ENTRY_POINT,
kernelVersion: ZERODEV_KERNEL_VERSION,
validatorContractVersion: PasskeyValidatorContractVersion.V0_0_3_PATCHED,
})

setTestResult('Creating kernel account...')

// Create kernel account
const kernelAccount = await createKernelAccount(publicClient, {
plugins: {
sudo: passkeyValidator,
},
entryPoint: USER_OP_ENTRY_POINT,
kernelVersion: ZERODEV_KERNEL_VERSION,
})

const accountAddress = kernelAccount.address
setSmartAccountAddress(accountAddress)

setTestResult(`Smart Account Address: ${accountAddress}\n\nCreating kernel client...`)

// Create kernel client with paymaster
const kernelClient = createKernelAccountClient({
account: kernelAccount,
chain: arbitrum,
bundlerTransport: http(BUNDLER_URL),
paymaster: {
getPaymasterData: async (userOperation) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain: arbitrum,
transport: http(PAYMASTER_URL),
})
return await zerodevPaymaster.sponsorUserOperation({
userOperation,
shouldOverrideFee: true,
})
},
},
})

setTestResult(`Smart Account: ${accountAddress}\n\nSending test transaction (0 ETH to self)...\n\n🔐 A passkey prompt should appear!`)

// Send a 0-value transaction to self (cheapest possible test)
const userOpHash = await kernelClient.sendUserOperation({
callData: await kernelAccount.encodeCalls([
{
to: accountAddress,
value: BigInt(0),
data: '0x',
},
]),
})

setTestResult(`Smart Account: ${accountAddress}\n\nUserOp Hash: ${userOpHash}\n\nWaiting for transaction receipt...`)

// Wait for the transaction
const receipt = await kernelClient.waitForUserOperationReceipt({
hash: userOpHash,
})

const txHash = receipt.receipt.transactionHash
const arbiscanUrl = `https://arbiscan.io/tx/${txHash}`

setTestResult(`🎉 SUCCESS! On-chain transaction confirmed!\n\nSmart Account: ${accountAddress}\n\nUserOp Hash: ${userOpHash}\n\nTx Hash: ${txHash}\n\n🔗 View on Arbiscan:\n${arbiscanUrl}`)
} catch (err: any) {
console.error('ZeroDev transaction failed:', err)
setError(err.message || 'Unknown error')
setTestResult(`❌ ZeroDev FAILED: ${err.name || 'Error'} - ${err.message}`)
} finally {
setIsLoading(false)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Gate the sponsored Arbitrum path behind an explicit non-production flag.

This handler creates a real kernel client and spends sponsor budget through BUNDLER_URL / PAYMASTER_URL; cross-file, those constants are backed by public NEXT_PUBLIC_* envs. Even the 0 ETH self-transfer still burns paymaster gas, so shipping this page outside the demo environment gives anyone a way to drain that budget.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/native-poc/page.tsx around lines 595 - 689, The
testZeroDevTransaction handler currently always constructs a real kernelClient
that uses BUNDLER_URL and PAYMASTER_URL (via createZeroDevPaymasterClient) and
can spend sponsor budget; add an explicit non-production guard (e.g., check a
feature flag like NEXT_PUBLIC_ENABLE_SPONSORED_ARBITRUM or a runtime boolean) at
the top of testZeroDevTransaction and abort early (setError and return) when the
flag is false, or alternatively fall back to a non-sponsored flow by
constructing kernelClient without the paymaster option; specifically gate the
code paths that reference BUNDLER_URL, PAYMASTER_URL,
createZeroDevPaymasterClient, and the kernelClient creation so sponsored
transactions cannot run unless the explicit flag is enabled.

* Exception: /dev/payment-graph is public (uses API key instead of user auth)
*/
export const PUBLIC_ROUTES_REGEX = /^\/(request\/pay|claim|pay\/.+|support|invite|qr|dev\/payment-graph)/
export const PUBLIC_ROUTES_REGEX = /^\/(request\/pay|claim|pay\/.+|support|invite|qr|dev\/payment-graph|native-poc)/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep /native-poc in the canonical route lists too.

Only the regex knows about this page right now. DEDICATED_ROUTES/RESERVED_ROUTES still exclude native-poc, so isReservedRoute('/native-poc') stays false and shared catch-all logic can still misclassify it. Add the slug to the dedicated/public route arrays as well, or move it to the dev-only list if this page should stay internal.

Minimal sync-up
 export const DEDICATED_ROUTES = [
     'qr',
     'api',
     'setup',
     'home',
     'history',
     'settings',
     'points',
     'claim',
     'pay',
     'request',
     'invite',
     'support',
     'dev',
+    'native-poc',
 ] as const
@@
-export const PUBLIC_ROUTES = ['request/pay', 'claim', 'pay', 'support', 'invite', 'qr', 'dev/payment-graph'] as const
+export const PUBLIC_ROUTES = ['request/pay', 'claim', 'pay', 'support', 'invite', 'qr', 'dev/payment-graph', 'native-poc'] as const
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/constants/routes.ts` at line 71, The PUBLIC_ROUTES_REGEX includes
"/native-poc" but the canonical route arrays do not, causing
isReservedRoute('/native-poc') to return false; update the route constants so
the slug is present in the same canonical lists — add "native-poc" to
DEDICATED_ROUTES and RESERVED_ROUTES (or move it into the dev-only list if it
should be internal) in src/constants/routes.ts so PUBLIC_ROUTES_REGEX and the
array-based checks (e.g., DEDICATED_ROUTES, RESERVED_ROUTES, isReservedRoute)
remain consistent.

Comment on lines +33 to +38
// TEMPORARY POC BYPASS: Always assume passkey support
// TODO: Remove this after native PoC testing
setBrowserSupported(true)
setConditionalMediationSupported(true)
setIsSupported(true)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scope the bypass to native builds only.

This hook is shared outside the Android POC. Returning true unconditionally here makes unsupported browsers and non-HTTPS contexts look passkey-capable, so normal web flows can advance into navigator.credentials.* calls that fail later. Gate the bypass behind the native build flag instead of short-circuiting the hook globally.

Minimal guard
         try {
-            // TEMPORARY POC BYPASS: Always assume passkey support
-            // TODO: Remove this after native PoC testing
-            setBrowserSupported(true)
-            setConditionalMediationSupported(true)
-            setIsSupported(true)
-            return
+            const isNativeBuild = process.env.NEXT_PUBLIC_IS_NATIVE_BUILD === 'true'
+            if (isNativeBuild) {
+                // TEMPORARY POC BYPASS: only for the native export
+                setBrowserSupported(true)
+                setConditionalMediationSupported(true)
+                setIsSupported(true)
+                return
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/usePasskeySupport.ts` around lines 33 - 38, In usePasskeySupport,
the temporary bypass currently unconditionally sets setBrowserSupported(true),
setConditionalMediationSupported(true), setIsSupported(true) and returns; change
this so the bypass only runs when the native build flag is present (e.g. check
the project’s native indicator like isNativeBuild / process.env.NATIVE_BUILD /
__ANDROID_POC__), and otherwise fall through to the existing web/HTTPS detection
logic; ensure the guarded branch references the same setters
(setBrowserSupported, setConditionalMediationSupported, setIsSupported) and
retains the early return only when the native flag is true.

Comment on lines +31 to +41
// TEMPORARY POC BYPASS: Always return supported
// TODO: Remove after native PoC testing
return {
isSupported: true,
warning: null,
diagnostics: {
hasPublicKeyCredential: true,
isHttps: true,
isAndroid: true,
},
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scope the bypass to native-only paths.

This short-circuit makes every caller look like a supported Android/HTTPS/WebAuthn environment and hardcodes isAndroid: true, so non-native sessions skip the warning path and can fail later when navigator.credentials.* runs. Keep the POC bypass behind NEXT_PUBLIC_IS_NATIVE_BUILD (or a dedicated debug flag) and preserve the real diagnostics for other builds.

🔧 Minimal guard for the POC bypass
 export async function checkPasskeySupport(): Promise<PasskeyPreflightResult> {
-    // TEMPORARY POC BYPASS: Always return supported
-    // TODO: Remove after native PoC testing
-    return {
-        isSupported: true,
-        warning: null,
-        diagnostics: {
-            hasPublicKeyCredential: true,
-            isHttps: true,
-            isAndroid: true,
-        },
-    }
+    if (process.env.NEXT_PUBLIC_IS_NATIVE_BUILD === 'true') {
+        return {
+            isSupported: true,
+            warning: null,
+            diagnostics: {
+                hasPublicKeyCredential: 'PublicKeyCredential' in window,
+                isHttps: window.location.protocol === 'https:' || window.location.hostname === 'localhost',
+                isAndroid: /android/i.test(navigator.userAgent),
+            },
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/passkeyPreflight.ts` around lines 31 - 41, The temporary
unconditional bypass in passkeyPreflight.ts is returning isSupported=true and
hardcoding diagnostics.isAndroid=true for all builds; change it to only
short-circuit when NEXT_PUBLIC_IS_NATIVE_BUILD (or a dedicated debug flag) is
truthy, otherwise run the real diagnostics path and return the actual
diagnostics/warning values; specifically wrap the current early return so it
only executes when the env flag is set, ensure diagnostics, isSupported and
warning values are preserved/derived from the existing checks (e.g., the
diagnostics object and any navigator.credentials checks) for non-native builds.

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.

1 participant