A pinned, always-on-top ticker for the things you actually care about.
Live market quotes, game scores, and RSS feeds — streaming into a compact bar that floats on top of whatever you're working on. Multi-monitor aware. No ads or data sales; analytics are limited and controllable.
- Marketing site — https://myscrollr.com
- Desktop download — https://myscrollr.com/download
- Pricing / plans — https://myscrollr.com/uplink
- Status — https://myscrollr.com/status
Pick the sources you care about — each gets a focused view, and the pinned ticker streams the same data across the top of your screen.
Scrollr is a monorepo. The desktop app is the product; everything else exists to feed it.
| Path | Service | Stack |
|---|---|---|
desktop/ |
Desktop app (the primary product) | Tauri v2 + React 19 + Vite 7 |
myscrollr.com/ |
Marketing, auth, billing — not where widgets are configured | React 19 + Vite 7 + TanStack Start (static prerender) |
api/ |
Core API: widget catalog, reads, SSE, billing, accounts, support | Go 1.25 + Fiber v2 + Postgres + Redis |
channels/finance/ |
Market-data ingester (TwelveData) | Rust |
channels/sports/ |
Scores + schedules ingester (api-sports.io) | Rust |
channels/rss/ |
RSS/Atom ingester | Rust |
k8s/ |
Production manifests | Kubernetes on DigitalOcean |
scripts/ |
Dev + ops tooling: make helpers, smoke tests, osTicket plugin |
Node, Shell, PHP |
docs/ |
Charter, ADRs, roadmap, runbooks — indexed here | Markdown |
┌──────────────────┐ ┌──────────────────┐
│ desktop app │ │ myscrollr.com │
│ (Tauri + React) │ │ (React + Vite) │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ JWTs via Logto │
▼ ▼
┌─────────────────────────────────────────┐
│ Core API (api/ · Go · Fiber) │ ← only JWT validator
│ GET /catalog the widget catalog │ ← owns every shared table
│ /users/me/widgets widget CRUD │
│ /dashboard /events reads + SSE │
│ /checkout billing │
│ finance · sports · rss │ ← served in-process
└──┬────────────────────────────────────┘
│ writes
▼
shared Postgres ◀── Rust ingesters: finance · sports · rss
│ (pure writers — they run no migrations)
└── Sequin CDC → Redis pub/sub → SSE → ticker
- Core API is the only service that validates JWTs, and the only thing that migrates the database. The ingesters are pure writers.
- The widget catalog is server-authoritative.
GET /catalogis the single definition of what widgets exist. The desktop fetches it and renders generically, so adding a widget that reuses an existing renderer is a server-only change — no client release. The marketing site does not fetch it; its widget counts are hardcoded and updated by hand. - No proxied channel service remains. Finance, sports and rss were folded into core by ADR-0002; fantasy was the last separately-deployed service and was removed outright (SCROLLR-239), along with the Redis service discovery and dynamic proxy that only it used.
- Data flows back via CDC. Sequin streams Postgres changes to Redis topics; every core replica fans them out to connected desktops over SSE (ADR-0001).
Read api/CHANNELS.md for the widget/source model
and how to add one.
Head to https://myscrollr.com/download and grab the build for your OS. macOS builds are signed and notarized. Windows signing is tracked separately; the download page carries the current installation guidance.
make setup # generate every .env file (once)
make up # start the whole backend in Docker
make dev # ...and open the site + desktop appmake on its own lists every command, grouped.
You need Docker Desktop, Node 22+ and make — that's it. No Go
or Rust toolchain: the backend compiles inside its containers, and editing
a .go or .rs file hot-reloads that service in place rather than
requiring a rebuild. make doctor checks your machine and names the fix
for anything missing.
The two front-ends run natively (a GUI window can't live in a Linux
container). Full topology, ports and troubleshooting:
docs/LOCAL_SETUP.md.
make check # both TypeScript suites — the ones that run without a toolchain- TypeScript:
npm testinmyscrollr.com/anddesktop/, ornpm run buildfor atsctypecheck. - Go and Rust: no host toolchain is required to run this project, so
the backend suites run in CI. To run them locally, use the containers the
stack already builds —
make shell svc=core-apithengo test ./..., ormake shell svc=rss-servicethencargo test. Installing Go or Rust on the host also works, but nothing here needs it.
Integration tests (GDPR purge cascade, Stripe webhook idempotency, the
read-query schema contract) need a real Postgres and skip without it, so
go test ./... is always safe. To run them, point TEST_DATABASE_URL at a
scratch database — never one with real data:
TEST_DATABASE_URL="postgres://scrollr:scrollr@postgres:5432/scrollr?sslmode=disable" go test ./...CI runs all of it: .github/workflows/backend-tests.yml covers Go (api
— the only Go module), the three Rust ingesters, and desktop/src-tauri;
frontend-tests.yml runs both Vitest suites;
desktop-release.yml builds and releases desktop binaries — though a
preflight job skips the build unless the version in tauri.conf.json is
unreleased; deploy.yml ships the API and website to production.
- The server is the authority; clients are projections. The widget
catalog, the database schema, and the TS wire types all have exactly
one definition in
api/. Every client copy is pinned by a test that fails CI on drift — but they differ in how they're produced:desktop/src/types/api.generated.tsandcatalog.snapshot.jsonare generated (api/cmd/gents, andgo test ./internal/widgets -update), so change the Go and regenerate.desktop/src/tierLimits.tsis a hand-kept mirror — there is no generator; edit it and the Go map together, as its header comment lists. - No ads, tracking pixels, or data sales. Website product analytics asks
first; signed-in desktop analytics is on by default with an easy opt-out.
Both use only the fixed, content-free events in
docs/analytics/POSTHOG.md. - Only core migrates. All schema lives in
api/migrations/; the ingesters are pure writers. A failed migration crashes the container. - Rollbacks via rolling forward. We prefer forward-only migrations once deployed; the schema is additive wherever possible.
- Ingesters stay independent. Each Rust service owns its polling and parsing; that duplication is intentional. What's shared is the schema and the wire contract, not ingest logic.
- Package manager is npm. Not pnpm, not yarn.
Per-service style (semis, quotes, path aliases) varies — see
AGENTS.md for the exact rules in each tree.
docs/README.md indexes every doc in the repo and says
which ones win when they disagree. The essentials:
AGENTS.md— the one-page cheatsheet: commands, ports, conventions, per-language rules.docs/VISION.md— the charter: what Scrollr is, the widget/slot model, and the ten decisions behind the current architecture. Start here to understand why.docs/ROLLOUT.md— how that charter was executed, phase by phase, including the deviations and why.api/CHANNELS.md— the widget/source model and how to add one.docs/adr/— numbered architecture decision records.docs/LOCAL_SETUP.md— the local stack topology.CONTRIBUTING.md— how to report issues, how to send PRs, what we do and don't merge.CODE_OF_CONDUCT.md— community rules.SECURITY.md— vulnerability reporting.
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
See LICENSE.
If you run a modified copy of Scrollr as a network service for others, AGPL requires you to offer users of that service access to your modified source under the same license. This is by design — Scrollr is a SaaS-style product, and the AGPL is what keeps it open even when operated remotely.
See CONTRIBUTING.md. Before your first PR,
please read the CODE_OF_CONDUCT.md and the
SECURITY.md.
- Built by Brandon Ruth and the Scrollr contributors.
- Desktop app powered by Tauri.
- Market data from TwelveData.
- Sports data from api-sports.io.
- Auth by Logto.
- Billing by Stripe.
- Infrastructure on DigitalOcean Kubernetes.



