Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions ui/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ui/consumer and ui/provider — portal plugin conventions

Both plugins here (`ui/consumer` for cloud-portal, `ui/provider` for
staff-portal) are Module Federation remotes rendered directly into a host
portal's DOM at runtime. Two things follow from that which aren't obvious
from reading either plugin's own source in isolation:

## Prefer datum-ui components over hand-rolled ones

Before writing a custom component (tabs, tables, empty states, code/YAML
viewers, stat rows, etc.), check `datum-ui/packages/datum-ui/src/components/`
(base/ and features/) for an existing one — `tabs`, `table`,
`empty-content` (the real "coming soon"/empty-state component), and
`code-editor` (read-only YAML/JSON display) all already exist there and
should be used instead of reimplementing the same thing with plain divs.

Only import `@datum-cloud/datum-ui/<subpath>` names that the target host's
`federation-host.ts` actually lists in its shared config — cloud-portal and
staff-portal each curate their own subset, and it differs between them.
Anything outside that list still works, it just bundles its own copy in the
plugin's remote instead of sharing the host's singleton instance.

## Neither plugin has a Tailwind build of its own

There's no `tailwind.config`/`postcss.config`/`tailwindcss` package in either
`ui/consumer` or `ui/provider`, and no CSS file of their own — a plugin's
JSX renders into the *host's* DOM, so a Tailwind class only takes visual
effect if that exact class string already happens to exist in the host's own
compiled CSS. Common/simple utilities (`flex`, `grid`, `gap-3`, `grid-cols-2`,
etc.) usually coincidentally exist there because most real apps use them
somewhere. **Arbitrary-value classes almost never do** —
`grid-cols-[minmax(0,1fr)_90px]`, `min-w-[200px]`, `tracking-[0.03em]`,
`max-h-[600px]`, etc. — and they fail *silently*: no error, the class is just
absent from the host's stylesheet, so the layout collapses or the style is
simply missing.

**Use datum-ui components first** (their own classes ship in datum-ui's
compiled CSS, so they're guaranteed to work). If a genuinely custom layout is
unavoidable, use an inline `style={{...}}` for anything beyond a handful of
extremely common utility classes — don't reach for a bracketed arbitrary
value.
82 changes: 48 additions & 34 deletions ui/provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,65 @@

Compute-authored UI meant for a host other than cloud-portal — as opposed to
[`ui/consumer`](../consumer), which holds compute's own cloud-portal-facing
plugin(s). This directory *is* the plugin (no further nesting): it declares a
`portal.resource/platform` extension so `staff-portal`'s
`/customers/resources` page can list compute Workloads (`compute.datumapis.com`)
across every project as a Type filter option, alongside its native AI
Edge/DNS/Domain types — shipped as a **Module Federation remote** loaded by
`staff-portal`'s plugin host (`app/modules/plugins/` there, ported from
cloud-portal's
[Portal Plugin System](https://github.com/datum-cloud/cloud-portal/blob/main/docs/enhancements/portal-plugin-system.md)).

## No page, no component — manifest only

Unlike a typical portal plugin (and unlike this repo's own
`compute/ui/consumer`, the per-project consumer-facing version of
this same data), this plugin exposes nothing at all. `portal.resource/platform`
is a data-only extension: it declares a label, an icon name, and the
`search.miloapis.com` target GVK (`{group, version, kind}`), and staff-portal
runs the search itself — with the *viewing staff user's own* credentials —
and renders the rows in its own trusted table. See
`staff-portal/app/modules/plugins/types.ts`'s `ResourcePlatformExtension` for
the full design and its trust-boundary reasoning, and
`staff-portal/app/routes/customer/resource/index.tsx` for where it's consumed.

`public/plugin-manifest.json` is the entire plugin. `exposedModules` is `{}`
and there's no `src/` — nothing here executes at runtime. `vite.config.ts`
still runs a full Module Federation build (a valid, empty remote) since the
host's plugin registry pipeline expects a working `remoteEntry.js` to exist,
even though it's never actually fetched unless this plugin grows a page.
plugin(s). This directory *is* the plugin (no further nesting): a Module
Federation remote loaded by `staff-portal`'s plugin host
(`app/modules/plugins/` there, ported from cloud-portal's
[Portal Plugin System](https://github.com/datum-cloud/cloud-portal/blob/main/docs/enhancements/portal-plugin-system.md)),
declaring three extensions:

- **`portal.resource/platform`** — a data-only extension: label, icon, and the
`search.miloapis.com` target GVK for compute Workloads
(`compute.datumapis.com`). staff-portal runs the search itself — with the
*viewing staff user's own* credentials — and lists Workloads as a Type
filter option on `/customers/resources`, across every project. See
`staff-portal/app/modules/plugins/types.ts`'s `ResourcePlatformExtension`
for the full design and its trust-boundary reasoning.
- **`portal.page/project`** (`WorkloadList`, `src/pages/workload-list.tsx`,
path `""` — the mount's index) — every Workload in one project, linking
into `WorkloadDetail` below. Reached from staff-portal's own project detail
nav (a native "Compute › Workloads" tab pointing at the plugin mount).
- **`portal.page/project`** (`WorkloadDetail`, `src/pages/workload-detail.tsx`,
path `:workloadName`) — the actual support view for a single Workload,
reached either from `WorkloadList` or by clicking a Workload row on
`/customers/resources`.

Both pages are mounted under
`/customers/projects/:projectName/plugins/<slug>/…` by staff-portal's
project-scoped plugin mount — `projectName` reaches them via `useParams()`
resolving the ancestor route match (shared react-router singleton, no extra
plumbing), and `:workloadName` (on the detail page only) from that
extension's own declared `path`.

## The support view

Built for a staff member fielding "why isn't my workload starting" / "what's
wrong with this workload" from a customer, not for general browsing —
Overview and Instances surface raw conditions (type/status/reason/message),
placements, network assignments, and scheduling gates, not just a coarse
health enum. Events/Logs/Metrics are honest "Coming Soon" placeholders (no
data source wired up for any of the three yet); YAML dumps the raw resource
(minus `metadata.managedFields`) as an escape hatch. All data is read client-side, polled via
`refetchInterval` (`src/lib/api.ts`) through staff-portal's own same-origin
proxy — no new credential, no plugin-owned backend.

## Local dev

```
bun install
bun run build
bun run preview # built dist/ served at :5199 — see below for why not `dev`
bun run preview # built dist/ served at :5199
bun run dev # standalone preview harness at :5199, direct (no proxy)
```

### Serve `dev` or `preview`?

staff-portal loads plugin assets through its **same-origin asset proxy**
(`/api/plugins/workloads/…`), never directly from `:5199`. `dev` (Vite, HMR)
emits a remote entry with host-absolute chunk URLs that 404 once proxied;
`preview` (built) is proxy-relative and safe. Since this plugin has no page to
preview standalone anyway, always use `build && preview` — same rule as the
sibling `compute/ui/consumer` plugin, see its README for the full
explanation.
`preview` (built) is proxy-relative and safe. Use `dev` only for the
standalone harness at `http://localhost:5199/` (direct, no proxy — data
calls 404 there since there's no staff-portal proxy to reach); use
`build && preview` whenever staff-portal will load the plugin.

To register it with a local staff-portal:

Expand All @@ -59,5 +73,5 @@ bun run build && bun run preview
PORTAL_PLUGINS=workloads=http://localhost:5199
```

then load `/customers/resources` in staff-portal and check "Workload" appears
in the Type filter.
then load `/customers/resources` in staff-portal, check "Workload" appears in
the Type filter, and click a Workload row to reach the support view.
Loading
Loading