feat(portal): master-admin portal console UI (FF-EPIC-17-S7) - #709
Merged
Conversation
Migrate the master-admin portal fleet console (@fuzefront/portal-admin-ui) off the anticipated, now-superseded @fuzefront/portal-client model onto the REAL, merged org-tree portal contract (@fuzefront/security-client 0.7.0, PR #704): GET/POST /api/v1/security/portals, GET/POST(suspend|resume) /api/v1/security/portals/{portalOrgId}. A portal is now an organizations row with parentOrgId = the platform root, not a portals-table entity, so the platform root is never listed — the fleet table, create form, detail panel, and suspend dialog all update accordingly (orgId, customDomain, real branding, appCatalogMode; no root-row guard, no domains[] table). Adds the tenant-attribute fields (custom domain, branding, app-catalog mode, reseller-billing mode) to the create-portal form per the real PortalCreate body. Wires the console behind fuzefront.platform.multi-tenant-portals via useFlag in the host (MasterAdminPortalsPage, pre-existing), and exposes that flag to the browser via WEB_EXPOSED_FLAGS/flag-registry.yaml (it wasn't web-exposed, so the gate always fell back to its OFF default — same class of gap as #697). Fixes two latent bugs in portal-admin-ui's HttpClient found while wiring the new client: the global fetch fallback wasn't bound to globalThis (breaks in a real browser with "Illegal invocation"), and HttpError.code preferred the ErrorBody's human message over its machine-readable `code` enum. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017zdYnxKQVVhFyRubDa1BmK
Contributor
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
Contributor
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
Contributor
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
izzywdev
approved these changes
Aug 26, 2026
izzywdev
left a comment
Owner
There was a problem hiding this comment.
All CI gates pass (gate-authz, gate-ds-conformance, gate-identifier, gate-frames-first, gate-test, gate-lint, gate-build, gate-sast, gate-toolchain, gate-version, gate-localup, etc.). Approving per governance policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📋 Description
Implements the master-admin portal fleet console UI for FF-EPIC-17-S7, against
design/frames/portal-admin-consoles/**(flowmaster-admin-portals) and the merged org-tree portal contract (@fuzefront/security-client0.7.0, PR #704).packages/portal-admin-ui'sMasterAdminPortalsFlowalready existed against the anticipated, now-superseded@fuzefront/portal-clientmodel (/api/v1/admin/portals, a standaloneportals-table entity). This PR migrates it onto the REAL, frozen contract: a portal is now anorganizationsrow whoseparentOrgIdis the platform root, reached viaGET/POST /api/v1/security/portalsandPOST /api/v1/security/portals/{portalOrgId}/(suspend|resume). Only the master-admin-portals flow is touched —PortalAdminConsoleFlow(portal-console / portal-billing flows, still anticipated contracts) is untouched.🔄 Type of Change
@fuzefront/security-clienttoportal-admin-ui)🧪 Testing
Results
packages/portal-admin-ui:npm run -w @fuzefront/portal-admin-ui test→ 40/40 passed (4 files:adminPortalsClient.test.ts,MasterAdminPortalsFlow.test.tsxrewritten for the new contract;portalConsoleClient.test.ts+PortalAdminConsoleFlow.test.tsxuntouched, still green — confirms no cross-flow breakage).packages/portal-admin-ui:npm run -w @fuzefront/portal-admin-ui type-check→ clean (with@fuzefront/security-client+@fuzefront/portal-clientdist built first, matching the CI ordering conventionidentity-ui/auth-uialready use).packages/portal-admin-ui:npm run -w @fuzefront/portal-admin-ui build→ clean (vite build+tsc --emitDeclarationOnly).frontend/src/__tests__/MasterAdminPortalsPage.test.tsx— covers the flag gate (OFF → no console chrome, zero fetches; ON → fetches the real contract) and the frame states (empty, error+retry, 403 FORBIDDEN in place) through the real host wiring → 5/5 passed.frontend:npm run type-check/npm run lint/npm run build→ all clean.npx vitest run→ 218/218 passed (38 files, includes the new page test).python3 scripts/gate_ds_conformance.py packages/portal-admin-ui/src→ OK, no raw design values.git diff --check→ clean (LF line endings).🔧 Implementation Details
Changes Made
packages/portal-admin-ui(frontend package, master-admin flow only):src/api/adminPortalsClient.ts— rewritten against@fuzefront/security-client's generatedcomponents['schemas'](Portal,PortalCreate,PortalPage,PortalStatus,PortalBranding,PortalBillingMode,PortalAppCatalogMode), hitting/api/v1/security/portals*via this package's ownHttpClient(same convention asidentity-ui'semployeeClient.ts—security-clientships types only, no runtime client).src/types.ts— addsAdminPortal*type aliases sourced straight from the generated contract, deliberately named distinctly from the existingPortal/PortalStatus/BillingMode(which the other, still-anticipated portal-console flow keeps using — the two models have genuinely different shapes:orgIdvsid/organizationId, a singlecustomDomainvs adomains[]list).src/flows/MasterAdminPortalsFlow.tsx,src/components/master/*— updated for the real shape. The platform root org is never returned byGET /api/v1/security/portals(per contract), so the fresh-install empty state is simply zero portals and no root-row suspend-guard is reachable through this UI — both are documented deviations from the (unapproved) frame, which was built against the superseded model.CreatePortalDialog— extended with the tenant-attribute fields the task calls for: custom domain, branding (display name + tagline), app-catalog mode (inherit/custom), plus the existing billing-mode picker.src/api/http.ts— fixes two latent bugs found while wiring the new client: (1) thefetchfallback wasn't bound toglobalThis, which throws"Illegal invocation"in a real browser; (2)HttpError.codepreferred theErrorBody's humanerrormessage over its machine-readablecodeenum, backwards from the family{ error, code }contract — this is whatisPortalsForbidden/isSlugConflictrely on.packages/feature-flags—fuzefront.platform.multi-tenant-portalsis now declared inWEB_EXPOSED_FLAGS(src/catalog.ts) andflag-registry.yaml'sweb_exposedflippedfalse → true. The flag already gated the console in the host (frontend/src/pages/MasterAdminPortalsPage.tsx, pre-existing) viauseFlag, but wasn't disclosed to the browser byGET /api/flags— same class of gap as feat(feature-flags): web-expose the FF-EPIC-17 identity UI flags #697, so the gate was silently always falling back to its OFF default regardless of the Unleash toggle.frontend/src/__tests__/MasterAdminPortalsPage.test.tsx(new) — end-to-end coverage of the flag gate (both states) and the frame states through the real host wiring.Frame / contract deviations (frames are
approved: false, superseded-model)design/frames/portal-admin-consoles/{01,02,03,04}-*.htmlwere authored against the anticipated@fuzefront/portal-clientmodel and are explicitly marked"Contract (anticipated — @fuzefront/portal-client, not frozen today)"in their own notes. Building against the real frozen contract instead required:Free/Pro/Scaleplan tiers —billingMode(free|platform|reseller) is rendered honestly, matching the pre-existingPlanBadgerationale.portal_domains, FF-EPIC-16) — a single nullablecustomDomainfield.CONFLICT(notSLUG_TAKEN/ROOT_PORTAL_PROTECTED) —data-error-code="CONFLICT"on the inline slug-taken message.📋 Checklist
tsc --noEmit, both packages)--max-warnings 0)gate-ds-conformanceclean); no base/DS primitive forked or shadowed —design-system/untouched📝 Additional Notes
design/frames/portal-admin-consoles/manifest.jsonareapproved: false(owner's design-approval step in progress separately). The manifest declares noimplementation.paths, so this PR lands undergate-frames-first's warn mode for uncovered paths (same as S4/S5/S9) — not blocked, per repo convention. Did not hand-editapproved.frontend-test-engineer(or a future session with the MCP available) should render/admin/portals(flag ON, desktop + small-screen) and confirm a clean console before this ships to production traffic.OUT OF SCOPE — NOT DONE
@fuzefront/security-client0.7.0 — already merged in feat(contract): portal CRUD as org-tree operations (FF-EPIC-17-S7) #704; no backend work here).test-engineer).frontend-test-engineer) —frontend/tests/portal-admin-consoles.red.spec.tsstill targets the superseded@fuzefront/portal-clientmodel/root-row shape and will need reconciliation once the frames are re-approved against the real contract; not touched here (out offrontend-engineerscope).devops-engineer) — no CI workflow currently builds/testspackages/portal-admin-uistandalone (it's exercised only indirectly viafrontend's vite/vitest source aliases); worth a follow-up but out of this PR's scope.feature-flags-engineer) — only the web-exposure fix above, which the task explicitly called out.portal-console/portal-billingflows (PortalAdminConsoleFlow,PortalBillingFlow) — separate stories (FF-EPIC-14-S3/S4), still targeting anticipated contracts, untouched.Generated by Claude Code