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) diff --git a/src/styles.css b/src/styles.css index aad1fbf..e650b0c 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; @@ -368,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; +/* 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 { - color: #5ee7d9 !important; -} - -.sidebar-pane a:hover { - color: #f0ede8 !important; +.sidebar-content a:hover { + color: var(--sl-color-white); } @@ -403,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; } @@ -420,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; } @@ -446,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;