بنقرة واحدة
update-portal-ui
// Guidelines for updating or designing pages in the portal React frontend (portal/src). Covers component conventions, link rendering rules, i18n patterns, and common pitfalls.
// Guidelines for updating or designing pages in the portal React frontend (portal/src). Covers component conventions, link rendering rules, i18n patterns, and common pitfalls.
Update Authgear email templates using the correct source files, translation files, and commit order. Use when editing email wording, email structure, or subject lines.
Write or extend Go unit tests in this repo. Use when the user asks to add or update Go tests.
Draft or update detailed implementation plans for authgear-server specs, design changes, and docs/plans files. Use when Codex needs to turn a spec or outdated plan into a concrete implementation plan with exact files, exact methods, runtime call flow, compatibility requirements, test coverage, and atomic commit steps.
Write end-to-end (e2e) tests for authgear-server. Use when the user asks to write, add, or create e2e tests. The tests live in e2e/tests/ and are YAML-driven.
Set up a fresh authgear-server development environment from scratch. Use when onboarding a new contributor, setting up a new machine, or when the user says "set up local dev from scratch" / "first-time setup". Covers the asdf + Homebrew install path on macOS; Nix users should follow CONTRIBUTING.md directly.
Full pipeline for adding a new Site Admin API feature — from OpenAPI spec through implementation plan to working service. Use when adding a new endpoint or filling in real data for an existing stub.
| name | update-portal-ui |
| description | Guidelines for updating or designing pages in the portal React frontend (portal/src). Covers component conventions, link rendering rules, i18n patterns, and common pitfalls. |
Follow this skill when adding, editing, or reviewing UI in portal/src.
The portal has three link components. Use the right one — using the wrong one causes links to render as unstyled plain text inside certain wrappers.
| Component | Import path | Use when |
|---|---|---|
Link | ../../Link (or relative path to portal/src/Link.tsx) | Internal navigation (React Router) |
ExternalLink | ../../ExternalLink | External URLs (href, opens in new tab) |
LinkButton | ../../LinkButton | A button that visually looks like a link |
Never use Link from react-router-dom directly — it renders a plain <a> tag with no FluentUI styling.
WidgetDescription wraps its children in a FluentUI Text component. FluentUI's Text overrides the colour of plain <a> tags to match surrounding text, making links invisible as links.
portal/src/Link.tsx and portal/src/ExternalLink.tsx both wrap FluentUI's FluentLink, which keeps its own link styling even inside Text. ✓react-router-dom's Link renders a bare <a> — styling is stripped inside Text. ✗Rule: Whenever a link appears inside WidgetDescription, Text (FluentUI), or any component that internally wraps FluentUI Text, use Link or ExternalLink from portal/src, not from react-router-dom.
To embed a clickable link inside a translated string:
In the translation string (portal/src/locale-data/en.json), use an XML-like tag:
"my-key": "Read the <docLink>documentation</docLink> for details."
In the component, pass a render function in FormattedMessage values whose key matches the tag name exactly:
<FormattedMessage
id="my-key"
values={{
// eslint-disable-next-line react/no-unstable-nested-components
docLink: (chunks: React.ReactNode) => (
<ExternalLink href="https://docs.authgear.com/...">
{chunks}
</ExternalLink>
),
}}
/>
Use Link for internal routes, ExternalLink for external URLs. Never use react-router-dom's Link here.
Some components (e.g. FluentUI ChoiceGroup via onRenderLabel) accept a label-render callback. If the description contains a link, the callback must accept React.ReactNode, not string:
// Correct — accepts ReactNode so JSX can be passed
const onRenderLabel = useCallback((description: React.ReactNode) => {
return (option?: IChoiceGroupOption) => (
<div>
<Text>{option?.text}</Text>
<Text>{description}</Text>
</div>
);
}, []);
// Then pass FormattedMessage directly — no cast needed
onRenderLabel(
<FormattedMessage id="..." values={{ reactRouterLink: ... }} />
)
Never cast JSX to string with as any as string — the link will not render correctly.
Before submitting a portal UI change:
WidgetDescription or FluentUI Text use Link or ExternalLink from portal/src, not from react-router-dom.FormattedMessage values use Link or ExternalLink from portal/src.React.ReactNode, not string.cd portal && npm run typecheck — must pass clean.