gravitee-api-management / graphene-snippets
Cursor rule469 starsChanged 5 months agoApache-2.0View on GitHub
// Snippet: Mix host-owned meta fields with a plugin JSON Schema // // Use when a host module renders a plugin config (immutable JSON Schema from // the backend) alongside host-owned meta fields (name, description, or any // extra logic validated by your favourite schema lib). // One useForm shared across both — composeResolvers merges Zod + ajv // errors so RHF surfaces them on the right inputs. // Do NOT use for the simple case (no meta) — see…
In this repository31 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .ai/AGENTS.mdAGENTS.md
- gravitee-apim-common/AGENTS.mdAGENTS.md
- gravitee-apim-console-webui/AGENTS.mdAGENTS.md
- gravitee-apim-definition/AGENTS.mdAGENTS.md
- gravitee-apim-distribution/AGENTS.mdAGENTS.md
- gravitee-apim-gateway/AGENTS.mdAGENTS.md
- gravitee-apim-plugin/AGENTS.mdAGENTS.md
- gravitee-apim-portal-webui/AGENTS.mdAGENTS.md
- gravitee-apim-portal-webui-next/AGENTS.mdAGENTS.md
- gravitee-apim-reporter/AGENTS.mdAGENTS.md
- gravitee-apim-repository/AGENTS.mdAGENTS.md
- gravitee-apim-rest-api/AGENTS.mdAGENTS.md
- .ai/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- gravitee-apim-common/CLAUDE.mdCLAUDE.md
- gravitee-apim-console-webui/CLAUDE.mdCLAUDE.md
- gravitee-apim-definition/CLAUDE.mdCLAUDE.md
- gravitee-apim-distribution/CLAUDE.mdCLAUDE.md
- gravitee-apim-gateway/CLAUDE.mdCLAUDE.md
- gravitee-apim-plugin/CLAUDE.mdCLAUDE.md
- gravitee-apim-portal-webui/CLAUDE.mdCLAUDE.md
- gravitee-apim-portal-webui-next/CLAUDE.mdCLAUDE.md
- gravitee-apim-reporter/CLAUDE.mdCLAUDE.md
- gravitee-apim-repository/CLAUDE.mdCLAUDE.md
- gravitee-apim-rest-api/CLAUDE.mdCLAUDE.md
- .cursor/rules/graphene.mdCursor rule
- .cursor/rules/graphene-snippets/context-sidebar-detail-page.tsxCursor rule
- .cursor/rules/graphene-snippets/creation-wizard-page.tsxCursor rule
- .cursor/rules/graphene-snippets/data-table-list-page.tsxCursor rule
- .cursor/rules/graphene-snippets/json-schema-form-simple.tsxCursor rule
The file
Files
// Snippet: Mix host-owned meta fields with a plugin JSON Schema
//
// Use when a host module renders a plugin config (immutable JSON Schema from
// the backend) alongside host-owned meta fields (name, description, or any
// extra logic validated by your favourite schema lib).
// One useForm shared across both — `composeResolvers` merges Zod + ajv
// errors so RHF surfaces them on the right inputs.
// Do NOT use for the simple case (no meta) — see snippets/json-schema-form-simple.tsx.
// Do NOT install Zod just for this — any RHF-compatible resolver works:
// `yupResolver`, `joiResolver`, `valibotResolver`, or a hand-written
// `Resolver<T>`. Only the first argument to `composeResolvers` changes.
//
// Replace {PLACEHOLDERS} with your actual values.
// See Storybook "Composed/JsonSchemaForm → With external (hand-coded) fields".
import { zodResolver } from '@hookform/resolvers/zod';
import { useMemo } from 'react';
import { type Control, type Resolver, useForm } from 'react-hook-form';
import { z } from 'zod';
import type { JsonSchema } from '@gravitee/graphene-core';
import {
Button,
composeResolvers,
extractDefaults,
Field,
FieldError,
FieldLabel,
Input,
JsonSchemaForm,
jsonSchemaResolver,
} from '@gravitee/graphene-core';
// Host-owned meta — extend with z.union / z.refine / async checks as needed.
const metaSchema = z.object({
name: z.string().min(1, 'Name is required'),
});
type FormValues = z.infer<typeof metaSchema> & { config: Record<string, unknown> };
interface PluginConfigPageProps {
readonly pluginSchema: JsonSchema;
readonly initialValue?: Partial<FormValues>;
// Host environment / feature flags read by `gioConfig.displayIf` / `gioConfig.disableIf`
// conditions of the form `{ "context.X": value }`. Remove if your schemas declare no
// `context.X` references.
readonly environment: 'production' | 'staging' | 'dev';
readonly onSave: (data: FormValues) => void;
}
export function PluginConfigPage({ pluginSchema, initialValue, environment, onSave }: PluginConfigPageProps) {
// `basePath: 'config'` must match the `name` prop on <JsonSchemaForm> below.
// Cast widens zodResolver to the composite values shape — each inner resolver reads
// only its own slice of `values` at runtime, so the cast is sound.
const resolver = useMemo(
() =>
composeResolvers<FormValues>(
zodResolver(metaSchema) as unknown as Resolver<FormValues>,
jsonSchemaResolver<FormValues>(pluginSchema, { basePath: 'config' }),
),
[pluginSchema],
);
// Defaults: schema defaults seed `config`, then saved values override per key. Spread
// `initialValue` at the top level for meta; explicit merge on `config` so saved-but-partial
// configs do NOT wipe schema defaults for fields the user never touched (or that the schema
// added in a newer plugin version). For deeply nested schemas, swap `Object.assign` for a
// recursive merge (e.g. lodash.merge).
const schemaDefaults = useMemo(
() => (extractDefaults(pluginSchema) as Record<string, unknown>) ?? {},
[pluginSchema],
);
// Stable identity for the `context` prop on JsonSchemaForm — inline `{{ environment }}`
// would create a fresh ref each render and re-render every field downstream.
const formContext = useMemo(() => ({ environment }), [environment]);
const form = useForm<FormValues>({
resolver,
mode: 'onTouched',
criteriaMode: 'all',
defaultValues: {
name: '',
...initialValue,
config: { ...schemaDefaults, ...(initialValue?.config ?? {}) },
},
});
const { name: nameError } = form.formState.errors;
return (
<form noValidate onSubmit={form.handleSubmit(onSave)} className="flex flex-1 flex-col gap-4 p-8">
{/* Host meta — validated by Zod. Duplicate this block for `description`, etc. */}
<Field data-invalid={!!nameError}>
<FieldLabel htmlFor="name">Name</FieldLabel>
<Input id="name" {...form.register('name')} aria-invalid={!!nameError} />
{nameError && <FieldError errors={[nameError]} />}
</Field>
{/* Plugin sub-tree — fields registered under `values.config.*`. JsonSchemaForm is typed
against the generic RHF FieldValues; cast widens the narrow composite control.
`context` is memoized at the call site (here, via the `formContext` const below) so
a fresh ref does not bust JsonSchemaForm's internal memo every parent render. */}
<JsonSchemaForm
schema={pluginSchema}
control={form.control as unknown as Control}
name="config"
context={formContext}
/>
<Button type="submit">Save</Button>
</form>
);
}
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

