CLAUDE.md raw

CLAUDE.md — Warden

Project Overview

Android browser router. When a user taps an HTTP/HTTPS link in any app — or shares text containing one to Warden — this app intercepts it via Android intent filters and presents a picker so the user chooses which browser opens it, in what privacy mode, with which intent extras. Built with Expo (React Native).

App display name: Warden (both launcher tile and in-app). App ID: org.vikingware.webwarden — kept as-is so existing installs receive updates.

Tech Stack

intent dispatch, default-browser detection, shared-text intake

Architecture

Single-file app — no router, no navigation library.

index.js   — entry; calls registerRootComponent(App)
App.tsx    — wraps Home with SafeAreaProvider + StatusBar
             Home() does everything: state, URL/share intake, UI

URL Flow

  1. Home mounts, calls Linking.getInitialURL() and our native

getInitialSharedText() on cold start.

  1. Linking.addEventListener('url', ...) catches subsequent VIEW intents

while the app is running.

  1. AppState.addEventListener('change', ...) polls the native shared-text

getter on every foreground transition (catches new SEND intents).

  1. The first valid https?:// URL we see populates the URL field and

marks the session as external.

  1. User taps a browser → native openInBrowser(url, pkg, mode) fires the

appropriate Intent. On success in external mode, BackHandler.exitApp() finishes our task so back-from-browser returns to the originating app.

Key Config

- VIEW + http/https + BROWSABLE/DEFAULT/APP_BROWSER — registers us as a browser candidate - SEND + text/plain — accepts shared text from any app's share sheet

support, so Android 11+ package-visibility lets us see them

stripped from release builds via a small config plugin at plugins/with-release-no-internet.js. Debug builds keep INTERNET so Metro can reach the app. The plugin writes android/app/src/release/AndroidManifest.xml with a tools:node="remove" directive — do NOT set blockedPermissions in app.json for INTERNET, that would strip it from debug too

Key Files

FilePurpose
App.tsxEntire app UI + state
index.jsRegisters App with React Native
constants/browsers.tsBrowser list + private-mode strategy per browser
constants/theme.tsPalette, Fonts
constants/build-info.tsAuto-generated version + commit at release time
modules/default-browser/Local Kotlin Expo module
app.jsonExpo config, intent filters, Android queries
scripts/release-apk.shBuilds signed arm64 APK, tags, uploads to Gitea

Design System

Dark forest theme. All colors from Palette in constants/theme.ts:

Commands

npm start              # Start Metro dev server
npm run android        # Build & install on emulator (expo run:android)
npm run lint           # ESLint
npx expo prebuild      # Regenerate android/ from app.json (required after intent filter changes)

Important Notes

flat is the lean trick. Don't add expo-router, react-navigation, react-native-reanimated, etc. unless we genuinely need them.

Release Conventions