Opik Frontend
SkillDev toolsThis skill teaches your AI the React conventions used in the Opik frontend project so the code it writes fits the existing codebase. Once added, your AI can work on apps/opik-frontend, handling components, state, and data fetching in the project's own style. It is aimed at anyone contributing to Opik's web interface.
Available today. Use it from your connected AI after setup.
No other account needed.
Add the skill, then give your AI a task in apps/opik-frontend, such as building or updating a component, state logic, or data fetching. It will apply the project's conventions as it works.
Then ask your AI: use the Opik Frontend skill
What your AI can do with it
- Write React components that follow Opik's frontend patterns
- Manage state in line with the project's conventions
- Set up data fetching the way the Opik frontend expects
- Make changes in apps/opik-frontend that match the existing code style
- Contribute frontend code to the Opik repository
What this skill tells your AI
The instructions your AI receives, as published by comet-ml/opik in .agents/skills/opik-frontend/SKILL.md and read by ahel’s review.
Architecture Decisions
- Routing: TanStack Router (file-based)
- Data fetching: TanStack Query (never raw fetch/useEffect)
- State: Zustand for global, React state for local
- Components: shadcn/ui + Radix UI base
- Forms: React Hook Form + Zod validation
Critical Gotchas
Never useEffect for Data Fetching
// ❌ BAD
useEffect(() => {
fetch('/api/data').then(setData);
}, []);
// ✅ GOOD
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
});
Selective Memoization
// ✅ USE useMemo for: complex computations, large data transforms
const filtered = useMemo(() =>
data.filter(x => x.status === 'active').map(transform),
[data]
);
// ✅ USE useCallback for: functions passed to children
const handleClick = useCallback(() => doSomething(id), [id]);
// ❌ DON'T memoize: simple values, primitives, local functions
const name = data?.name ?? ''; // No useMemo needed
Zustand Selectors
// ✅ GOOD - specific selector
const selectedEntity = useEntityStore(state => state.selectedEntity);
// ❌ BAD - selecting entire store causes re-renders
const { selectedEntity, filters } = useEntityStore();
Browser Translation Safety (Google Translate)
Many users auto-translate the page; the translator wraps text nodes in <font> elements, so React throws NotFoundError: removeChild when it reconciles a bare dynamic text node it re-parented. Wrap dynamic/conditional strings in their own element instead of rendering bare text.
// ❌ bare dynamic text → crash under translation
<button>{icon}{label}</button>
// ✅ wrap it → React swaps a stable element, stays translatable
<button>{icon}<span>{label}</span></button>
For timer-driven text (typewriter/counter), also avoid per-tick setState — write into a ref'd node's textContent (React never reconciles it), or mark a decorative node translate="no". Ref: facebook/react#11538 (OPIK-7428, OPIK-7435).
Layer Architecture
Shared layers (used by all versions)
ui → shared (one-way only)
Per-version layers
ui → shared → v1/pages-shared → v1/pages (one-way only)
ui → shared → v2/pages-shared → v2/pages (one-way only)
Module boundaries
- v1/ CANNOT import from v2/
- v2/ CANNOT import from v1/
src/components/is BLOCKED (old structure, no longer exists)- After modifying imports:
npm run deps:validate
Shared component rules
- Backward-compatible changes only
- Must not be version-aware (use
showProjectSelector={true}notisV2={true}) - If behavior needs to change, create a new component instead
State Location Decisions
- URL state: filters, pagination, selected items
- Zustand: user preferences, cross-component state
- React state: form inputs, UI toggles
Component Structure
const Component: React.FC<Props> = ({ prop }) => {
// 1. State hooks
// 2. Queries/mutations
// 3. Memoization (only when needed)
// 4. Event handlers
if (isLoading) return <Loader />;
if (error) return <ErrorComponent />;
return <div>...</div>;
};
Query Patterns
// Query with params
const { data } = useQuery({
queryKey: [ENTITY_KEY, params],
queryFn: (context) => fetchEntity(context, params),
});
// Mutation with invalidation
const mutation = useMutation({
mutationFn: updateEntity,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [ENTITY_KEY] });
},
});
Reference Files
- forms.md - React Hook Form + Zod patterns
- ui-components.md - Button variants, typography, dark theme
- responsive-design.md - Tailwind breakpoints vs useIsPhone
- testing.md - When to test, Vitest patterns
- code-quality.md - Lodash imports, naming, deps:validate
- performance.md - Bundle optimization, rendering, memoization
- permissions.md -
usePermissions()guard guidance for UI actions
Signals
- GitHub stars
- 22k
- Forks
- 2k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
opik-frontend- Source
- github.com/comet-ml/opik