Skip to content

About

Local-first anime tracker that downloads new episodes on a schedule and syncs your progress to your phone over your own LAN. No cloud, no account. Go + Wails + React.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,256 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Autoreas Bridge

A local-first desktop app that tracks your anime, downloads new episodes on a schedule, and syncs your progress to your phone over your own LAN — no cloud, no account, no internet required.

Release Release workflow Go Wails React Platform License

Autoreas Bridge — Today view listing the anime airing today with one-tap progress controls

Table of contents


Overview

Autoreas Bridge is a Windows/Linux desktop application for people who follow a lot of seasonal anime and are tired of doing the same four chores by hand: remembering what airs today, bumping the episode counter, fetching the new episode, and keeping all of that in sync with the phone they actually watch on.

It does all four locally. Every catalog entry, schedule, rating and progress value lives in one embedded SQLite database on your machine. There is no account to create, no server to reach, and no telemetry. The only network traffic the app generates is the traffic you ask for: your phone talking to your desktop over your own LAN, and the download engine fetching episodes.

Design principles

Principle What it means in practice
Local-first The database is the product. It lives in your user profile, it is yours, and the app is fully usable with the network cable unplugged.
One owner for state SQLite is the sole owner of anime state. No sidecar file, no companion process, no second source of truth to reconcile.
The disk is the truth for downloads Episode-on-disk counts are read from the filesystem, never cached in the database, so the app can never disagree with your folder.
Non-blocking conflict handling Concurrent edits from desktop and phone are detected with optimistic concurrency and surfaced as a conflict you resolve — never silently overwritten.
Architecture enforced by linters, not by prose Hexagonal boundaries and frontend rails are checked by depguard and ESLint in a pre-commit gate, so the documented design and the shipped design cannot drift.
Historical note: why it is called a "Bridge"

Bridge started as a synchronization bridge that observed a legacy Windows desktop app's NeDB-style data file (animes.dat) and mirrored it to companion devices over LAN.

That channel was retired entirely: Bridge no longer reads, writes or watches any legacy file, and does not synchronize with the legacy desktop app. Anyone still running the old app gets no synchronization from this version — the two catalogs are independent going forward. The name is kept for continuity, even though it no longer bridges to anything external. The decision is recorded in ADR-008.


Features

Daily tracking

The landing screen answers one question — what do I watch today? — and lets you bump progress in a single click. Episode counts accept half steps (0.5) for split episodes and specials, and a counter can go down: correcting a mistake is a legitimate edit, not a conflict.

Today view with per-anime increment, decrement and overflow actions

Library editor

A focused two-pane workspace: searchable library on the left, full metadata form on the right. Title, status, watched and total episodes, download page, target folder, cover art and repetition history — with the selection preserved across refreshes so a save never loses your place.

Editor view with the anime library on the left and the metadata form on the right

Weekly schedule board

Airing days are edited as a drag-and-drop board across the seven weekdays plus the Sin ver / Ver hoy / Visto queues, with a staging area to park titles mid-rearrange. Changes are drafted locally and committed atomically — the counter in the footer tells you exactly how many moves are pending before you apply them.

Anime schedule board with weekday columns, watch-state queues and a staging area

Automated episode downloads

  • Scheduled runs. Pick the days and the daily run time; the app checks for new episodes on its own and reports each run's outcome (ok, partial, jd_offline, no_animes_today, canceled, …).
  • Hoster priority with fallback. Reorder which hoster is tried first (Mediafire, Mega, Vidhide, Mp4upload, Mixdrop); if the preferred one has no working link, the next is tried automatically.
  • JDownloader integration. Links are handed to JDownloader over the My.JDownloader API, with the app watching progress, renaming finished files and flattening them into the target folder.
  • Filesystem as source of truth. What counts as "already downloaded" is what is actually on disk — never a cached number that can go stale.
  • Manual catch-up. A one-off "download missing episodes" action for a single title, outside the schedule.

Season selection and grading

A dedicated season workspace for the start-of-season ritual: grade first episodes, let the verdict derive from the grade, and override it with explicit considerations (insufficient_quota, temporarily_approved, spare_quota) when the slots do not line up. Ordering across the week is drag-and-drop, and the resulting selection feeds the daily view.

LAN device pairing and sync

Pair the companion mobile app by scanning a QR code — or by copying a one-shot token as a manual fallback. Paired devices hold a persistent auth token, receive live updates over WebSocket, and can be revoked from the desktop at any time. Concurrent edits are detected with optimistic concurrency (base version token) and preserved as conflicts with both sides intact, rather than resolved by overwrite.

Devices view showing the pairing QR code, the manual token fallback and the paired-device table

Note

The pairing token, LAN address and device identifier are intentionally blurred in this screenshot.

Built-in API activity inspector

Every HTTP transaction and WebSocket event between mobile clients and the bridge is captured and browsable in-app: filter by route, outcome, kind or status class, then open a transaction to inspect its request and response. A separate runtime events tab covers domain events. This is the debugging surface that removes the need to attach a proxy when a phone misbehaves.

Activity view listing captured HTTP transactions with a detail panel for one request

And the rest

Feature Detail
Notification center In-app notification history with actionable entries, plus native Windows toasts for run outcomes and missed schedules.
Backup and restore Export a versioned backup bundle and import it back with an explicit safety model (ADR-009, ADR-010).
System tray and autostart Runs quietly in the tray and can register itself to start with the OS.
Activity history An audit trail of user and system actions over the catalog.
MCP sidecar cmd/autoreas-request-mcp exposes captured requests read-only over the Model Context Protocol, so an AI assistant can debug a sync session against real traffic.

Tech stack

Layer Technology Why
Language (backend) Go 1.27 Single static binary, first-class concurrency for the event bus and download workers.
Desktop shell Wails v2 (v2.15.0) Native webview instead of a bundled Chromium — a fraction of an Electron install.
Database SQLite via modernc.org/sqlite Pure-Go, cgo-free, so the app cross-compiles without a C toolchain. WAL mode + busy_timeout for concurrent writers.
Transport net/http + gorilla/websocket REST for commands, WebSocket for live push to paired devices.
Download engine jdownloader-go Drives JDownloader through the My.JDownloader API.
Desktop notifications go-toast, systray Native Windows toasts and tray integration.
UI framework React 19 + React Router 7
Component library HeroUI v3 + Tailwind CSS v4 Accessible primitives (React Aria) over hand-rolled widgets.
State Zustand Small, unopinionated stores for cross-feature state.
Drag and drop @dnd-kit/react Pointer-based, React 19 + StrictMode safe, works inside WebView2.
Charts / icons / QR Nivo, Iconify, qrcode
Build tooling Bun 1.3, Vite 8, TypeScript 6
Testing go test, Vitest + Testing Library, Stryker & ditto for mutation testing
Quality gates Lefthook, golangci-lint (+ custom plugin), ESLint 9 flat config, Fallow, SonarQube
CI/CD GitHub Actions Tag-triggered cross-platform build and release.
Interop MCP Go SDK Read-only request-capture sidecar.

Installation

From a release (recommended)

Grab the latest build from the Releases page.

Platform Artifact
Windows (x64) autoreas-bridge-<version>-windows-amd64-installer.exe — NSIS installer
Debian / Ubuntu (x64) autoreas-bridge-<version>-linux-amd64.deb — declares its GTK/WebKit runtime dependencies
Other Linux (x64) autoreas-bridge-<version>-linux-amd64.tar.gz — portable binary

Every release ships per-platform SHA256SUMS-*.txt. Verify before running:

# Linux
sha256sum -c SHA256SUMS-linux-amd64.txt
# Windows
Get-FileHash .\autoreas-bridge-1.8.3-windows-amd64-installer.exe -Algorithm SHA256

Optional: JDownloader

The download features drive JDownloader 2 through the My.JDownloader API. Install it and sign in to My.JDownloader if you want automated downloads; everything else in the app works without it.

Where your data lives

Platform Path
Windows %APPDATA%\Autoreas\data\bridge.db
Linux ~/.config/Autoreas/data/bridge.db

The database is deliberately kept out of the install directory so Windows UAC cannot block writes when the app is installed under C:\Program Files. Back it up by copying that file, or use the in-app backup export.


Running from source

Prerequisites

Tool Version Notes
Go 1.27+ Matches the go directive in go.mod.
Bun 1.3+ Node.js works too, but the scripts and lockfile assume Bun.
Wails CLI v2 Not optional — see below.
Platform SDK — Windows: WebView2 runtime (preinstalled on Windows 11). Linux: libgtk-3-dev, libwebkit2gtk-4.1-dev.

Important

The Wails CLI is required even for frontend-only work. frontend/wailsjs/ holds the generated TypeScript bindings for every bound Go method, it is not tracked in git, and many frontend files import from it. bun install regenerates it through a postinstall hook — but without the CLI on your PATH, the frontend will not typecheck.

Setup

git clone git@github.com:Disble/autoreas-bridge.git
cd autoreas-bridge

go mod download
bun --cwd="frontend" install    # also generates frontend/wailsjs/

Development

wails dev

This starts a Vite dev server with hot reload for the frontend alongside the Go backend. A browser-based inspector is available at http://localhost:34115.

After changing a bound Go method, regenerate the bindings so the frontend sees the new signature:

bun --cwd="frontend" run generate:bindings

wails dev and wails build regenerate them too; the script exists for when you are working on the frontend alone.

Production build

wails build            # binary into build/bin/
wails build -nsis      # Windows: also produce the NSIS installer

A local build is a rehearsal for smoke-testing. Actual releases ship through CI: pushing a vX.Y.Z tag builds both platforms and publishes a GitHub Release with the installer, the Linux packages and the checksums. See DEPLOYMENT.md.


Configuration

Listen address

The HTTP API binds 0.0.0.0:9876 by default. Two things can change it, and the environment wins over the stored value:

AUTOREAS_BRIDGE_ADDR=9911 ./autoreas-bridge    # a bare port works too

The persisted setting (GetAPIAddress / SetAPIAddress, editable in Settings) is the ordinary route; the environment variable is the recovery route. They are not redundant: if the configured port is already taken, the app never reaches a settings screen, so an override that lived only behind the UI would be unreachable in the one situation it exists for.

An unusable value is never fatal — it is logged, skipped, and the next source is used, because refusing to start is exactly the failure this is meant to prevent. A change of address applies on the next start rather than rebinding underneath paired devices mid-session.

Local HTTP API

The API consumed by the companion mobile app is documented in docs/openapi.yaml.

Method Route Purpose
POST /api/devices/pair Redeem a one-shot pairing token
GET / DELETE /api/devices, /api/devices/{id} List and revoke paired devices
GET / PATCH /api/animes, /api/animes/{id} Read the catalog, patch anime state
GET /api/animes/changes Changes after a timestamp
POST /api/sync/reconcile Trigger reconciliation and fetch changes
GET / POST /api/conflicts, /api/conflicts/{id}/resolve Inspect and resolve conflicts
GET / POST /api/seasons/active, /api/seasons/active/ratings Active season snapshot and first-episode grades
GET /api/status Bridge health
WS /ws Live state push

Project structure

autoreas-bridge/
├── main.go                  # Entry point only: //go:embed frontend/dist + wails.Run
├── cmd/
│   └── autoreas-request-mcp/  # Read-only MCP sidecar over captured requests
├── internal/                # Hexagonal core — no Wails runtime below this line except desktop/
│   ├── desktop/             # Wails composition root — App and the bindings the frontend calls
│   ├── anime/               # Anime domain: aggregate, storage codec, write pipeline
│   │   ├── domain/          #   pure business rules (no I/O, no SQL, no HTTP)
│   │   └── store/           #   SQLite gateway, stage→finalize→publish, outbox
│   ├── season/              # Season selection: grading, verdicts, ordering
│   ├── download/            # Download orchestration
│   │   ├── jdownloader/     #   My.JDownloader client, launcher, renaming
│   │   ├── filesystem/      #   on-disk episode counting (source of truth)
│   │   └── sites/           #   per-site scraping adapters
│   ├── sync/                # Changelog, OCC conflict detection, SQLite bootstrap
│   ├── device/              # Pairing tokens, trusted devices, auth
│   ├── api/                 # HTTP router, handlers, capture middleware
│   │   ├── contracts/       #   ports — never imports handlers or drivers
│   │   └── handlers/        #   adapters
│   ├── realtime/            # WebSocket hub
│   ├── events/              # In-memory event bus
│   ├── notification/        # Dispatcher, desktop toasts, notification center
│   ├── observability/       # Request capture, event log
│   ├── persistence/         # Shared SQLite schema and helpers
│   ├── schedule/            # Scheduler for automated runs
│   ├── backup/              # Versioned export/import bundles
│   ├── settings/ activity/ tray/ autostart/ logger/ pathutil/
│   └── testsupport/         # Shared test fixtures and harnesses
├── frontend/
│   └── src/
│       ├── App.tsx          # Delivery/composition only — no hooks, no bindings
│       ├── app/             # Layout shell and routed surfaces
│       ├── features/        # Domain-driven modules (catalog, season, download, …)
│       ├── shared/          # Cross-feature UI, hooks, helpers, stores
│       ├── infrastructure/  # Source adapters over the Wails bindings
│       └── test/            # Test bootstrap
├── tools/                   # Repo-owned Go guards (file size, gofmt, architecture, OpenAPI…)
├── docs/                    # Architecture, ADRs, OpenAPI, postmortems, learning log
├── openspec/                # Historical planning artifacts, not an execution contract
└── .github/workflows/       # Build Windows, Build Linux, Release

Frontend module layout

Complex frontend modules use strict colocation and no barrel files (ADR-011) — every module is imported by concrete path:

features/season/ui/SeasonWorkspace/
├── SeasonWorkspace.tsx            # dumb UI: HeroUI + Tailwind, no hooks, no bindings
├── use-season-workspace.ts        # smart hook, strict 10-step anatomy
├── season-workspace.types.ts      # every *Props field is readonly
├── season-workspace.helpers.ts    # pure functions
├── season-workspace.constants.ts
└── __tests__/                     # colocated, test-first

Architecture

The backend follows Hexagonal Architecture (Ports & Adapters) with strongly separated bounded contexts; the frontend enforces a smart hooks / dumb UI split.

flowchart TB
    subgraph desktop["Desktop app (Wails)"]
        ui["React 19 frontend<br/><i>dumb UI + smart hooks</i>"]
        root["Composition root<br/>main.go + internal/desktop"]
        ui <-->|"generated bindings"| root
    end

    subgraph core["internal/ — hexagonal core"]
        domain["Domain<br/><i>anime · season · device</i>"]
        services["Application services<br/><i>download · sync · schedule</i>"]
        bus(["In-memory event bus"])
        services --> domain
        services --> bus
        bus --> services
    end

    subgraph adapters["Adapters"]
        db[("SQLite<br/>bridge.db")]
        http["REST + WebSocket<br/>:9876"]
        jd["JDownloader<br/><i>My.JDownloader API</i>"]
        fs[("Episode folders<br/><i>source of truth</i>")]
    end

    phone["📱 Autoreas Mobile<br/><i>paired over LAN</i>"]

    root --> services
    services --> db
    services --> jd
    services --> fs
    bus --> http
    http <-->|"REST + WS"| phone
Loading

Boundaries are enforced, not documented: depguard rules in .golangci.yml fail the build if internal/anime/domain imports net/http, database/sql or the Wails runtime; if internal/api/contracts imports handlers or drivers; or if any internal/** package imports the Wails runtime.

Deeper reading:


Testing and quality gates

Tests are not optional here — TDD is the working mode, and the cycle is RED → GREEN → MUTATE → REFACTOR.

# Backend
go test ./...

# Frontend
bun --cwd="frontend" run test
bun --cwd="frontend" run validate       # lint + typecheck

# Go linting — reproduces the gate exactly
powershell -ExecutionPolicy Bypass -File scripts/lint.ps1 -Profile all

# Does the production bundle actually paint? (~4s, headless Edge)
bun --cwd="frontend" run render:smoke

-Profile all is base plus advanced: base is stock golangci-lint, advanced builds the tracked plugin, and some rules — gocognit among them — exist only in the plugin. Running -Profile base and reading 0 issues is therefore not evidence the gate will pass.

What the pre-commit gate enforces

git commit runs the full Lefthook gate (budget ~90s for a change touching both Go and the frontend; jobs are globbed to the files they judge, so a docs-only commit runs almost nothing):

Group Jobs
Go gofmt guard · file-size guard · architecture guard · golangci-lint (base + plugin) · go vet · go test ./... -cover
Repo app-icon guard (genicons -check) · OpenAPI contract guard
Frontend TypeScript typecheck · ESLint 9 flat config (delivery purity, dumb-UI rules, hook anatomy, strict colocation, readonly props, JSDoc, no import cycles, cognitive complexity, 500-line max) · Vitest · render smoke · layout smoke · Stryker mutation testing on staged lines

Tip

Give git commit a generous timeout (≥ 5 minutes). A killed commit leaves the changes staged but unrecorded — just re-run it. Never use --no-verify.

Mutation testing

Line coverage does not tell you whether a test asserts anything. Both surfaces are covered:

# Frontend — runs automatically in the pre-commit hook (frontend-mutation job),
# over staged lines. Narrow manual runs use test:mutation --mutate <file:range>.
git commit

# Go — manual, scoped to the owning package
ditto staged --exclude-prefix frontend/ --threshold 0.80 \
  --test-command "go test -count=1 -json ./internal/<package>/"

Naming the owning package is not optional: ditto runs the test command once per mutant, sequentially. See docs/mutation-testing.md.


Deployment

Releases are cut by pushing a vX.Y.Z tag onto main. GitHub Actions builds both platforms, runs a set of release-blocking guards, and publishes a GitHub Release whose notes are the matching CHANGELOG.md section.

# On dev: bump wails.json -> info.productVersion, update CHANGELOG.md, commit.
# Then:
git checkout main && git merge dev && git push
git tag vX.Y.Z && git push origin refs/tags/vX.Y.Z

There is no server and no environment to promote through — "deployment" here means publishing installable artifacts, and there is no rollback: corrections ship as a new patch release.

DEPLOYMENT.md carries the full procedure: the version source of truth, what each workflow does, every release-blocking guard and what it catches, local builds, what the installed app touches on a user's machine, and how to read a failed run.


Contributing

The codebase and current product documentation are the execution contract. openspec/ is retained only as historical planning evidence and is not an execution contract.

Before opening a PR:

  1. Read AGENTS.md — the primary project instruction file — and docs/learning-log.md, the "why" journal.
  2. Write the failing test first. Frontend helpers and hooks need a colocated __tests__/ sibling.
  3. Keep files under the size policy: warning at 400 effective lines, hard fail above 500, on both Go and the frontend (docs/file-size-policy.md).
  4. Code, identifiers, comments and commit messages are English. Spanish is confined to the retained storage codec, runtime data literals (Sin ver, Ver hoy, Visto, No me gusto) and some UI copy (ADR-007).
  5. Use Conventional Commits.
  6. Make sure the full Lefthook gate passes locally.

Branch model

dev carries development; main carries deployments. Land work on dev and never commit development directly to main. A release exists only after dev is merged into main, and the tag goes on the main commit — the guard job in .github/workflows/release.yml fails a tag build whose commit is not an ancestor of main.

Release notes live in CHANGELOG.md and become the GitHub Release body; a tag whose version has no changelog section fails the pipeline. The full release procedure is in DEPLOYMENT.md.


License

Apache License 2.0 — Copyright 2026 disble.

You may use, modify and redistribute this software, commercial use included, as long as you keep the license and copyright notice and state your changes. It comes with no warranty. Apache 2.0 additionally grants an explicit patent license from every contributor and revokes it for anyone who starts patent litigation over the work — the practical difference from MIT.

About

Local-first anime tracker that downloads new episodes on a schedule and syncs your progress to your phone over your own LAN. No cloud, no account. Go + Wails + React.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages