@curator/web
Browser UI over @curator/api: Vite, React 19, Mantine, TanStack Router and
TanStack Query, laid out like ats/apps/web.
Running
pnpm --filter @curator/api dev # the API on :14100
pnpm --filter @curator/web dev # this app on :14102
Vite proxies /api to the API and strips the prefix, so the browser stays same-origin. A
production deployment needs the same: the API reachable at /api on the app's origin.
Pages
| Path | Does |
|---|---|
/ | Opens the TV this browser last looked at, else the first one, else /tvs |
/tvs | The stored TVs (rename, remove), and adding one: search the network or type its address, then pair |
/tvs/$tvId | Now showing: the artwork on screen, and an art-mode switch |
/tvs/$tvId/library | The TV's art by category (?category=) as a thumbnail grid, or a list (?view=list), with Show on TV, Change matte and Delete |
/tvs/$tvId/upload | Drop a JPEG or PNG (up to 20MB), pick its mattes, and optionally show it at once |
/tvs/$tvId/status | The TV's address, model, power state and art API version |
The header's TV picker switches TV and stays on the same page. A link to a TV that has been removed
says so and points back to /tvs.
The TV doesn't confirm most writes, so pages read its state back afterwards rather than trusting
the request. Delete is offered only for uploads (anything in My Photos, or with an upload's MY-F/MY_F id). MY-C0008 is shown as Other: the reference calls it the Art Store, but a 2025 Frame lists uploads there too. Each category's TV id shows on hover. Mattes are a style and a colour from
the lists the reference library documents; a matte that doesn't fit the image is refused by the TV.
Thumbnails load lazily from the API, which batches and caches them; a piece the TV has no thumbnail for shows a placeholder.
Clicking a thumbnail (or a content id in the list) opens the piece in a viewer: the image, its
categories, mattes, original size and date, and its actions. ← and → step through the current
filter and Esc closes it; the open piece is in the URL (?piece=), so Back closes it too. The TV
only gives thumbnails (384×216 on a 2025 Frame), so the viewer shows them at up to twice that.
Layout
src/
main.tsx providers and the router
api/ api.client.ts (typed fetch: `api` for TVs, `tvApi(tvId)` for one TV's art), query options and keys
tvs/ the TV a page is about (`useTv()`), and the TV this browser last looked at
routes/ TanStack file routes; routeTree.gen.ts is generated by the Vite plugin and committed
components/ pages and pieces, one folder per area
tests/
fake.api.ts stubs fetch with a table of "METHOD /path" responders
test.render.tsx renders the real route tree at a path under the app's providers
TV failures surface as the API reports them (src/api/api.error.ts): unreachable, didn't answer,
refused, or rejected.