updated readme
This commit is contained in:
203
README.md
203
README.md
@@ -1,20 +1,19 @@
|
|||||||
# synq, a tiny android-to-org capture system.
|
# synq, a tiny android-to-org capture system.
|
||||||
|
synq consists of:
|
||||||
|
- an Android app for capturing notes items
|
||||||
|
- a docker-based agent that pulls notes and saves them to a local .org file
|
||||||
|
|
||||||
open app.
|
1. Open Synq app
|
||||||
type, optionally mark as todo, optionally add tags, save.
|
2. Type note, mark as TODO (opt), add tags (opt), save.
|
||||||
if home server accessible, post unsynced captures to lan-only service and appended to `synq.org`.
|
3. If home server accessible, post unsynced captures to lan-only service and appended to `synq.org`.
|
||||||
|
|
||||||
## non-goals
|
<p>
|
||||||
- no org parser on android
|
<img src="docs/home-v1.png" alt="home-screen-v1" width="220">
|
||||||
- no agenda on android
|
<img src="docs/history-v1.png" alt="history-screen-v1" width="220">
|
||||||
- no sync provider dependency
|
<img src="docs/settings-v1.png" alt="settings-v1" width="220">
|
||||||
- no nextcloud file editing
|
</p>
|
||||||
- no conflict resolution
|
|
||||||
- no public internet exposure
|
|
||||||
- no ai tagging in v1
|
|
||||||
- no account system in v1
|
|
||||||
|
|
||||||
## architecture
|
## Architecture
|
||||||
|
|
||||||
```text
|
```text
|
||||||
android app
|
android app
|
||||||
@@ -28,137 +27,22 @@ home server
|
|||||||
python + fastapi
|
python + fastapi
|
||||||
sqlite idempotency store
|
sqlite idempotency store
|
||||||
append-only org writer
|
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
|
1. user saves capture locally
|
||||||
|
2. app marks it pending
|
||||||
|
3. sync runs when server is reachable
|
||||||
|
4. app posts pending captures to fastapi
|
||||||
|
5. server validates, dedupes by id, appends to phone.org
|
||||||
|
6. app marks capture synced
|
||||||
|
|
||||||
the app launches into a focused text box. the user can type immediately.
|
|
||||||
|
|
||||||
controls:
|
## Build & Deploy
|
||||||
|
1. First, build and deploy `synq-server`, the docker container that listens for new notes and writes to the local file.
|
||||||
|
2. Then, build and install the `synq` app.
|
||||||
|
3. Finally, retrieve the token (`token.txt` in the docker container's data directory, or from the container logs `docker logs synq-server`)
|
||||||
|
|
||||||
- note/todo toggle
|
### 1. synq-server (docker)
|
||||||
- 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.
|
|
||||||
|
|
||||||
## build & deploy
|
|
||||||
|
|
||||||
### server (docker)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# build
|
# build
|
||||||
@@ -171,9 +55,9 @@ docker run -d --name synq --restart=unless-stopped \
|
|||||||
synq-server
|
synq-server
|
||||||
```
|
```
|
||||||
|
|
||||||
the server generates a random token on first start and logs it prominently. copy it into the android app settings. to use a fixed token instead, pass `-e PHONE_CAPTURE_TOKEN=yourtoken`.
|
The server generates a random token on first start and logs it prominently. copy it into the android app settings. To use a fixed token instead, pass `-e PHONE_CAPTURE_TOKEN=yourtoken`.
|
||||||
|
|
||||||
to rebuild after a `git pull`:
|
To rebuild after a `git pull`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git pull
|
git pull
|
||||||
@@ -182,9 +66,9 @@ docker stop synq && docker rm synq
|
|||||||
# re-run the docker run command above
|
# re-run the docker run command above
|
||||||
```
|
```
|
||||||
|
|
||||||
### android
|
### 2. synq (android)
|
||||||
|
|
||||||
open `android/` in Android Studio and hit **Sync**. then either:
|
Open `android/` in Android Studio and hit **Sync**. then either:
|
||||||
|
|
||||||
- **run on device/emulator directly** from Android Studio (▶), or
|
- **run on device/emulator directly** from Android Studio (▶), or
|
||||||
- **build a release APK** from the terminal:
|
- **build a release APK** from the terminal:
|
||||||
@@ -211,36 +95,3 @@ git push gitea v1.0.0
|
|||||||
```
|
```
|
||||||
|
|
||||||
then go to **Releases → New Release** in the Gitea UI, pick the tag, and drag the APK into the assets box. anyone on the LAN can download it from there.
|
then go to **Releases → New Release** in the Gitea UI, pick the tag, and drag the APK into the assets box. anyone on the LAN can download it from there.
|
||||||
|
|
||||||
## backup
|
|
||||||
|
|
||||||
### what to back up
|
|
||||||
|
|
||||||
the server writes to one directory (the `/data` volume). back up the whole thing:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/data/synq.org ← the canonical org capture file
|
|
||||||
/data/capture.sqlite3 ← idempotency store (dedup ids)
|
|
||||||
/data/token.txt ← auto-generated token (if not using PHONE_CAPTURE_TOKEN env var)
|
|
||||||
```
|
|
||||||
|
|
||||||
on unraid the host path is whatever you mapped in compose, e.g. `/mnt/user/ben/synq/phone-capture`.
|
|
||||||
|
|
||||||
### restore behavior
|
|
||||||
|
|
||||||
- restore the `/data` volume to a new container and start it. the server will pick up where it left off.
|
|
||||||
- `synq.org` is append-only plain text — it is human-readable and recoverable even without the sqlite db.
|
|
||||||
- if `capture.sqlite3` is lost but `synq.org` is intact, the server will accept re-posted captures that were already in the org file (no dedup). the android app marks them synced either way (`already_seen` or `accepted`), so the only side effect is duplicate org entries for anything re-synced. restore the db from backup to avoid this.
|
|
||||||
- if `token.txt` is lost, delete it and let the server generate a new one on next start, then update the app settings.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|||||||
BIN
docs/history-v1.png
Normal file
BIN
docs/history-v1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
BIN
docs/home-v1.png
Normal file
BIN
docs/home-v1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/settings-v1.png
Normal file
BIN
docs/settings-v1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
150
docs/spec.md
Normal file
150
docs/spec.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Specifications
|
||||||
|
|
||||||
|
## 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 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
|
||||||
|
## backup
|
||||||
|
|
||||||
|
### what to back up
|
||||||
|
|
||||||
|
the server writes to one directory (the `/data` volume). back up the whole thing:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/data/synq.org ← the canonical org capture file
|
||||||
|
/data/capture.sqlite3 ← idempotency store (dedup ids)
|
||||||
|
/data/token.txt ← auto-generated token (if not using PHONE_CAPTURE_TOKEN env var)
|
||||||
|
```
|
||||||
|
|
||||||
|
on unraid the host path is whatever you mapped in compose, e.g. `/mnt/user/ben/synq/phone-capture`.
|
||||||
|
|
||||||
|
### restore behavior
|
||||||
|
|
||||||
|
- restore the `/data` volume to a new container and start it. the server will pick up where it left off.
|
||||||
|
- `synq.org` is append-only plain text — it is human-readable and recoverable even without the sqlite db.
|
||||||
|
- if `capture.sqlite3` is lost but `synq.org` is intact, the server will accept re-posted captures that were already in the org file (no dedup). the android app marks them synced either way (`already_seen` or `accepted`), so the only side effect is duplicate org entries for anything re-synced. restore the db from backup to avoid this.
|
||||||
|
- if `token.txt` is lost, delete it and let the server generate a new one on next start, then update the app settings.
|
||||||
BIN
logo/icon-fg-ns-512.png
Normal file
BIN
logo/icon-fg-ns-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Reference in New Issue
Block a user