soul-browser/AGENTS.md
KaKi87 4e0f473f9b Tell agents to discard prior failed mission attempts.
A new thread for the same task means the previous try failed, so old branches, PRs, and conclusions should not be trusted by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 16:56:57 +02:00

8.3 KiB

AGENTS.md

This repo rebuilds a signed Android APK (Soul2 / Soul2⁺ Browser) from Apktool smali sources under app/. There is no Gradle app module, no npm/pip app stack, and no long-running service. The practical check for most changes is ./scripts/build.sh plus apksigner verify on the APK in dist/.

sources/java/ and sources/resources/ are JADX output for reading and searching. The buildable truth is app/ (smali + res). Some injected helpers live as Java under sources/warp-runtime/, sources/ocr-runtime/, etc., and are compiled to dex by scripts/compile-*.sh then merged by the build.

Package ID and CI

  • Tracked app/ sources use the testing application ID (net.kaki87.soul2.testing, Soul2⁺ Browser). Feature-branch work should leave that alone.
  • Stable releases (net.kaki87.soul2, Soul2 Browser) are produced only when CI builds a tag whose commit is on main and sets SOUL_PACKAGE_ID=net.kaki87.soul2. Plain pushes to main skip the APK job.
  • Do not hand-edit package names across the tree. Use scripts/set-package-id.py when a rewrite is required, and run python3 scripts/set-package-id.py --self-test after changing that script.
  • Smali must reference Lnet/kaki87/soul2/testing/R$string (or other R$*) in the tracked tree. A leftover .../soul2/testing/testing/R$string or a set-package-id double rewrite has crashed Copy URL and similar paths before.

Worktrees and PRs

Feature work normally lands in a git worktree on its own branch, not on the primary main checkout. Parallel agent sessions each get their own worktree so builds, dirty trees, and emulator runs do not collide.

  • If this thread is a retry of a mission that was tried before, treat earlier attempts as failed. Discard their branches, PRs, patches, and conclusions unless the user points at something specific to keep. A new thread exists because the previous one did not succeed.
  • Always create and use a feature branch unless the user explicitly says not to. Prefer names like fix/... or feature/... (short and descriptive). Hosts may also create t3code/... or cursor/... branches; keep using that branch if the session already checked it out.
  • Do not commit or push to main unless the user explicitly asks. Open a PR into main instead.
  • Always push the branch after commits. Always open a PR into main if one does not exist yet for that branch; if a PR already exists, push updates to it.
  • One concern per PR. After a PR merges, start the next change from an updated main (new branch/worktree), rather than piling unrelated commits onto the old branch.
  • Before opening or updating a PR, rebase or merge main if the branch has drifted. Resource-ID and smali conflicts are common after adjacent features land; resolve them in the worktree, then push.
  • Non-main branch pushes get a CI APK build. Use that artifact (or a local ./scripts/build.sh) for device checks. Keep the PR focused: no generated build_date.xml / soul2_info.xml, no accidental bundled-warp/lib/ noise, no unrelated reformatting.
  • PR summaries should say what changed and how to verify (build and/or a short UI path). Do not merge unless asked.

Build

  • Run ./scripts/build.sh (details in the README Development notes). Needs Java 21+, curl, zip/unzip, keytool, and Android SDK build-tools with apksigner on PATH (or via ANDROID_HOME / ANDROID_SDK_ROOT).
  • Output: signed APK under dist/. Signature schemes v1+v2+v3 are required (targetSdk 36).
  • apktool is downloaded into tools/ by the build script (gitignored).
  • app/res/values/build_date.xml and app/res/values/soul2_info.xml are generated at build time and gitignored. INFO screen text comes from the README <!-- BEGIN INFO --> block via scripts/prepare-info-string.py. Do not commit those XML files.
  • bundled-warp/lib/ is gitignored for normal git add but kept in the repo history; CI refreshes it with git add -f when bundled-warp/soulamz.inputs changes. Local soulamz rebuilds should not dirty commits.
  • OCR/WARP dex prebuilts live under prebuilts/ (tracked; CI updates when prebuilts/tooling.inputs changes).
  • There is no separate unit-test suite. Prefer a green local (or CI) APK build over guessing.

Editing smali and resources

New strings and resource IDs

When adding a string (or other resource) used from smali:

  1. Add the entry in app/res/values/strings.xml (English only here).
  2. Allocate a new ID in app/res/values/public.xml and the matching field in app/smali_classes3/com/mycompany/app/soulbrowser/R$string.smali.
  3. Keep sources/java/.../R.java in sync if you touch it for readability, but apktool does not build from that tree.

ID collisions break main after merge even when a PR build was green on an older base. Never reuse an ID that already exists on main. After Eruda, reload_saved_tabs reused dev_tools's 0x7f12059d and apktool failed only once both landed together. Scan the highest existing custom IDs before picking the next one. scripts/sync-public-xml.py can add missing public.xml rows from R$*.smali; it does not invent new IDs for you.

Copy an existing pattern

Before inventing control flow, find the closest shipped feature and mirror it:

Kind of change Good anchors
Main-menu item MainConst.smali, MenuListAdapter*.smali, Eruda/DevTools (DevToolsHelper.smali)
Settings row The target Setting*.smali + SettingListAdapter*.smali; match group corner modes (top of group ≠ middle row)
Link/image long-press DialogUrlLink.smali, preview hooks in DialogWebView*.smali / WebViewActivity*.smali
Prefs Existing Pref*.smali getters/setters; do not invent fields on foreign classes
Tab list / multi-select WebTabAdapter.smali, DialogTabMain.smali

Settings list crashes in the WARP work came from writing final adapter fields or assuming ViewHolder members that are not in this decompile. Read the class you call into. If JADX and smali disagree on a field, trust the smali.

Smali mechanics that have burned builds

  • Keep .locals / register widths honest after inserts. Wide values (long/double) take two registers.
  • Do not blindly rewrite or "refresh" unrelated sparse-switch / packed-switch tables. A package-id helper once remapped colliding :sswitch_N labels file-wide and corrupted Jsoup/SVGParser, which made Image viewer show "No images" (VerifyError). Scope switch edits to the method you mean.
  • Anonymous classes are separate Foo$N.smali files; wire listeners there the same way neighboring features do.
  • After process-death / intent bugs, clear consumed launch extras once handled (WebViewActivity external-link reopen).

i18n

English strings are owned in this repo. Other locales come from soul-browser-i18n at build time (scripts/sync-i18n.py). Do not commit downloaded locale trees. CI can push English to the i18n repo from main via scripts/push-i18n-english.py.

Emulator and device smoke

Native libs in the APK are ARM-only. On an x86_64 emulator you need an image that advertises ARM ABIs via NDK translation, e.g. system-images;android-30;google_apis;x86_64. API 33 google_apis x86_64 has failed install with INSTALL_FAILED_NO_MATCHING_ABIS.

./scripts/run-emulator.sh creates the soul_test AVD if needed and starts a headless emulator. It forces -accel off (TCG) for nested-VM hosts where KVM misbehaves; on a normal desktop with working KVM you can boot the same AVD with acceleration enabled instead. Start adb first. Avoid pkill -f emulator (it can kill the calling shell); prefer killall qemu-system-x86_64-headless when that is the process in use.

Smoke after boot (local/feature APKs use the testing id):

adb wait-for-device
adb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 2; done'
adb install -r -g dist/soul-browser-*.apk
adb shell am start -a android.intent.action.VIEW -d 'https://example.com/' \
  -n net.kaki87.soul2.testing/com.mycompany.app.web.WebLauncher
# First launch shows onboarding; tap Start (id/splash_apply_view)

Stable-tag APKs use net.kaki87.soul2 in the component name. When UI is not part of the task, skip the emulator and rely on build + apksigner verify. Physical devices via ADB are fine when the user already has one connected; prefer CI or local dist/ APKs the user asked you to use.