WhiteBalloon is a socially-authenticated mutual aid network built on an open, transparent social graph where every connection and interaction is publicly inspectable but personally private. Identities are represented by disposable private keys that are socially cross-authenticated — your inviter cryptographically vouches for you, forming a chain of verified trust rather than a database of static profiles. The result is a decentralized web of relationships where authenticity is earned, not claimed.
At its core, WhiteBalloon is a trust-driven coordination engine: an invite-only network where every participant begins by expressing a real need and where visibility, recommendations, and introductions are shaped by degrees of separation. The AI layer continuously reads the open graph to suggest helpers, draft introductions, and surface mutual aid opportunities across clusters of trust. The infrastructure — built on FastAPI, SQLModel, and open cryptographic primitives — is deliberately minimal, designed to make social cooperation computationally legible without centralizing control. By merging open data, social verification, and AI mediation, WhiteBalloon reimagines the social network as an infrastructure of care and reciprocity, not performance.
WhiteBalloon is a modular FastAPI + SQLModel application that ships with invite-only authentication and a lightweight help-request feed. The project serves as a foundation for layering additional atomic modules without adopting heavy frontend frameworks.
- Invite-based registration with automatic admin bootstrap for the first user
- Auto-approval for invite-based registrations (configurable per invite), allowing trusted users to land in a fully authenticated session immediately
- Multi-device login approvals powered by verification codes
- Session management via secure cookies backed by the database
- Help-request feed with progressive enhancement for creating and completing requests
- Admin-toggleable direct messaging module backed by a dedicated SQLite store (disabled by default)
- Admin-only profile directory (
/admin/profiles) with per-account drill-downs to review contact info, sharing scope, requests, and invite history - Members directory (
/members) for fully authenticated members to browse public profiles plus their invitees with filters and scope-aware contact visibility - Admin control panel (
/admin) that centralizes links to the directory, sync dashboard, and future operator tools - Private RSS feeds (
/settings/notifications) so members/admins can subscribe to all-open, invite-circle, completed, or admin-pending request slices with tokenized URLs - Per-request detail pages with shareable URLs and consistent permissions
- Comment threads on request detail pages with progressive enhancement (vanilla JS) for instant posting
- Manual sync bundles (
*.sync.txt) use email-style headers plus plain-text bodies so exports are Git-friendly - Vanilla CSS design system with reusable layout primitives and components
- JSON API under
/api/requestsfor programmatic access to the request feed - Invite generation returns share-ready links using the current request origin (fallback to
SITE_URL) - Animated, bubbly theme with gradient background/responsive cards inspired by mutual-aid celebrations (respects
prefers-reduced-motion) - Admin-only Sync Control Center (
/admin/sync-control) for managing peers, triggering push/pull jobs, and reviewing recent sync activity
WhiteBalloon ships self-hosted variable fonts to keep typography distinctive without third-party requests:
- Sora (headings, accents) — SIL Open Font License
- Inter (body copy, UI) — SIL Open Font License
Both WOFF2 files live under static/fonts/ and load via @font-face in static/css/app.css. When updating fonts, download the latest releases from the upstream projects, convert to WOFF2 if needed, and replace the existing files with the same filenames so the CSS continues to resolve them. Keep the font-weight axis between 400–700 to match the existing usage.
All new feature work follows the four-step process outlined in FEATURE_DEVELOPMENT_PROCESS.md. That file now serves as the entry point to a chained set of step-specific instructions in docs/dev/feature_process/step{N}_*.md. Only open the file for the step you’re currently on, deliver the artifact in docs/plans/, and wait for an “Approved Step N” reply before moving to the next document. This keeps later-step guidance in the context window when you need it instead of up front.
Requires Python 3.10+.
-
Setup the environment (creates venv, installs deps, initializes database)
./wb setup
On Windows:
wb.bat setup
-
Run the development server
./wb runserverOn Windows:
wb.bat runserverVisit http://127.0.0.1:8000 to access the interface.
Running inside WSL?
wb runservernow binds to0.0.0.0automatically so Windows browsers can still reach127.0.0.1:8000. Pass--hostto override the binding.
Note: The first registered user (no invite token required) becomes an administrator automatically.
Database integrity: Re-run
./wb init-dbwhenever you suspect schema drift. The command now checks tables/columns against SQLModel definitions, auto-creates missing pieces, and reports mismatches that require manual attention.
Invite links: By default invite links use the incoming request origin. Set
SITE_URLin.envto provide a fallback host for CLI usage or non-HTTP contexts.Sync Control Center: After logging in as an administrator, open
/admin/sync-control(also linked from/sync/public) to edit peers, run push/pull jobs, and monitor activity without leaving the browser.WSL port binding issues: Windows 11 23H2 introduced host networking bugs that can stop Windows from reaching WSL ports even after
wsl --shutdown. Followdocs/dev/wsl_port_binding.mdto reset HNS safely if localhost stops responding.Chat indexing & embeddings: Run
./wb chat index --request-id <id>whenever you import new Signal chats so the search caches and optional LLM tags stay fresh. Follow up with./wb chat embed --request-id <id> --adapter dedalus(or--adapter localfor offline dev) to build the semantic vectors that power the “Related chat mentions” panel.Profile reviews: Administrators can open
/people/<username>to see the latest comments inline and follow the “View all comments” link for a full history when auditing members or Signal personas.Signal profile glazing: Run
./wb profile-glaze --username <member>to analyze that member’s Signal comments (LLM sentiment/tags) and immediately regenerate their bio. Add--allto sweep every Signal-linked account or pass--dry-runto preview the comment batches without spending tokens.
Developer reminder: Whenever you add a new configuration flag or environment variable, update
.env.examplewith the default and a short description so fresh installs pick it up.Frontend skins: Whenever you change files under
static/skins/or adjust shared design tokens, run./wb skins build(or./wb skins watchduring development) so the hashed bundles instatic/build/skins/stay in sync. Use./wb skins listto see which entry bundles exist before rebuilding. Skipping the rebuild leaves browsers serving the previous CSS and makes refreshed layouts look broken.
Direct messaging ships disabled. After flipping the toggle on /sync/public, initialize the dedicated database once:
./wb messaging init-dbThe CLI will create data/messages.db, leaving the primary data/app.db untouched so backups and retention policies stay isolated.
Stage 5 of the sync plan introduces signed bundles so operators can trust data pulled from peers:
- Generate a signing keypair once per instance:
The private key lives under
./wb sync keygen
.sync/keys/and the CLI prints the base64 public key you should share with peers. The key is auto-created the first time you run any sync command if it’s missing. - Register peers with their bundle directory and public key so pulls can authenticate signatures:
./wb sync peers add --name hub --path ../shared/public_sync --public-key <base64>
- Export/push flows now sign
manifest.sync.txt, emitbundle.sig, and drop your public key underdata/public_sync/public_keys/<key-id>.pub. Multiple operators can share the same bundle directory; each signer writes/updates only their own file. ./wb sync pull <peer>and./wb sync import <dir> --peer <name>verify the signature before applying data. Use--allow-unsignedonly when working with historical bundles that predate signatures.
- Clone the repo (or otherwise sync
data/public_sync/) and run./wb setup. - Register the bundle + public key so imports can verify it:
The
./wb sync peers add --name origin --path data/public_sync --public-key <base64>
<base64>value lives indata/public_sync/public_keys/<key-id>.pub. - Import the dataset with verification:
Only fall back to
./wb sync import data/public_sync --peer origin
--allow-unsignedif you must ingest historical unsigned bundles.
The signature file stores the manifest digest plus the signer’s key ID. Keep .sync/keys/ out of version control and rotate keys with ./wb sync keygen --force if a secret ever leaks.
Instead of syncing via shared folders, you can run the lightweight hub service in this repo (app/hub/).
- Start the hub (once) and configure peers in
.sync/hub_config.json(seedocs/hub/README.md).(Use./wb hub serve --config .sync/hub_config.json --host 127.0.0.1 --port 9100
Ctrl+Cto stop; for production, follow the Ubuntu guide underdocs/hub/.) - On each instance, register the hub peer:
./wb sync peers add --name hub --url https://hub.example --token <secret> --public-key <base64>
- Use the same commands as before:
The CLI detects
./wb sync push hub ./wb sync pull hub
--urland uploads/downloads bundles via HTTPS, verifying signatures before import. - Optional: create an admin token with
./wb hub admin-token --config .sync/hub_config.jsonand visit/adminon the hub to view peer stats.
Administrators can now operate sync workflows entirely from the browser:
- Log in as an admin and open
/admin/sync-control(there’s a shortcut button on/sync/public). - Review existing peers, edit tokens/keys, or add new filesystem/hub peers inline.
- Use the “Push now” / “Pull now” buttons to queue jobs. Status chips update on refresh, and a rolling activity log records who ran what and whether it succeeded.
Jobs reuse the same signing and verification pipeline as the CLI. You can queue a push, continue browsing, and refresh to see the completion state once the background task finishes.
- Visit
/settings/notificationsfrom the Menu → Account section to grab private RSS URLs for each request slice: all open items you can see, your invite circle, recently completed requests, and (for admins) pending verifications. - Each URL is tokenized (
/feeds/<token>/<category>.xml). Paste it into any reader to stay current without logging in—the backend enforces the same permissions the web feed already uses. - Use the “Regenerate link” button beside a feed to rotate its token immediately; the settings page also shows when each feed was last accessed so you can spot stale or suspicious clients at a glance.
- While signed in, use the “Send Welcome” button (header menu) to generate an invite instantly.
- The page shows the invite link, token, QR code, and optional fields for suggested username/bio to share with the invitee.
- Shared links pre-fill the token when invitees visit
/register.
- A user registers with an invite token (unless they are the first user).
- If the invite was issued by an approved admin (auto-approve default), the user is fully authenticated instantly and receives a logged-in session. Otherwise, the user submits their username on the login page, creating an authentication request and half-authenticated session.
- While waiting, the user lands on a pending dashboard: they can browse existing requests, save private drafts, and view their verification code.
- The user (or an administrator) completes verification by submitting the generated code, upgrading the session to fully authenticated.
- Sessions are stored in the database and tracked through the
wb_session_idcookie.
The CLI exposes helpers for administration:
./wb create-admin <username>
./wb create-invite --username <admin> --max-uses 3 --expires-in-days 7
./wb session list # Inspect pending authentication requests
./wb session approve <request_id> # Approve a request from the CLI
./wb session deny <request_id> # Deny a pending request- Lightweight JavaScript helpers drive optional in-place updates; initial pages remain server-rendered.
- The feed page loads from
/and the backing API lives under/api/requests. - Authenticated users can post new requests, optionally sharing a contact email for follow-up.
- Half-authenticated users can submit requests which remain private (
pendingstatus) until approval. - Authors and administrators can mark requests as completed; the UI reflects updates instantly.
- Each request has a canonical page at
/requests/<id>that mirrors feed visibility rules and offers a shareable link.
app/
config.py # Environment and settings helpers
db.py # Engine factory and session dependency
dependencies.py # FastAPI dependency utilities
main.py # App factory and router registration
models.py # SQLModel tables (users, sessions, requests, invites)
modules/ # Pluggable feature modules (requests feed)
routes/ # API + UI routers
services/ # Domain services (authentication helpers)
static/css/app.css # Vanilla CSS design system
templates/ # Jinja templates and enhancement-friendly partials
tools/dev.py # Click CLI (invoked via wb/wb.bat)
wb.py # Cross-platform Python launcher
wb / wb.bat # Thin wrappers for Linux/macOS and Windows
- Create a package under
app/modules/<module_name>/withservices.pyand optional routers. - Register the module in
app/modules/__init__.pysoregister_modules()wires it into the app. - Provide templates and static assets under
templates/<module_name>/andstatic/as needed. - Document the feature using the four-step planning process in
docs/plans/(seeFEATURE_DEVELOPMENT_PROCESS.mdfor the chained instructions). - Add CLI helpers or UI routes when the module requires interactive workflows.
- Static assets are served directly by FastAPI; for production behind a proxy, ensure
/staticis cached appropriately. - Migrate from SQLite to another database by adjusting
DATABASE_URLin.envand ensuring the driver is installed. - Set
COOKIE_SECURE=trueand supply a strongSECRET_KEYbefore running in production.