rename: the docs say Inkwell, and nothing else still says ThoughtSync by accident
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 16s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / Python tests (push) Successful in 17s
CI & Build / integration (push) Successful in 50s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m14s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m2s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m36s

Step 6 of milestone 481. README, docs/*, ci-requirements.md, the desktop and
Arch READMEs, alembic.ini, .gitignore, the frontend package name, the service
worker's cache name (its activate handler deletes any cache by another name, so
the old one is cleaned up), and the Android names in the release body.

What still says thoughtsync does so on purpose (Scribe note 5071):
- the desktop data crossover (crossover.rs) and its startup log
- the old-export import marker
- the "Upgrading from ThoughtSync" block in .env.example, and compose's pointer
  to it
- the packages being retired: deb conflicts/replaces thought-sync, pacman
  thoughtsync and thoughtsync-desktop
- the Android signing keyAlias, which names a key in the existing keystore
- history: shipped alembic migrations, and the test-binary hashes that
  ci-requirements.md records from 2026-08-18

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 14:35:02 -04:00
co-authored by Claude Opus 5.5
parent 81cd719327
commit 6d082b2ad8
13 changed files with 67 additions and 65 deletions
+2 -2
View File
@@ -209,5 +209,5 @@ android/local.properties
# The Android client CI bakes into the server image. Fetched fresh on every image
# build, so it is never worth 55 MiB of git history. client/.keep IS tracked, so
# the Dockerfile's COPY always has a directory to copy.
client/thoughtsync.apk
client/thoughtsync-android.json
client/inkwell.apk
client/inkwell-android.json
+14 -14
View File
@@ -1,4 +1,4 @@
# ThoughtSync
# Inkwell
Self-hosted personal thought-capture web app in the **FabledSword** family — a
Google-Keep-style **masonry post-it board** for capturing disparate thoughts in
@@ -23,7 +23,7 @@ docker-compose.yml local app + Postgres stack
## Development
Backend (needs a Postgres reachable at `THOUGHTSYNC_DATABASE_URL`):
Backend (needs a Postgres reachable at `INKWELL_DATABASE_URL`):
```sh
pip install -e ".[dev]"
@@ -64,40 +64,40 @@ services:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: thoughtsync
POSTGRES_USER: inkwell
POSTGRES_PASSWORD: CHANGE_ME # change this
POSTGRES_DB: thoughtsync
POSTGRES_DB: inkwell
volumes:
- thoughtsync-db:/var/lib/postgresql/data
- inkwell-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U thoughtsync"]
test: ["CMD-SHELL", "pg_isready -U inkwell"]
interval: 5s
timeout: 5s
retries: 10
app:
image: git.fabledsword.com/bvandeusen/thoughtsync:latest # :dev for the current dev build
image: git.fabledsword.com/bvandeusen/inkwell:latest # :dev for the current dev build
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
THOUGHTSYNC_DATABASE_URL: postgresql+asyncpg://thoughtsync:CHANGE_ME@db:5432/thoughtsync
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:CHANGE_ME@db:5432/inkwell
volumes:
- thoughtsync-data:/var/thoughtsync # uploaded images; omit if you don't use attachments
- inkwell-data:/var/inkwell # uploaded images; omit if you don't use attachments
ports:
- "5000:5000"
volumes:
thoughtsync-db:
thoughtsync-data:
inkwell-db:
inkwell-data:
```
Then open `http://<host>:5000` and register — **the first account becomes the admin**.
- **Only `THOUGHTSYNC_DATABASE_URL` is required.** `THOUGHTSYNC_SECRET_KEY` is optional; if
- **Only `INKWELL_DATABASE_URL` is required.** `INKWELL_SECRET_KEY` is optional; if
unset, a signing key is generated and persisted in the database (sessions survive restarts).
- Uploaded images live under the `thoughtsync-data` volume at `/var/thoughtsync`.
- Uploaded images live under the `inkwell-data` volume at `/var/inkwell`.
- The app waits for the database and runs migrations (`alembic upgrade head`) automatically on start.
- **Image tags:** `:latest` (stable, built from `main`) · `:dev` (latest `dev`
build) · `:<git-sha>` on `main` only (immutable, the rollback unit). There are
@@ -108,7 +108,7 @@ Then open `http://<host>:5000` and register — **the first account becomes the
port, and back up the attachment volume as well as the database. See
[docs/public-hosting.md](docs/public-hosting.md), which also lists what the app
hardens on its own and what it deliberately doesn't.
- **Install as an app (PWA):** ThoughtSync is installable ("Add to Home Screen" / the
- **Install as an app (PWA):** Inkwell is installable ("Add to Home Screen" / the
browser's install button) for an app-like window. Browsers only offer install over a
**secure context**, so put the app behind a reverse proxy terminating **HTTPS** (or reach
it via `localhost`) — plain `http://<host>:5000` won't show the install prompt.
+1 -1
View File
@@ -1,4 +1,4 @@
# Alembic single-database async configuration for ThoughtSync.
# Alembic single-database async configuration for Inkwell.
[alembic]
script_location = %(here)s/alembic
+8 -7
View File
@@ -1,4 +1,4 @@
# CI Requirements — ThoughtSync
# CI Requirements — Inkwell
> Spec lives in [`docs/process.md`](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/process.md)
> in the CI-Runner repo.
@@ -39,7 +39,7 @@ entirely on `ci-python:3.14`.
install` cold cost is a non-blocker.
- Build gates on `typecheck` + `lint` only. The `test` job runs in parallel for
visibility but does not block the dev image push. DB-backed / integration tests
run against the dev image manually — ThoughtSync's unit tests are DB-free (no
run against the dev image manually — Inkwell's unit tests are DB-free (no
Postgres service lane in CI yet).
- `dev` push -> `:dev` + `:<sha>`; `v*` tag -> `:latest` + `:<version>` + `:<sha>`
(family rule 46).
@@ -87,7 +87,7 @@ parts. Three of them are family rules for a reason:
`docker ps` by it. A spaced or underscored name breaks the filter.
- **Service hostnames are not routable** on this runner (rule 79), so the step resolves
the Postgres container's bridge IP with `docker ps --filter` + `docker inspect` and
builds `THOUGHTSYNC_DATABASE_URL` from it. `postgres:5432` will not connect.
builds `INKWELL_DATABASE_URL` from it. `postgres:5432` will not connect.
- **`run:` is busybox sh** (rule 81) — no `/dev/tcp` — so the readiness wait is a small
Python heredoc. Its terminator must dedent to column 0 after YAML strips the block
indent; check with `yaml.safe_load` and print the `run` string if you edit it.
@@ -205,7 +205,7 @@ backend/frontend push.
## Android lane — being rebuilt (M12)
The Tauri-mobile Android lane is gone. Android is a native Kotlin/Compose client
over the shared `thoughtsync-core` crate instead — see Scribe note 2730 for the
over the shared `inkwell-core` crate instead — see Scribe note 2730 for the
decision and milestone M12 for the arc.
The image it will run on already exists: **`ci-rust-android:1.97`**, repurposed
@@ -218,7 +218,7 @@ second image, and JDK 25 (which requires **Gradle 9.1+** in this repo's wrapper
the old JDK 17 pin existed only because Tauri generated a Gradle 8.x project).
The Rust pin is in LOCKSTEP with `ci-tauri` and `ci-tauri-win`. All three build
`thoughtsync-core` from one workspace `Cargo.lock` under `--locked`, so a
`inkwell-core` from one workspace `Cargo.lock` under `--locked`, so a
mismatched Rust minor across the lanes would mean divergent resolution for no
reason. Bump the three together or not at all.
@@ -333,8 +333,9 @@ differs from CI is worse than none.
**This reproduces CI exactly, not approximately.** On the 2026-08-18 run the
local test binary hashes (`thoughtsync_core-bbaae79723888ad1`,
`thoughtsync_desktop_lib-9d162263f8d0aca3`, `thoughtsync_ffi-fc557b96dc795e27`)
matched CI run 3931's byte for byte. Same image, same lockfile, same units.
`thoughtsync_desktop_lib-9d162263f8d0aca3`, `thoughtsync_ffi-fc557b96dc795e27`,
named for the crates as they were before the rename to Inkwell) matched CI run
3931's byte for byte. Same image, same lockfile, same units.
`target/` persists on the host between runs, so after the first cold build these
take seconds (~30s for clippy). It is gitignored and reaches ~1.4 GB; delete it
+2 -2
View File
@@ -1,9 +1,9 @@
# ThoughtSync desktop (Tauri v2)
# Inkwell desktop (Tauri v2)
Local-first desktop client. The window loads the shared **Vue 3 frontend** from
`../frontend`; the Rust core (`src-tauri`) owns the on-device store and the opt-in
sync engine (built out across the M10 milestone). Works fully offline; optionally
syncs to a self-hosted ThoughtSync server.
syncs to a self-hosted Inkwell server.
## Layout
+15 -14
View File
@@ -1,6 +1,6 @@
# ThoughtSync desktop — Arch package
# Inkwell desktop — Arch package
A **prebuilt** native pacman package, published as an asset on every ThoughtSync
A **prebuilt** native pacman package, published as an asset on every Inkwell
release. Nothing to compile, no toolchain to install.
Installing natively on Arch matters for more than tidiness: pacman pulls
@@ -15,22 +15,22 @@ Easiest — the one-command installer picks this package automatically on any
pacman system:
```sh
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh
curl -fsSL https://git.fabledsword.com/bvandeusen/inkwell/raw/branch/dev/desktop/packaging/install.sh | sh
```
That installs the newest build from `main`. To follow the rolling development
channel instead, pass the flag through the pipe:
```sh
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh -s -- --channel dev
curl -fsSL https://git.fabledsword.com/bvandeusen/inkwell/raw/branch/dev/desktop/packaging/install.sh | sh -s -- --channel dev
```
Or grab the `.pkg.tar.*` from the
[releases page](https://git.fabledsword.com/bvandeusen/thoughtsync/releases)
[releases page](https://git.fabledsword.com/bvandeusen/inkwell/releases)
and install it directly:
```sh
sudo pacman -U thoughtsync-*-x86_64.pkg.tar.*
sudo pacman -U inkwell-*-x86_64.pkg.tar.*
```
The compression suffix depends on what the build image provides — `.zst` when
@@ -39,17 +39,18 @@ all of them; only the filename differs.
Either way you get:
- `/usr/bin/thoughtsync` — the app
- `/usr/share/applications/thoughtsync.desktop` — the menu entry
- `/usr/share/icons/hicolor/*/apps/thoughtsync.png` — themed icons
- `/usr/bin/inkwell` — the app
- `/usr/share/applications/inkwell.desktop` — the menu entry
- `/usr/share/icons/hicolor/*/apps/inkwell.png` — themed icons
Launch **ThoughtSync** from your app menu, or run `thoughtsync`.
Launch **Inkwell** from your app menu, or run `inkwell`.
Uninstall: `sudo pacman -R thoughtsync`.
Uninstall: `sudo pacman -R inkwell`.
The package was called `thoughtsync-desktop` before; it declares `replaces`/
`conflicts` on that name, so an upgrade from it is a normal `pacman -U` and
leaves nothing behind.
The package was called `thoughtsync` until the app was renamed Inkwell, and
`thoughtsync-desktop` before that. It declares `replaces`/`conflicts` on both
names, so an upgrade from either is a normal `pacman -U` and leaves nothing
behind.
## How the package is built
+1 -1
View File
@@ -149,7 +149,7 @@ fi
echo "==> Creating release for $TAG"
BODY=$(cat <<JSON
{"tag_name":"$TAG","name":"Inkwell $CHANNEL_LABEL","draft":false,"prerelease":$RELEASE_PRERELEASE,
"body":"Inkwell $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/inkwell/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
"body":"Inkwell $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`inkwell.apk\`. Copy it and \`inkwell-android.json\` into your server's \`/var/inkwell/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
JSON
)
+10 -10
View File
@@ -1,6 +1,6 @@
# Getting the Android app onto your server
ThoughtSync's server hands out the Android client it syncs with. Once an APK is in
Inkwell's server hands out the Android client it syncs with. Once an APK is in
place, anyone with an account on that server can download it from **Account →
Linked devices**, and linked phones can update themselves from it.
@@ -34,7 +34,7 @@ pinning.
## Overriding it
If you want a specific build — testing something, or holding back — drop it in
`/var/thoughtsync/client/` and it wins over the image's copy.
`/var/inkwell/client/` and it wins over the image's copy.
That directory is shared with the desktop clients the server hands out, and
**precedence is decided per platform**: dropping in an APK overrides the baked APK
@@ -44,14 +44,14 @@ Two files, both required:
| File | What it is |
| --- | --- |
| `thoughtsync.apk` | the client |
| `thoughtsync-android.json` | `{version_name, version_code, size, sha256}` |
| `inkwell.apk` | the client |
| `inkwell-android.json` | `{version_name, version_code, size, sha256}` |
The sidecar exists because an APK keeps its version in a binary manifest that
needs the Android build tools to read. CI writes it beside the APK, where the
real values are already known.
`/var/thoughtsync` is the same volume that holds attachments (`Config.DATA_DIR`),
`/var/inkwell` is the same volume that holds attachments (`Config.DATA_DIR`),
so a build dropped there survives container recreation — and survives an image
upgrade, which is the point of an override.
@@ -59,10 +59,10 @@ Both files are published to the rolling dev-channel release (tag `dev-rolling`)
on every green Android build:
```sh
REPO=https://git.fabledsword.com/bvandeusen/thoughtsync
REPO=https://git.fabledsword.com/bvandeusen/inkwell
TOKEN=... # a Fabled-Git token with read access; the instance is private
for f in thoughtsync.apk thoughtsync-android.json; do
for f in inkwell.apk inkwell-android.json; do
curl -fsSL -H "Authorization: token $TOKEN" \
-o "/tmp/$f" "$REPO/releases/download/dev-rolling/$f"
done
@@ -70,8 +70,8 @@ done
# Copy the sidecar LAST. A sidecar that does not match the APK beside it is not a
# client, so a half-finished copy falls back to the image's build rather than
# advertising a lie.
docker compose cp /tmp/thoughtsync.apk app:/var/thoughtsync/client/
docker compose cp /tmp/thoughtsync-android.json app:/var/thoughtsync/client/
docker compose cp /tmp/inkwell.apk app:/var/inkwell/client/
docker compose cp /tmp/inkwell-android.json app:/var/inkwell/client/
```
To go back to whatever the image ships, delete both files.
@@ -88,7 +88,7 @@ offering a button that fails.
## What happens if you get it wrong
All of these describe an override in `/var/thoughtsync/client/`. A broken
All of these describe an override in `/var/inkwell/client/`. A broken
override does not take the feature away — it falls through to the build the image
shipped with, which is the whole reason precedence runs in that direction.
+7 -7
View File
@@ -1,6 +1,6 @@
# Putting ThoughtSync on the public internet
# Putting Inkwell on the public internet
ThoughtSync is built to run on a LAN and works fine there with no ceremony. Exposing
Inkwell is built to run on a LAN and works fine there with no ceremony. Exposing
it changes the threat model: anyone can now reach the login form, and any account is
one guessed password away from someone's whole note history.
@@ -65,7 +65,7 @@ Which fix depends on where your proxy runs:
the `ports:` block from `docker-compose.yml`. The proxy reaches the app over the
compose network; no published port is needed at all, and this is the safest of the
two because there is no host port to reach even from the host.
- **Proxy on the host**: set `THOUGHTSYNC_BIND=127.0.0.1` in `.env`, so the port
- **Proxy on the host**: set `INKWELL_BIND=127.0.0.1` in `.env`, so the port
exists but only the host itself can use it.
To check which you have: `docker compose ps` shows the published ports, and
@@ -73,12 +73,12 @@ To check which you have: `docker compose ps` shows the published ports, and
app is still answering around the proxy. It should not be.
**5. Have a backup that includes the files.** Attachments are files on the
`thoughtsync-data` volume, not rows — a `pg_dump` restores notes whose images are all
`inkwell-data` volume, not rows — a `pg_dump` restores notes whose images are all
gone. Back up both:
```
docker compose exec -T db pg_dump -U thoughtsync thoughtsync > notes.sql
docker run --rm -v thoughtsync-data:/d -v "$PWD":/out alpine tar czf /out/media.tgz -C /d .
docker compose exec -T db pg_dump -U inkwell inkwell > notes.sql
docker run --rm -v inkwell-data:/d -v "$PWD":/out alpine tar czf /out/media.tgz -C /d .
```
## What the app already does
@@ -132,7 +132,7 @@ know. They are the reason not to hand out open registration to strangers.
The app allows plain HTTP so a self-hosted server on a LAN is usable at all — Android
blocks cleartext by default from API 28, and `http://192.168.1.10:8000` is exactly the
case ThoughtSync is built for. Over the public internet, link the phone to the
case Inkwell is built for. Over the public internet, link the phone to the
**HTTPS** hostname. The sync screen shows a warning before any credential field
whenever the address it probed was `http://`; on a public network that warning means
what it says.
+3 -3
View File
@@ -1,4 +1,4 @@
# ThoughtSync sync protocol
# Inkwell sync protocol
The contract the local-first native clients (Tauri desktop, Android) implement
against. The server is the **sync hub**: each client keeps a full local store
@@ -38,8 +38,8 @@ token, or even has an account:
```
The client identifies itself on every request with
`X-ThoughtSync-Client: thoughtsync-desktop/<app version>` and
`X-ThoughtSync-Protocol: <n>`.
`X-Inkwell-Client: inkwell-desktop/<app version>` and
`X-Inkwell-Protocol: <n>`.
### `sync_features` — why versions alone aren't enough
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"dependencies": {
"pinia": "^2.2.0",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "thoughtsync-frontend",
"name": "inkwell-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
+1 -1
View File
@@ -6,7 +6,7 @@
// and avoid deploy staleness, this SW does NOT cache the app shell, hashed build
// assets, or any /api response — everything but the offline fallback goes
// straight to the network.
const CACHE = "thoughtsync-shell-v1";
const CACHE = "inkwell-shell-v1";
const OFFLINE_URL = "/offline.html";
self.addEventListener("install", (event) => {