Ctrl K
ComunidadGuías…

CLAUDE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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.