bvandeusen f38864088b
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m19s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m37s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (debug APK) (push) Successful in 8m9s
core: a completed recurring reminder advances instead of ending
`complete_reminder` cleared `remind_at` and said so in its own comment —
"(Recurrence advancement is a later refinement.)". So Done on a daily reminder
was quietly the last time it ever fired. Reminder notifications made that much
easier to hit, because Done is now a button in the notification shade.

**The server already had this.** `src/thoughtsync/notes/recurrence.py` has done
it correctly all along, which means the web behaved one way and the desktop and
Android the other, on the same note, in the same account. This is a port of that
file rather than a fresh implementation, kept behaviourally identical rather than
merely similar: the same reminder can be completed from a browser or a client,
and a disagreement would move it depending on which one you happened to use.

The seven new tests in `core/src/local/recur.rs` mirror the Python suite case for
case, including the one that matters most in practice — 31 January plus a month
is 28 February, and the step after that is 28 March rather than back to the 31st.
That clamp is sticky, and it is now asserted on both sides so a future "fix" to
either has to change both.

Advancement is measured from the reminder's own time, never from now, which is
what keeps a 09:00 daily reminder at 09:00 when it is dealt with at 09:47. A
phone left in a drawer for a fortnight rolls forward to tomorrow rather than
arriving at fourteen pending occurrences of the same thing.

Also matched from the server, and a latent bug of its own: the non-recurring
branch now clears `recurrence` as well as `remind_at`. Before, completing a note
that carried a rule left the rule behind with no reminder attached — invisible in
every UI, since they only render recurrence when there is a reminder to recur
from, and waiting to surprise whoever next set a time on that note.

Documented rather than hidden, and shared with the server: the arithmetic is in
UTC and a note carries no timezone, so a daily reminder crossing a DST boundary
keeps its UTC time and shifts by an hour locally. Fixing that means a zone per
note, which is a wire-format change.

Verified in the CI image before pushing: fmt, clippy --all-targets -D warnings,
and the full suite — core 89 to 96, ffi 11 to 12. The new FFI test walks the path
the notification's Done button actually takes.
2026-08-19 21:22:22 -04:00

ThoughtSync

Self-hosted personal thought-capture web app in the FabledSword family — a Google-Keep-style masonry post-it board for capturing disparate thoughts in under a second, designed to grow into a lightweight second brain (labels, search, [[wiki-links]], graph).

  • Stack: Quart (async Python) + Vue 3 + PostgreSQL, Docker, Fabled-Git CI.
  • Auth: native email + password (signed-cookie sessions). Multi-user with the family owner + direct-share + group-share ACL.
  • Web-first. An Android companion is a later, conditional milestone.

Layout

src/thoughtsync/     Quart app (app factory, auth, models, ACL, config, db)
alembic/             async migrations (schema built via `alembic upgrade head`)
tests/               DB-free unit tests (pytest)
frontend/            Vue 3 + Vite + TypeScript + Tailwind SPA
Dockerfile           2-stage build (Vue -> python:3.12-slim runtime)
docker-compose.yml   local app + Postgres stack

Development

Backend (needs a Postgres reachable at THOUGHTSYNC_DATABASE_URL):

pip install -e ".[dev]"
alembic upgrade head
hypercorn 'thoughtsync.app:create_app()' --bind 0.0.0.0:5000

Frontend (proxies /api to :5000):

cd frontend
npm install
npm run dev        # http://localhost:5173

Or run it in Docker. Hot-reload dev stack (edit code, changes reload live) — Postgres + backend + Vite dev server, app at http://localhost:5173:

docker compose -f docker-compose.dev.yml up

Or the full production image (SPA baked in) at http://localhost:5000:

docker compose up --build

Deploy (self-host)

A complete two-container stack (app + Postgres) you can copy and run. Save it as docker-compose.yml, change the two CHANGE_ME passwords (they must match), then docker compose up -d:

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: thoughtsync
      POSTGRES_PASSWORD: CHANGE_ME          # change this
      POSTGRES_DB: thoughtsync
    volumes:
      - thoughtsync-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U thoughtsync"]
      interval: 5s
      timeout: 5s
      retries: 10

  app:
    image: git.fabledsword.com/bvandeusen/thoughtsync: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
    volumes:
      - thoughtsync-data:/var/thoughtsync   # uploaded images; omit if you don't use attachments
    ports:
      - "5000:5000"

volumes:
  thoughtsync-db:
  thoughtsync-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 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.
  • 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> (immutable, for pinning / rollback).
  • Install as an app (PWA): ThoughtSync 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.

Milestones

  • M0 — Foundation & Identity : Quart+Vue+Postgres skeleton, native auth, sharing-ACL spine, Fabled-Git CI.
  • M1 — Capture Core : note model + masonry board (quick-add, colors, pin, edit-in-place, archive, trash+restore).
  • M1.5 — Roles & DB-backed Settings : first user is admin, admin Settings UI, DATABASE_URL is the only required env.
  • M2 — Organize: labels/tags, search, checklists, attachments.
  • M3 — Second-brain seeds: [[wiki-links]], backlinks, graph view, reminders.
  • M4 — Launch hardening: responsive/PWA, settings UI, polish.

Work happens on dev; main is protected (PR-only).

S
Description
Self-hosted personal thought-capture web app (FabledSword family) — a Google-Keep-style masonry post-it board for fast idea capture, growing into a lightweight second brain. Quart + Vue 3 + PostgreSQL.
Readme
3.2 MiB
2026-08-23 16:50:53 -04:00
Languages
Python 26.3%
Rust 23.9%
Kotlin 22.7%
Vue 13.3%
TypeScript 6.6%
Other 7.2%