Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use for any work in the Operate legacy frontend (operate/client/) — bugs, changes, tests, components, API hooks, styled-components, MobX stores, React Router, or questions about Operate patterns, conventions, or architecture.
.claude/skills/camunda-operate-frontend/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 63% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 564% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 100% | 0% |
Operate's frontend lives in two codebases while the migration runs:
webapp/client/apps/orchestration-cluster-webapp/src/operate/ (~240 files). All newwork goes here. React 19, TanStack Router + Query, Carbon, styled-components (temporary).
operate/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.
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.
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.
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.
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.
| Concern | Where it goes | | ---------------------------- | ------------------------------------------------------------------------------------------------------- | | Polling / cache policy | Feature-local query options, local hook, or call site | | Multi-page fetching | Local hook exporting a queryOptions function (for route prefetch) + a use* hook (for the component) | | Aggregation / transformation | select 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.
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.
request(endpoints.xxx(...)) in the handler, thenqueryClient.invalidateQueries({queryKey: [...]}) for affected lists.
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.
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.
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.
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.
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.
Target: follow frontend-unit-test. Put reusable response data in shared-test-modules/api-mocks/ factories rather than constructing large literals in tests.
Target: follow docs/monorepo-docs/frontend/forms.md.
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.
Follow docs/monorepo-docs/frontend/development-process/working-on-large-feature.md.
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.
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).
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 86,607 | 27,016 | -69% | 1 | 1 | 0% | 5,727 | 6,879 | +20% | 0 | 0 | — |
case-02 | fail→fail | 23,638 | 30,032 | +27% | 1 | 1 | 0% | 4,323 | 8,121 | +88% | 0 | 0 | — |
case-03 | fail→pass | 45,740 | 35,338 | -23% | 1 | 1 | 0% | 8,899 | 8,265 | -7% | 0 | 0 | — |
case-04 | fail→fail | 27,222 | 14,556 | -47% | 1 | 1 | 0% | 2,442 | 2,809 | +15% | 0 | 0 | — |
case-05 | fail→pass | 18,139 | 8,018 | -56% | 1 | 1 | 0% | 1,803 | 2,939 | +63% | 0 | 0 | — |
case-06 | fail→fail | 17,120 | 18,438 | +8% | 1 | 1 | 0% | 3,147 | 3,846 | +22% | 0 | 0 | — |
case-07 | fail→pass | 3,181 | 8,353 | +163% | 1 | 1 | 0% | 362 | 2,405 | +564% | 0 | 0 | — |
case-08 | fail→pass | 11,458 | 8,699 | -24% | 1 | 1 | 0% | 1,318 | 2,632 | +100% | 0 | 0 | — |
case-09 | pass→pass | 12,499 | 9,842 | -21% | 1 | 1 | 0% | 1,920 | 2,694 | +40% | 0 | 0 | — |
case-10 | fail→pass | 19,120 | 10,642 | -44% | 1 | 1 | 0% | 2,041 | 2,909 | +43% | 0 | 0 | — |
case-11 | pass→pass | 12,124 | 9,733 | -20% | 1 | 1 | 0% | 2,047 | 2,731 | +33% | 0 | 0 | — |
case-12 | pass→pass | 15,776 | 11,452 | -27% | 1 | 1 | 0% | 1,572 | 2,892 | +84% | 0 | 0 | — |
case-13 | fail→pass | 19,780 | 13,276 | -33% | 1 | 1 | 0% | 2,051 | 2,699 | +32% | 0 | 0 | — |
case-14 | fail→pass | 17,699 | 15,478 | -13% | 1 | 1 | 0% | 1,983 | 2,985 | +51% | 0 | 0 | — |
case-15 | fail→pass | 16,928 | 9,672 | -43% | 1 | 1 | 0% | 1,502 | 2,599 | +73% | 0 | 0 | — |
case-16 | fail→pass | 22,091 | 8,084 | -63% | 1 | 1 | 0% | 2,196 | 2,397 | +9% | 0 | 0 | — |
case-17 | fail→pass | 22,528 | 4,479 | -80% | 1 | 1 | 0% | 2,759 | 2,656 | -4% | 0 | 0 | — |
case-18 | fail→pass | 17,851 | 8,411 | -53% | 1 | 1 | 0% | 1,825 | 2,524 | +38% | 0 | 0 | — |
case-19 | fail→pass | 26,838 | 9,857 | -63% | 1 | 1 | 0% | 3,584 | 3,776 | +5% | 0 | 0 | — |
case-20 | fail→pass | 16,303 | 8,712 | -47% | 1 | 1 | 0% | 1,655 | 2,518 | +52% | 0 | 0 | — |
case-21 | fail→fail | 12,053 | 9,087 | -25% | 1 | 1 | 0% | 1,836 | 2,477 | +35% | 0 | 0 | — |
case-22 | pass→pass | 16,877 | 8,454 | -50% | 1 | 1 | 0% | 1,947 | 2,430 | +25% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/10/2026 | +54% |
Other measured skills in the registry, with their headline benchmark lift.