@knkcs/mediahub-ui
v0.7.0
Published
Reusable mediahub asset-management UI surfaces (AssetPicker, AssetLibrary) for host apps
Readme
@knkcs/mediahub-ui
Mediahub's reusable UI, published as a versioned npm package so that every host renders the same media surfaces instead of re-implementing them.
Two hosts consume it today: knkCMS core, which embeds it as a build-time dependency, and mediahub's own standalone app, which consumes it through the workspace.
Screens, not routes
A screen takes typed paths and emits callbacks; the host owns routing and draws the page frame. No screen builds a URL, and no host builds UI a screen already owns.
That contract is the point of the package. When a host re-implements a surface instead of composing it, the two copies diverge and bugs are fixed in one of them — see knkCS/mediahub#138, which consolidated the asset-type screens after four such bugs, and mediahub ADR 0008, which removed a host-built browse switcher for the same reason.
What it exports
| Surface | What it is |
|---|---|
| AssetLibrary | The media browser: folder tree, filters, upload, and the context rail carrying Tags and Collections |
| AssetDetail | One asset, its versions and its metadata |
| AssetPicker, CollectionPicker | Embeddable pickers for a host's own forms |
| AssetTypeIndex, AssetTypeCreate, AssetTypeDetail | Asset-type administration, including the AI-processes tab |
| CollectionIndex | The Collections the user can see, marking the Restricted ones. Listing is not gated by authority — per ADR 0001 seeing a Collection and managing it are separate privileges. Each row offers a way into that Collection's contents, pointing at the host's links.collectionHref — a host that registers none renders no such control at all |
| CollectionCreate | Create a Collection from a name, slug and description. Static only — a smart Collection's rules are stored and never evaluated, so no type selector and no rules editor appear (#173) |
| CollectionDetail | One Collection's identity — name, description, delete — and its access control: the Restricted toggle (with the leak-guard refusal naming what it would expose) and the roster, offered only where the can-manage probe says the reader is an authority (ADR 0001: owner-on-the-Collection or admin-on-its-workspace, and grantees never see the roster). Also the way into the Collection's contents, via the host's links.collectionHref, offered to everyone who can see the Collection and absent where the host registers no such route — looking is neither management nor Curation. Not Curation: adding and removing assets happens in the library, where the assets are |
| MediahubProvider | The provider a host mounts once, holding the clients and query client |
| Clients | createConnectMediahubClients adapts per-service Connect clients into the domain clients the screens' hooks read, and owns the mapping — a host supplies transport only |
| Hooks and stores | The queries and mutations behind the screens, for a host composing its own view |
| filtersFromSearchParams, searchParamsFromFilters | How a filter set is written into a URL, so a link means the same thing in every host (ADR 0009). Not a route: a host passes the parameters it holds and decides whether to sync at all |
Everything lands at the package root (.). A second entry point, ./testing, ships a stub client
and fixtures so a host can test its own composition without a server.
The URL encoding is the one export whose names are a contract rather than a type: folderId,
tags, collectionId, search, statusSlug, behavior are spelled in every link anyone has
shared, and renaming one would compile. ADR 0009 accepts that cost deliberately; the names are
pinned by test so a rename fails the suite.
Narrowing composes
Folder, Collection and Tag narrow the library together — one folder, many collections, many tags.
They are not alternative modes, and a UI that makes them mutually exclusive cannot express a question
the data model permits. AssetLibrary accounts for what is applied with a row of dismissible filter
chips. See ADR 0008 and the Narrowing entry in CONTEXT.md.
Layout comes from anker
Screens use anker's templates and primitives — IndexPageTemplate, DetailPageTemplate,
SubNavLayout, ContextRail — and report their frame through anker's host contract. anker's own
guidance is explicit: if a template does not fit, file an issue rather than hand-rolling a layout.
Peers
Every non-trivial dependency is a peer the host supplies once, so a host and the package can
never load two copies of React, the query client or anker. The Rollup external list is derived from
peerDependencies, so declaring a peer is the whole change.
Releases
Versioned with release-please from conventional commits, and published to npm by CI. A commit whose
subject loses its feat:/fix: prefix — which a squash merge can do — produces no release at all,
silently (knkCS/mediahub#185).
