diff --git a/README.md b/README.md index dcc8871..5d36e08 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,19 @@ # 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. -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`. +1. Open Synq app +2. Type note, mark as TODO (opt), add tags (opt), save. +3. 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 +

+ home-screen-v1 + history-screen-v1 + settings-v1 +

-## architecture +## Architecture ```text android app @@ -28,137 +27,22 @@ home server 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 +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 -- 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) +### 1. synq-server (docker) ```bash # build @@ -171,9 +55,9 @@ docker run -d --name synq --restart=unless-stopped \ 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 git pull @@ -182,9 +66,9 @@ docker stop synq && docker rm synq # 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 - **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. - -## 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 diff --git a/docs/history-v1.png b/docs/history-v1.png new file mode 100644 index 0000000..e5b0435 Binary files /dev/null and b/docs/history-v1.png differ diff --git a/docs/home-v1.png b/docs/home-v1.png new file mode 100644 index 0000000..473bdfc Binary files /dev/null and b/docs/home-v1.png differ diff --git a/docs/settings-v1.png b/docs/settings-v1.png new file mode 100644 index 0000000..1f966e1 Binary files /dev/null and b/docs/settings-v1.png differ diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..54f3295 --- /dev/null +++ b/docs/spec.md @@ -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. diff --git a/logo/icon-fg-ns-512.png b/logo/icon-fg-ns-512.png new file mode 100644 index 0000000..a35c0a4 Binary files /dev/null and b/logo/icon-fg-ns-512.png differ