feat(app): spec-driven, multidomain admin UI (#161) #162

Closed
nabil_salah wants to merge 4 commits from feat/dioxus-spec-driven-multidomain-ui into integration
Member

Summary

Migrates the Dioxus admin UI (hero_aibroker_app) to the post-oschema
multidomain server contract, per the hero_skills (web/hero_admin_ui.md,
web/dioxus_bootstrap.md, blueprint/server/hero_server_implemenation.md).

It replaces the old hard-wired design — single /rpc, dotted method names,
hand-written static API docs — with a spec-driven UI that reads OpenRPC specs
at runtime, with all server-contract knowledge isolated in one module.

Part of #161 (Dioxus side).

What's included

  • Contract layer (rpc.rs contract) — single source of truth: Domain
    enum, /api/{domain}/rpc + /api/{domain}/openrpc.json + /api/domains.json
    paths, domain_for() method→domain map, wire_name(). rpc() routes through
    it; adds rpc_in() / get_json() / fetch_spec() / fetch_domains().
  • OpenRPC parser (openrpc.rs) — methods/params/result, $ref resolution,
    ParamKind + type labels. Pure serde.
  • Shared spec loader (specs.rs) — one flattened MethodEntry model + a
    use_specs() hook, used by both spec-driven tabs.
  • API Docs tab — rewritten spec-driven; removes the static MethodDoc
    consts, renders methods/params/returns from the fetched specs, and warns when
    a domain's spec fails to load.
  • Playground tab (new) — pick a method, spec-driven param form by
    ParamKind, dispatch via rpc_in on the method's own domain.
  • Navbar — dead /openrpc.json link → discovery Domains link.
  • Dev launcher — gated demo feature + src/bin/standalone.rs + a
    Dioxus.toml /api proxy for local dx serve. No effect on default builds.

Design decisions

  • No legacy fallback — the UI targets the post-migration server only
    (deliberate; it won't talk to the current server until the server PR lands).
  • Models read/write split encoded in domain_for() (reads → models;
    writes + control-plane → admin), derived from the existing specs.

Verification

  • 12 unit tests pass; host and wasm32-unknown-unknown builds clean.
  • Ran in a browser via dx serve + a local mock of the multidomain contract:
    Docs and Playground render from the fetched specs, and the failed-domain
    banner appears for a domain whose spec 500s.
  • A high-effort code review ran; its fixes are in the second commit (shared
    loader, get_json non-2xx handling, failed-domain banner, filtered-selection
    fix, contract::rpc_path).

Deferred — depends on the server oschema/multidomain PR (TODO(#161))

These are determined by the generated specs, not by us, so they're handled
when those land (the spec-driven tabs already auto-adapt to whatever names/domains
the specs contain):

  • Exact wire method names, the final domain split, and DTO/response shapes —
    reconcile wire_name() / domain_for() / the remaining hardcoded call sites
    then (mostly by making more of the app spec-driven).
  • /api/domains.json discovery (currently enumerates contract::Domain::ALL).
  • Real end-to-end verification against the live server (the mock proves
    plumbing/rendering, not naming/dispatch).

Not done (optional polish)

Concurrent spec fetch (join_all), dropping per-render clones, dead-code
cleanup, and deriving domain_for from discovery — cosmetic, left for the
merge-cleanup pass.

## Summary Migrates the Dioxus admin UI (`hero_aibroker_app`) to the post-oschema **multidomain** server contract, per the hero_skills (`web/hero_admin_ui.md`, `web/dioxus_bootstrap.md`, `blueprint/server/hero_server_implemenation.md`). It replaces the old hard-wired design — single `/rpc`, dotted method names, hand-written static API docs — with a **spec-driven** UI that reads OpenRPC specs at runtime, with all server-contract knowledge isolated in one module. Part of #161 (Dioxus side). ## What's included - **Contract layer** (`rpc.rs` `contract`) — single source of truth: `Domain` enum, `/api/{domain}/rpc` + `/api/{domain}/openrpc.json` + `/api/domains.json` paths, `domain_for()` method→domain map, `wire_name()`. `rpc()` routes through it; adds `rpc_in()` / `get_json()` / `fetch_spec()` / `fetch_domains()`. - **OpenRPC parser** (`openrpc.rs`) — methods/params/result, `$ref` resolution, `ParamKind` + type labels. Pure serde. - **Shared spec loader** (`specs.rs`) — one flattened `MethodEntry` model + a `use_specs()` hook, used by both spec-driven tabs. - **API Docs tab** — rewritten spec-driven; removes the static `MethodDoc` consts, renders methods/params/returns from the fetched specs, and warns when a domain's spec fails to load. - **Playground tab** (new) — pick a method, spec-driven param form by `ParamKind`, dispatch via `rpc_in` on the method's own domain. - **Navbar** — dead `/openrpc.json` link → discovery `Domains` link. - **Dev launcher** — gated `demo` feature + `src/bin/standalone.rs` + a `Dioxus.toml` `/api` proxy for local `dx serve`. No effect on default builds. ## Design decisions - **No legacy fallback** — the UI targets the post-migration server only (deliberate; it won't talk to the current server until the server PR lands). - **Models read/write split** encoded in `domain_for()` (reads → `models`; writes + control-plane → `admin`), derived from the existing specs. ## Verification - 12 unit tests pass; host **and** `wasm32-unknown-unknown` builds clean. - Ran in a browser via `dx serve` + a local mock of the multidomain contract: Docs and Playground render from the fetched specs, and the failed-domain banner appears for a domain whose spec 500s. - A high-effort code review ran; its fixes are in the second commit (shared loader, `get_json` non-2xx handling, failed-domain banner, filtered-selection fix, `contract::rpc_path`). ## Deferred — depends on the server oschema/multidomain PR (TODO(#161)) These are determined by the *generated* specs, not by us, so they're handled when those land (the spec-driven tabs already auto-adapt to whatever names/domains the specs contain): - Exact wire method names, the final domain split, and DTO/response shapes — reconcile `wire_name()` / `domain_for()` / the remaining hardcoded call sites then (mostly by making more of the app spec-driven). - `/api/domains.json` discovery (currently enumerates `contract::Domain::ALL`). - Real end-to-end verification against the live server (the mock proves plumbing/rendering, not naming/dispatch). ## Not done (optional polish) Concurrent spec fetch (`join_all`), dropping per-render clones, dead-code cleanup, and deriving `domain_for` from discovery — cosmetic, left for the merge-cleanup pass.
Migrate the Dioxus admin UI to the multidomain Hero server contract.

- openrpc.rs: OpenRPC spec parser (methods/params/result, $ref resolution,
  ParamKind + type labels). Pure serde, reusable.
- rpc.rs: `contract` module as the single source of truth — Domain enum,
  domain_for() method->domain map (model reads -> models; writes + all
  control-plane -> admin), wire_name() dotted->underscore, and
  /api/{domain}/rpc + per-domain spec + discovery paths. rpc() routes via the
  contract; adds rpc_in/get_json/fetch_spec/fetch_domains. No legacy fallback.
- api_docs_tab.rs: rewritten spec-driven; removes static MethodDoc consts,
  fetches per-domain specs and renders methods/params/return types.
- playground_tab.rs: new tab — pick a method, spec-driven param form by
  ParamKind, dispatch via rpc_in on the method's own domain.
- navbar: dead /openrpc.json link -> discovery "Domains" link.

Dev/verification aids (gated; no effect on default builds):
- `demo` feature + src/bin/standalone.rs (dx serve launcher)
- Dioxus.toml /api proxy + scripts/mock_multidomain.py (serves the existing
  specs at the new paths). Verified in-browser via dx serve + headless Chrome:
  Docs renders 99 methods across domains end-to-end.

12 unit tests pass; compiles host + wasm32. TODO(#161): confirm exact endpoint
paths, the naming rule, and the domain split with the oschema/server owner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses code-review findings on the spec-driven UI:

- specs.rs: new shared module — one flattened MethodEntry/ParamEntry model,
  a `flatten` helper, and a `use_specs()` hook. api_docs_tab and playground_tab
  now go through it, removing the duplicated load loop, filter, card chrome, and
  the parallel view/spec structs (#3).
- get_json: reject non-2xx responses. Previously a JSON error body (e.g. 500
  {"error":...}) parsed into a default OpenRpcDoc and read as success with zero
  methods, so a failed domain vanished silently. Now surfaced (#1).
- SpecLoad.failed + a warning banner in both tabs: names domains whose spec
  failed to load so a partial list isn't mistaken for complete (#1).
- playground: hide the request form when the selected method is filtered out of
  the picker, so no stale/sendable form remains (#2); use contract::rpc_path for
  the displayed POST path instead of a hand-built string (#4).
- playground: extract ParamInput, collapsing the four repeated input-write
  closures into one path.
- untrack the local-only dev mock (scripts/mock_multidomain.py) and gitignore it,
  plus request_logs.db.

12 unit tests pass; host + wasm32 build clean. Verified in-browser via dx serve:
Docs/Playground render and the failed-domain banner appears for a 500ing domain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per skills/web/dioxus.md ("never use raw Bootstrap classes; use the typed RSX
components"), migrate the API Docs and Playground tabs off raw Bootstrap
component classes onto typed components: Card, Alert, Badge, Table,
ListGroup/ListGroupItem, and Input/Select/Textarea/FormText. ListGroupItem's
`active` prop + auto-button-on-onclick replaces the hand-rolled active class.

Plain utility classes (text-muted, small, d-flex, gap-*) and non-component
elements (code, pre, label) are left as-is. Like-for-like markup — verified
in-browser (dx serve) that both tabs render identically.

12 unit tests pass; host + wasm32 build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thabeta closed this pull request 2026-06-23 19:04:15 +00:00
Owner

this should be against development branch not integration, integration / main live indepdently

this should be against development branch not integration, integration / main live indepdently

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_aibroker!162
No description provided.