From 792614b0951075b0e0ff24bc1cd4c2e126f30b03 Mon Sep 17 00:00:00 2001 From: ElianCodes Date: Sat, 26 Sep 2026 22:13:10 +0200 Subject: [PATCH 1/3] docs: describe the code that actually ships MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module repos changed daily; these pages did not. Every claim below was checked against the source before being rewritten or removed. Encore: the page sold a moderated music-request app. `encore/src` is the unmodified template — its home route renders "Coral Module — Ready to build." and `.env.example` holds only HOST and PORT. The three JELLYFIN_* variables the page told you to set are read by nothing. Rewritten as the scaffold it is. Librarian: removed the Features and Analytics trees. `grep -ril` over librarian/src returns zero files for each of duplicate, bulk, analytic, statistic, backup and codec. Documented what exists instead — import plans, hardlink-with-copy-fallback, roots, path mappings, scan jobs — and added the access model: LIBRARIAN_REQUIRE_LOGIN defaults to true and every filesystem operation additionally requires a Jellyfin administrator. The compose guide walked a first-run user straight into that login wall without mentioning it. Fathom: removed reading-progress tracking, ratings and reviews, personal collections and recommendation algorithms. None of them have code. fathom/README.md:10-17 is the honest scope. Marquee: replaced the template boilerplate opener with what it does, and documented MARQUEE_DATA_DIR and the /setup flow. KAPOW!: SUPABASE_DB_PASSWORD is a `supabase link` argument, not an app variable — dropped from the env table, with SUPABASE_DB_URL and the six accepted key aliases added. /host/:code requires ?token=. Replaced the eyJ.../AIzaS... placeholders, which read like leaked credentials. Aurora: dropped the genre "filtering" claim; README lists browsing, sorting and pagination only. Added AURORA_STREAM_TOKEN_SECRET, HOST, PORT and AURORA_DISABLE_SPA_PRERENDER, and noted that PLEX_URL/PLEX_TOKEN are read by nothing. Tide: added the cgroup memory guard, the TORRENT_DOWNLOADS_DIR legacy alias and CORAL_SERVICE_TOKEN. Module contracts: files.move was listed as "defined in spec 1" on a page that says a module advertises a capability only when it works. It is implemented nowhere; moved to Reserved. Contributing: Coral has never used ESLint — every repo ships a biome.json. The script is `typecheck`, not `type-check`. The licence claim is now what is provable: several module repos carry no LICENSE file at all. Project templates: the template has no api/example.ts, no routes/components/ and no src/integrations/. Fixed the getLibraryItems signature, which omitted the positional media type and passed an undocumented parentId. Also: every module now states a status — Shipping, Early or Scaffold — so a reader can tell Aurora from Encore without cloning both; Docker Hub image names added for Fathom, Marquee, Encore and KAPOW! (all verified to exist on the registry); Vercel dropped as a deploy target for the three modules with no vercel.json; /healthz documented; and the homepage cards are links, which none of the nine were. --- astro.config.mjs | 2 +- .../docs/contributing/getting-started.md | 61 +++-- .../docs/contributing/project-templates.md | 51 ++-- .../docs/getting-started/create-coral.md | 33 ++- .../docs/getting-started/docker-compose.md | 24 +- .../docs/getting-started/introduction.md | 39 +-- .../docs/getting-started/module-contracts.md | 16 +- src/content/docs/index.mdx | 200 ++++++++++------ src/content/docs/libraries/coral-ui.md | 109 +++++++-- src/content/docs/libraries/jellyfin.md | 29 ++- src/content/docs/libraries/npm-packages.md | 84 +++++-- src/content/docs/modules/aurora.md | 54 ++++- src/content/docs/modules/encore.md | 131 +++++----- src/content/docs/modules/fathom.md | 191 ++++++--------- src/content/docs/modules/kapow.md | 100 ++++++-- src/content/docs/modules/librarian.md | 226 ++++++++++-------- src/content/docs/modules/marquee.md | 146 ++++++----- src/content/docs/modules/tide.md | 76 ++++-- 18 files changed, 974 insertions(+), 598 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 61df478..b1e78c4 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -65,7 +65,7 @@ export default defineConfig({ slug: 'modules/librarian', }, { - label: 'KAPOW', + label: 'KAPOW!', slug: 'modules/kapow', }, { diff --git a/src/content/docs/contributing/getting-started.md b/src/content/docs/contributing/getting-started.md index acb8f49..0825367 100644 --- a/src/content/docs/contributing/getting-started.md +++ b/src/content/docs/contributing/getting-started.md @@ -1,6 +1,6 @@ --- title: Getting Started Contributing -description: How to contribute to the Coral ecosystem +description: Set up a Coral repository locally, follow the shared tooling and commit conventions, and get a pull request merged. --- ## Contributing to Coral @@ -23,12 +23,26 @@ That gives you the standard Coral app template with TypeScript, Biome, and relea Choose where you want to contribute: -- **Aurora** - Video client ([github.com/Get-Coral/aurora](https://github.com/Get-Coral/aurora)) -- **Fathom** - Reading interface ([github.com/Get-Coral/fathom](https://github.com/Get-Coral/fathom)) -- **Librarian** - Library management ([github.com/Get-Coral/librarian](https://github.com/Get-Coral/librarian)) -- **KAPOW** - Karaoke manager ([github.com/Get-Coral/KAPOW](https://github.com/Get-Coral/KAPOW)) -- **Jellyfin Client** - API library ([github.com/Get-Coral/jellyfin](https://github.com/Get-Coral/jellyfin)) -- **Ecosystem** - Any module or library +**Modules** + +- **Aurora** — video client ([Get-Coral/aurora](https://github.com/Get-Coral/aurora)) +- **Tide** — torrent client ([Get-Coral/tide](https://github.com/Get-Coral/tide)) +- **Librarian** — download imports and library hygiene ([Get-Coral/librarian](https://github.com/Get-Coral/librarian)) +- **Fathom** — reading interface ([Get-Coral/fathom](https://github.com/Get-Coral/fathom)) +- **Marquee** — ambient display ([Get-Coral/marquee](https://github.com/Get-Coral/marquee)) +- **KAPOW!** — karaoke queue ([Get-Coral/KAPOW](https://github.com/Get-Coral/KAPOW)) +- **Encore** — the module scaffold ([Get-Coral/encore](https://github.com/Get-Coral/encore)) + +**Libraries and tooling** + +- **Jellyfin client** — typed API client ([Get-Coral/jellyfin](https://github.com/Get-Coral/jellyfin)) +- **Coral UI** — shared components ([Get-Coral/coral-ui](https://github.com/Get-Coral/coral-ui)) +- **create-coral** — the scaffolder ([Get-Coral/create-coral](https://github.com/Get-Coral/create-coral)) +- **template** — what create-coral clones ([Get-Coral/template](https://github.com/Get-Coral/template)) +- **dev-standards** — shared Biome and TypeScript configs ([Get-Coral/dev-standards](https://github.com/Get-Coral/dev-standards)) + +If a module's page says *Early* or *Scaffold*, that is where help goes furthest. +See [Introduction](/getting-started/introduction/) for the current status of each. ### 2. Set Up Locally @@ -60,8 +74,7 @@ pnpm dev ## Branch Strategy -- **main** - Production-ready code -- **develop** - Development branch (if used) +- **main** - Production-ready code, and the branch releases cut from - **feature/** - New features - **fix/** - Bug fixes - **docs/** - Documentation changes @@ -98,20 +111,25 @@ Types: ## Code Style -All Coral projects use: +All Coral projects share their tooling through +[`@get-coral/dev-standards`](https://github.com/Get-Coral/dev-standards): -- **Biome** - Linting and formatting -- **TypeScript** - Type checker -- **ESLint** - Code quality +- **[Biome](https://biomejs.dev)** — linting *and* formatting, via + `@get-coral/biome-config`. Coral does not use ESLint or Prettier anywhere +- **TypeScript** — via `@get-coral/tsconfig` +- **Vitest** — tests Run checks before submitting: ```bash -pnpm lint -pnpm type-check -pnpm format +pnpm check # Biome lint + format check +pnpm typecheck # TypeScript +pnpm test # Vitest ``` +`pnpm lint` runs Biome with auto-fix. Note the script is `typecheck`, with no +hyphen. + ## Testing Include tests for new features: @@ -159,9 +177,14 @@ Don't worry about feedback — it's how we maintain quality! ## License -By contributing, you agree to license your contributions under the project's license: -- Most Coral projects: **MIT** -- Some may vary — check the LICENSE file +Coral is MIT licensed. The published npm packages +(`@get-coral/jellyfin`, `@get-coral/ui`, `@get-coral/tsconfig`, +`@get-coral/biome-config`, `create-coral`) declare `"license": "MIT"`, and +Aurora, KAPOW! and Coral UI carry a `LICENSE` file. + +Several module repositories do not yet carry one. If you are contributing to a +repository with no `LICENSE` file, ask before assuming — and adding the file is +itself a welcome pull request. ## Building a New Module diff --git a/src/content/docs/contributing/project-templates.md b/src/content/docs/contributing/project-templates.md index 0a138e7..bfa4f84 100644 --- a/src/content/docs/contributing/project-templates.md +++ b/src/content/docs/contributing/project-templates.md @@ -1,6 +1,6 @@ --- title: Project Templates -description: Building new Coral modules with project templates +description: What the Coral module template contains, how to rename it for your own module, and the conventions every module follows. --- ## Creating a New Coral Module @@ -78,7 +78,9 @@ If you cloned manually, replace `coral-module` throughout: } # In .github/workflows/docker-publish.yml -IMAGE_NAME: my-module # bare name; the workflow builds getcoral/ +IMAGE_NAME: my-module # bare name. The workflow publishes two images: + # ghcr.io/get-coral/ + # / # In README.md # My Module @@ -92,17 +94,22 @@ pnpm install ### 4. Create Your App -The template includes: +The template is deliberately close to empty: ``` -src/routes/ -├── index.tsx # Home page -├── api/ -│ └── example.ts # API endpoint -└── components/ # Reusable components +src/ +├── routes/ +│ ├── __root.tsx # Root layout +│ └── index.tsx # Home page +├── router.tsx # Router setup +├── routeTree.gen.ts # Generated — do not edit +├── styles.css +├── env.d.ts +└── example.test.tsx ``` -Add your pages and components following TanStack Start conventions. +There is no `api/` or `components/` directory yet; create them as you need +them, following TanStack Start's file-based routing conventions. ### 5. Configure Jellyfin Connection @@ -128,12 +135,12 @@ Runs on `http://localhost:3000` ### Key Directories - `src/routes/` - Page components and API routes -- `src/components/` - Reusable UI components -- `src/lib/` - Utilities and helpers -- `src/integrations/` - External service integration - `public/` - Static assets - `.github/workflows/` - CI/CD pipelines +Existing modules also use `src/components/`, `src/lib/` and `src/server/`. Those +are conventions worth following, not directories the template ships. + ### Configuration Files - `package.json` - Dependencies @@ -178,8 +185,9 @@ const client = createClient({ userId: process.env.JELLYFIN_USER_ID }) -const items = await getLibraryItems(client, { - parentId: 'library-id' +const items = await getLibraryItems(client, 'Movie', { + limit: 24, + sortBy: 'SortName' }) ``` @@ -261,16 +269,16 @@ Reference existing modules: - Update dependencies regularly: `pnpm update` - Monitor security advisories -- Update Tailwind CSS v4 -- Update TanStack packages +- Keep `@get-coral/biome-config` and `@get-coral/tsconfig` current — they carry + the shared defaults ### Upgrading TanStack Start ```bash -pnpm add -u @tanstack/start -pnpm add -u @tanstack/router +pnpm update @tanstack/react-start @tanstack/react-router ``` +The package is `@tanstack/react-start`; `@tanstack/start` was its old name. Check release notes for breaking changes. ## Getting Help @@ -299,3 +307,10 @@ When ready to share: --- Happy building! 🚀 + +## Related + +- [create-coral CLI](/getting-started/create-coral/) — the supported way to start from this template +- [Encore](/modules/encore/) — a live, unmodified copy of the template +- [Module contracts](/getting-started/module-contracts/) — if your module needs to talk to another +- [Contributing](/contributing/getting-started/) — tooling and commit conventions diff --git a/src/content/docs/getting-started/create-coral.md b/src/content/docs/getting-started/create-coral.md index 42bb4cb..0b6ba2c 100644 --- a/src/content/docs/getting-started/create-coral.md +++ b/src/content/docs/getting-started/create-coral.md @@ -1,6 +1,6 @@ --- title: create-coral CLI -description: Scaffold a new Coral module with the official create-coral package +description: Scaffold a new Coral module from the official template — usage, flags, and what you get. --- ## What it is @@ -74,4 +74,33 @@ The CLI is the recommended path. Manual cloning of `Get-Coral/template` is mainl - [Project Templates](/contributing/project-templates/) - [Contributing](/contributing/getting-started/) - [create-coral on npm](https://www.npmjs.com/package/create-coral) -- [create-coral on GitHub](https://github.com/Get-Coral/create-coral) \ No newline at end of file +- [create-coral on GitHub](https://github.com/Get-Coral/create-coral) + +## Options + +``` +pnpm create coral@latest my-module +pnpm create coral@latest +``` + +| Flag | Purpose | +|---|---| +| `--module-name ` | Override the module/package name | +| `--template-repo ` | Use a custom template repository (default `Get-Coral/template`) | +| `--template-ref ` | Template git ref: branch, tag or SHA (default `main`) | +| `--yes` | Skip prompts and use defaults | +| `--install` | Run `pnpm install` after scaffolding | +| `--no-install` | Skip `pnpm install` | +| `--help` | Show usage | + +Non-interactive, for CI or a script: + +```bash +pnpm create coral@latest my-module --yes --install +``` + +## Related + +- [Project templates](/contributing/project-templates/) — what the scaffold contains and how to rename it +- [Encore](/modules/encore/) — a live, unmodified copy of the template +- [Contributing](/contributing/getting-started/) — tooling and commit conventions diff --git a/src/content/docs/getting-started/docker-compose.md b/src/content/docs/getting-started/docker-compose.md index 92242f5..69d4647 100644 --- a/src/content/docs/getting-started/docker-compose.md +++ b/src/content/docs/getting-started/docker-compose.md @@ -1,10 +1,8 @@ --- title: Running a stack with Docker Compose -description: A working Jellyfin + Aurora + Tide stack, with the wiring between them explained +description: A working Jellyfin, Aurora, Tide and Librarian stack — including the mount layout that makes hardlinked imports work. --- -## Running a stack with Docker Compose - Coral modules are independent containers. They connect to each other through a shared Jellyfin server and, where it matters, a shared filesystem. This page is a working example of that: Jellyfin, Aurora, Tide and Librarian, with @@ -27,6 +25,11 @@ Jellyfin scans media/ and finds it named the way it expects Aurora reads Jellyfin at http://jellyfin:8096 and shows it ``` +Each service has its own page: [Aurora](/modules/aurora/), +[Tide](/modules/tide/) and [Librarian](/modules/librarian/). The cross-module +link between Tide and Librarian is described in +[Module contracts](/getting-started/module-contracts/). + Aurora proxies all Jellyfin traffic server-side, so the browser never contacts Jellyfin directly. That is why an internal service name works for `JELLYFIN_URL` — it only has to resolve from inside Aurora's container. @@ -239,9 +242,13 @@ Jellyfin has to exist before Aurora can be pointed at it. 4. Put the key, your user's **UUID** (not the username), your username and password, and your `PUID`/`PGID` into `.env` 5. `docker compose up -d` -6. Open Librarian at `http://localhost:3002`, connect it to Jellyfin, and turn - on the two roots it seeded. Roots arrive switched off: a mounted directory - is not permission to write to it. +6. Open Librarian at `http://localhost:3002`. **It will ask you to sign in.** + Librarian requires a Jellyfin sign-in by default, and anything that touches + the filesystem additionally requires that account to be a Jellyfin + **administrator** — it moves and deletes files, so it does not default open + the way Aurora and Tide do. +7. Connect it to Jellyfin, then turn on the two roots it seeded. Roots arrive + switched off: a mounted directory is not permission to write to it. `JELLYFIN_USER_ID` must be the UUID. The API key alone is enough to browse; username and password additionally open a real playback session, which is what @@ -253,3 +260,8 @@ Jellyfin cannot reach VideoToolbox from a Linux container, so transcoding is CPU-only — Direct Play is fine, 4K transcoding is not. Jellyfin's real-time library monitoring also depends on inotify, which is unreliable over macOS bind mounts; rely on the scheduled scan or trigger one by hand. + +## Related + +- [Aurora](/modules/aurora/), [Tide](/modules/tide/), [Librarian](/modules/librarian/) — the three modules in this stack +- [Module contracts](/getting-started/module-contracts/) — how Tide tells Librarian a download finished diff --git a/src/content/docs/getting-started/introduction.md b/src/content/docs/getting-started/introduction.md index 457ff2a..2e1b7b6 100644 --- a/src/content/docs/getting-started/introduction.md +++ b/src/content/docs/getting-started/introduction.md @@ -1,18 +1,29 @@ --- title: Introduction to Coral -description: Get started with the Coral ecosystem +description: What Coral is, how its modules relate to Jellyfin and to each other, and which one to start with. --- ## What is Coral? Coral is an open-source ecosystem of independent, modular interfaces for [Jellyfin](https://jellyfin.org/) — a free media system that puts you in control of your entertainment. -Each Coral module is purpose-built for a specific use case: +Each module runs as its own Docker container, reads Jellyfin over its HTTP API, +and does one thing: -- **Aurora** - Full-featured video client with personalized home, search, and playback -- **Fathom** - Elegent reading interface for books, manga, comics, and PDFs -- **Librarian** - Tools for organizing and enriching your media libraries -- **KAPOW** - Interactive song selection and voting for group karaoke +| Module | What it does | Status | +|---|---|---| +| [Aurora](/modules/aurora/) | Cinematic video frontend with playback that syncs back to Jellyfin | Shipping | +| [Tide](/modules/tide/) | Torrent client with real queue limits and a memory guard | Shipping | +| [KAPOW!](/modules/kapow/) | Karaoke queue for bars and parties, with phone-based voting | Shipping | +| [Librarian](/modules/librarian/) | Imports finished downloads into your media tree by hardlinking | Early | +| [Fathom](/modules/fathom/) | Cover-first reading room for books, manga, comics and PDFs | Early | +| [Marquee](/modules/marquee/) | Always-on ambient display for a spare TV or tablet | Early | +| [Encore](/modules/encore/) | The module scaffold. Named for a feature that is not built yet | Scaffold | + +**Status is not decoration.** *Shipping* means feature-complete for its stated +purpose. *Early* means it runs and does something useful, but the surface is +small and moving. *Scaffold* means the published image serves a placeholder. +Each module page repeats its status and says exactly what is and is not there. ## Why Coral? @@ -56,14 +67,13 @@ bun create coral@latest That bootstraps the current Coral template with TypeScript, Biome, and release automation already wired in. For the full flow, see [create-coral CLI](/getting-started/create-coral/). -Choose a module to explore: +If you want to *run* Coral rather than build on it, start here instead: -- [**Aurora**](/modules/aurora/) - Start building with the video client -- [**Fathom**](/modules/fathom/) - Set up your reading interface -- [**Librarian**](/modules/librarian/) - Organize your media -- [**KAPOW**](/modules/kapow/) - Create the ultimate karaoke experience -- [**Encore**](/modules/encore/) - Template for building custom modules -- [**Marquee**](/modules/marquee/) - Template for building custom modules +- [**Running a stack with Docker Compose**](/getting-started/docker-compose/) — + Jellyfin, Aurora, Tide and Librarian together, with the mount layout that + makes hardlinked imports work. This is the page most people want. +- [**Aurora**](/modules/aurora/) — the single most useful module to add to an + existing Jellyfin server ## Development @@ -76,7 +86,8 @@ All Coral modules are built with: ## Learn More -- Visit [getcoral.dev](https://getcoral.dev) for the main website +- [getcoral.dev](https://getcoral.dev) — what each module is for, and how it + compares to the alternatives. These docs cover how to run them - Explore the [Jellyfin API Client](/libraries/jellyfin/) for building with the API - Use the [create-coral CLI guide](/getting-started/create-coral/) to scaffold a new module - See [Contributing](/contributing/getting-started/) to build your own module diff --git a/src/content/docs/getting-started/module-contracts.md b/src/content/docs/getting-started/module-contracts.md index b0f981e..c1e2882 100644 --- a/src/content/docs/getting-started/module-contracts.md +++ b/src/content/docs/getting-started/module-contracts.md @@ -81,7 +81,10 @@ Defined in spec 1: | `downloads.list` | Tide | A point-in-time snapshot of what is downloading | | `downloads.events` | Tide | The same snapshots as a stream | | `library.refresh` | Librarian | Ask Jellyfin to rescan | -| `files.move` | Librarian | Place a file into a library | + +Librarian's manifest additionally carries a top-level `roots` array describing +the filesystem roots it has enabled. It is not part of the shared shape — a +caller should ignore fields it does not recognise. A module advertises a capability only when it actually works. Librarian does not offer `library.refresh` before it is connected to a Jellyfin. Advertising @@ -90,9 +93,10 @@ caller can only find out by failing. ### Reserved -Named here so nobody else takes them, not implemented: +Named here so nobody else takes them. **None of these is implemented**, and a +module will not advertise one until it is: -`files.browse`, `library.import`, `downloads.webhook`. +`files.move`, `files.browse`, `library.import`, `downloads.webhook`. ### Never @@ -120,3 +124,9 @@ has better access to. **Honest gap:** if a torrent completes *and* is removed from Tide while the consumer is down, the consumer never sees it. The file is still in `downloads/complete`; import it by hand. + +## Related + +- [Tide](/modules/tide/) — exposes `downloads.list` and `downloads.events` +- [Librarian](/modules/librarian/) — exposes `library.refresh` +- [Running a stack with Docker Compose](/getting-started/docker-compose/) — the two of them wired together diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx index 5017fea..f1d03dd 100644 --- a/src/content/docs/index.mdx +++ b/src/content/docs/index.mdx @@ -6,47 +6,96 @@ hero: title: Coral Docs tagline: Purpose-built interfaces for your self-hosted Jellyfin media actions: - - text: Get Started - link: /getting-started/introduction/ + - text: Run a full stack + link: /getting-started/docker-compose/ icon: right-arrow - - text: Explore All Modules - link: /modules/aurora/ - icon: rocket + - text: What is Coral? + link: /getting-started/introduction/ + icon: information variant: minimal --- -import { Card, CardGrid, Aside } from '@astrojs/starlight/components'; - -## 🎯 Featured Modules - - - - Premium video frontend. Cinematic home experience, rich detail views, favorites, and smart recommendations. - - - Elegant reading interface. Books, manga, comics, PDFs in a cover-first, distraction-free design. - - - Media management hub. Organize, enrich, deduplicate, and maintain your entire library. - - - Torrent download board. Queue controls, file priorities, piece maps, and optional auth for self-hosted setups. - - - Interactive karaoke hub. Create rooms, search songs, vote as a group, and manage playback. - +import { Card, CardGrid, Aside, LinkCard } from '@astrojs/starlight/components'; + +Coral is an open-source ecosystem of independent Jellyfin modules. Each module +runs as its own Docker container, reads your Jellyfin server over its HTTP API, +and does one thing well. Modules never share a database — Jellyfin stays the +source of truth. Everything is MIT licensed, with no paid tier and no hosted +service. + +These docs cover **how to run them**. For what each module is for and how it +compares to the alternatives, see [getcoral.dev](https://getcoral.dev). + +## Start here + + + + + + +## Modules + +Every module states a status. **Shipping** is feature-complete for its stated +purpose, **Early** runs and does something useful but has a small and moving +surface, and **Scaffold** means the published image serves a placeholder. + + + + + + + + + ## Why Coral? -Most media centers try to do *everything*. Coral does one thing really well — provide beautiful, purpose-built interfaces for each type of content. +Most media centers try to do *everything*. Coral does one thing really well — +provide beautiful, purpose-built interfaces for each type of content. **Choose what you need:** -- Run Aurora for movies and shows -- Add Fathom for your reading library -- Use Librarian to manage it all -- Use Tide for torrent intake and queue control -- Deploy KAPOW for group entertainment +- Run [Aurora](/modules/aurora/) for movies and shows +- Add [Fathom](/modules/fathom/) for your reading library +- Use [Tide](/modules/tide/) for torrent intake and queue control +- Let [Librarian](/modules/librarian/) file what Tide finishes +- Deploy [KAPOW!](/modules/kapow/) for group entertainment - Mix and match what works for you **Built for self-hosters:** @@ -55,65 +104,62 @@ Most media centers try to do *everything*. Coral does one thing really well — - Local-first where possible - Full control, full privacy -## 🪸 Start Fast with the CLI +## Building a module -The fastest way to build a new Coral module is with the official scaffolder: +The fastest way to start a new Coral module is the official scaffolder: ```bash pnpm create coral@latest -npm create coral@latest -bun create coral@latest ``` -It pulls the latest module template and sets up a TypeScript, Biome, and release-ready Coral app. See the dedicated [create-coral CLI guide](/getting-started/create-coral/) for the full workflow. - -## 🏗️ Built on Modern Tech - -Every Coral module is crafted with the latest tools: +It pulls the latest module template and sets up a TypeScript, Biome and +release-ready Coral app. - - Full-stack React framework with server-side rendering and modern DX. - - - Modern utility-first styling with responsive design and dark mode. - - - Type-safe code across frontend and backend for confidence and clarity. - - - Direct Jellyfin integration keeps your data where it belongs — on your server. - + + + + -## 🔧 Building with Coral - -Interested in creating your own module? - -- Start with [Project Templates](/contributing/project-templates/) for a pre-configured foundation -- Check [Contributing](/contributing/getting-started/) for setup and best practices -- Explore [Encore](/modules/encore/) or [Marquee](/modules/marquee/) as reference implementations -- Use the [Jellyfin API Client](/libraries/jellyfin/) for seamless Jellyfin integration -- Start with the [create-coral CLI guide](/getting-started/create-coral/) -- Browse all official [NPM Packages](/libraries/npm-packages/) - -## 📦 More Modules & Tools +## Libraries - - Moderated guest music requests. Guests browse and queue Jellyfin music while the host controls what plays. - - - Ambient now-playing display. A passive screen for TVs and monitors that surfaces what's playing and what's next. - - - Type-safe TypeScript client library for the Jellyfin API. - - - Official CLI for scaffolding a Coral module with the current template and defaults. - + + + diff --git a/src/content/docs/libraries/coral-ui.md b/src/content/docs/libraries/coral-ui.md index f765e9b..4f1a140 100644 --- a/src/content/docs/libraries/coral-ui.md +++ b/src/content/docs/libraries/coral-ui.md @@ -1,11 +1,15 @@ --- title: Coral UI -description: Shared React components and design tokens for Coral apps +description: The shared React component library and design tokens behind every Coral module — five components, TypeScript-first, React 19. --- -## @get-coral/ui +`@get-coral/ui` is the shared design-system package for Coral modules. It ships +a small set of React components and a CSS custom-property token layer, so +Aurora, Fathom, Librarian and the rest look like one family without copying +component code between repositories. -`@get-coral/ui` is the shared design-system package for Coral repositories. It provides reusable React components and a common style token layer so modules can ship faster while keeping a consistent UI language. +It is deliberately small. Components arrive here once a second module needs +them, not before. ## Installation @@ -15,7 +19,9 @@ pnpm add @get-coral/ui npm install @get-coral/ui ``` -## Quick Start +React 19 or newer is a peer dependency (`react` and `react-dom`, both `>=19`). + +## Quick start ```tsx import { CoralButton, CoralCard } from '@get-coral/ui' @@ -30,29 +36,88 @@ export function Example() { } ``` -## What It Includes +The stylesheet is a separate export. Import it once, at your app root. + +## Components + +Every export, as of `@get-coral/ui` 1.0.2. Each component also exports its props +type (`CoralButtonProps`, `CoralCardProps`, and so on). + +### `CoralButton` + +Extends `ButtonHTMLAttributes`. + +| Prop | Type | Default | +|---|---|---| +| `variant` | `'primary' \| 'neutral' \| 'danger'` | `'neutral'` | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | + +### `CoralCard` + +Extends `HTMLAttributes`. Renders a `
` with an optional +header. + +| Prop | Type | +|---|---| +| `title` | `ReactNode` | + +### `CoralSection` + +Extends `HTMLAttributes`. A titled page section. + +| Prop | Type | +|---|---| +| `eyebrow` | `ReactNode` | +| `title` | `ReactNode` (required) | +| `subtitle` | `ReactNode` | +| `footer` | `ReactNode` | + +### `CoralMediaCard` + +Extends `HTMLAttributes`. A poster tile with an optional +progress bar, clamped to 0–100. + +| Prop | Type | +|---|---| +| `title` | `ReactNode` (required) | +| `subtitle` | `ReactNode` | +| `description` | `ReactNode` | +| `imageUrl` | `string` | +| `imageAlt` | `string` | +| `badge` | `ReactNode` | +| `progress` | `number` | + +### `CoralErrorState` + +A full error or empty state with up to two actions. Also exports the +`CoralErrorAction` type. -- Shared primitives for common interface patterns (buttons, cards, and more over time) -- Design tokens exposed through CSS custom properties -- TypeScript-first component APIs -- React 19 compatible peer dependency setup +| Prop | Type | +|---|---| +| `title` | `ReactNode` (required) | +| `code` | `ReactNode` | +| `eyebrow` | `ReactNode` | +| `description` | `ReactNode` | +| `primaryAction` | `CoralErrorAction` | +| `secondaryAction` | `CoralErrorAction` | -## Why Use It +A `CoralErrorAction` is `{ label, href?, onClick?, variant?, target?, rel? }`. -- Keep visual consistency across Aurora, Fathom, Librarian, and other modules -- Reduce duplicated component code between repositories -- Publish improvements once and adopt everywhere +## Design tokens -## Publish Flow +Tokens are exposed as CSS custom properties from `@get-coral/ui/styles.css`, so +you can consume them from plain CSS, Tailwind, or inline styles without +importing anything else. -`@get-coral/ui` is released with the same Release Please automation pattern as other Coral NPM packages: +## Releases -- Push conventional commits to `main` -- Release Please opens or updates a release PR -- Merging the PR triggers NPM publish with `NPM_TOKEN` +`@get-coral/ui` uses Release Please: push conventional commits to `main`, and +merging the generated release PR publishes to npm. Publishing uses **npm trusted +publishing (OIDC) with provenance** — there is no `NPM_TOKEN` secret. -## Links +## Related -- [GitHub Repository](https://github.com/Get-Coral/coral-ui) -- [npm Package](https://www.npmjs.com/package/@get-coral/ui) -- [NPM Packages Overview](/libraries/npm-packages/) +- [NPM packages](/libraries/npm-packages/) — every published Coral package +- [Jellyfin API client](/libraries/jellyfin/) — the other half of a Coral module +- [Project templates](/contributing/project-templates/) +- [Get-Coral/coral-ui on GitHub](https://github.com/Get-Coral/coral-ui) · [npm](https://www.npmjs.com/package/@get-coral/ui) diff --git a/src/content/docs/libraries/jellyfin.md b/src/content/docs/libraries/jellyfin.md index 77e6950..f379a8d 100644 --- a/src/content/docs/libraries/jellyfin.md +++ b/src/content/docs/libraries/jellyfin.md @@ -1,11 +1,11 @@ --- title: Jellyfin API Client -description: A fully typed TypeScript client for the Jellyfin API +description: A fetch-based, fully typed Jellyfin API client with zero runtime dependencies. Works in Node, the browser and edge runtimes. --- -## @get-coral/jellyfin - -A modern, fetch-based Jellyfin API client with full TypeScript types and zero dependencies. Works in Node.js, browsers, and edge runtimes. It powers Aurora and the other Coral modules. +`@get-coral/jellyfin` is a fetch-based Jellyfin API client with full TypeScript +types and **zero runtime dependencies**. It works in Node.js, browsers and edge +runtimes, and it is what every Coral module uses to talk to Jellyfin. ## Installation @@ -70,9 +70,16 @@ The client is passed as the first argument to standalone functions, grouped roug - **Authentication & sessions**: `authenticateUserByName`, `logoutUserSession` - **URL builders**: `imageUrl`, `personImageUrl`, `streamUrl`, `transcodeUrl`, `subtitleUrl` - **Mapper**: `fromJellyfin`, `fromJellyfinDetailed` — normalise raw `JellyfinItem`s into a UI-friendly `MediaItem` shape -- **Admin**: `getSystemInfo`, `getItemCounts`, `getActiveSessions`, `getUsers`, `getUserById`, `createUser`, `deleteUser`, `updateUserPolicy`, `getVirtualFolders`, `scanAllLibraries`, `scanLibrary` +- **Admin**: `getSystemInfo`, `getItemCounts`, `getActiveSessions`, `getUsers`, `getUserById`, `createUser`, `deleteUser`, `disableUser`, `enableUser`, `updateUserPolicy`, `patchUserPolicy`, `updateUserPassword`, `updateUserPrimaryImage`, `uploadUserPrimaryImage`, `deleteUserPrimaryImage`, `getVirtualFolders`, `scanAllLibraries`, `scanLibrary` +- **Artwork**: `getRemoteImages`, `getCoverCandidates`, `getCoverCandidatesForItem`, `downloadRemoteImage`, `uploadItemImageFromUrl`, `applyRemoteImageWithFallback` +- **Metadata quality**: `getMetadataGapKeys`, `getMetadataGapReasons`, `metadataGapReasonForKey` — find items missing artwork or metadata. This is what a hygiene tool is built on +- **Remote playback**: `describeRemotePlaybackSupport` +- **Paths and naming**: `getItemPath`, `updateItemName` + +`JellyfinClient` is exported as a class, `isResumable` as a mapper helper, and +types are additionally available from the `@get-coral/jellyfin/types` subpath. -See the [repository README](https://github.com/Get-Coral/Jellyfin#api-reference) for the full reference with options and return types. +See the [repository README](https://github.com/Get-Coral/jellyfin#api-reference) for the full reference with options and return types. ## Authentication & Sessions @@ -171,8 +178,10 @@ If you see this against a Jellyfin 12 server, upgrade `@get-coral/jellyfin`. Community contributions are welcome! See the [Contributing](/contributing/getting-started/) guide. -## Links +## Related -- [GitHub Repository](https://github.com/Get-Coral/Jellyfin) -- [npm Package](https://www.npmjs.com/package/@get-coral/jellyfin) -- [Jellyfin Docs](https://jellyfin.org/) +- [NPM packages](/libraries/npm-packages/) — everything Coral publishes +- [Coral UI](/libraries/coral-ui/) — the component half of a Coral module +- [Project templates](/contributing/project-templates/) — using the client in a new module +- [Aurora](/modules/aurora/) — the largest consumer of this client +- [Get-Coral/jellyfin on GitHub](https://github.com/Get-Coral/jellyfin) · [npm](https://www.npmjs.com/package/@get-coral/jellyfin) · [Jellyfin docs](https://jellyfin.org/) diff --git a/src/content/docs/libraries/npm-packages.md b/src/content/docs/libraries/npm-packages.md index f02d771..7448ceb 100644 --- a/src/content/docs/libraries/npm-packages.md +++ b/src/content/docs/libraries/npm-packages.md @@ -1,19 +1,37 @@ --- title: NPM Packages -description: Official Coral packages published to npm +description: The five published Coral packages — what each one is for, which you need, and how they fit together. --- -Coral publishes reusable packages for app scaffolding, Jellyfin integration, and shared tooling. +Coral publishes five packages to npm: one scaffolder, two runtime libraries, and +two shared tooling presets. Everything else in the ecosystem is a Docker image, +not a package. -## Package Catalog +## Package catalog -| Package | Purpose | -|---|---| -| [create-coral](https://www.npmjs.com/package/create-coral) | CLI to scaffold a new Coral module from the official template | -| [@get-coral/ui](https://www.npmjs.com/package/@get-coral/ui) | Shared React UI component library and design tokens for Coral apps | -| [@get-coral/jellyfin](https://www.npmjs.com/package/@get-coral/jellyfin) | Typed Jellyfin API client for Node, browser, and edge runtimes | -| [@get-coral/tsconfig](https://www.npmjs.com/package/@get-coral/tsconfig) | Shared TypeScript config presets for Coral repositories | -| [@get-coral/biome-config](https://www.npmjs.com/package/@get-coral/biome-config) | Shared Biome lint/format configuration for Coral repositories | +Version badges are live, so this table cannot go stale. + +| Package | Version | Purpose | +|---|---|---| +| [create-coral](https://www.npmjs.com/package/create-coral) | ![npm](https://img.shields.io/npm/v/create-coral?color=2dd4bf&labelColor=0b1820&label=) | CLI to scaffold a new Coral module from the official template | +| [@get-coral/jellyfin](https://www.npmjs.com/package/@get-coral/jellyfin) | ![npm](https://img.shields.io/npm/v/%40get-coral%2Fjellyfin?color=2dd4bf&labelColor=0b1820&label=) | Typed Jellyfin API client for Node, browser and edge runtimes | +| [@get-coral/ui](https://www.npmjs.com/package/@get-coral/ui) | ![npm](https://img.shields.io/npm/v/%40get-coral%2Fui?color=2dd4bf&labelColor=0b1820&label=) | Shared React component library and design tokens | +| [@get-coral/tsconfig](https://www.npmjs.com/package/@get-coral/tsconfig) | ![npm](https://img.shields.io/npm/v/%40get-coral%2Ftsconfig?color=2dd4bf&labelColor=0b1820&label=) | Shared TypeScript config presets | +| [@get-coral/biome-config](https://www.npmjs.com/package/@get-coral/biome-config) | ![npm](https://img.shields.io/npm/v/%40get-coral%2Fbiome-config?color=2dd4bf&labelColor=0b1820&label=) | Shared Biome lint and format configuration | + +## Which do you need? + +**Building a Coral module?** Run `pnpm create coral@latest`. The template already +depends on all four of the others; you do not install them by hand. + +**Talking to Jellyfin from your own project?** You only need +`@get-coral/jellyfin`. It has zero runtime dependencies and no React, so it works +in a script, a worker or a server as happily as in an app. + +**Matching Coral's look?** Add `@get-coral/ui`. It needs React 19. + +**Matching Coral's tooling in an unrelated repo?** `@get-coral/tsconfig` and +`@get-coral/biome-config` are standalone and useful on their own. ## Installation @@ -25,31 +43,49 @@ pnpm create coral@latest npm create coral@latest ``` -### Add shared tooling presets - -```bash -pnpm add -D @get-coral/tsconfig @get-coral/biome-config -# or -npm install --save-dev @get-coral/tsconfig @get-coral/biome-config -``` - ### Add the Jellyfin client ```bash pnpm add @get-coral/jellyfin -# or -npm install @get-coral/jellyfin ``` ### Add shared UI components ```bash pnpm add @get-coral/ui -# or -npm install @get-coral/ui ``` +### Add shared tooling presets + +```bash +pnpm add -D @get-coral/tsconfig @get-coral/biome-config +``` + +Then extend them. In `tsconfig.json`: + +```json +{ "extends": "@get-coral/tsconfig" } +``` + +And in `biome.json`: + +```json +{ "extends": ["@get-coral/biome-config"] } +``` + +## Releases + +Every package uses Release Please: conventional commits on `main` open a release +PR, and merging it publishes. Publishing uses **npm trusted publishing (OIDC) +with provenance**, so there is no long-lived `NPM_TOKEN` in any repository. + +The two tooling presets live together in +[Get-Coral/dev-standards](https://github.com/Get-Coral/dev-standards); the other +three have their own repositories. + ## Related -- [create-coral CLI](/getting-started/create-coral/) -- [Jellyfin API Client](/libraries/jellyfin/) +- [create-coral CLI](/getting-started/create-coral/) — flags and the full flow +- [Jellyfin API client](/libraries/jellyfin/) — the API surface +- [Coral UI](/libraries/coral-ui/) — component reference +- [Project templates](/contributing/project-templates/) diff --git a/src/content/docs/modules/aurora.md b/src/content/docs/modules/aurora.md index 3fd0797..e9eb24d 100644 --- a/src/content/docs/modules/aurora.md +++ b/src/content/docs/modules/aurora.md @@ -1,18 +1,23 @@ --- title: Aurora -description: A premium Jellyfin frontend built with TanStack Start and React +description: A cinematic web frontend for Jellyfin. One Docker container, your existing server, and playback progress that syncs back. --- -## Aurora UI +Aurora is a cinematic web frontend for Jellyfin. It runs as one Docker container +alongside your existing server, reads your libraries over the Jellyfin API, and +reports playback progress back to Jellyfin. It keeps Jellyfin as the source of +truth and never duplicates your data. -Aurora is a premium Jellyfin frontend built with TanStack Start and React. It keeps Jellyfin as the source of truth while layering on a more cinematic home experience, richer detail views, embedded playback, favorites, genre browsing, and translation-ready UI foundations. +:::note[Shipping] +The most complete Coral module. Everything below is implemented. +::: -### Highlights +## Highlights - **Jellyfin-powered home screen** with featured, continue watching, favorites, and recommendation rails - **Embedded playback** with progress sync back to Jellyfin - **Rich title detail views** with cast, related titles, and series episode context -- **Movie and series library pages** with genre browsing, filtering, sorting, and pagination +- **Movie and series library pages** with genre browsing, sorting, and pagination - **My List / Favorites workflow** backed by Jellyfin favorites - **Multi-user profiles** with a Netflix-style profile picker for shared households - **Optional required sign-in** with per-user Jellyfin sessions, so playback and watch progress are attributed to the right account @@ -41,6 +46,12 @@ docker run -d -p 3000:3000 \ Open `http://localhost:3000` and complete the onboarding flow once — Aurora persists the Jellyfin connection in `/data/aurora.sqlite`. Alternatively, skip onboarding by passing the Jellyfin environment variables below. +Aurora serves on port `3000` and exposes `/healthz`, which returns non-200 until +the server is ready — useful as a Docker healthcheck. + +For a full stack with Jellyfin, Tide and Librarian alongside it, see +[Running a stack with Docker Compose](/getting-started/docker-compose/). + ## Local Development ### Prerequisites @@ -75,6 +86,17 @@ All configuration can be done through the in-app setup and settings screens; env | `AURORA_REQUIRE_LOGIN` | `true` forces required sign-in and locks the toggle | | `AURORA_MULTI_USER` | `true` forces multi-user profiles and locks the toggle | | `AURORA_DATA_DIR` | Where the local SQLite database lives (default `./data`) | +| `HOST` | Interface the server binds to (default `0.0.0.0`) | +| `PORT` | Port the server listens on (default `3000`) | +| `AURORA_STREAM_TOKEN_SECRET` | Signing secret for AirPlay/Cast stream URLs. Generated and stored automatically if unset — set it to keep URLs valid across data directories. Changing it invalidates every stream URL already handed to a TV | +| `AURORA_DISABLE_SPA_PRERENDER` | Build-time. Skips SPA prerendering; the published image sets it | + +`AURORA_APP_URL` is a build-time variable for the Capacitor native shells only +(`capacitor.config.ts`); it has no effect on the server. + +`aurora/.env.example` still lists `PLEX_URL` and `PLEX_TOKEN`. Nothing reads +them — they are leftovers from a dropped experiment, and setting them does +nothing. ## User Profiles & Sign-In @@ -86,17 +108,33 @@ Aurora supports shared households out of the box: ## Deployment -Aurora ships as a Node server with a production Dockerfile. The GitHub Actions workflows build and publish the container to GitHub Container Registry on every release. +Aurora ships as a Node server with a production Dockerfile. On every release the +GitHub Actions workflow publishes the image to **both** Docker Hub +(`getcoral/aurora`) and the GitHub Container Registry +(`ghcr.io/get-coral/aurora`). The examples here use Docker Hub. ```bash pnpm build pnpm start ``` +`pnpm start` runs `node server.mjs` and is what the image runs. + +### Native builds + +Aurora also ships PWA and Capacitor wrappers for installable web, Android and +iOS builds. Those need JDK 21 and the `pnpm cap:*` scripts; the +[repository README](https://github.com/Get-Coral/aurora#readme) is the reference +for that workflow. + ## Contributing Aurora is open source and welcomes contributions. See the [Contributing](/contributing/getting-started/) guide for details. -## Repository +## Related -[Get-Coral/aurora on GitHub](https://github.com/Get-Coral/aurora) +- [Aurora vs the Jellyfin web client](https://getcoral.dev/compare/aurora-vs-jellyfin-web) — what the stock client still does better +- [Running a stack with Docker Compose](/getting-started/docker-compose/) +- [Marquee](/modules/marquee/) — the passive display counterpart +- [Jellyfin API Client](/libraries/jellyfin/) — the typed client Aurora is built on +- [Get-Coral/aurora on GitHub](https://github.com/Get-Coral/aurora) diff --git a/src/content/docs/modules/encore.md b/src/content/docs/modules/encore.md index 8dfe318..0d01cde 100644 --- a/src/content/docs/modules/encore.md +++ b/src/content/docs/modules/encore.md @@ -1,98 +1,87 @@ --- title: Encore -description: Moderated guest music requests for house-party mode +description: The Coral module scaffold. The published image serves a placeholder — the guest music requests it is named for are not built yet. --- -## Encore +Encore is the reference scaffold for a Coral module: TanStack Start, Tailwind v4, +Biome and release automation, wired together and ready to build on. It is named +for a planned feature — moderated guest music requests against a Jellyfin music +library — that **does not exist yet**. -Encore is a Coral ecosystem module built on TanStack Start, Tailwind v4, and the Jellyfin API. It serves as a template and foundation for building new Coral applications. +:::caution[Scaffold — do not deploy this expecting a product] +`getcoral/encore` builds and runs, but `src/` is the unmodified Coral template, +so the container serves a placeholder page that reads *"Coral Module — Ready to +build."* There is no music browsing, no request queue, no host approval, and no +Jellyfin connection of any kind. -## Getting Started +If you want a working Coral module today, see [Aurora](/modules/aurora/), +[Tide](/modules/tide/) or [KAPOW!](/modules/kapow/). +::: -### Prerequisites +## What it is useful for -- Node.js 24 LTS -- pnpm (or npm/yarn) +As a starting point. Encore tracks the current template, so it is a live example +of how a Coral module is laid out — routing, the Node server entrypoint, the +Docker build, and the release workflow. -### Installation +To start your own module from the same base, use the CLI rather than forking +Encore: -1. Clone the repository: ```bash -git clone https://github.com/Get-Coral/encore.git -cd encore -``` - -2. Install dependencies: -```bash -pnpm install -``` - -3. Configure environment variables: -```bash -cp .env.example .env -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id -``` - -4. Start development server: -```bash -pnpm dev +pnpm create coral@latest ``` -Encore runs on `http://localhost:3000` +See the [create-coral CLI guide](/getting-started/create-coral/) and +[Project templates](/contributing/project-templates/). -## Stack +## Requirements -| Tool | Purpose | -|------|---------| -| [TanStack Start](https://tanstack.com/start) | Full-stack React framework | -| [TanStack Router](https://tanstack.com/router) | Type-safe file-based routing | -| [TanStack Query](https://tanstack.com/query) | Server state management | -| [Tailwind v4](https://tailwindcss.com) | Styling | -| [Biome](https://biomejs.dev) | Linting & formatting | -| [@get-coral/jellyfin](https://github.com/Get-Coral/jellyfin) | Jellyfin API client | -| [Vitest](https://vitest.dev) | Testing | +- Node.js 24 LTS. Node 22.5 is the hard floor — the template uses the built-in + `node:sqlite` module, which does not exist on Node 18 or 20. +- pnpm -## Available Scripts +## Running it ```bash -pnpm dev # Start dev server on :3000 -pnpm build # Production build -pnpm start # Run production server -pnpm typecheck # TypeScript check -pnpm check # Biome lint + format check -pnpm lint # Biome lint with auto-fix -pnpm test # Run tests +docker run -p 3000:3000 getcoral/encore:latest ``` -## Deployment - -### Docker +Encore serves on port `3000` and exposes `/healthz`. -```bash -# Build -docker build -t encore . - -# Run -docker run -p 3000:3000 \ - -e JELLYFIN_URL=http://your-nas:8096 \ - -e JELLYFIN_API_KEY=your-key \ - -e JELLYFIN_USER_ID=your-user-id \ - encore -``` +## Environment -### CI/CD +The entire environment surface, verified against `encore/.env.example` and +`encore/src`: -Automated workflows handle: -- **ci.yml** - Run on every PR and push (typecheck, lint, test, build) -- **docker-publish.yml** - Publish to GHCR on release -- **release-please.yml** - Automated versioning and releases +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `HOST` | No | `0.0.0.0` | Interface the server binds to | +| `PORT` | No | `3000` | Port the server listens on | -## Contributing +There are no `JELLYFIN_*` variables. Nothing in Encore reads them, and setting +them has no effect. -See [Contributing](/contributing/getting-started/) to contribute to Encore. +## From source -## Repository +```bash +git clone https://github.com/Get-Coral/encore.git +cd encore +pnpm install +pnpm dev +``` -[Get-Coral/encore on GitHub](https://github.com/Get-Coral/encore) +| Script | Purpose | +|---|---| +| `pnpm dev` | Dev server on `:3000` | +| `pnpm build` | Production build | +| `pnpm start` | Run the production server (`node server.mjs`) | +| `pnpm typecheck` | TypeScript check | +| `pnpm check` | Biome lint + format check | +| `pnpm test` | Vitest | + +## Related + +- [Project templates](/contributing/project-templates/) — what the scaffold contains +- [create-coral CLI](/getting-started/create-coral/) — the supported way to start a module +- [Module contracts](/getting-started/module-contracts/) — how modules talk to each other +- [Get-Coral/encore on GitHub](https://github.com/Get-Coral/encore) diff --git a/src/content/docs/modules/fathom.md b/src/content/docs/modules/fathom.md index 23ddb2a..cf0d773 100644 --- a/src/content/docs/modules/fathom.md +++ b/src/content/docs/modules/fathom.md @@ -1,146 +1,103 @@ --- title: Fathom -description: A reading module for Jellyfin. Books, manga, comics, and PDFs in a calm interface +description: A cover-first reading interface for the books, manga, comics and PDFs already in your Jellyfin libraries. --- -## Fathom +Fathom is a reading interface for books, manga, comics and PDFs that already +live in Jellyfin. It runs as one Docker container, reads your reading libraries +over the Jellyfin API, and presents them cover-first rather than as rows of +filenames. -Fathom is the Coral reading room. It connects to Jellyfin and turns reading libraries into a cleaner browsing experience with a featured shelf, recent additions, library browsing, collection browsing, and rich title details with metadata. +:::note[Early] +Browsing works: a featured shelf, recent additions, library and collection +browsing, and a title detail view with contributors and metadata. -A cover-first interface designed for a calm, focused reading experience. +There is **no reading-progress tracking, no ratings or reviews, no personal +collections and no recommendation engine**. Fathom is a nicer way to look at a +reading library, not yet a reader. +::: -### What It Is +## Requirements -Fathom provides: -- **Featured shelf** - Curated reading selections -- **Recent additions** - New books, manga, comics -- **Library browsing** - Organized by collections -- **Collection browsing** - Group related content -- **Title detail** - Complete metadata and contributor information -- **Local connection** - SQLite-backed Jellyfin settings, with `.env` support +- A running Jellyfin server with at least one book, comic or mixed library +- A Jellyfin API key and the user's **UUID** (not their username) +- Node.js 24 LTS from source. Node 22.5 is the hard floor — Fathom uses the + built-in `node:sqlite` module, which does not exist on Node 18 or 20. -### Supported Media - -- Books (EPUB, PDF, etc.) -- Manga -- Comics -- Audiobooks and more - -## Getting Started - -### Prerequisites - -- Node.js 24 LTS (Node 22.5+ is the hard floor — this module uses the - built-in `node:sqlite` module, which does not exist on Node 18 or 20) -- pnpm (or npm/yarn) -- Running Jellyfin server with reading library - -### Installation - -1. Clone the repository: -```bash -git clone https://github.com/Get-Coral/fathom.git -cd fathom -``` - -2. Install dependencies: -```bash -pnpm install -``` - -3. Configure environment variables: -```bash -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id -``` - -Connection details can also be configured in the web UI on first run, and will be stored locally in SQLite. - -4. Start the development server: -```bash -pnpm dev -``` - -Fathom runs on `http://localhost:3000` - -## Configuration - -Fathom supports the same connection model as other Coral modules. - -### Environment Variables +## Running it ```bash -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id +docker run -d \ + --name fathom \ + -p 3000:3000 \ + -v ./fathom-data:/data \ + -e FATHOM_DATA_DIR=/data \ + -e JELLYFIN_URL=http://your-server:8096 \ + -e JELLYFIN_API_KEY=your-api-key \ + -e JELLYFIN_USER_ID=your-user-uuid \ + getcoral/fathom:latest ``` -If these are not set, Fathom will open a setup screen on first run and store the connection details locally. +Fathom serves on port `3000` and exposes `/healthz`. -### Local Storage +Every `JELLYFIN_*` variable is optional. With none set, Fathom sends you to +`/setup` on first run and stores the connection in its own SQLite database. The +environment variables exist so an operator who configures everything through +compose never has to open the UI; `/setup` still lets you override them locally. -Connection settings are stored in SQLite at `./data/fathom.sqlite` for a self-hosted setup without external database requirements. +## Environment -## Features +Verified against `fathom/.env.example` and `fathom/src`. -### Browse -- View all reading libraries -- Browse by collection -- Search across your library -- Filter by media type +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `JELLYFIN_URL` | No | — | Jellyfin base URL. Must resolve from inside the container | +| `JELLYFIN_API_KEY` | No | — | API key from **Dashboard → API Keys** | +| `JELLYFIN_USER_ID` | No | — | The user's UUID, not their username | +| `JELLYFIN_USERNAME` | No | — | Optional. Opens a real playback session | +| `JELLYFIN_PASSWORD` | No | — | Optional, paired with `JELLYFIN_USERNAME` | +| `FATHOM_DATA_DIR` | No | `./data` | Where `fathom.sqlite` lives | +| `HOST` | No | `0.0.0.0` | Interface the server binds to | +| `PORT` | No | `3000` | Port the server listens on | -### Discover -- Featured and highlighted titles -- Recent additions -- Curated collections -- Recommendation algorithms +## Storage -### Reading Management -- Track reading progress -- Mark as favorites -- Create personal collections -- Rating and reviews +Fathom keeps its Jellyfin connection and local overrides in a SQLite database at +`./data/fathom.sqlite`, or under `FATHOM_DATA_DIR` if you set it. In the +published image that is `/data` — mount it, or you will redo setup on every +container replacement. -### Library Metadata -- Full book information -- Contributors and authors -- Descriptions and summaries -- Cover art and thumbnails +## Library layout -## Development +Fathom reads whatever Jellyfin already exposes as a book or mixed-content +library. It does not scan the filesystem itself and does not write to your +media, so how you organise files on disk is entirely Jellyfin's business. -Fathom is built with: -- [TanStack Start](https://tanstack.com/start) -- React 19 -- [Tailwind CSS v4](https://tailwindcss.com) -- Jellyfin API +## From source -### Project Structure - -- `src/routes/` - Page components -- `src/components/` - Reusable UI components -- `src/lib/` - Utilities and helpers -- `src/data/` - Data directory for SQLite - -## Deployment - -Deploy Fathom to: -- Vercel -- Docker -- Self-hosted servers - -For production: ```bash -pnpm build -pnpm preview +git clone https://github.com/Get-Coral/fathom.git +cd fathom +pnpm install +cp .env.example .env +pnpm dev ``` -## Learn More +| Script | Purpose | +|---|---| +| `pnpm dev` | Dev server on `:3000` | +| `pnpm build` | Production build | +| `pnpm start` | Run the production server (`node server.mjs`) | +| `pnpm typecheck` | TypeScript check | +| `pnpm check` | Biome lint + format check | +| `pnpm test` | Vitest | -- [Jellyfin API Client](/libraries/jellyfin/) - Building custom reading interfaces -- [Contributing](/contributing/getting-started/) - Extend Fathom +`pnpm start` is the production entrypoint and is what the Docker image runs. +`pnpm preview` serves the Vite build and is for local inspection only. -## Repository +## Related -[Get-Coral/fathom on GitHub](https://github.com/Get-Coral/fathom) +- [Fathom vs Kavita and Komga](https://getcoral.dev/compare/fathom-vs-kavita-komga) — whether your books belong in Jellyfin at all +- [Aurora](/modules/aurora/) — the same idea for video +- [Jellyfin API Client](/libraries/jellyfin/) — the typed client Fathom is built on +- [Get-Coral/fathom on GitHub](https://github.com/Get-Coral/fathom) diff --git a/src/content/docs/modules/kapow.md b/src/content/docs/modules/kapow.md index 26e4794..2f4c235 100644 --- a/src/content/docs/modules/kapow.md +++ b/src/content/docs/modules/kapow.md @@ -1,15 +1,25 @@ --- -title: KAPOW -description: Comic-book karaoke queue manager for group entertainment +title: KAPOW! +description: A comic-book karaoke queue for bars and parties. Guests join by QR code from their own phones, vote songs up, and a TV view drives the room. --- -## KAPOW +KAPOW! is a karaoke queue system for bars, events and parties. The host opens a +room, guests join by scanning a QR code on their own phone with nothing to +install, everyone searches and votes songs up the queue, and a separate display +view drives the screen in the room. -KAPOW is a comic-book karaoke queue manager. Hosts spin up a room, guests scan a QR code and search for tracks, the crowd votes songs up the queue, and the host runs the night from a dedicated control booth. +:::note[Shipping] +Rooms, guest join, search, voting, host controls and the display view all work. +::: -Perfect for parties, events, and group entertainment nights. +:::caution[Not self-contained] +Unlike every other Coral module, KAPOW! is not a single container you point at +Jellyfin. It needs a **Supabase** project for its database and realtime layer, +and a **YouTube Data API v3** key for song search. Budget for both before you +start. +::: -### How It Works +## How It Works 1. **Host creates a room** → gets a host token, a 6-character join code, and a QR code 2. **Guests join via code or QR** → search YouTube for karaoke tracks → add to queue with their name @@ -77,14 +87,42 @@ pnpm install cp .env.example .env ``` -4. Add your credentials: -``` +4. Add your credentials to `.env`: + +```ini SUPABASE_URL=https://your-project.supabase.co -SUPABASE_PUBLISHABLE_KEY=eyJ... -SUPABASE_DB_PASSWORD=your-db-password -YOUTUBE_API_KEY=AIzaS... +SUPABASE_PUBLISHABLE_KEY= +YOUTUBE_API_KEY= ``` +### Environment + +Verified against `KAPOW/.env.example` and `KAPOW/src/lib/env.ts`. + +| Variable | Required | Purpose | +|---|---|---| +| `SUPABASE_URL` | Yes | Supabase project URL | +| `SUPABASE_PUBLISHABLE_KEY` | Yes | Supabase publishable (anon) key | +| `YOUTUBE_API_KEY` | Yes | YouTube Data API v3 key, used for song search | +| `SUPABASE_DB_URL` | No | Direct database connection, for the migration scripts | +| `SUPABASE_DB_PASSWORD` | No | Database password. Used by `supabase link` and in CI — **not** read by the app | +| `HOST` | No | Interface the server binds to (default `0.0.0.0`) | +| `PORT` | No | Port the server listens on (default `3000`) | + +`SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` each accept aliases, so an +existing Supabase `.env` usually works unchanged: + +- URL: `SUPABASE_URL`, `VITE_SUPABASE_URL` +- Key: `SUPABASE_ANON_KEY`, `SUPABASE_PUBLISHABLE_KEY`, `SUPABASE_KEY`, + `VITE_SUPABASE_ANON_KEY`, `VITE_SUPABASE_PUBLISHABLE_KEY`, `VITE_SUPABASE_KEY` + +### Database migrations + +`supabase/migrations/` is the source of truth for the schema; `schema.sql` is a +reference snapshot. The `pnpm db:*` scripts (`db:start`, `db:reset`, `db:lint`, +`db:test`, `db:push:remote`) drive it. Publishing to a remote project needs the +`SUPABASE_ACCESS_TOKEN` and `SUPABASE_PROJECT_REF` CI secrets. + ### Get Supabase Credentials 1. Create a Supabase project at [supabase.com](https://supabase.com) @@ -115,7 +153,8 @@ KAPOW runs on `http://localhost:3000` 1. Visit `http://localhost:3000` 2. Create a new room 3. Share the code or QR code with guests -4. Go to host control at `/host/:code` +4. Go to host control at `/host/:code?token=` — the host token from step 2 is + required; the route will not open without it ### As Guest @@ -125,21 +164,38 @@ KAPOW runs on `http://localhost:3000` 4. Add songs to queue 5. Vote on pending songs +## Routes + +| Route | Description | +|---|---| +| `/` | Landing — create or join a room | +| `/room/:code` | Guest view — search songs, add to queue, vote | +| `/host/:code?token=` | Host control booth — manage queue, advance songs | +| `/display/:code` | TV display — now playing, full-screen comic mode | + ## Deployment -Deploy KAPOW to: -- Vercel -- Docker -- Self-hosted servers +KAPOW! publishes to `getcoral/kapow` on Docker Hub and +`ghcr.io/get-coral/kapow`. + +:::caution[Check the image before you rely on it] +The Docker build currently copies the server output to `./dist` while the +container's start command points at `.output/server/index.mjs`. If the container +exits immediately on start, that is why — run from source until it is fixed, and +see [Get-Coral/KAPOW](https://github.com/Get-Coral/KAPOW) for status. +::: -Configure Supabase connection for production and ensure YouTube API limits are set appropriately. +Running from source: -Production build: ```bash pnpm build -pnpm preview +pnpm start ``` +Whichever way you deploy, Supabase and the YouTube API key have to be configured +for the environment, and YouTube's daily quota is the limit you will hit first +on a busy night. + ## Architecture ### Database Schema @@ -166,6 +222,8 @@ Uses Supabase realtime to push: - [Supabase Docs](https://supabase.com/docs) - [Contributing](/contributing/getting-started/) -## Repository +## Related -[Get-Coral/KAPOW on GitHub](https://github.com/Get-Coral/KAPOW) +- [KAPOW! vs Karaoke Eternal](https://getcoral.dev/compare/kapow-vs-karaoke-eternal) — local library versus YouTube search +- [Encore](/modules/encore/) — the planned equivalent for Jellyfin music requests +- [Get-Coral/KAPOW on GitHub](https://github.com/Get-Coral/KAPOW) diff --git a/src/content/docs/modules/librarian.md b/src/content/docs/modules/librarian.md index 8b28bbe..c6d98b5 100644 --- a/src/content/docs/modules/librarian.md +++ b/src/content/docs/modules/librarian.md @@ -1,141 +1,177 @@ --- title: Librarian -description: A Coral module for organizing and enriching self-hosted media libraries +description: Import finished downloads into your Jellyfin media tree by hardlinking them — zero extra disk, and the torrent keeps seeding. --- -## Librarian +Librarian imports finished downloads into your media tree by hardlinking them, +so the file appears in your library at zero extra bytes and the torrent carries +on seeding the same data. It runs as one Docker container, reads Jellyfin over +its HTTP API, and never takes ownership of your library away from Jellyfin. -Librarian is the Coral module focused on media hygiene and enrichment. It provides tools for organizing, enriching, and maintaining your self-hosted Jellyfin media libraries. +:::note[Early] +The import workflow is real and in daily use: filesystem roots, import plans +with a preview, hardlink-with-copy-fallback, path mappings, and scan jobs. -Keep your media organized, metadata enriched, and everything running smoothly. +Duplicate detection, bulk metadata editing, backup export and library analytics +are **on the product direction, not in the code**. If you need those today, +Librarian is not the tool yet. +::: -### What It Does +:::caution[It signs you in by default] +Unlike the read-only Coral modules, Librarian moves and deletes files, so it +requires a Jellyfin sign-in out of the box. Anything touching the filesystem +additionally requires the signed-in user to be a Jellyfin **administrator**, and +that gate does not relax when sign-in is switched off. See +[Access control](#access-control). +::: -Librarian offers: -- **Library management** - Organize and optimize your collections -- **Metadata enrichment** - Enhance missing information -- **Duplicate detection** - Find and clean up duplicates -- **Quality assurance** - Tools for maintaining library health -- **Bulk operations** - Manage items across your library -- **Local connection** - SQLite-backed Jellyfin settings +## Requirements -## Getting Started +- A running Jellyfin server reachable from the container +- A Jellyfin API key and the user's **UUID** +- A Jellyfin **administrator** account for any file operation +- Media and downloads under **one** mount point — see + [Why one mount](#why-one-mount) +- Node.js 24 LTS from source (22.5 is the hard floor, for `node:sqlite`) -### Prerequisites +## Running it -- Node.js 24 LTS (Node 22.5+ is the hard floor — this module uses the - built-in `node:sqlite` module, which does not exist on Node 18 or 20) -- pnpm (or npm/yarn) -- Running Jellyfin server - -### Installation - -1. Clone the repository: ```bash -git clone https://github.com/Get-Coral/librarian.git -cd librarian +docker run -d \ + --name librarian \ + -p 127.0.0.1:3002:3000 \ + --user "$(id -u):$(id -g)" \ + -v ./librarian-data:/data \ + -v ./library:/library \ + -e LIBRARIAN_DATA_DIR=/data \ + -e JELLYFIN_URL=http://your-server:8096 \ + -e JELLYFIN_API_KEY=your-api-key \ + -e JELLYFIN_USER_ID=your-user-uuid \ + -e LIBRARIAN_DOWNLOADS_DIR=/library/downloads/complete \ + -e LIBRARIAN_MEDIA_DIR=/library/media/movies \ + getcoral/librarian:latest ``` -2. Install dependencies: -```bash -pnpm install -``` +Librarian serves on port `3000` inside the container and exposes `/healthz`. -3. Configure environment variables (optional): -```bash -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id -``` +For the full four-service stack — Jellyfin, Aurora, Tide and Librarian — with +the mount layout worked out, see +[Running a stack with Docker Compose](/getting-started/docker-compose/). -If not set in environment, Librarian will prompt for connection on startup and store settings locally. +## Environment -4. Start the development server: -```bash -pnpm dev -``` +Verified against `librarian/.env.example` and `librarian/src`. -App runs at `http://localhost:3000` +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `JELLYFIN_URL` | No | — | Jellyfin base URL. Must resolve from inside the container | +| `JELLYFIN_API_KEY` | No | — | API key from **Dashboard → API Keys** | +| `JELLYFIN_USER_ID` | No | — | The user's UUID, not their username | +| `JELLYFIN_USERNAME` | No | — | Optional, opens a real playback session | +| `JELLYFIN_PASSWORD` | No | — | Optional, paired with `JELLYFIN_USERNAME` | +| `LIBRARIAN_DATA_DIR` | No | `./data` | Where `librarian.sqlite` lives | +| `LIBRARIAN_REQUIRE_LOGIN` | No | `true` | Setting it pins the value and hides the UI toggle | +| `LIBRARIAN_DOWNLOADS_DIR` | No | — | *Seeds* a downloads root. Arrives switched off | +| `LIBRARIAN_MEDIA_DIR` | No | — | *Seeds* a media root. Arrives switched off | +| `CORAL_SERVICE_TOKEN` | No | — | Grants another module full access without issuing a token in the UI | +| `HOST` | No | `0.0.0.0` | Interface the server binds to | +| `PORT` | No | `3000` | Port the server listens on | -## Features +Leave the Jellyfin variables unset and configure at `/setup` instead; Librarian +persists the connection in SQLite. -### Library Organization +## Access control -- **Organize by type** - Movies, TV shows, music, books -- **Create collections** - Group related content -- **Manage folders** - Physical library structure -- **Cleanup tools** - Remove orphaned files +| Variable | Default | Effect | +|---|---|---| +| `LIBRARIAN_REQUIRE_LOGIN` | `true` | Pins the setting and removes the toggle from the UI | +| `CORAL_SERVICE_TOKEN` | unset | Full access for another module, never stored, not revocable from the Connections page | -### Metadata Management +`CORAL_SERVICE_TOKEN` is an escape hatch for operators who configure everything +through compose and never open a UI. It grants full access and cannot be revoked +from the interface. Prefer issuing tokens on the Connections page. -- **Missing metadata** - Identify items needing information -- **Bulk editing** - Update multiple items at once -- **Match against sources** - Auto-populate from external APIs -- **Custom metadata** - Add your own tags and fields +## Filesystem roots -### Library Health +Librarian only touches directories you have enabled. -- **Duplicate detection** - Find and manage duplicates -- **File validation** - Check format compatibility -- **Performance optimization** - Index and cache management -- **Backup tools** - Export library configuration +`LIBRARIAN_DOWNLOADS_DIR` and `LIBRARIAN_MEDIA_DIR` **seed root records — they +do not grant permission**. A seeded root arrives switched off and a human turns +it on in the UI. A container that happens to have `/media` bind-mounted can do +nothing with it until somebody says so: a mounted directory is not permission to +write to it. -### Analytics +## Importing -- **Library statistics** - Size, growth, composition -- **Format analysis** - What codecs and containers you use -- **Utilization metrics** - Storage and performance data +The `/organize` page is the working surface. It lists finished downloads, builds +an **import plan**, and shows you what it will do before it does it. The plan +tells you, per file, whether the transfer will be a hardlink or a copy — check +that column, it is where a broken mount layout shows up. -## Configuration +Librarian never chowns anything. It is not going to start rewriting ownership on +your library. -### Environment Variables +### Why one mount -Set these for automatic connection: +Librarian imports by hardlinking: the file appears in your library at zero extra +bytes and the torrent carries on seeding the same data. -```bash -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id -``` +A hardlink cannot cross a filesystem — but it also cannot cross a *mount point*, +even when both sides are the same filesystem. Bind-mounting `./media` and +`./downloads` separately is enough to break it: -### Local Storage +``` +/media dev = 36 +/downloads dev = 36 <- same device +link() -> EXDEV <- refused anyway +``` -Connection settings are stored in SQLite at `./data/librarian.sqlite`. +Mount one tree instead, with media and downloads as directories inside it. +Awkward-looking paths, working hardlinks. -## Development +Nothing that inspects `statSync().dev` can predict this, which is why Librarian +decides by *attempting* the link rather than comparing device ids. If it does +fall back, nothing breaks — the import becomes a verified copy: correct, just +slower and twice the space. You will see "Copy" rather than "Hardlink" in the +import preview, which is the place to check. -Librarian is built with: -- [TanStack Start](https://tanstack.com/start) -- React 19 -- [TanStack Query](https://tanstack.com/query) -- [Tailwind CSS v4](https://tailwindcss.com) -- Jellyfin API +### Path mappings -### Key Directories +If Jellyfin and Librarian mount the same tree at different paths, the mappings +table translates between them. Mount everything at the same path in every +container and the table stays empty, which is how it is meant to be. -- `src/routes/` - Page and API routes -- `src/components/` - UI components -- `src/lib/` - Utilities and helpers -- `src/data/` - Data storage +## Cross-module access -## Deployment +Librarian publishes a Coral module manifest at `/api/coral/manifest` and +implements the `library.refresh` capability, which asks Jellyfin to rescan after +an import. See [Module contracts](/getting-started/module-contracts/). -Deploy Librarian to: -- Vercel -- Docker (Dockerfile included) -- Self-hosted servers +## From source -Production build: ```bash -pnpm build -pnpm preview +git clone https://github.com/Get-Coral/librarian.git +cd librarian +pnpm install +cp .env.example .env +pnpm dev ``` -## Learn More +The SQLite database lives at `./data/librarian.sqlite` by default. -- [Jellyfin API Client](/libraries/jellyfin/) - API integration details -- [Contributing](/contributing/getting-started/) - Contribute features +| Script | Purpose | +|---|---| +| `pnpm dev` | Dev server on `:3000` | +| `pnpm build` | Production build | +| `pnpm start` | Run the production server (`node server.mjs`) | +| `pnpm typecheck` | TypeScript check | +| `pnpm check` | Biome lint + format check | +| `pnpm test` | Vitest | -## Repository +## Related -[Get-Coral/librarian on GitHub](https://github.com/Get-Coral/librarian) +- [Running a stack with Docker Compose](/getting-started/docker-compose/) — the mount layout, worked out +- [Tide](/modules/tide/) — the download client Librarian imports from +- [Module contracts](/getting-started/module-contracts/) +- [What Librarian is for](https://getcoral.dev/apps/librarian) — the overview on getcoral.dev +- [Get-Coral/librarian on GitHub](https://github.com/Get-Coral/librarian) diff --git a/src/content/docs/modules/marquee.md b/src/content/docs/modules/marquee.md index d2d0fb5..a587dc5 100644 --- a/src/content/docs/modules/marquee.md +++ b/src/content/docs/modules/marquee.md @@ -1,99 +1,97 @@ --- title: Marquee -description: Ambient now-playing display for Jellyfin spaces +description: Turn a spare TV or tablet into an always-on Jellyfin display showing what is playing now and what was recently added. Nothing to click. --- -## Marquee +Marquee turns a spare TV, tablet or wall panel into an always-on display for a +Jellyfin server. It runs as one Docker container, polls Jellyfin over its HTTP +API, and shows what is playing right now, who is watching, and what was recently +added. There are no controls — it is a screen, not an app. -Marquee is a Coral ecosystem module built on TanStack Start, Tailwind v4, and the Jellyfin API. It serves as a template and foundation for building new Coral applications. +:::note[Early] +The display and its first-run setup flow work. The surface is deliberately +small: now playing and active sessions, recently added movies and shows, server +name and total library count. There is no playback control, no scheduling and no +multi-server support. +::: -## Getting Started +## Requirements -### Prerequisites +- A running Jellyfin server reachable from the container +- A Jellyfin API key and the user's **UUID** (not their username) +- Node.js 24 LTS if you are running from source. Node 22.5 is the hard floor — + Marquee uses the built-in `node:sqlite` module, which does not exist on Node + 18 or 20. -- Node.js 24 LTS (Node 22.5+ is the hard floor — this module uses the - built-in `node:sqlite` module, which does not exist on Node 18 or 20) -- pnpm (or npm/yarn) +## Running it -### Installation - -1. Clone the repository: ```bash -git clone https://github.com/Get-Coral/marquee.git -cd marquee +docker run -d \ + --name marquee \ + -p 3000:3000 \ + -v ./marquee-data:/data \ + -e MARQUEE_DATA_DIR=/data \ + -e JELLYFIN_URL=http://your-server:8096 \ + -e JELLYFIN_API_KEY=your-api-key \ + -e JELLYFIN_USER_ID=your-user-uuid \ + getcoral/marquee:latest ``` -2. Install dependencies: -```bash -pnpm install -``` +Then point the display's browser at `http://:3000` and leave it there. -3. Configure environment variables: -```bash -cp .env.example .env -JELLYFIN_URL=http://your-server:8096 -JELLYFIN_API_KEY=your-api-key -JELLYFIN_USER_ID=your-user-id -``` +You can skip every `JELLYFIN_*` variable and configure Marquee at `/setup` +instead — it stores the connection in its own SQLite database. The environment +variables exist so an operator who configures everything through compose never +has to open the UI. -4. Start development server: -```bash -pnpm dev -``` +Marquee exposes `/healthz`, which returns non-200 until the server is ready. -Marquee runs on `http://localhost:3000` +## Environment -## Stack +Verified against `marquee/.env.example` and `marquee/src`. -| Tool | Purpose | -|------|---------| -| [TanStack Start](https://tanstack.com/start) | Full-stack React framework | -| [TanStack Router](https://tanstack.com/router) | Type-safe file-based routing | -| [TanStack Query](https://tanstack.com/query) | Server state management | -| [Tailwind v4](https://tailwindcss.com) | Styling | -| [Biome](https://biomejs.dev) | Linting & formatting | -| [@get-coral/jellyfin](https://github.com/Get-Coral/jellyfin) | Jellyfin API client | -| [Vitest](https://vitest.dev) | Testing | +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `JELLYFIN_URL` | No | — | Jellyfin base URL. Must resolve from inside the container | +| `JELLYFIN_API_KEY` | No | — | API key from **Dashboard → API Keys** | +| `JELLYFIN_USER_ID` | No | — | The user's UUID, not their username | +| `JELLYFIN_USERNAME` | No | — | Optional. Opens a real playback session | +| `JELLYFIN_PASSWORD` | No | — | Optional, paired with `JELLYFIN_USERNAME` | +| `MARQUEE_DATA_DIR` | No | `./data` | Where the SQLite database lives | +| `HOST` | No | `0.0.0.0` | Interface the server binds to | +| `PORT` | No | `3000` | Port the server listens on | -## Available Scripts +Nothing is strictly required: with no Jellyfin variables set, Marquee redirects +to `/setup` on first run. -```bash -pnpm dev # Start dev server on :3000 -pnpm build # Production build -pnpm start # Run production server -pnpm typecheck # TypeScript check -pnpm check # Biome lint + format check -pnpm lint # Biome lint with auto-fix -pnpm test # Run tests -``` +## Storage -## Deployment +Marquee keeps its Jellyfin connection and settings in a SQLite database under +`MARQUEE_DATA_DIR` (`/data` in the image, `./data` from source). Mount it if you +do not want to redo setup on every container replacement. -### Docker +## From source ```bash -# Build -docker build -t marquee . - -# Run -docker run -p 3000:3000 \ - -e JELLYFIN_URL=http://your-nas:8096 \ - -e JELLYFIN_API_KEY=your-key \ - -e JELLYFIN_USER_ID=your-user-id \ - marquee +git clone https://github.com/Get-Coral/marquee.git +cd marquee +pnpm install +cp .env.example .env +pnpm dev ``` -### CI/CD - -Automated workflows handle: -- **ci.yml** - Run on every PR and push (typecheck, lint, test, build) -- **docker-publish.yml** - Publish to GHCR on release -- **release-please.yml** - Automated versioning and releases - -## Contributing - -See [Contributing](/contributing/getting-started/) to contribute to Marquee. - -## Repository - -[Get-Coral/marquee on GitHub](https://github.com/Get-Coral/marquee) +| Script | Purpose | +|---|---| +| `pnpm dev` | Dev server on `:3000` | +| `pnpm build` | Production build | +| `pnpm start` | Run the production server (`node server.mjs`) | +| `pnpm typecheck` | TypeScript check | +| `pnpm check` | Biome lint + format check | +| `pnpm test` | Vitest | + +## Related + +- [What Marquee is for](https://getcoral.dev/apps/marquee) — the overview on getcoral.dev +- [Aurora](/modules/aurora/) — the interactive frontend Marquee complements +- [Running a stack with Docker Compose](/getting-started/docker-compose/) +- [Get-Coral/marquee on GitHub](https://github.com/Get-Coral/marquee) diff --git a/src/content/docs/modules/tide.md b/src/content/docs/modules/tide.md index 7be450d..f550a3e 100644 --- a/src/content/docs/modules/tide.md +++ b/src/content/docs/modules/tide.md @@ -1,11 +1,17 @@ --- title: Tide -description: A torrent downloader for Jellyfin-adjacent media workflows with queue controls, piece maps, and optional Jellyfin sign-in +description: A torrent client with a web interface for self-hosted download boxes — real queue limits, per-file piece priorities and a memory guard. --- -## Tide +Tide is a torrent client with a web interface, built for self-hosted download +boxes. It runs as one Docker container, enforces real active-download and +seeding limits, lets you set per-file piece priorities, and pauses torrents +automatically as it approaches its memory cap. -Tide is Coral's torrent download manager. It gives you a cleaner, self-hosted interface for adding torrents, managing queue order, limiting active downloads and seeders, adjusting file priorities, and inspecting live swarm health with an expandable piece map. +:::note[Shipping] +Queue controls, piece maps, seeding goals, SQLite-backed state, the memory guard +and optional Jellyfin sign-in are all implemented. +::: ## Highlights @@ -70,18 +76,26 @@ Tide runs on `http://localhost:3000`. ### Environment Variables -```bash -TIDE_DOWNLOADS_DIR=./data/downloads -TIDE_DATA_DIR=./data +Verified against `tide/.env.example` and `tide/src`. -# Optional HTTP basic auth -TIDE_AUTH_USERNAME=admin -TIDE_AUTH_PASSWORD=change-me - -# Optional Jellyfin sign-in. The URL can also be set from the UI. -TIDE_JELLYFIN_URL=https://jellyfin.example.com -TIDE_REQUIRE_LOGIN=true -``` +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `TIDE_DOWNLOADS_DIR` | No | `./data/downloads` | Where finished downloads land | +| `TORRENT_DOWNLOADS_DIR` | No | — | Legacy alias, still honoured when `TIDE_DOWNLOADS_DIR` is unset | +| `TIDE_DATA_DIR` | No | `./data` | Where `tide.sqlite` lives | +| `TIDE_AUTH_USERNAME` | No | — | HTTP basic auth. Both halves must be set | +| `TIDE_AUTH_PASSWORD` | No | — | HTTP basic auth | +| `TIDE_JELLYFIN_URL` | No | — | Jellyfin server used purely as an identity provider | +| `TIDE_REQUIRE_LOGIN` | No | stored setting | Forces the sign-in requirement on or off, overriding SQLite | +| `TIDE_MEMORY_LIMIT_MB` | No | cgroup cap | Overrides the detected container memory limit | +| `TIDE_MEMORY_PAUSE_MB` | No | — | Pause torrents above this RSS | +| `TIDE_MEMORY_RESUME_MB` | No | — | Resume only once RSS falls below this | +| `TIDE_MEMORY_CHECK_INTERVAL_MS` | No | `5000` | How often the guard re-checks memory | +| `CORAL_SERVICE_TOKEN` | No | — | Grants another module full access without issuing a token in the UI | +| `HOST` | No | `0.0.0.0` | Interface the server binds to | +| `PORT` | No | `3000` | Port the server listens on | + +Tide serves on port `3000` and exposes `/healthz`. ### Storage @@ -97,6 +111,32 @@ Tide stores persistent state in SQLite at `./data/tide.sqlite` by default. That Downloaded content goes to `TIDE_DOWNLOADS_DIR`. If that variable is missing, Tide falls back to `./data/downloads`. +### Memory safety + +Tide runs an RSS-based memory guard over torrent activity. If it can read the +container memory cap from cgroups, the guard enables itself; when RSS crosses +the pause threshold it pauses active torrents and disconnects peers, and +activity resumes only once RSS falls back below the lower resume threshold. + +For an 8 GB container limit on a NAS, a reasonable starting point: + +```bash +TIDE_MEMORY_LIMIT_MB=8192 +TIDE_MEMORY_PAUSE_MB=7168 +TIDE_MEMORY_RESUME_MB=6144 +``` + +Without a `mem_limit` on the container there is nothing for Tide to read from +cgroups and nothing to pause against, so set one — or set +`TIDE_MEMORY_LIMIT_MB` explicitly. + +## Cross-module access + +Tide publishes a Coral module manifest at `/api/coral/manifest` and implements +the `downloads.list` and `downloads.events` capabilities, which is how +[Librarian](/modules/librarian/) learns that a download has finished. See +[Module contracts](/getting-started/module-contracts/). + ## Access Control Tide has two independent layers. Both are optional, and both are off by default. @@ -177,6 +217,10 @@ docker run -p 3000:3000 \ getcoral/tide:latest ``` -## Repository +## Related -[Get-Coral/tide on GitHub](https://github.com/Get-Coral/tide) +- [Running a stack with Docker Compose](/getting-started/docker-compose/) — Tide feeding Librarian, with the mount layout worked out +- [Librarian](/modules/librarian/) — imports what Tide finishes +- [Module contracts](/getting-started/module-contracts/) — Tide is the worked example +- [Tide vs qBittorrent](https://getcoral.dev/compare/tide-vs-qbittorrent) — an honest comparison +- [Get-Coral/tide on GitHub](https://github.com/Get-Coral/tide) From 7c9ea6fd91a4f5f151c836399b27f54c29d50bbe Mon Sep 17 00:00:00 2001 From: ElianCodes Date: Sat, 26 Sep 2026 22:22:47 +0200 Subject: [PATCH 2/3] fix: correct the grey scale, which made card text invisible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starlight's grey scale runs light to dark — gray-1 is body text, gray-5 a border, gray-6 and gray-7 surfaces. This palette defined it backwards and extended it to gray-10, so every Starlight component that coloured text with gray-3 rendered #1a2438 on a #090f22 background: a contrast ratio of 1.23:1, against the 4.5:1 WCAG AA asks for. Unreadable. It went unnoticed because the pages only used , which the stylesheet overrides explicitly. The homepage's descriptions have no such override, so they came out invisible. Same hues, correct order, with the measured contrast recorded per step. gray-3 now sits at 8.3:1. gray-5 is 3.1:1, the WCAG 1.4.11 floor for a border. It also fixes a second-order bug: link cards took their hover background from gray-7, which was #77839a — a light grey fill on a dark card. Separately, `overflow: hidden` on tables — there to clip the rounded corners — was clipping the last column instead. The four-column environment tables made it obvious: the Purpose text ran off the edge with no scrollbar to reach it. Corners are now rounded on the header cells, and long identifiers like TIDE_MEMORY_CHECK_INTERVAL_MS can wrap rather than forcing the table wide. --- src/styles.css | 64 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/styles.css b/src/styles.css index aad1fbf..4ada08a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5,16 +5,18 @@ --sl-font-serif: 'Fraunces', serif; --sl-color-white: #f0ede8; - --sl-color-gray-1: #0d1324; - --sl-color-gray-2: #121b2e; - --sl-color-gray-3: #1a2438; - --sl-color-gray-4: #273347; - --sl-color-gray-5: #36445a; - --sl-color-gray-6: #55627a; - --sl-color-gray-7: #77839a; - --sl-color-gray-8: #a2acbf; - --sl-color-gray-9: #ced6e2; - --sl-color-gray-10: #e8edf5; + /* Starlight's scale runs light to dark: gray-1 is body text, gray-5 is a + border, gray-6 and gray-7 are surfaces. This palette had it reversed, so + every component that styled text with gray-3 rendered it at #1a2438 on a + #090f22 background — 1.23:1, effectively invisible. Same hues, correct + order. Contrast against the background is noted per step. */ + --sl-color-gray-1: #e8edf5; /* 16.2:1 body text */ + --sl-color-gray-2: #ced6e2; /* 13.0:1 emphasised text, hover borders */ + --sl-color-gray-3: #a2acbf; /* 8.3:1 secondary text, card descriptions */ + --sl-color-gray-4: #77839a; /* 5.0:1 muted text — the AA floor */ + --sl-color-gray-5: #55627a; /* 3.1:1 borders, per WCAG 1.4.11 */ + --sl-color-gray-6: #1a2438; /* surface */ + --sl-color-gray-7: #121b2e; /* surface, hover fill */ --sl-color-accent: #2dd4bf; --sl-color-accent-light: #5ee7d9; @@ -38,16 +40,18 @@ html[data-theme='light'] { color-scheme: dark; --sl-color-white: #f0ede8; - --sl-color-gray-1: #0d1324; - --sl-color-gray-2: #121b2e; - --sl-color-gray-3: #1a2438; - --sl-color-gray-4: #273347; - --sl-color-gray-5: #36445a; - --sl-color-gray-6: #55627a; - --sl-color-gray-7: #77839a; - --sl-color-gray-8: #a2acbf; - --sl-color-gray-9: #ced6e2; - --sl-color-gray-10: #e8edf5; + /* Starlight's scale runs light to dark: gray-1 is body text, gray-5 is a + border, gray-6 and gray-7 are surfaces. This palette had it reversed, so + every component that styled text with gray-3 rendered it at #1a2438 on a + #090f22 background — 1.23:1, effectively invisible. Same hues, correct + order. Contrast against the background is noted per step. */ + --sl-color-gray-1: #e8edf5; /* 16.2:1 body text */ + --sl-color-gray-2: #ced6e2; /* 13.0:1 emphasised text, hover borders */ + --sl-color-gray-3: #a2acbf; /* 8.3:1 secondary text, card descriptions */ + --sl-color-gray-4: #77839a; /* 5.0:1 muted text — the AA floor */ + --sl-color-gray-5: #55627a; /* 3.1:1 borders, per WCAG 1.4.11 */ + --sl-color-gray-6: #1a2438; /* surface */ + --sl-color-gray-7: #121b2e; /* surface, hover fill */ --sl-color-accent: #2dd4bf; --sl-color-accent-light: #5ee7d9; --sl-color-accent-lighter: #99f6e4; @@ -297,11 +301,29 @@ table { margin: 1.25rem 0 1.75rem; border-collapse: separate !important; border-spacing: 0; - overflow: hidden; border-radius: 0.65rem; border-color: rgba(45, 212, 191, 0.2) !important; } +/* `overflow: hidden` used to live on the table, to clip its rounded corners. + It clipped the last column too, which the four-column environment tables + made obvious — the Purpose text simply ran off the edge with no scrollbar. + The corners are rounded by hand below instead. */ +table thead tr th:first-child { + border-top-left-radius: 0.65rem; +} + +table thead tr th:last-child { + border-top-right-radius: 0.65rem; +} + +/* Identifiers like TIDE_MEMORY_CHECK_INTERVAL_MS set a min-content width the + column cannot otherwise shrink below, pushing the whole table wide. */ +th, +td { + overflow-wrap: anywhere; +} + table thead { background-color: rgba(45, 212, 191, 0.1) !important; color: var(--sl-color-white) !important; From ee99110532fe577d504225dcf848bd26e4ef98b7 Mon Sep 17 00:00:00 2001 From: ElianCodes Date: Sat, 26 Sep 2026 22:32:30 +0200 Subject: [PATCH 3/3] fix: stop the sidebar repainting the current page's text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The active sidebar item rendered as near-white on the teal pill — 1.59:1, where the rule two hundred lines up asks for #07242a on teal, which is 8.71:1. `.sidebar-pane * { color: #f0ede8 !important }` was the cause. Starlight puts the link text in a inside the anchor, and a universal selector paints that span directly, so the anchor's own colour never reached it. Inheritance loses to a direct !important declaration every time. That rule, and the matching ones on the mobile table of contents, existed to force light text into panels that the inverted grey scale had made dark-on- dark. With the scale the right way round they are not needed, and they are actively harmful: `*` and `[class*="toc"]` are broad enough that the next component added to either panel would have hit the same trap. Removed the universal overrides. Sidebar links are now coloured on the anchor only, kept neutral so the current-page pill is the only accent in the rail — 13.0:1 for the links, 8.7:1 for the pill. The panels keep their opaque background, which is a real requirement: they sit over page content, and --sl-color-bg-sidebar is deliberately translucent for the desktop rail. Verified in a browser: current page, inactive links, and the expanded mobile table of contents. --- src/styles.css | 63 +++++++++++++------------------------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/src/styles.css b/src/styles.css index 4ada08a..e650b0c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -390,23 +390,27 @@ strong { font-weight: 600; } -/* Mobile sidebar when expanded */ +/* The expanded mobile sidebar sits over page content, so it needs an opaque + fill; --sl-color-bg-sidebar is deliberately translucent for the desktop rail. + Nothing here sets a colour: these panels used to force one on every + descendant with `*`, which painted over the current-page pill's dark text + and left it at 1.6:1 on the teal fill. Starlight's own colours are correct + now that the grey scale is the right way round. */ #starlight__sidebar, .sidebar-pane { background-color: #0a1019 !important; - color: #f0ede8 !important; -} - -.sidebar-pane * { - color: #f0ede8 !important; } -.sidebar-pane a { - color: #5ee7d9 !important; +/* Navigation, not prose: the rail stays neutral so the current-page pill is + the only accent in it. Set on the anchor, never on its descendants — the + inside is what the old `*` rule was painting over. */ +.sidebar-content a { + color: var(--sl-color-gray-2); + font-weight: 400; } -.sidebar-pane a:hover { - color: #f0ede8 !important; +.sidebar-content a:hover { + color: var(--sl-color-white); } @@ -425,11 +429,10 @@ strong { } #starlight__on-this-page--mobile, -#starlight__on-this-page--mobile *, #starlight__on-this-page--mobile .toggle { color: #5ee7d9 !important; - fill: #5ee7d9 !important; - stroke: #5ee7d9 !important; + fill: currentColor; + stroke: currentColor; background-color: transparent !important; } @@ -442,19 +445,6 @@ strong { background-color: #0a1019 !important; } -/* All content inside the mobile toc details element */ -#starlight__mobile-toc > * { - background-color: #0a1019 !important; - color: #f0ede8 !important; -} - -#starlight__mobile-toc ul, -#starlight__mobile-toc ol, -#starlight__mobile-toc li { - background-color: #0a1019 !important; - color: #f0ede8 !important; -} - #starlight__mobile-toc a { color: #5ee7d9 !important; } @@ -468,27 +458,6 @@ summary { background-color: transparent !important; } -/* Force dark background on all toc/toc-list content */ -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) { - background-color: #0a1019 !important; - color: #f0ede8 !important; -} - -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) a, -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) li, -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) ul, -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) ol { - background-color: #0a1019 !important; - color: #f0ede8 !important; -} - -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) a { - color: #5ee7d9 !important; -} - -:is(.toc-list, [class*="toc"], .mobile-starlight-toc, .toc) a:hover { - color: #f0ede8 !important; -} button[aria-label="Menu"] { color: #f0ede8 !important; fill: #f0ede8 !important;