a love letter to tangled (android, iOS, and a search API)

Files
README.md

Twisted #

Twisted is a monorepo for a Tangled mobile client and the supporting Tap-backed indexing API.

Projects #

  • apps/twisted: Ionic Vue client for browsing Tangled repos, profiles, issues, PRs, and indexed search results
  • packages/api: Go service that consumes Tangled records through Tap, fills gaps in the public Tangled API, and serves search
  • docs: top-level specs and plans, split by project under docs/app and docs/api

Architecture #

The app still uses Tangled's public knot and PDS APIs for canonical repo and profile data. The API project adds two complementary capabilities:

  1. Global search over indexed Tangled content
  2. Index-backed summaries for data that is hard to derive from the public API alone, such as followers

That keeps direct browsing honest while giving the client one place to ask for cross-network discovery and graph augmentation.

Development #

Use the top-level justfile for common workflows:

just dev
just build
just test
just api-run-api

The committed apps/twisted/.env points at production. Use apps/twisted/.env.local for machine-local overrides such as a localhost API or OAuth callback.

Run Locally #

Install dependencies once from the repo root:

pnpm install

Start the Ionic/Vite app:

pnpm dev # or: just dev

That serves the client from apps/twisted with Vite.

To run the Go API locally for routine experimentation, no Turso credentials are required.

Start the API in local file mode:

pnpm api:run:api # or: just api-dev

This serves the API and search site on http://localhost:8080 using packages/api/twister-dev.db.

To run the API against remote Turso instead:

just api-dev remote

Run the API smoke checks from the repo root:

uv run --project packages/scripts/api twister-api-smoke

To verify admin endpoints as well, ensure ADMIN_AUTH_TOKEN is present in the environment before running the smoke script.

To run the indexer in local file mode as well:

pnpm api:run:indexer # or: just api-run-indexer

To run the indexer against remote Turso, packages/api/.env needs:

  • TAP_URL
  • TAP_AUTH_PASSWORD
  • INDEXED_COLLECTIONS
just api-run-indexer remote

Typical local setup is three terminals:

  1. pnpm dev
  2. pnpm api:run:api
  3. pnpm api:run:indexer

If you want the app to call the local API, put this in apps/twisted/.env.local:

VITE_TWISTER_API_BASE_URL=http://localhost:8080

Dev builds keep the current OAuth flow available. Production builds are read-only and hide auth entry points for now.

Local API DB #

The experimental local API database lives at packages/api/twister-dev.db. Treat it as disposable unless you explicitly back it up.

Operational rules:

  1. Stop the API before copying or restoring the file.
  2. Copy twister-dev.db and any matching -wal or -shm sidecars together.
  3. Prefer restore-or-rebuild over manual repair if the DB looks suspect.
  4. Let the file grow during experiments, then compact or delete it afterward.

Useful local commands:

cd packages/api
du -h twister-dev.db*
ls -lh twister-dev.db*

Infrastructure Setup #

Turso #

Use one Turso database per environment, for example:

  • twister-dev
  • twister-prod

Do not introduce separate app variable names for dev and prod. Always use the same variables:

  • TURSO_DATABASE_URL
  • TURSO_AUTH_TOKEN

Only the values change per environment.

Example:

# Development
TURSO_DATABASE_URL=libsql://twister-dev-your-org.turso.io
TURSO_AUTH_TOKEN=...

# Production
TURSO_DATABASE_URL=libsql://twister-prod-your-org.turso.io
TURSO_AUTH_TOKEN=...

Railway #

Create or reuse one Railway project containing:

  • existing tap
  • api running twister api
  • indexer running twister indexer

Set these shared variables on the Railway services:

  • TURSO_DATABASE_URL
  • TURSO_AUTH_TOKEN
  • LOG_LEVEL
  • LOG_FORMAT

Set these API-specific variables:

  • HTTP_BIND_ADDR
  • SEARCH_DEFAULT_LIMIT
  • SEARCH_MAX_LIMIT
  • ENABLE_ADMIN_ENDPOINTS
  • ADMIN_AUTH_TOKEN
  • READ_THROUGH_MODE
  • READ_THROUGH_COLLECTIONS
  • READ_THROUGH_MAX_ATTEMPTS

Set these indexer-specific variables:

  • TAP_URL
  • TAP_AUTH_PASSWORD
  • INDEXED_COLLECTIONS

If you use separate Railway environments for dev and prod, keep the same variable names in both and only swap the Turso values.

First Bootstrap #

For a brand-new environment:

  1. Point TURSO_DATABASE_URL and TURSO_AUTH_TOKEN at the target database.
  2. Deploy api and indexer on Railway.
  3. Verify API readiness and indexer health.
  4. Run twister backfill with your seed file.
  5. Treat the environment as search-ready only after historical backfill completes.