React Native/Expo app for stashing photos, links and articles for later. Offline-first with local SQLite.
cd ~/projects/stash && direnv allow # first time
cd ~/projects/stash && direnv exec . <command> # run in devshellProvides: node, pnpm, java (JDK 17), gradle, adb
cd ~/projects/stash && direnv exec . make deployThis runs pnpm prebuild && cd android && ./gradlew assembleRelease then adb install -r.
Debug APKs must embed the JS bundle — no Metro dependency. Set in android/app/build.gradle:
react {
debuggableVariants = []
}This forces bundling for the debug variant; without it the debug app tries to fetch its JS from Metro at runtime and fails with Unable to load script over wireless ADB (reverse tunnel + Metro entry-path issues). android/ is gitignored, so this setting must be re-applied by the debug-variant config plugin after every expo prebuild (see plugins/debug-variant/index.js).
adb connect <ip>:<port> # connect to phone
cd ~/projects/stash && direnv exec . make deployCREATE TABLE IF NOT EXISTS does NOT alter existing tables. When adding columns to the schema, you must add a matching ALTER TABLE migration. This was the root cause of folders disappearing (the icon column was added to CREATE TABLE but missing from existing DBs).
Pattern to follow (see src/db/database.ts):
const cols = await db.getAllAsync<{ name: string }>("PRAGMA table_info(<table>)");
if (!cols.some((c) => c.name === "<column>")) {
await db.execAsync("ALTER TABLE <table> ADD COLUMN <column> <type>");
}WHERE f.archived_at IS NULLwas afterGROUP BY f.idinsrc/db/folders.ts→ SQL syntax error →getFolders()threw → store never updated → "No folders yet"- Fix: Moved WHERE before GROUP BY
- Initial schema had no
iconcolumn onfolders. Schema was updated to includeicon TEXT NOT NULLbut noALTER TABLEmigration existed for existing databases. - Fix: Added migration in
database.tsusingPRAGMA table_info(folders)pattern
updateItemArticleHtmlsetarticle_text = NULLwhen saving HTML. ItemCard and ListenScreen read from DB field which was always NULL.- Fix: Made
updateItemArticleHtmlaccept optionaltextparam, passhtmlToText(html)at all call sites