Template Statuses & Workflow Builder
Overview
This feature adds two layers on top of Application Templates:
- Statuses — each template defines its own custom statuses (replacing the old hardcoded enum). Statuses have a state-machine of allowed transitions between them.
- Workflow — visual process graph (ReactFlow canvas) of workflow steps. Each step references a status and optionally runs an automated system handler. Steps are connected by transitions.
When an application moves through the workflow, its currentWorkflowStepId changes and its internal/external status is updated to match the step's linked TemplateStatus.
Architecture: Two Layers
Layer 1 — Status Machine (TemplateStatus + TemplateStatusTransition)
Defines what statuses exist and which transitions are allowed (USER/SYSTEM/BOTH).
Layer 2 — Workflow Steps (WorkflowStep + WorkflowStepTransition)
Visual process graph drawn on ReactFlow canvas. Each step references a TemplateStatus
and optionally has a system handler (automated logic). Steps are connected by edges.
Backend is authoritative for all workflow advancement. The frontend only:
- Sends
advanceWorkflowStepfor USER-type transitions (officer decision) - Sends
triggerWorkflowStepCheckto manually re-run a SYSTEM step's handler
System transitions fire automatically via WorkflowEventListener (domain events) → WorkflowEngine.
File Structure
Frontend
src/features/application-templates/
├── data/
│ ├── use-template-statuses.ts # All status + transition GQL hooks
│ └── use-workflow.ts # All workflow step + transition + definition GQL hooks
└── detail/
└── components/
├── application-template-statuses.tsx # Statuses tab: CRUD table + transitions matrix
├── application-template-workflow.tsx # Workflow tab: ReactFlow canvas builder
└── workflow/
├── workflow-step-node.tsx # Custom ReactFlow node component
├── workflow-step-panel.tsx # Right-side sheet for step config (inc. stepId field)
├── workflow-transition-panel.tsx # Right-side sheet for create/edit transitions
├── workflow-json-dialog.tsx # Wide dialog: "Edit as JSON" bulk editor
└── workflow-canvas.css # Dark-mode overrides for ReactFlow controls
src/features/applications/
├── data/
│ └── use-application-workflow.ts # Hooks: status history, workflow info, advance, triggerCheck
└── detail/
└── components/
├── application-workflow-flow.tsx # Read-only ReactFlow on application overview
└── application-workflow-history.tsx # Status transition history table
src/routes/_authenticated/o/$orgSlug/application-setup/templates/$templateId/
├── statuses/index.tsx # Route → ApplicationTemplateStatuses
└── workflow/index.tsx # Route → ApplicationTemplateWorkflow
Deleted:
workflow-transition-dialog.tsx— replaced entirely byworkflow-transition-panel.tsx
Backend
core/src/main/java/com/axvero/ams/core/
├── document/domain/
│ ├── TemplateStatus.java # Custom status per template (inc. manuallySettable, sortOrder)
│ ├── TemplateStatusTransition.java # Allowed from→to with USER/SYSTEM/BOTH
│ └── TemplateTransitionType.java # Enum: USER | SYSTEM | BOTH
├── workflow/
│ ├── domain/
│ │ ├── WorkflowStep.java # Inc. stepId slug (unique per template)
│ │ ├── WorkflowStepRepository.java # Inc. findByApplicationTemplateIdAndStepId etc.
│ │ ├── WorkflowStepTransition.java
│ │ └── WorkflowStepTransitionRepository.java
│ ├── handlers/
│ │ ├── WorkflowStepHandler.java # Interface: getHandlerId, getDisplayName, execute
│ │ ├── WorkflowHandlerRegistry.java
│ │ └── DocumentCompletenessCheckHandler.java
│ ├── WorkflowDefinitionResult.java # Record: List<WorkflowStep> steps, List<WorkflowStepTransition> transitions
│ ├── WorkflowEngine.java
│ ├── WorkflowEventListener.java
│ ├── WorkflowService.java # CRUD + replaceWorkflow + stepId auto-gen + lazy backfill
│ └── web/
│ ├── WorkflowController.java # Inc. replaceWorkflow mutation
│ ├── WorkflowStepMapper.java # Inc. toDefinitionStepDto, toDefinitionTransitionDto
│ └── dto/
│ ├── WorkflowStepDto.java # Inc. stepId
│ ├── WorkflowStepInput.java # Inc. stepId
│ ├── WorkflowStepTransitionDto.java
│ ├── WorkflowStepTransitionInput.java
│ ├── WorkflowDefinitionDto.java # { steps, transitions } for JSON editor response
│ ├── WorkflowDefinitionStepDto.java # stepId(slug), name, statusId(slug), ...
│ ├── WorkflowDefinitionTransitionDto.java # fromStepId(slug), toStepId(slug), ...
│ ├── WorkflowDefinitionInput.java # top-level input for replaceWorkflow
│ ├── WorkflowStepDefinitionInput.java # stepId!(slug), name!, statusId(slug), ...
│ ├── WorkflowTransitionDefinitionInput.java # fromStepId(slug), toStepId(slug)
│ ├── WorkflowHandlerInfoDto.java
│ └── WorkflowCheckResultDto.java
core/src/main/resources/graphql/
├── workflow.graphqls # WorkflowStep (inc. stepId), WorkflowDefinition types, queries, mutations
├── workflow-input.graphqls # WorkflowStepInput (inc. stepId), WorkflowDefinitionInput types
└── documents.graphqls # TemplateStatus (inc. manuallySettable, sortOrder), transitions
Data Hooks
use-template-statuses.ts
// Types
TemplateStatus // id, statusId(slug), numericCode, internalName, externalName,
// useInternalAsExternal, description, color, isInitial, isFinal,
// manuallySettable, sortOrder
TemplateStatusTransition // id, fromStatus, toStatus, transitionType, label
// Query hooks
useTemplateStatuses(templateId) // → TemplateStatus[]
useTemplateStatusTransitions(templateId) // → TemplateStatusTransition[]
// Mutation hooks
useCreateTemplateStatus(templateId)
useUpdateTemplateStatus(templateId) // takes { id, input } — statusId CAN be renamed
useDeleteTemplateStatus(templateId)
useCreateTemplateStatusTransition(templateId)
useDeleteTemplateStatusTransition(templateId)
useReorderTemplateStatuses() // takes { templateId, statusIds: string[] }
use-workflow.ts
// Types
WorkflowStep // id, applicationTemplateId, stepId(slug), name, description,
// stepType (MANUAL|SYSTEM), systemHandler, templateStatus, positionX, positionY, sortOrder
WorkflowStepTransition // id, fromStep, toStep, transitionType, label
WorkflowHandlerInfo // handlerId, displayName
WorkflowStepType // 'MANUAL' | 'SYSTEM'
TransitionType // 'USER' | 'SYSTEM' | 'BOTH'
// JSON definition types (used by WorkflowJsonDialog)
WorkflowDefinitionStep // stepId(slug), name, description, stepType, systemHandler,
// statusId(slug), positionX, positionY, sortOrder
WorkflowDefinitionTransition // fromStepId(slug), toStepId(slug), transitionType, label
WorkflowDefinition // { steps: WorkflowDefinitionStep[], transitions: WorkflowDefinitionTransition[] }
// Query hooks
useWorkflowSteps(templateId)
useWorkflowStepTransitions(templateId)
useAvailableWorkflowHandlers()
// Mutation hooks
useCreateWorkflowStep(templateId)
useUpdateWorkflowStep(templateId)
useDeleteWorkflowStep(templateId)
useUpdateWorkflowStepPosition(templateId) // debounced drag save
useCreateWorkflowStepTransition(templateId)
useUpdateWorkflowStepTransition(templateId) // can move from/to step
useDeleteWorkflowStepTransition(templateId)
useReplaceWorkflow(templateId) // atomic bulk replace via JSON definition
use-application-workflow.ts
useApplicationStatusHistory(applicationId) // → ApplicationStatusTransition[]
useApplicationWorkflow(applicationId, enabled?) // → ApplicationWorkflowInfo | null
useAdvanceWorkflowStep(applicationId) // { applicationId, toStepId }
useTriggerWorkflowStepCheck(applicationId) // { applicationId }
Query Keys
queryKeys.templateStatuses(templateId)
queryKeys.templateStatusTransitions(templateId)
queryKeys.workflowSteps(templateId)
queryKeys.workflowStepTransitions(templateId)
queryKeys.applicationStatusHistory(applicationId)
Template Statuses Page (application-template-statuses.tsx)
Route: /o/$orgSlug/application-setup/templates/$templateId/statuses/
Layout
- Page header:
flex flex-wrap items-end justify-between gap-2with h2 title + muted description on left, "Add Status" button on right - Table wrapped in
overflow-x-auto rounded-md borderfor horizontal scroll on small screens
Status Table
Columns: drag handle, # (sort order), statusId badge, numericCode, internalName, externalName (actual value — not "(same)"), flags (Initial/Final/"manual allowed" badges), color swatch, row actions kebab menu
Drag-and-drop reordering:
- Native HTML5 drag events:
draggable,onDragStart,onDragOver,onDrop,onDragEnd - Local
orderedStatusesstate for optimistic UI; rolls back on error dragFromIndex/dragOverIndexareuseState(NOTuseRef) — ref access during render is a lint error- On drop: reorders locally, calls
useReorderTemplateStatuseswith new ID order
Row actions (kebab menu):
- Edit (pencil icon) — opens sheet pre-filled
- Delete (trash icon) — rare action, moved to kebab to reduce clutter
Create/Edit Sheet (Shadcn Sheet pattern)
<SheetContent className="flex flex-col">
<SheetHeader className="text-start">...</SheetHeader>
<div className="flex-1 space-y-5 overflow-y-auto px-4 py-2">
{/* form fields */}
</div>
<SheetFooter className="gap-2">
{/* Delete (mr-auto destructive) | Cancel (SheetClose) | Save */}
</SheetFooter>
</SheetContent>
statusIdfield: editable in both create and edit mode (uniqueness enforced by backend)- Hint: "Used as identifier in JSON definitions"
useInternalAsExternaltoggle hides the externalName field when on- Description:
<Textarea rows={3}> manuallySettabletoggle: "Can officers manually set this status?" — displayed as "manual allowed" badge in table
Transitions Matrix
- Shown only when
statuses.length >= 2 style={{ minWidth: \${120 + orderedStatuses.length * 108}px` }}` — ensures proper width- Numbered column/row headers (
#1,#2…) - Each cell: cycles
null → USER → SYSTEM → BOTH → nullon click; colored by type TRANSITION_COLORSrecord maps type to Tailwind classes
EMPTY ARRAY PATTERN — CRITICAL
const EMPTY_STATUSES: TemplateStatus[] = []
const { data: statuses = EMPTY_STATUSES } = useTemplateStatuses(templateId)
Never write const { data: statuses = [] } — the inline [] is a new reference every render,
causing any useEffect that includes statuses in its dependency array to loop infinitely.
Workflow Builder (application-template-workflow.tsx)
Route: /o/$orgSlug/application-setup/templates/$templateId/workflow/
Layout
- Page header: same pattern as statuses —
flex flex-wrap items-end justify-between gap-2 border-b px-6 py-4with h2 title + description on left, button group on right - Button group (right-aligned):
[Edit as JSON](outline, Braces icon) ·[Add Transition](outline, Plus icon, disabled whensteps.length < 2 || isLocked) ·[Add Step](primary, Plus icon) - Minimap toggle: rendered as
<Panel position="bottom-right">inside<ReactFlow>— not in the toolbar
Canvas
@xyflow/reactReactFlow v12 (useNodesState<Node>,useEdgesState<Edge>— explicit type params required)- Node type:
workflowStep→WorkflowStepNode proOptions={{ hideAttribution: true }}
Lock / Unlock
isLockedstate controlsnodesDraggable,nodesConnectable,elementsSelectable, and all handlers<WorkflowControls>renders<Controls showInteractive={false}>+<ControlButton>for lock (amberLockicon when locked)- Must be rendered inside
<ReactFlow>(requires ReactFlow context) - When locked,
WorkflowStepPanelreceivesreadOnly={true};onNodeClick/onConnect/onNodeDragStopare set toundefined
Node Interactions
- Node click → opens
WorkflowStepPanel(right Sheet) - Edge click → opens
WorkflowTransitionPanelin edit mode (pre-filled with existing transition) - Draw connection (
onConnect) → opensWorkflowTransitionPanelin create mode withinitialFromStepId/initialToStepIdpre-filled - "Add Transition" button → opens
WorkflowTransitionPanelin create mode with empty selects - Auto-save position:
handleNodeDragStopdebounces 800ms →useUpdateWorkflowStepPosition
transitionMap
const transitionMap = useMemo(() => {
const m = new Map<string, WorkflowStepTransition>()
for (const t of transitions) m.set(t.id, t)
return m
}, [transitions])
Used in handleEdgeClick to look up the full transition object from the ReactFlow edge id.
Panels and Dialogs
All rendered at the bottom of the component (outside the canvas div):
<WorkflowTransitionPanel transition={selectedTransition} ... open={transitionPanelOpen} />
<WorkflowStepPanel step={selectedStep} ... open={panelOpen} readOnly={isLocked} />
<WorkflowTransitionPanel initialFromStepId={...} initialToStepId={...} ... open={newTransitionPanelOpen} />
<WorkflowJsonDialog steps={steps} transitions={transitions} ... open={jsonDialogOpen} />
Workflow Step Node (workflow-step-node.tsx)
Custom ReactFlow node:
- Blue border = MANUAL, Purple border = SYSTEM
- Green dot (top-left) = initial status, Red dot (top-right) = final status
- Shows: step name, stepType badge, linked status pill (colored by
templateStatus.color) Handlefor source (right) and target (left)
Workflow Step Panel (workflow-step-panel.tsx)
Right-side Sheet for configuring a step. Uses the standard Sheet pattern:
<SheetContent className="flex flex-col overflow-x-hidden">
<SheetHeader className="text-start">...</SheetHeader>
<div className="flex-1 space-y-5 overflow-y-auto px-4 py-2">
{/* Step Name, Step ID, Description, Step Type, System Handler, Linked Status */}
</div>
<SheetFooter className="gap-2">
{/* Delete (mr-auto) | Cancel (SheetClose) | Save */}
</SheetFooter>
</SheetContent>
overflow-x-hidden on SheetContent is required — long Select option text can overflow the sheet width.
Fields
| Field | Notes |
|---|---|
| Step Name | Input, required |
| Step ID | Input, editable slug (e.g. "initial-review"), auto-sanitised to lowercase-kebab-case on input, hint: "Used as identifier in JSON definitions and transitions." |
| Description | Textarea rows={3} |
| Step Type | Select MANUAL / SYSTEM |
| System Handler | Select, shown only when stepType === 'SYSTEM' |
| Linked Status | Select with '__none__' sentinel — Radix forbids value="" |
Effect Dependency
const stepId = step?.id // use the UUID, not the full object
useEffect(() => {
if (!step) return
setStepSlug(step.stepId ?? '')
setName(step.name)
// ...
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stepId])
Workflow Transition Panel (workflow-transition-panel.tsx)
Handles both create and edit mode in a single Sheet component:
type Props = {
transition?: WorkflowStepTransition | null // present → edit mode
initialFromStepId?: string // pre-fill for draw/button create
initialToStepId?: string
steps: WorkflowStep[]
templateId: string
open: boolean
onClose: () => void
}
isEditMode = !!transitionuseEffectkeyed onopento repopulate form for both modes- "To" step select filters out the selected "From" step
- Edit mode: Delete button left of footer (destructive,
mr-auto) disabled={!fromStepId || !toStepId || fromStepId === toStepId || isPending}
Workflow JSON Editor (workflow-json-dialog.tsx)
"Edit as JSON" button opens a wide Dialog (max-w-3xl) for bulk editing the entire workflow.
JSON Format
{
"steps": [
{
"stepId": "initial-review",
"name": "Initial Review",
"description": null,
"stepType": "MANUAL",
"systemHandler": null,
"statusId": "pending",
"positionX": 100,
"positionY": 200,
"sortOrder": 0
}
],
"transitions": [
{
"fromStepId": "initial-review",
"toStepId": "document-collection",
"transitionType": "USER",
"label": null
}
]
}
No UUIDs. Both steps and transitions use the stepId slug as identifier. statusId is also a slug.
Save Rules (backend replaceWorkflow)
stepIdmatches existing step → update that stepstepIdnot found → create new step with that slug- Step absent from list → delete (cascades its transitions)
- All existing transitions are deleted and recreated from the
transitionsarray fromStepId/toStepIdmust match astepIdin thestepsarray of the same payload
Frontend Dialog Behaviour
buildDefinition(steps, transitions)called on open — falls back to UUID ifstepIdis null (legacy steps)- "Format JSON" button re-pretty-prints
- Inline error displayed below textarea on invalid JSON or missing arrays
- On save:
JSON.parse→ validate shape →replaceWorkflow.mutateAsync→ invalidates both query keys
stepId Slug on WorkflowStep
WorkflowStep.stepId is a human-readable slug unique per template (e.g. "initial-review").
| Scenario | Behaviour |
|---|---|
createStep with stepId provided | Use as-is; error if duplicate within template |
createStep without stepId | Auto-generated from name via toSlug() + numeric suffix for conflicts |
updateStep with new stepId | Uniqueness check excluding self; error if duplicate |
Existing step with null stepId (pre-feature) | Lazy backfill: findStepsByTemplateId auto-generates and saves slugs on first call |
toSlug(name): lowercase, strip non-[a-z0-9 -], trim, spaces→hyphens, collapse --.
manuallySettable on TemplateStatus
TemplateStatus.manuallySettable: boolean = true (default true).
true— officers can manually set the application to this statusfalse— status is set only by the workflow engine (system-only)- Displayed as "manual allowed" badge in the flags column of the status table (shown only when
true) - Toggle in create/edit sheet: "Can officers manually set this status?"
statusId Slug on TemplateStatus
statusId is editable in both create and edit mode. The backend enforces uniqueness per template (excluding self on update). No side effects from renaming: the workflow engine uses UUID + copies name strings internally.
Backend: replaceWorkflow Mutation
replaceWorkflow(templateId: ID!, input: WorkflowDefinitionInput!): WorkflowDefinition!
Atomic @Transactional service method in WorkflowService.replaceWorkflow():
- Load existing steps → build
slug → WorkflowStepmap - Delete all existing transitions
- For each step in input: matched by
stepIdslug → update; unknown slug → create - Delete steps not present in input
- Recreate transitions, resolving
fromStepId/toStepIdslugs from the saved step map statusIdslug resolved viaTemplateStatusRepository.findByApplicationTemplateIdAndStatusId()- Returns fresh
WorkflowDefinitionResult(reloaded from DB)
Backend: reorderTemplateStatuses Mutation
reorderTemplateStatuses(templateId: ID!, statusIds: [ID!]!): [TemplateStatus!]!
Assigns sortOrder = index for each status in the provided order. Frontend sends optimistic reorder and rolls back on error.
GraphQL Mutations Reference
# Status CRUD
createTemplateStatus(templateId: ID!, input: TemplateStatusInput!): TemplateStatus!
updateTemplateStatus(id: ID!, input: TemplateStatusInput!): TemplateStatus!
deleteTemplateStatus(id: ID!): Boolean!
reorderTemplateStatuses(templateId: ID!, statusIds: [ID!]!): [TemplateStatus!]!
createTemplateStatusTransition(templateId: ID!, input: TemplateStatusTransitionInput!): TemplateStatusTransition!
deleteTemplateStatusTransition(id: ID!): Boolean!
# Workflow step CRUD
createWorkflowStep(templateId: ID!, input: WorkflowStepInput!): WorkflowStep!
updateWorkflowStep(id: ID!, input: WorkflowStepInput!): WorkflowStep!
deleteWorkflowStep(id: ID!): Boolean!
updateWorkflowStepPosition(id: ID!, x: Float!, y: Float!): WorkflowStep!
# Workflow transition CRUD
createWorkflowStepTransition(input: WorkflowStepTransitionInput!): WorkflowStepTransition!
updateWorkflowStepTransition(id: ID!, input: WorkflowStepTransitionInput!): WorkflowStepTransition!
deleteWorkflowStepTransition(id: ID!): Boolean!
# Bulk JSON replace
replaceWorkflow(templateId: ID!, input: WorkflowDefinitionInput!): WorkflowDefinition!
# Application workflow advancement (in ApplicationController)
advanceWorkflowStep(applicationId: ID!, toStepId: ID!): Application!
triggerWorkflowStepCheck(applicationId: ID!): WorkflowCheckResult!
Application Detail Workflow Display
Added to src/features/applications/detail/overview/index.tsx:
- Workflow card hidden when
applicationTemplateIdis null - Two tabs: Flow (read-only ReactFlow) and History (transition table)
Flow Tab (application-workflow-flow.tsx)
Visual states:
- Past: greyed (
opacity: 0.6), checkmark overlay — step invisitedStepIds - Current: indigo border + background, pulsing dot
- Future: default styling
Action bar (when canDecide):
- USER-type outgoing transitions:
[→ Advance to X]buttons - SYSTEM step:
[Run Check Now]button + result badge - Final step: completion badge, no buttons
History Tab (application-workflow-history.tsx)
ApplicationStatusTransition table (newest first): #, From, To, Triggered By, Step, Date (relative)
Backend Workflow Engine
workflowEngine.advanceByUser(applicationId, toStepId, currentUserId)
workflowEngine.triggerSystemCheck(applicationId) // → WorkflowHandlerResult
workflowEngine.onDomainEvent(applicationId) // called by WorkflowEventListener
advanceByUser throws if the transition is SYSTEM-only.
System Handlers
Implement WorkflowStepHandler + @Component → auto-registers via WorkflowHandlerRegistry.
Current: document-completeness-check — all required doc slots have ≥1 APPROVED document.
Event Flow
reviewDocument() → ApplicationDocumentStatusChangedEvent
→ WorkflowEventListener → workflowEngine.onDomainEvent()
→ handler.execute() → if satisfied → enterStep(nextStepId, "SYSTEM")
Known Patterns & Pitfalls
1. Empty array defaults cause infinite loops
// ❌ WRONG — new [] reference every render
const { data: steps = [] } = useWorkflowSteps(templateId)
// ✅ CORRECT — stable module-level constant
const EMPTY_STEPS: WorkflowStep[] = []
const { data: steps = EMPTY_STEPS } = useWorkflowSteps(templateId)
Required for all hooks whose data feeds into a useEffect dependency array.
2. useNodesState/useEdgesState require explicit type params
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([])
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([])
3. Radix SelectItem forbids empty string value
<SelectItem value="__none__">None</SelectItem>
// convert: templateStatusId === '__none__' ? null : templateStatusId
4. Lucide Map shadows built-in Map
import { Map as MapIcon, Plus } from 'lucide-react'
5. WorkflowControls must be inside <ReactFlow>
<Controls> and <ControlButton> require the ReactFlow context.
6. WorkflowStepPanel useEffect dependency
Use step?.id (UUID), not the full step object:
const stepId = step?.id
useEffect(() => { ... }, [stepId])
7. Drag state must be useState, not useRef
// ❌ ref access during render — lint error
const dragFromIndex = useRef<number | null>(null)
// ... className={dragFromIndex.current === i ? 'opacity-50' : ''}
// ✅ state
const [dragFromIndex, setDragFromIndex] = useState<number | null>(null)
8. Sheet overflow-x-hidden for step panel
Without overflow-x-hidden on SheetContent, long Select option text (e.g. status names) causes a horizontal scrollbar inside the sheet.
9. MapStruct stale mapper during mvn spring-boot:run
pom.xml has <useIncrementalCompilation>false</useIncrementalCompilation> in the maven-compiler-plugin config. This forces full recompile and ensures MapStruct always regenerates *MapperImpl classes. Do not remove it.
10. stepId lazy backfill
WorkflowService.findStepsByTemplateId() is @Transactional and auto-assigns stepId slugs to any step that doesn't have one. This is a one-time migration on first load after the feature was added.
Translations
All keys in src/lib/i18n/translations/{en,de,ja}/applicationTemplates.ts.
Key groups:
applicationTemplates.sidebar.statuses / workflow
applicationTemplates.statuses.title / description / addStatus / noStatuses / initial / final
applicationTemplates.statuses.columns.statusId / numericCode / internalName / externalName / flags / color
applicationTemplates.statuses.sheet.createTitle / editTitle / statusId / statusIdHint / numericCode /
internalName / sameAsInternal / externalName / description / color / isInitial / isFinal / manuallySettable
applicationTemplates.statuses.transitions.title / description
applicationTemplates.workflow.title / description
applicationTemplates.workflow.addStep / editJson / minimap / noSteps
applicationTemplates.workflow.step.create.success/error
applicationTemplates.workflow.step.update.success/error
applicationTemplates.workflow.step.delete.success/error
applicationTemplates.workflow.transition.add
applicationTemplates.workflow.transition.create.success/error
applicationTemplates.workflow.transition.update.success/error
applicationTemplates.workflow.transition.delete.success/error
applicationTemplates.workflow.replace.success/error
applicationTemplates.workflow.stepType.manual / system
applicationTemplates.workflow.panel.title / stepName / stepId / description / stepType /
systemHandler / linkedStatus / delete / save
Sidebar Navigation
Registered in src/components/layout/data/sidebar-data.tsx via getApplicationTemplateDetailNavItems():
- Statuses — icon:
Tag, route:.../statuses/ - Workflow — icon:
GitBranch, route:.../workflow/