.claude/skills/relay-patterns/SKILL.md
GraphQL/Relay integration patterns for Backend.AI WebUI React components. Covers useLazyLoadQuery, useFragment, useRefetchableFragment, fragment architecture (query orchestrator + fragment component), naming conventions, modern directives (@required, @alias), client directives (@since, @deprecatedSince, @skipOnClient), and query optimization.
npx skillsauth add lablup/backend.ai-webui relay-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
useLazyLoadQuery, useFragment, useRefetchableFragment, usePaginationFragmentuseLazyLoadQuery - Fetch data on component mountuseFragment - Read fragment data from parent queryuseRefetchableFragment - Fragment with refetch capabilityusePaginationFragment - Fragment with pagination supportqueryRef (type: ComponentQuery$key){typeName}Frgmt (e.g., userFrgmt: UserProfile_user$key)Query Orchestrator (useLazyLoadQuery, manages fetchKey/transitions)
└── Fragment Component (useFragment, receives ref as prop, focused on presentation)
Separate data fetching (query orchestrator) from presentation (fragment component):
// Query Orchestrator Component
const UserManagement: React.FC = () => {
const [fetchKey, updateFetchKey] = useUpdatableState('first');
const [isPendingRefetch, startRefetchTransition] = useTransition();
const { users } = useLazyLoadQuery<UserManagementQuery>(
graphql`
query UserManagementQuery {
users {
...UserNodes_users
}
}
`,
{},
{ fetchPolicy: 'store-and-network', fetchKey },
);
return (
<UserNodes
usersFrgmt={users}
loading={isPendingRefetch}
onRefresh={() => {
startRefetchTransition(() => {
updateFetchKey();
});
}}
/>
);
};
// Fragment Component
interface UserNodesProps {
usersFrgmt: UserNodes_users$key;
loading?: boolean;
onRefresh?: () => void;
}
const UserNodes: React.FC<UserNodesProps> = ({
usersFrgmt,
loading,
onRefresh,
}) => {
const data = useFragment(
graphql`
fragment UserNodes_users on Query {
users { id, email, username, is_active }
}
`,
usersFrgmt,
);
return <BAITable dataSource={data.users} loading={loading} />;
};
// useLazyLoadQuery
const data = useLazyLoadQuery(
graphql`query MyQuery { user { id ...UserProfile_user } }`,
{},
);
// useFragment
const data = useFragment(
graphql`fragment UserProfile_user on User { id, name, email }`,
userRef,
);
// useRefetchableFragment
const [data, refetch] = useRefetchableFragment(
graphql`
fragment UserList_users on Query
@refetchable(queryName: "UserListRefetchQuery") {
users { id, name }
}
`,
usersRef,
);
@required directive - Type-safe null handling in fragments@alias directive - Rename fields for better semanticsusePaginationFragment for lists@defer and @stream for progressive loadingBackend.AI uses custom client-side directives to handle multi-version backend compatibility. These directives are defined in data/client-directives.graphql and processed at runtime by react/src/helper/graphql-transformer.ts before queries are sent to the server.
| Directive | Purpose | When field is removed |
|---|---|---|
| @since(version: "X.Y.Z") | Field introduced in version X.Y.Z | Backend version < X.Y.Z |
| @deprecatedSince(version: "X.Y.Z") | Field removed/replaced in version X.Y.Z | Backend version >= X.Y.Z |
| @sinceMultiple(versions: [...]) | Multi-branch version support | Backend incompatible with version array |
| @deprecatedSinceMultiple(versions: [...]) | Multi-branch deprecation | Backend compatible with version array |
| @skipOnClient(if: $var) | Conditionally skip field at runtime | Variable evaluates to true |
# Field added in a specific version
query SessionDetailQuery {
compute_session {
name
image
permissions @since(version: "24.09.0")
replicas @since(version: "24.12.0")
}
}
# Field replaced in a newer version
fragment ImageInfo_image on ImageNode {
name @deprecatedSince(version: "24.12.0")
namespace @since(version: "24.12.0")
}
# Conditional field based on runtime feature flag
query UserSettingsQuery($isNotSupportTotp: Boolean!) {
user {
email
totp_activated @skipOnClient(if: $isNotSupportTotp)
}
}
data/client-directives.graphql and merged into the Relay schema via relay-base.config.jsmanipulateGraphQLQueryWithClientDirectives() in react/src/helper/graphql-transformer.ts strips incompatible fields before sending the querybackendaiclient.isManagerVersionCompatibleWith() (PEP 440 semver)@since when using fields introduced in newer backend versions@deprecatedSince + @since together when a field is replaced (old and new coexist)@skipOnClient over complex runtime conditionals for feature-flagged fields@sinceMultiple / @deprecatedSinceMultiple are for fields backported across version branches (rare)$key typesqueryRef / {typeName}Frgmt)@required directive used for non-null fields@since / @deprecatedSince used for version-dependent fields@skipOnClient used for feature-flagged fields instead of runtime conditionalsdevelopment
Find WebUI dev server address and Backend.AI API endpoint/credentials for testing. Trigger on: "which server", "connection info", "login credentials", "dev server URL", "API endpoint", "where to connect", "how to login", "test server", or when needing to interact with the running WebUI (screenshots, live checks, E2E).
data-ai
Create Relay-based infinite scroll select components extending BAISelect. Supports name-based values (usePaginationFragment) and id-based values (useLazyLoadQuery + useLazyPaginatedQuery) with search, optimistic updates, and multiple selection modes.
development
# record-e2e-gif Skill Record Playwright e2e tests as GIF animations, one GIF per test case. ## Prerequisites - `ffmpeg` must be installed (`/opt/homebrew/bin/ffmpeg` on macOS) - Playwright dev server must be running. The endpoint is read from `e2e/envs/.env.playwright` (`E2E_WEBUI_ENDPOINT`). Do NOT hardcode the port — it varies per environment. ## How It Works 1. Run specified Playwright tests with `--video=on` (Playwright saves `.webm` per test in `test-results/`) 2. For each `.webm` fil
tools
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.