170 lines
3.2 KiB
Markdown
170 lines
3.2 KiB
Markdown
# synq, a tiny android-to-org capture system.
|
|
|
|
open app.
|
|
type, optionally mark as todo, optionally add tags, save.
|
|
if home server accessible, post unsynced captures to lan-only service and appended to `synq.org`.
|
|
|
|
## non-goals
|
|
- no org parser on android
|
|
- no agenda on android
|
|
- no sync provider dependency
|
|
- no nextcloud file editing
|
|
- no conflict resolution
|
|
- no public internet exposure
|
|
- no ai tagging in v1
|
|
- no account system in v1
|
|
|
|
## architecture
|
|
|
|
```text
|
|
android app
|
|
kotlin + jetpack compose
|
|
room sqlite local queue
|
|
workmanager + manual sync
|
|
okhttp/retrofit client
|
|
|
|
home server
|
|
docker container on jeeves/unraid
|
|
python + fastapi
|
|
sqlite idempotency store
|
|
append-only org writer
|
|
|
|
flow
|
|
user saves capture locally
|
|
app marks it pending
|
|
sync runs when server is reachable
|
|
app posts pending captures to fastapi
|
|
server validates, dedupes by id, appends to phone.org
|
|
app marks capture synced
|
|
```
|
|
|
|
## v1 behavior
|
|
|
|
the app launches into a focused text box. the user can type immediately.
|
|
|
|
controls:
|
|
|
|
- note/todo toggle
|
|
- optional tags field
|
|
- save
|
|
- save and close
|
|
- sync now
|
|
- small history view showing pending, synced, and failed entries
|
|
|
|
each capture has:
|
|
|
|
- stable client-generated id
|
|
- created timestamp with timezone
|
|
- kind: `note` or `todo`
|
|
- body text
|
|
- tags
|
|
- device name
|
|
- sync status
|
|
- optional last error
|
|
|
|
## org output
|
|
|
|
todo:
|
|
|
|
```org
|
|
* TODO buy printer paper :home:errands:
|
|
:PROPERTIES:
|
|
:CREATED: [2026-05-17 sun 14:31]
|
|
:SOURCE: android
|
|
:ID: phone-20260517-143122-a8f2
|
|
:END:
|
|
```
|
|
|
|
note:
|
|
|
|
```org
|
|
* note :retcon:
|
|
:PROPERTIES:
|
|
:CREATED: [2026-05-17 sun 14:33]
|
|
:SOURCE: android
|
|
:ID: phone-20260517-143322-b91c
|
|
:END:
|
|
|
|
mobile capture should stay dumb and append-only.
|
|
```
|
|
|
|
## server paths
|
|
|
|
default container paths:
|
|
|
|
```text
|
|
/data/synq.org
|
|
/data/capture.sqlite3
|
|
/data/rejected.log
|
|
```
|
|
|
|
recommended unraid host mapping:
|
|
|
|
```text
|
|
/mnt/user/synq/phone-capture:/data
|
|
```
|
|
|
|
or map `/data/synq.org` directly to wherever the real org file lives. prefer a dedicated capture file first; emacs can include it in agenda later.
|
|
|
|
## security model
|
|
|
|
v1 is lan-only. bind the service to the host lan and do not expose it through swag/cloudflare/public dns.
|
|
|
|
minimum useful controls:
|
|
|
|
- shared bearer token in app and server env
|
|
- server only accepts json
|
|
- server rejects empty body
|
|
- server dedupes ids
|
|
- server writes append-only org
|
|
- server never edits or parses existing org
|
|
- docker volume is backed up
|
|
|
|
## repo layout
|
|
|
|
suggested monorepo:
|
|
|
|
```text
|
|
synq/
|
|
android/
|
|
app/
|
|
server/
|
|
app/
|
|
main.py
|
|
models.py
|
|
org_writer.py
|
|
store.py
|
|
tests/
|
|
Dockerfile
|
|
pyproject.toml
|
|
docs/
|
|
api.md
|
|
android-notes.md
|
|
server-notes.md
|
|
docker-compose.example.yml
|
|
.env.example
|
|
tasks.org
|
|
README.md
|
|
```
|
|
|
|
## build order
|
|
|
|
1. implement server first using curl tests.
|
|
2. implement android local capture with room.
|
|
3. implement manual sync.
|
|
4. add workmanager opportunistic sync.
|
|
5. add history screen and resend handling.
|
|
6. polish launch speed and widget/share-target only after core path works.
|
|
|
|
## v2 parking lot
|
|
|
|
- android share target
|
|
- quick settings tile or widget
|
|
- tag chips from recent tags
|
|
- configurable default tag
|
|
- edit unsynced entries only
|
|
- multi-device capture
|
|
- wireguard-aware sync
|
|
- local export/import
|
|
- optional emacs ingest helpers
|