# `PhoenixKitPhotos`
[🔗](https://github.com/BeamLabEU/phoenix_kit_photos/blob/v0.3.0/lib/phoenix_kit_photos.ex#L1)

A photo and video library for PhoenixKit, in the spirit of Apple Photos and
Google Photos.

The timeline is the first view and the spine of the product: albums, people,
map and memories are scopes and views over the same date-ordered, virtualized
engine. See `dev_docs/plans/2026-09-21-phoenix-kit-photos.md` for why this is
a separate package rather than a MediaBrowser view mode in core.

This is the PhoenixKit module entry point: `PhoenixKit.ModuleDiscovery` finds
it by scanning beam attributes of deps that depend on `:phoenix_kit`, so a
host application needs no configuration beyond the dependency itself.

## What this module is, and is not

Three media widgets exist and must not be conflated:

  * `PhoenixKitWeb.Components.MediaBrowser` — file work: folders, ingest, search.
  * `PhoenixKitWeb.Components.MediaGallery` — picking and ordering a set of
    images for a form.
  * `PhoenixKitPhotos.Components.PhotoTimeline` — a person's library
    ordered by capture date, virtualized and scrubbable.

## Status

Stage 1 reads one storage library, ordered by capture date. The scope is
`{:library, uuid}`. Capture date and the library partition live in
PhoenixKit core (V200 and V202); this package does not migrate either.

# `api_tools`

The tools agents get through the machine API: see `PhoenixKitPhotos.API`.

# `css_sources`

Tailwind source roots, so classes used only in this package's HEEx survive
the host's CSS build.

Without this the `:phoenix_kit_css_sources` compiler warns that a
phoenix_kit-dependent dep contributes zero `@source` lines, and any
responsive or variant utility used only here is silently missing from the
host bundle.

# `js_sources`

The prebuilt hook bundle, built by `mix assets.build` into this app's `priv/`.

`js_sources/0` does not compile anything — it declares a bundle that already
exists, which is why building it is a Stage 1 deliverable and not a packaging
afterthought.

# `permission_metadata`

Permissions, specified before the first route ships rather than after.

An owner sees their own library; `photos.view_any` additionally
allows viewing another user's. Nobody else sees anything: a signed thumbnail
URL is not authorization (`URLSigner` tokens are 4 hex characters and never
expire), so the window API is what enforces scope.

# `reserved_route_prefixes`

Claim `photos` so no other module's routes take it.

# `user_dashboard_tabs`

The package's default page, at `/photos`.

A `user_dashboard_tabs/0` entry carrying a `live_view` becomes a real route
in PhoenixKit's authenticated surface, which is emitted **twice** — once at
the root and once locale-scoped — so this one callback yields both `/photos`
and `/:locale/photos`, permission-gated.

That is also why there is no `route_module/0` here: it would emit a third,
duplicate `/photos` and the host router would warn that the clause can never
match. The one trade is that this page depends on the user dashboard being
enabled in the host.

The `path` is absolute on purpose — a relative one resolves under
`/dashboard/`.

A host wanting its own branded page declares `/photos` before
`phoenix_kit_routes()`; first match wins, so the host shadows the root shape
without this package knowing about it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
