Operate Frontend

SkillDev tools

Guides your agent to correctly fix bugs and make changes in the Operate legacy frontend codebase.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Operate Frontend skill

About this capability

Use for any Operate frontend work — the target pod at webapp/client/apps/orchestration-cluster-webapp/src/operate/ and the legacy client at operate/client/. Covers routing, data fetching, state, styling, testing, forms and conventions in both. For porting a page from legacy to target, use frontend-o

What this skill tells your AI

The instructions your AI receives, as published by camunda/camunda in .claude/skills/operate-frontend/SKILL.md and read by ahel’s review.

Operate's frontend lives in two codebases while the migration runs:

  • Targetwebapp/client/apps/orchestration-cluster-webapp/src/operate/ (~240 files). All new work goes here. React 19, TanStack Router + Query, Carbon, styled-components (temporary).
  • Legacyoperate/client/ (~1200 files). Bug fixes, small adjustments and maintenance only. React 19, React Router 7, TanStack Query 5, MobX 7, Carbon, styled-components, React Final Form. Winding down; don't add architecture.

Both render BPMN/DMN with bpmn-js / dmn-js and edit JSON with Monaco (@monaco-editor/react). The target rules follow. For legacy work, read references/legacy.md; for a migration, read both and use frontend-operate-migrator.

For target unit tests, follow frontend-unit-test. For other target concerns, read the relevant canonical guide under docs/monorepo-docs/frontend/. For app-wide layout and boundaries, start with docs/monorepo-docs/frontend/orchestration-cluster-webapp.md. The rules below record Operate-specific choices and overrides.

Project structure

Target: routes in src/routes/_carbon/_auth/operate/, pages and their query/search contracts in src/operate/, cross-pod primitives in src/shared/. Dependencies flow routes → operate → shared: feature code must not import route files, shared code must not import feature code. Define shared route/feature schemas in the feature. Never import across the legacy app boundary.

Pages are directories named after their primary export — Dashboard/Dashboard.tsx, not DashboardPage.

Routing

Target: TanStack file-based routes under src/routes/_carbon/_auth/operate/. Route IDs include /_carbon/_auth; browser URLs stay /operate/.... Do not introduce historical /_auth/operate/... IDs. The guard, Dashboard and Processes list already exist — inspect before adding or splitting. beforeLoad is for auth/guards only; loader prefetches data. Route files are thin: they wire a page component and own loader, pendingComponent, errorComponent.

URL as state (target)

Follow docs/monorepo-docs/frontend/development-process/creating-a-new-page.md: entity identity goes in path params, shareable view state in validated search params, and ephemeral UI state locally. Operate links must preserve validated tenant, definition/version, and incident identity filters. Cover duplicate definition IDs across tenants and browser back/forward.

Data fetching

Target: endpoint factories in #/shared/http/endpoints.ts. Operate-only query options live beside the owning feature in <feature>.queries.ts or a local hook; only cross-app query options belong in #/shared/http/queries.ts. Do not add Operate-specific polling, aggregation or multi-page fetching to the shared registry.

ConcernWhere it goes
Polling / cache policyFeature-local query options, local hook, or call site
Multi-page fetchingLocal hook exporting a queryOptions function (for route prefetch) + a use* hook (for the component)
Aggregation / transformationselect on useSuspenseQuery, or inside the local hook's queryFn

Reference: operate/pages/Dashboard/useRunningInstancesCount.ts exports runningInstancesCountQuery() and useRunningInstancesCount(); the route imports the query options, the component imports the hook. Check @camunda/camunda-api-zod-schemas/8.10 before writing a custom endpoint — most Operate endpoints are already there.

Suspense queries throw initial errors without data, but a failed refetch can retain cached data — handle those explicitly. Where a panel needs independent loading and recovery, use a granular boundary or useQuery. See docs/monorepo-docs/frontend/data-loading.md.

Writes (target)

Use direct request() handlers for simple writes, and XState machines for accepted/pending lifecycles. This is an Operate rule; it does not change other pods' write patterns.

  • Simple write, then refresh: call request(endpoints.xxx(...)) in the handler, then queryClient.invalidateQueries({queryKey: [...]}) for affected lists.
  • Write with a lifecycle (API returns 202 and the resource passes through pending states): model it as an XState machine (setup + fromPromise actors) taking queryClient as input — optimistic update via setQueryData with rollback, poll via fetchQuery until the resource leaves the transitional state, then invalidate affected lists. Behavior reference: tasklist/modules/task-details/taskCompletionMachine.ts (the machine's shape, not Tasklist's route tree or design-system wrappers).

Operate's batch operations follow this lifecycle. Distinguish starting a new batch (returns a key) from suspend/resume/cancel of an existing one (bodyless response — don't parse JSON from an empty body). Never put write logic in queries.ts; it stays a read-only registry.

State management

Target: URL search params own shareable state, useState owns ephemeral UI, and TanStack Query owns server data. Reuse shared session, theme, and notification modules rather than porting stores. Complex pending state may use a local reducer or MobX when simpler state is insufficient.

Styling

Target: styled-components and Carbon are kept temporarily for the legacy-to-unified migration. This is a compatibility step, not the target design system and not a frontend-wide default — the Camunda design system replaces it later via design-system-migrator. Reuse existing Carbon components; custom JSX is a last resort at this stage. Tasklist's design-system migration runs independently — never apply these Carbon rules there.

Component structure

Both: named exports only, never export default. No code comments — if something needs one, rewrite the code. Prefer declarative and functional (const, map/filter/reduce); a local let/for is fine for tight data aggregation where it reads clearer (see useRunningInstancesCount.ts).

Target: one file, one primary export, filename matches it. A colocated query module may export both its queryOptions and its use* hook.

i18n (target)

Operate strings go under operate.* inside the shared translation namespace in src/shared/i18n/locales/, used as t('operate.dashboard.title'). Add all four locales (en/de/fr/es) — LLM-translate de/fr/es and note "LLM-translated — not yet verified by native speakers" in the PR description.

Testing

Target: follow frontend-unit-test. Put reusable response data in shared-test-modules/api-mocks/ factories rather than constructing large literals in tests.

Forms

Target: follow docs/monorepo-docs/frontend/forms.md.

Before building a target feature

Read docs/monorepo-docs/frontend/development-process/before-starting.md. Preserve each endpoint's pagination contract and legacy UX: keep the existing paginated table for offset-based pages rather than converting it to infinite scroll. Honor eventual-consistency metadata, keep authorization server-driven, and cover multi-tenancy. Verify imports are declared dependencies and inspect tsconfig.browser.json before adding global types.

Feature flags (target)

Follow docs/monorepo-docs/frontend/development-process/working-on-large-feature.md.

Validation

For target validation commands and tracked end-to-end delivery, follow operate-engineering-loop. This skill owns coding conventions, not a separate execution or PR-review loop.

Boundaries

Target — don't introduce: /_auth/operate/... route IDs, Operate-specific policy in #/shared/http/queries.ts, per-consumer copies of shared logic, imports across the legacy app boundary, or Mixpanel tracking (the app has none — when porting a callback that mixes tracking with behavior, keep the behavior and drop the tracking).

Signals

GitHub stars
4k
Forks
818
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
operate-frontend
Source
github.com/camunda/camunda