wso2/api-platform/apicp-ui

Claude Code and agent skill by wso2/api-platform

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Build or change UI in portals/api-control-plane — pages, components, forms, dialogs, listings, app shell/navigation, theming, and MUI→Oxygen migration. Covers the Oxygen UI (@wso2/oxygen-ui) component/theming API plus this app's own rules for data access (hooks only), i18n (react-intl), routing/scope gating, and tests. Use for any .tsx/.ts work under portals/api-control-plane/src, and whenever asked how a page, listing, form, sidebar item, or theme override should be built here.

Install

npx vskill@latest install wso2/api-platform/apicp-ui

SKILL.md

UI development — api-control-plane

One skill for all UI work in portals/api-control-plane. It replaces the four generated oxygen-* skills and .claude/oxygen-ui/*.md: everything load-bearing from those is condensed below, and the full vendor reference is still on disk (see Where the truth lives).

Where the truth lives

Verify before you invent. In priority order:

QuestionSource
Does this component/prop/sub-component exist?node_modules/@wso2/oxygen-ui/dist/**/*.d.ts — the compiled types, ground truth
Full Oxygen component/pattern/theming referencenode_modules/@wso2/oxygen-ui/.ai/{components,patterns,theming,migration}.md (shipped with the package; identical to the docs previously copied into .claude/oxygen-ui/)
Which icon names existnode_modules/@wso2/oxygen-ui-icons-react/dist/index.d.ts → 8 brand icons + export * from "lucide-react"
Data-access layer rulessrc/api/README.md
i18n rulessrc/i18n/README.md
Test conventionssrc/test/README.md
Layer bans, i18n lint ruleseslint.config.js — the restrictions are documented inline there
Online / upstream<https://github.com/wso2/oxygen-ui> (packages/oxygen-ui/.ai/*.md, src/components/**). No hosted docs site exists as of v0.13.1 — fetch the repo, not a guessed URL.

Vendor docs lag the package. Two known errors in them — do not copy:

  • <Grid item xs={12}> → this app is on @mui/material v9 Grid v2: <Grid size={{ xs: 12, md: 4 }}>, no item.
  • HomeIcon/TrashIcon naming → this codebase uses bare lucide names (Home, Trash2, Plus). Both aliases resolve; stay with the bare form.
  • They also omit components that do exist (AppBreadcrumbs, PageTitle.Actions, PageTitle.BackButton, ColorSchemeSVG).

npx @wso2/oxygen-ui init --claude / update --claude regenerates .claude/oxygen-ui/ and the oxygen-* skills. If someone runs it, delete the regenerated files again — this skill is the project's single entry point, and node_modules/@wso2/oxygen-ui/.ai/ already carries the reference.

Non-negotiables

  1. Every source file starts with the Apache-2.0 header (Copyright (c) 2026, WSO2 LLC. block — copy it from any neighbouring file).
  2. All components come from `@wso2/oxygen-ui`, never @mui/material. All icons from @wso2/oxygen-ui-icons-react, never lucide-react/@mui/icons-material.
  3. Backend access goes through a hook in `src/api/resources/<resource>` — nothing else. ESLint blocks endpoints, queries, query keys, transport, generated types and legacy clients.
  4. No user-facing literal strings. <FormattedMessage> for JSX, intl.formatMessage for string props. IDs are apiControlPlane.<area>.<Module>.<slug>.
  5. No colour, radius, blur, shadow or border literals. Theme tokens (bgcolor: 'background.paper', color: 'text.secondary', spacing numbers) or a recipe from src/theme/receipes.ts. Local sx is for layout only.
  6. Scoped pages wrap their body in `ScopeGate` (see Routing, scope and navigation).
  7. Never “resolve” a rule from `.claude/rules/*` with a `// TODO`. Those rules (XSS/output encoding, dependency management, error handling) apply to this portal's .ts/.tsx too.

Verification loop, in order: npm run typecheck → npm run lint → npm run test → npm run i18n (commit src/i18n/messages/, never src/i18n/compiled/).

Project map

src/
api/ resources/<name>/{endpoints,queries,hooks}.ts + core/ (http, scope, spec, queryKeys)
components/ app-wide: StateViews (Loading/Empty/Error), ConfirmDialog, Notifications,
ErrorBoundary, AppLoader, ComingSoon, cards/*, common/*
contexts/auth/ AuthProvider + AuthStateContext
hooks/ cross-cutting hooks (ProductActivation, useDocumentTitle)
i18n/ I18nProvider, useLocale, useFormatters, formats.ts, messages/ (source catalogs)
navigation/ navigationRegistry.tsx (the sidebar), navigationTypes, useNavigationItems,
usePageTitle (browser tab title)
pages/
auth/ LoginPage, AuthCallbackPage
appShell/ AppLayout, AppHeader, AppSidebar, *QuickSelector
appShellPages/<feature>/ the actual pages (+ components/, utils/ per feature)
routes/ paths.ts (route builders), AppRoutes.tsx, ProtectedRoute
scope/ ConsoleScopeProvider/Context, ScopeGate, consoleRouteParams
slots/ Slot + Hideable extension primitives
theme/ receipes.ts (shared style recipes)
test/ renderWithProviders, MSW server + toolkit, mock scope/auth
extensions.tsx extension registration; hostPort.tsx the value handed to extensions

Provider order (App.tsx, outermost first): I18nProvider → OxygenUIThemeProvider → NotificationProvider → AppQueryProvider → ApiClientProvider → ErrorBoundary → BrowserRouter → AuthProvider → ExtensionsProvider → AppRoutes. Inside a protected route: ConsoleScopeProvider → AppLayout. Don't add a provider without a reason that names its position.


Oxygen UI essentials

Imports
tsx
import { Box, Button, Card, PageTitle, Stack, Typography } from '@wso2/oxygen-ui';
import { Plus, Search, Trash2 } from '@wso2/oxygen-ui-icons-react'; // lucide names, size={18}
import { DataGrid, DatePickers, TreeView, AdapterDateFns } from '@wso2/oxygen-ui'; // MUI X namespaces

@wso2/oxygen-ui re-exports all of @mui/material plus Oxygen's own components, styled, alpha, useTheme, Theme. MUI X ships as namespaces: <DataGrid.DataGrid …>, <DatePickers.DatePicker …>, <TreeView.SimpleTreeView>. Charts live in @wso2/oxygen-ui-charts-react, which is not installed here — adding it is a dependency change (.claude/rules/js-dependency-management.md).

Oxygen's own components (beyond MUI)
ComponentSub-components / notes
AppShell.Navbar .Sidebar .Main .Footer .NotificationPanel; props initialCollapsed, collapseOnSelectOnMobile. Wired once in AppLayout — pages never touch it.
Header.Toggle .Brand .BrandLogo .BrandTitle .Switchers .Actions .Spacer; minimal hides switchers
Sidebar.Nav .Category .CategoryLabel .Item .ItemIcon .ItemLabel .ItemBadge .Footer .User*; props collapsed, activeItem, expandedMenus, onSelect, onToggleExpand. An Item with nested children toggles instead of navigating.
Footer.Copyright .Version .Divider (left) · .Link (right)
UserMenu.Trigger .Header .Item .Logout .Divider
NotificationPanel.Header{,Icon,Title,Badge,Close} .Tabs .Actions .List .Item{,Avatar,Title,Message,Timestamp,Action} .EmptyState
PageTitle.Header .SubHeader .Avatar .Link .Actions .BackButton — the standard page heading
PageContentpadding/max-width wrapper; fullWidth. Already applied by AppLayout, so a page starts at its own content.
AppBreadcrumbsitems: BreadcrumbItem[]; rendered by AppLayout from route scope
ListingTable.Provider .Container .Toolbar .Head .Body .Footer .Row .Cell .SortLabel .RowActions .CellIcon .EmptyState .DensityControl; `variant='table'\'card', density, striped, bordered. Prefer over raw MUI Table`.
Form.Section .Header .Subheader .Body .Stack .ElementWrapper .Wizard .CardButton .Card{Header,Content,Actions,Media}
ComplexSelect.MenuItem + .MenuItem.Icon / .MenuItem.Text — icon+text options inside a Select
SearchBar, SearchBarWithAdvancedFilter, StatCard, CodeBlock, ColorSchemeToggle, ColorSchemeImage, ColorSchemeSVG, ThemeSwitcher, NotificationBanner, ParticleBackground, Layout (.Navbar .Sidebar .Content .Header)single-purpose; check the .d.ts for props

Hooks: useTheme, useThemeSwitcher, useThemeContent, useAppShell, useNotifications (Oxygen's own — not this app's src/components/Notifications), useHeader, useSidebar, useNotificationPanel, useListingTable / useListingTableRequired. Utils: formatRelativeTime, pxToRem, alpha.

Theming

OxygenUIThemeProvider is mounted once, directly in App.tsx, with a one-entry registry (ChoreoLabsTheme) declared at module scope in that same file — module scope because the provider keys a theme-resolving effect on the array's identity. Don't call the provider anywhere else, and don't wrap it in an app-level provider of your own.

It needs nothing else from us: it already renders <CssBaseline enableColorScheme /> internally, and with no emotionCache/nonce prop it falls back to <StyledEngineProvider injectFirst> — so app styles override Oxygen styles without a hand-rolled Emotion cache. Under a CSP, pass the provider's own nonce prop rather than building a cache.

Three tiers, in order of preference:

  1. Theme — global decisions owned by Oxygen (theme.palette.*, theme.typography.*, theme.border.*, theme.zIndex.*, theme.oxygen.* for blur/gradient/glass/syntax).
  2. Recipes — src/theme/receipes.ts: hairline(theme), glassSurfaceSx(theme), interactiveCardSx, stickyBottomBarSx(theme), overlayBarShadow. A repeated multi-property treatment goes here, once.
  3. Local `sx` — layout only: flex, gap, grid, min/max sizing.
tsx
<Card sx={(theme) => ({ ...glassSurfaceSx(theme), display: 'flex', gap: 2 })} />

Dark mode: the theme is CSS-variable based (--oxygen-*, data-color-scheme attribute), so token usage adapts for free. Read the mode with useTheme().palette.mode only when behaviour (not colour) must branch.


Page patterns

Page skeleton
tsx
export function ThingListPage() {
// Gate the whole body, not just the JSX: out of scope the query stays disabled
// and `isPending` never clears, so a loading branch would hang forever.
return (
<ScopeGate
prompt="Things are created and managed at the project level."
requires="project"
to={routes.things}
>
<ThingList />
</ScopeGate>
);
}
function ThingList() {
const intl = useIntl();
const thingsQuery = useThings();
const { notify } = useNotifications();
// `isPending`, not `isLoading` — a disabled query has isLoading=false with no
// data, which would flash the empty state.
if (thingsQuery.isPending) return <LoadingState label="Loading things" />;
if (thingsQuery.error) return <ErrorState message="Unable to load things" />;
const things = thingsQuery.data?.list ?? [];
return (
<>
<PageTitle>
<PageTitle.Header><FormattedMessage {...messages.title} /></PageTitle.Header>
<PageTitle.SubHeader><FormattedMessage {...messages.subtitle} /></PageTitle.SubHeader>
<PageTitle.Actions>
<Button startIcon={<Plus />} variant="contained" onClick={create}>
<FormattedMessage {...messages.create} />
</Button>
</PageTitle.Actions>
</PageTitle>
{things.length === 0 ? (
<EmptyState
title={intl.formatMessage(messages.emptyTitle)}
description={intl.formatMessage(messages.emptyBody)}
actionLabel={intl.formatMessage(messages.create)}
onAction={create}
/>
) : (
<Stack spacing={2}>{/* toolbar row, then grid/list */}</Stack>
)}
</>
);
}

State views come from src/components/StateViews — LoadingState({label, fullScreen}), EmptyState({title, description, actionLabel, onAction}), ErrorState({title, message}). Never hand-roll a spinner or an error Alert.

Reference implementations to copy from: apis/ApiListPage.tsx (list + search + grid/list toggle + delete), apis/ApiDetailPage.tsx (tabbed detail), gateways/GatewayCreatePage.tsx (create flow), apis/overview/ApiKeysPanel.tsx (ListingTable), projects/NewProjectDialog.tsx (dialog form).

Feedback and confirmation

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
5

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/wso2/api-platform/apicp-ui/badge)](https://verified-skill.com/skills/wso2/api-platform/apicp-ui)

Something wrong with this skill or its page? Tell us.