Skip to content

Repository files navigation

忍

Shinobu (忍)

A DB-less, cross-platform harness for your media trackers — log a movie, show, or anime once and every one you've connected stays in sync.

Shinobu home — “One log. Every tracker.” hero with floating provider tiles, beside the web sidebar

A film's detail screen — backdrop, poster, rating, overview, and cast A person's page — headshot, biography, and filmography grouped by department
Detail and person screens, both TMDB-first — no tracker connected.

✨ What it does

  • 📝 Log once — mark something watched a single time and Shinobu writes it to every connected tracker it applies to, in parallel. If one write fails, you're told exactly which one.
  • 📰 One unified feed — your history from every connected tracker, merged into a single timeline. A tracker having a bad day degrades to a missing row, never a blank screen.
  • 📔 A unified diary — every watch and read log from every connected tracker in one reverse-chronological list, with same-day cross-tracker entries collapsed into a single row.
  • 🔌 Bring your own trackers — connect any subset of your media trackers through each service's own sign-in flow. There is no Shinobu account to create.
  • 🕶️ No backend, no database — your tokens (encrypted, on device) are the session. Nothing about you is stored anywhere else.
  • ⏭️ Up Next, timezone-correct — an episode only counts as watchable once it has actually aired in your timezone, not the show's.
  • 🔔 Release notifications, local-only — upcoming episodes for what you track are scheduled ahead of time on-device (iOS/Android). No push server, ever.
  • 🚪 Never a dead end — if a write is impossible or fails on some tracker, you get a one-tap link to log it there yourself instead of an error.
  • 📱 Four platforms, one codebase — Web, iOS, iPadOS, and Android from a single Expo project.

🛠️ Tech stack

⚛️ Expo + Expo Router One codebase for all four platforms, file-based routing, CNG — native projects are generated, never committed
🎨 Uniwind Tailwind CSS for React Native, from the Unistyles team — theme tokens, light/dark variants
🔄 TanStack Query Every read and write — caching, invalidation, and the cross-tracker write path
🧬 Effect Typed errors, retries, and rate-limit backoff — contained to the provider service layer, never leaks into components
🧵 better-all Promise.all with automatic dependency optimization — how the write fan-out and the feed aggregation stay maximally parallel
Nitro Modules react-native-mmkv (encrypted token storage), react-native-nitro-fetch (Cronet/URLSession networking on native), nitro-webview (the tracker sign-in and write-bridge WebViews)
📜 Legend List Virtualized lists everywhere — pure TS, works on web, behind a single components/List wrapper
🖼️ expo-image + galeria Memory/disk-cached posters so long grids don't thrash memory; tap-to-zoom on posters and headshots
👆 pressto Every tappable surface — debounced, animated presses on gesture-handler + reanimated
📳 pulsar Haptics on press and on log outcomes (native only — iOS Safari has no equivalent)
🍎 @expo/ui + bottom sheet Platform-native SwiftUI/Compose controls (date pickers, switches) and the sheets behind card actions & log confirmation
📋 react-hook-form + zod Every connect form — validated client ids, tokens, and credentials before a single request goes out
🔤 torph Text that morphs in place when your own state changes it — progress counts, watched lines (web only, a pure enhancement)
⌨️ react-native-keyboard-controller Consistent keyboard avoidance on every platform
🔔 expo-notifications + expo-background-task Locally scheduled release reminders, refreshed in the background — no push server
🎬 TMDB Metadata source (never a tracker) — powers detail screens, cast/crew, and the person/studio routes
☁️ Cloudflare Workers Hosts the web build, plus two narrow, path-allowlisted relays for the calls a browser origin isn't allowed to make itself
🧠 React Compiler Automatic memoization — useMemo/useCallback are lint-banned
🥟 bun Package manager, script runner, and test runner (bun:test — no Jest)
🦀 oxlint Conventions enforced as lint rules, not prose

🚀 Getting started

Note

Shinobu links native Nitro modules, so it can't run in plain Expo Go — iOS/Android need a custom dev client.

bun install

bun web            # run in the browser
bun ios            # build & run the iOS dev client
bun android        # build & run the Android dev client

bun ios.clean      # regenerate the native project first — needed after
bun android.clean  # app.json / config-plugin / native-dependency changes

Web dev also wants the Worker running alongside it, since /api/* doesn't exist on Metro:

bun run dev:worker  # wrangler dev on :8787 — the Serializd/Letterboxd read proxies

✅ Checks

bun lint        # oxlint — conventions, import rules, filename casing
bun typecheck   # tsc --noEmit
bun test        # bun:test
bun check:links # probe the provider URLs the app depends on for rot

lint, typecheck, and test are the merge gate (ci.yml) and run on every PR. check:links is time-driven rather than diff-driven, so it runs daily on a schedule instead (link-health.yml) — plus on PRs that touch the provider layer.

🌐 Web

bun run build:web   # static export
bun run deploy:web  # export + wrangler deploy

Deployed at shinobu.glpecile.xyz.

📦 Releases

Android releases are cut from a tag on free GitHub-hosted runners — no EAS. bun release:bump patch|minor|major|X.Y.Z moves expo.version and expo.android.versionCode together, and pushing the matching vX.Y.Z tag triggers release.yml, which builds a signed universal and arm64-v8a APK, checksums both, and publishes a GitHub Release with auto-categorized notes. Full runbook: docs/releasing.md.

📚 Docs

  • AGENTS.md — the conventions everything here is held to.
  • plan.md — product vision and architecture rationale.
  • docs/plans/ — one blueprint per feature, written before building it.
  • docs/solutions/ — one file per solved bug or non-obvious pattern.
  • docs/brainstorms/ — raw exploration notes.

⚖️ License

GPL-3.0-only.

Releases

Packages

Used by

Contributors

Languages