PhoenixKitPhotos (PhoenixKit Photos v0.3.0)

Copy Markdown View Source

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:

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.

Summary

Functions

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

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

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

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

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

The package's default page, at /photos.

Functions

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.