CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Ambry is a self-hosted audiobook library server built with Elixir/Phoenix. Audiobooks arrive through the inbox, are placed into a library root, and are served as the files they already are ("direct play") to the browser and the mobile app. The mobile app code is in a separate repository.
## Development Commands
```bash
# Setup (first time)
mix deps.get && mix npm_deps.get
mix ecto.setup # Creates and migrates database
mix seed # Populate with example data
# Run development server
iex -S mix phx.server # Runs at http://localhost:4000
# Testing
mix test # Run all tests
mix test path/to/test.exs # Run single test file
mix test path/to/test.exs:42 # Run specific test at line
# Code quality
mix format # Format code (uses Styler, TailwindFormatter, Phoenix.LiveView.HTMLFormatter)
mix check # Runs format check, compile warnings, credo, dialyzer
mix credo # Linter
mix dialyzer # Static analysis
# Database migrations
mix ecto.gen.migration migration_name # Generate a new migration file
mix ecto.migrate # Run pending migrations
mix ecto.rollback # Rollback last migration
```
## Important Conventions
### Comments and documentation
**When in doubt, delete it.** The code says what it does. Prose is for what
the code cannot say, and nothing else.
- **No comment that narrates the line under it.** If the comment restates the
code, the code was already clear enough.
- **Moduledocs and `@doc`s are brief.** A sentence or two saying what the
thing is for. Not an essay, not a design rationale, not a tour of the
alternatives.
- **Keep only:** a non-obvious invariant, a gotcha that will bite the next
person, or the reason the obvious implementation is wrong. One or two
sentences each.
- **No history.** Never "this used to be X", "previously", "no longer", "the
old behaviour", version numbers, PR numbers, dates, or a record of what was
tried and rejected. State the rule and the consequence that makes it right.
Git has the history.
- **No references to a particular deployment.** No "in production", no "the
operator's library", no counts or measurements taken from one instance, no
links to working notes outside the repo. Ambry is self-hosted open source:
whoever runs it is not whoever wrote it. Where a measurement is worth
keeping, state the finding generically ("roughly 96% of releases carry a
title in tags"), never its provenance.
- **Nothing rendered may encode one operator's taste.** No placeholder or
example drawn from what somebody happened to be doing; a placeholder either
teaches the _format_ of a field or is absent. No feature that grades one
valid arrangement as better than another.
The admin's own visual rules live in `docs/admin-design-language.md`, which
follows the same standard: read it before touching an admin surface.
### Creating Migrations
Always use `mix ecto.gen.migration` to create new migration files - never manually create migration files or generate timestamps. The mix task handles timestamp generation and proper file placement automatically.
## Architecture
### Boundary-Based Module Structure
The codebase uses the [Boundary](https://github.com/sasa1977/boundary) library to enforce module dependencies. Each top-level module under `lib/` defines its allowed dependencies and exports:
- **Ambry** - Core business logic contexts (accounts, books, media, people, search)
- **AmbryApp** - OTP application supervision tree
- **AmbryWeb** - Phoenix web layer (controllers, LiveViews, components)
- **AmbrySchema** - GraphQL API (Absinthe schema, resolvers)
- **AmbryScraping** - API clients for external metadata services (Audible catalog, Audnexus), wrapped by the `Ambry.Metadata` provider layer
### Core Contexts (lib/ambry/)
Each context manages a domain with Ecto schemas, queries, and business logic:
- `Accounts` - User authentication, sessions, admin management
- `Books` - Books, Series, SeriesBook associations
- `People` - Person (authors/narrators), Author, Narrator entities
- `Media` - Audiobook media files, tracks, player state, bookmarks
- `Search` - Full-text search with PostgreSQL trigrams
- `PubSub` - Event broadcasting via Phoenix.PubSub + Oban for async
### Audio: direct play, and the transcoded recordings beside it
Ambry does not transcode. `Ambry.Media.Scanner` probes a file with ffprobe
and the importer writes `media_tracks`; clients are served those files
directly.
Some recordings are instead served from packaged artifacts — `mp4_path` /
`mpd_path` / `hls_path` — which the server serves and deletes but cannot
produce. A recording is one of these exactly when it has no tracks, and its
`source_path` / `source_files` state what its transcode consumed. An
imported recording has neither.
### Flat Views Pattern
Several contexts use "Flat" view schemas (e.g., `BookFlat`, `PersonFlat`, `MediaFlat`) - these are PostgreSQL views that denormalize data for efficient listing/filtering queries.
### Web Layer (lib/ambry_web/)
- Uses Phoenix LiveView extensively
- Admin interface under `live/admin/`
- Components in `components/` including `core_components.ex`
- GraphQL endpoint at `/gql` using Absinthe
### GraphQL API (lib/ambry_schema/)
Relay-compatible GraphQL schema for mobile app. Uses Dataloader for batching.
## Key Dependencies
- Phoenix 1.8 with LiveView
- Ecto with PostgreSQL
- Absinthe (GraphQL)
- Oban (background jobs)
- Boundary (module dependency enforcement)
- Image/Vix (image processing via libvips)
## External Requirements
- PostgreSQL database
- FFmpeg (ffprobe, for reading durations, tags and chapters)
## Tidewave MCP Server
When the dev server is running (`iex -S mix phx.server`), the Tidewave MCP
server provides direct access to the running application.
**If the `mcp__tidewave__*` tools are missing, the config is almost certainly
fine.** The MCP client connects once, at editor startup, so a server that
wasn't running at that moment leaves the tools absent for the whole session —
starting it later does not make them appear. Start the dev server first, or
reconnect the `tidewave` server afterwards.
Without restarting anything, it is also a plain HTTP MCP server and can be
driven directly:
```bash
curl -s -X POST http://localhost:4000/tidewave/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"project_eval","arguments":{"code":"1 + 1"}}}'
```
Available tools:
| Tool | Description |
| ------------------------------------ | ------------------------------------------------- |
| `mcp__tidewave__get_ecto_schemas` | List all Ecto schemas in the project |
| `mcp__tidewave__get_logs` | Retrieve live server logs with optional filtering |
| `mcp__tidewave__get_source_location` | Find source file location for a module/function |
| `mcp__tidewave__get_docs` | Get documentation for modules and functions |
| `mcp__tidewave__project_eval` | Execute Elixir code in the running server context |
| `mcp__tidewave__execute_sql_query` | Run SQL queries against the database |
| `mcp__tidewave__search_package_docs` | Search Hex documentation for dependencies |
### Hot Reloading Code Changes
After editing Elixir files, recompile without restarting the server:
```elixir
# Via mcp__tidewave__project_eval
IEx.Helpers.recompile()
```
This hot-reloads code changes into the running server. Avoid using `mix compile` in a separate shell as it can interfere with the running IEx session.
### Test User Account
A test user exists for development/testing:
- **Email:** `agent@test.local`
- **Password:** `AgentTestPassword123!`
### GraphQL Testing
The best way to test GraphQL queries is directly through Absinthe using `mcp__tidewave__project_eval`, bypassing HTTP entirely:
```elixir
# Run authenticated GraphQL query directly (via mcp__tidewave__project_eval)
user = Ambry.Accounts.get_user_by_email("agent@test.local")
query = """
query {
me { email admin insertedAt }
}
"""
Absinthe.run(query, AmbrySchema, context: %{current_user: user})
```
This avoids shell escaping issues and is faster than HTTP requests.