save drafts per conversation, keep them on every device
mv3 browser extension · lsposed android module · next.js + neon backend
| 🧵 per-chat drafts one row per conversation — drafts never mix across chats |
🔄 sync everywhere start a reply on the phone, finish it in the browser |
🖼️ text + images paste images into drafts too, not just text |
| 📱 android overlay floating draft panel inside the chat apps themselves |
🕐 timestamps created + modified times on every card, on both platforms |
🔌 bring your own backend your next.js + neon, or the legacy firestore path |
| surface | web extension | android module |
|---|---|---|
| messenger | ✅ | ✅ (native) |
| facebook messages | ✅ | ✅ (in-app webviews) |
| discord | ✅ | ✅ |
| ✅ (web) | ✅ (native) | |
| instagram dm | ✅ | 🔧 hook landed, device test pending |
chat ids are scoped per conversation (messenger_web_<fbid>_<name> vs
messenger_android_<thread>_<name>, instagram_web_<thread>_<name>, …), and a
name-slug matcher bridges the two sides so the same conversation resolves to the
same drafts even though web and android derive ids differently.
flowchart LR
A["web extension<br/>content.js + background.js"] -->|REST + x-api-key| C["next.js api<br/>/api/drafts/*"]
B["android module<br/>OverlayController + DraftRepository"] -->|REST + x-api-key| C
C --> D[(neon postgres)]
A -.legacy.- E[(firestore)]
B -.legacy.- E
both clients speak the same small REST API. sync polling runs on a 10-second tick and only while the panel/tab is visible, so an idle chat costs no backend compute.
-
grab the latest
glitchdraft-<version>.zipfrom releases — every push publishes one — and unzip it (or clone the repo and useextension/directly) -
open
edge://extensions(orchrome://extensions), enable developer mode, load unpacked → pick the unzipped folder (orextension/) -
open a chat, click the extension icon or press Alt+M
-
to sync across devices, click the popup and paste your backend config:
{"apiBaseUrl": "https://your-backend.vercel.app", "apiKey": "your_api_key"}
npm run android:run— builds, installs and launches the app (needs adb;tools/android.ps1picks a JDK 21 on its own)- enable the module in your xposed framework (LSPosed / Vector) and scope it to the chat apps
- grant display over other apps
- open the GlitchDraft app and paste the same
{"apiBaseUrl": "...", "apiKey": "..."}JSON - tap the floating icon inside a chat to open the panel
signed release builds: npm run android:release:apk (signing comes from
GD_KEYSTORE_FILE / GD_KEYSTORE_PASSWORD / GD_KEY_ALIAS env vars — the
build fails loudly instead of shipping an unsigned apk).
cd backend
pnpm install
# .env: DATABASE_URL=<neon postgres url> API_KEY=<client api key>
pnpm dev- every client request is authenticated with the
x-api-keyheader set toAPI_KEY - health check:
GET /api/healthwith that header - deploy anywhere node runs (vercel: set the project root to
backend/)
- Alt+M toggles the panel (web); on android it's the floating icon
- type into the panel input and hit + / Alt+S to save a draft — it never touches the real composer until you say so
- per card: use (insert into the chat), copy, edit, delete
- the panel menu has export all, import and sync now
- cards show when the draft was written and a
Modified:line when it was changed later from another device
the extension and module can also sync through a personal firestore instead of the backend above — paste the firebase config JSON into the same popup / app screen. if you go that route, tighten the firestore rules (the permissive version below is for a throwaway test project only):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{document=**} {
allow read, write: if true;
}
match /settings/{document=**} {
allow read, write: if true;
}
}
}
Important: these rules allow anyone to read/write your data. For production, add proper authentication.
| Capability | GlitchDraft | Form History Control II | Textarea Cache | Form Recover / Form Recovery | Typio Form Recovery | Built-in chat drafts |
|---|---|---|---|---|---|---|
| Chat-thread-specific saved drafts | ✅ | - | - | partial | partial | partial |
| Generic form/text-area autosave | partial | ✅ | ✅ | ✅ | ✅ | - |
| Restore/search/edit form history | partial | ✅ | ✅ | ✅ | ✅ | - |
| Messenger/Discord/WhatsApp-focused adapters | ✅ | - | - | - | - | partial |
| Image/media draft support | partial | - | - | - | - | partial |
| Cross-device/backend sync | ✅ | partial | - | - | - | platform-dependent |
| Android/WebView or LSPosed overlay path | ✅ | - | - | - | - | platform-dependent |
| Bring-your-own backend | ✅ | - | - | - | - | - |
| Local-only privacy by default | partial | ✅ | ✅ | ✅ | ✅ | platform-dependent |
| Open-source, self-modifiable stack | ✅ | partial | ✅ | partial | partial | - |
General form-recovery extensions are stronger at broad autosave, search, cleanup, export/import, and one-click field injection. GlitchDraft is narrower but reaches a different problem: chat drafts tied to conversations, plus Android/WebView-style surfaces where normal browser form history tools do not reach.
The obvious backlog is a safer auth model, better generic form support, cleaner import/export, stronger image draft handling, and a simpler production setup story.
| path | what |
|---|---|
extension/ |
mv3 browser extension, plain js, no build step |
android/ |
lsposed module (kotlin) + injected webview assets |
backend/ |
next.js REST api + drizzle on neon |
userscript/ |
lightweight browser injection experiment |
tools/ |
reload-extension.ps1, android.ps1 helpers |
AGENTS.md |
working notes: gotchas, cross-platform contracts, deploy steps |
- extension — edit
extension/*.jsraw, bumpversioninextension/manifest.jsonin the same change, thenpwsh tools/reload-extension.ps1 - every push runs
.github/workflows/release-extension.yml: it packs the extension into a zip + crx and publishes a github release named after the manifest version, which is what the badge up top reports - android —
npm run android:run(debug),npm run android:build:release,npm run android:release:apk - backend —
pnpm devinsidebackend/ draftSync.js,draftImport.jsandstyles.cssexist byte-identical in bothextension/andandroid/app/src/main/assets/glitchdraft/— copy your edit across.content.jsis forked on purpose (native-overlay glue on the android side); its shared call contract with draftSync must change in both files.