Skip to content

Mobile Process Designer

openwcs-docs-agent edited this page Jun 18, 2026 · 15 revisions

Mobile Process Designer

All three phases are now built (feature complete). The user-facing guide lives at Process Designer; this page is the design-detail reference. One correction from the original draft: the implemented service uses the base path /api/process-designer/** (the Flowable process-engine owns /api/process/**), so the API paths below read /api/process-designer/....

The mobile process designer lets a non-developer build, version, and publish configurable handheld operator processes (goods-in, packing, stock-check, ad-hoc moves) without writing code. A process is a JSON definition of ordered screens and tasks; the PWA operator shell fetches the active definition and runs it on the handheld.

Spec: docs/process-designer-spec.md — authoritative detail on the model, API, and designer UI.

Status: Phase 1 + Phase 2 + Phase 3 built, feature complete (service process-designer :8097 + the designer UI and client-driven handheld runtime). Phase 2 added version management (duplicate/clone, JSON import/export), step-level skipWhen skip conditions, four more curated tasks (host.confirm, inventory.adjust, counting.capture, order.lookup) + a live task catalog, and a read-only Process instances monitoring screen. Phase 3 added a sandboxed scripting escape hatch (a script task type in a locked-down GraalJS sandbox, gated by the OPENWCS_PROCESS_SCRIPTING_ENABLED flag and the new PROCESS_SCRIPT_AUTHOR permission) and design-time AI task-assist (POST /assist/task → a curated-task mapping or a draft sandboxed snippet for a human to review; never auto-deployed) plus a GET /capabilities endpoint. Post-Phase-3 increments (where the designer matured most): scan verification (a per-screen verify block that resolves a scanned/typed value against read-only resolve endpoints, branches on not-found, and writes resolved values into data-object variables, all on the curated-API path), with kinds grown from barcode / sku / location to also skuScan (barcode-or-SKU-code with an automatic UOM-picker when the SKU has multiple units), order (outbound order/picksheet barcode) and asn (inbound ASN barcode, both resolved via order-management GET /api/orders/resolve); resolved fields may be scalars or whole objects / drilled properties. No-code compute steps (type:"compute"): ordered { var, expr } rows evaluated client-side with the safe expression grammar (arithmetic + comparisons + boolean, no eval). No-code decision steps (type:"decision"): an ordered if/elseif rule list (transitions) plus an else/default next, evaluated client-side as a pure router (no screen, no write, no checkpoint). A guided-dialog UX: a definitions landing table entry point (one row per process key with title, key, status, active version, total version count, a + New process button and a ← Processes back button), and an Edit-screen / Edit-task / Verify-flow / Data-object dialog set. The visual node-canvas (React Flow) became the sole flow editor (the old Canvas / List toggle and structured list were removed): drag nodes to reposition (dragging is allowed even on read-only versions; position is pure layout and persists only on a draft), draw links between nodes (first link = next, further links = branches), edit branch conditions inline on edges, loops shown, a Tidy auto-layout. inventory.lookup gained huId lookup and auto-injected warehouseId. See Process Designer and Roadmap and Status.

Concepts

Concept Meaning
Process key Stable identifier for a process type, e.g. goods-in, packing. The operator menu shows the active version per key.
Process definition An immutable, versioned flow: ordered steps + transitions + the data-object schema. Status: DRAFT, ACTIVE, or ARCHIVED. Exactly one ACTIVE per key at any time.
Step A node in the flow — a screen step (renders a handheld screen, captures input), a task step (runs server-side work), a compute step (no-code, client-evaluated variable assignments; no screen rendered; no server call), or a decision step (no-code, client-evaluated if/elseif/else router; routes based on data-object conditions; no screen, no write, no server call).
Transition A directed edge between steps; may be unconditional or guarded by a condition over the data object for branching.
Data object The typed key/value context for one running instance; screens write to it, {{placeholder}} text and conditions read from it.
Instance One run of a process by an operator. Pinned to the definition version it started on.

Screen step types

All screens are glove-friendly, high-contrast, and support keyboard-wedge barcode scan capture via scanBinding. Common config fields: header, detail (both support {{placeholder}} interpolation), writeTo (data-object variable), and per-type validation.

Type Captures Notes
textInput string required, regex, maxLength, mustEqual validation; scan binding
numberInput number required, min, max, integerOnly, mustEqual
dateInput ISO date required, date range bounds; default = today
acknowledge nothing confirmLabel; optional required checkbox
questionYesNo boolean drives transitions via the data object
questionChoice string configurable answer options; choice drives transitions

Scan verification (verify block)

Text and Number input screens can carry an optional config.verify block. Where validation (regex / maxLength / mustEqual) only checks the shape of a value, verification confirms the code actually exists in master data and writes its linked ids (including the resolved UUID) into the data object, so a later task that needs a UUID gets it.

"verify": {
  "kind": "barcode",                  // barcode | sku | location | skuScan | order | asn
  "write": {                          // resolved field -> data-object variable
    "id":   "skuId",                  // the resolved UUID (a scalar)
    "code": "skuCode",
    "name": "skuName",
    "uomCode": "uom",
    "sku":  "skuObject"               // OR a whole object; OR a drilled "uom.factor"
  },
  "onNotFound": { "mode": "reprompt" } // or { "mode": "goto", "step": "<stepId>" }
}
  • kind picks what to resolve; the designer's picker is server-driven by capabilities.verifyKinds (["barcode","sku","location","skuScan","order","asn"]): barcode (a scanned barcode → SKU + UOMs + attribute-schema graph), sku (a SKU code; also accepts a barcode), location (a location code), skuScan (combined: tries the value as a product barcode first; if no match, as a SKU code; a barcode pins its UOM, a single-UOM SKU auto-picks, a multi-UOM SKU triggers a runtime UOM picker before the step advances), order (an outbound order / picksheet barcode), asn (an inbound order / ASN barcode). barcode/sku/location/skuScan resolve via master-data; order/asn resolve via order-management (GET /api/orders/resolve).
  • write maps resolved field paths to declared data-object variables. A path is a scalar key (id, code, …), an object whole key (stores the entire resolved object as an object-typed variable), or a dotted sub-field (uom.factor, sku.code, location.locationType, stores one property). Valid paths differ by kind (served via capabilities.verifyFields): location has scalars id, code, purpose, locationType, status plus object location (sub-fields locationId, code, locationType, purpose, status); SKU-like kinds (barcode / sku / skuScan) have scalars id, code, name, uomCode, schemaCategory plus object sku (skuId, code, description, status) and object uom (uomId, code, factor, baseUnit); order has scalars id, code, status, orderType, customerRef, lineCount plus object order; asn has the same scalar set plus object asn. A dotted path on a scalar field, or an unknown sub-field, fails 422 at publish. The per-kind catalog is the single source of truth shared by the designer, the runtime (VerifyResult.fields), and publish validation.
  • onNotFound is reprompt or goto a step; a goto target must exist and counts toward reachability.

At runtime the client posts POST /api/process-designer/verify {warehouseId, kind, code}, which proxies the read-only resolve endpoints (master-data for barcode/sku/location/skuScan, order-management GET /api/orders/resolve for order/asn) with the operator's forwarded identity and returns {found, ambiguous, id, code, name, uomCode, schemaCategory, matchedAs, uoms, needsUomChoice, detail, fields}. fields carries the per-kind resolved values: scalar fields are plain strings; object fields (sku, uom, location, order, asn) hold nested {subKey → value} maps. A dotted write path (uom.factor) drills into the nested map; a whole-object key (uom) stores the entire map. The runtime reads fields first, falling back to the legacy top-level scalars for older servers. The skuScan-specific fields: matchedAs ("barcode" or "sku") signals which path fired; uoms lists {code, baseUnit} entries for the SKU's available units; needsUomChoice is true when the SKU has more than one UOM. When needsUomChoice:true, the runtime shows a UOM picker overlay (button per unit, base unit labeled) and advances only after the operator selects one; the chosen code is written into the uomCode-mapped variable. Offline holds; found:false re-prompts or routes; found:true merges the write mappings and continues; ambiguous shows a subtle note. Simulate resolves locally (a "simulate not found" toggle). Validated at publish (valid kind, write keys / object roots map known per-kind fields to declared variables, goto step exists). No raw SQL: the resolve endpoints reuse the master-data card-read repositories (MASTER_DATA_VIEW) and the order read (ORDER_VIEW), returning 200 found:false on a miss (never 404). Base url OPENWCS_MASTER_DATA_BASE_URL; order/asn via openwcs.process-designer.orders-base-url.

Task steps (server-side work)

Task steps are server checkpoints that call existing audited services. Three tiers:

1 — Curated task library (Phase 1 + Phase 2, default path)

Pre-built parameterized task types; the designer maps data-object variables to inputs/outputs. RBAC and warehouse scope from the operator's identity are forwarded automatically. A task catalog endpoint GET /api/process-designer/tasks returns [{type, label, inputs[], outputs[]}], which drives the designer's task picker (so it always lists the real available task types).

Task type Phase Calls
slotting.putaway 1 POST /api/slotting/decant/putaway
inventory.move 1 POST /api/flow/moves
picking.confirm 1 POST /api/orders/pick-tasks/{lineId}/confirm
inventory.lookup 1 GET /api/inventory/availability (by locationId) or GET /api/inventory/handling-units/{huId}/contents (by huId; sums SKU lines on the HU); falls back to warehouse-wide when neither supplied. warehouseId auto-injected from the instance.
txlog.post 1 POST /api/txlog/events
host.confirm 2 POST /api/host/inventory/adjustments
inventory.adjust 2 POST /api/txlog/events (a StockAdjusted event)
counting.capture 2 POST /api/counting/tasks/{taskId}/lines/{lineId}/station-count
order.lookup 2 GET /api/orders/{id} (read into a variable)

New task types are added in code and land via the normal PR/CI pipeline.

2. Sandboxed script (Phase 3, controlled escape hatch, built)

A built-in script task type (config { script:"<js>", outputs:[{name}] }) runs server-side in a locked-down GraalJS sandbox (org.graalvm.polyglot community 24.1.1): allowAllAccess(false), HostAccess.NONE, no host class lookup/loading, no native/threads/process, IOAccess.NONE, no environment, PolyglotAccess.NONE: interpreted guest JS, never compiled to host JVM bytecode. Limits: a statement limit (default 100000), a wall-clock timeout (default 2000 ms), and an output cap (default 65536 bytes). The script sees only a deep-frozen read-only data global and returns an object whose fields become the declared outputs. Gated: a script step saves/publishes only when the OPENWCS_PROCESS_SCRIPTING_ENABLED flag is on (else 422) and the caller holds PROCESS_SCRIPT_AUTHOR (ADMIN only; else 403); each script is parse-validated in the sandbox at publish (malformed → 422).

3. AI task-assist (Phase 3, design-time only, built)

POST /api/process-designer/assist/task {description, variables:[{name,type}]}{kind:"curated"|"script"|"none", taskType?, inputs?, outputs?, script?, rationale, confidence} (gated PROCESS_DESIGN_EDIT). Uses the Anthropic Java SDK (default claude-haiku-4-5, ANTHROPIC_API_KEY), grounded with the live task catalog + the supplied variables; it either maps the description to a curated task type with variable mappings, or drafts a sandboxed JS snippet for a human to review and insert as a script step. The suggestion is never saved, compiled, or executed, and nothing is auto-deployed. Absent a key → 503 (the service still starts). AI text → compiled Java → a live server is permanently out of scope.

Compute steps (no-code, client-evaluated)

A compute step (type: "compute") carries an ordered set of { var, expr } rows. The handheld evaluates each row in sequence against the data object and writes the result into the named variable before resolving next/transitions. No screen is rendered; no server call is made; no checkpoint is created. Later rows see earlier writes. Use compute steps to derive flags and intermediate values:

{ "type": "compute", "set": [
    { "var": "match",    "expr": "qty == expected or qty == prevCount" },
    { "var": "delta",    "expr": "expected - qty" }
  ],
  "next": "decide"
}

Expression grammar — a superset of the when-condition grammar that adds arithmetic: identifiers; number, string ('…'/"…"), boolean (true/false) literals; arithmetic + - * / with unary minus and parentheses; comparisons == != <> < <= > >=; boolean and / or / not. No function calls, no member access. Server validates every expr with ExpressionParser at publish (non-empty set, every var declared, every expr parseable — 422 on failure). The designer mirrors this validation inline with live syntax feedback.

The walker processes compute steps the same way as skipWhen-true steps (no rendering, advances immediately), but also applies the set writes so downstream transitions see the computed values.

Decision steps (if/elseif/else router)

A decision step (type: "decision") is a no-code router: it evaluates ordered { when, to } if/elseif rules top-to-bottom (first match wins) and falls back to next (the else target). No screen, no server call, no writes. The runtime passes through it like a compute step — fully client-side, fully offline.

{ "type": "decision",
  "transitions": [
    { "when": "qty < expected", "to": "tooLow" },
    { "when": "qty > expected", "to": "tooHigh" }
  ],
  "next": "done"
}

Publish validation: at least one transition or a next required (dead-end decision → 422). Every when must parse and reference only declared data-object variables; every to/next must be an existing step id.

Branching

when conditions on transitions are a restricted grammar — comparisons (==, !=, <, <=, >, >=) and and/or/not over data-object variables and literals. Evaluated by a small interpreter; no eval, no host access. A condition (and a skipWhen) may reference only declared data-object variables, validated live in the designer and again at publish.

Versioning

  • Draft → publish sets the version ACTIVE and archives the prior one.
  • Rollback = re-publish a prior version (creates a new pointer).
  • In-flight pinning: running instances keep the version they started on; publishing never mutates live instances.

Offline execution

The handheld PWA is the runtime. Execution is client-driven:

  • On process start the handheld fetches the active definition (cached by the service worker).
  • Screen steps, compute steps, and decision steps run entirely client-side — no server round-trip.
  • Task steps are server checkpoints: the client posts {stepId, data}; the server runs the curated task and returns the updated data object + next step.
  • While offline, task calls queue (the existing offline queue); screen-only stretches keep working.
  • Instance state is checkpointed server-side at each task step — resume after device swap/reload.

The designer (WYSIWYG, desktop)

A desktop screen (Engineering section, gated by the process-design screen permission). It opens to a definitions landing table (a searchable list of processes: title, key, status, version, version count, with a New process button); selecting a row opens the editor (back button to return). The editor is built around a visual canvas and a live preview:

Area Content
Visual node canvas (the flow editor) The sole flow view (the old Canvas/List toggle + flow list were removed). Each step is a draggable React Flow node whose position persists into the model ui:{x,y} (nodes are draggable even on read-only ACTIVE/ARCHIVED versions, position being pure layout, but persisted only on a draft). Draw the flow by dragging a connector node→node: the first link is the default next (solid edge), a further link adds a branch (a dashed, condition-labelled edge) whose when you edit inline on the edge. Click an edge to select and delete it; loop-back edges are animated amber; orphan nodes render "not connected"; a mini-map aids navigation; a Tidy button auto-lays-out the graph. Default-next and branches live only on the canvas.
Live handheld preview The selected step rendered in a phone frame using the same ProcessScreenView the real handheld runtime uses, with {{placeholder}} resolved against sample data. Edits update the preview instantly.
Guided dialogs Buttons near the preview open dedicated dialogs: Edit screen (name, header/detail + placeholder picker, writeTo, validation builder, scan-binding, choice answers, acknowledge), Edit task (server task / compute / sandboxed script, a searchable task picker with task + per-input/output descriptions, AI task-assist), Edit decision (ordered if/elseif rule rows — each a condition input with live validation + a target-step picker — plus an "Otherwise / else go to" picker for the default target; rules evaluated top-to-bottom, first match wins), Verify flow (the scan-verification block), and Data object (typed variables; a VarCombobox autocomplete offers declared names in condition/when/write editors). The properties panel itself is now a per-step summary + skip-when editor.

Additional tools: simulate/test mode to step through the flow with fake input before publishing (task steps are dry-run; verify resolves locally with a "simulate not found" toggle); and validate + publish which blocks publication until all unreachable steps, dangling transitions, unknown placeholders, malformed conditions/expressions, and undeclared variables are resolved.

API surface

Served by the new process-designer service (port 8097), under the /api/process-designer base path:

  • GET /api/process-designer/defs?status= — list definitions
  • GET /api/process-designer/defs/{key}/{version} — get one version's full model JSON
  • GET /api/process-designer/defs/{key}/active — active definition for a process key
  • POST /api/process-designer/defs — create a draft
  • PUT /api/process-designer/defs/{key}/{version} — edit a draft (409 if not DRAFT)
  • POST /api/process-designer/defs/{key}/{version}/publish — publish (validates, activates, archives prior; 422 with problem list on failure)
  • POST /api/process-designer/defs/{key}/{version}/archive
  • GET /api/process-designer/processes — distinct process keys with their active version title/icon (used by the operator menu)
  • POST /api/process-designer/defs/{key}/{version}/duplicate — clone a version into a new draft (Phase 2)
  • POST /api/process-designer/defs/import — create a draft from a full definition JSON (Phase 2; export = GET /defs/{key}/{version} above)
  • GET /api/process-designer/tasks — task catalog driving the designer's picker (Phase 2)
  • POST /api/process-designer/instances {processKey} — start an instance (returns instance + active def)
  • POST /api/process-designer/instances/{id}/checkpoint {stepId, data} — run a task step (idempotent per instance+step)
  • GET /api/process-designer/instances/{id} — resume
  • GET /api/process-designer/instances?processKey=&status=&warehouseId=&limit= — instance history/monitoring summaries, newest-first (Phase 2)
  • POST /api/process-designer/assist/task {description, variables} (design-time AI task-assist, Phase 3): returns a curated-task mapping or a draft sandboxed snippet to review; never auto-deploys (gated PROCESS_DESIGN_EDIT; 503 when no Anthropic key)
  • GET /api/process-designer/capabilities (Phase 3): {scriptingEnabled, aiAssistEnabled, canAuthorScript, verifyKinds, verifyFields} driving the designer's show/hide of the script editor + AI assist, the scan-verify kind picker (["barcode","sku","location","skuScan","order","asn"]), and the per-kind resolvable-field catalog (scalar + object fields with drill-down sub-fields) for the step-by-step Verify dialog (gated PROCESS_DESIGN_VIEW)
  • POST /api/process-designer/verify {warehouseId, kind, code} (scan verification): proxies the read-only resolve endpoints (master-data for barcode/sku/location/skuScan, order-management GET /api/orders/resolve for order/asn) with the operator's forwarded identity; returns {found, ambiguous, id, code, name, uomCode, schemaCategory, matchedAs, uoms, needsUomChoice, detail, fields{...}} (fields = per-kind resolved values incl. nested objects; matchedAs/uoms/needsUomChoice populated for skuScan; clean found:false 200, downstream failure 502); gated PROCESS_DESIGN_VIEW
  • order-management GET /api/orders/resolve?warehouseId=&ref= (ORDER_VIEW): resolves an order/ASN by its reference → {found, order{orderId, orderRef, orderType, status, customerRef, lineCount}} (200 found:false on a miss); backs the order and asn verify kinds

Security

  • No designer-authored Java executes in-process. A script step runs only interpreted guest JS in a locked-down GraalJS sandbox (no host/Java/IO/threads/process/env, statement + wall-clock + output limits, a deep-frozen read-only data global), never compiled to host bytecode.
  • A script step saves/publishes only when the OPENWCS_PROCESS_SCRIPTING_ENABLED flag is on and the caller holds PROCESS_SCRIPT_AUTHOR (ADMIN only); else 422 (disabled) / 403 (no permission), and each script is parse-validated at publish.
  • AI task-assist is design-time only: it returns a suggestion the designer reviews; it never saves, compiles, or executes code, and never auto-deploys.
  • Task steps run with the operator's forwarded identity + warehouse scope; RBAC from existing services applies.
  • when conditions and {{placeholders}} are parsed/whitelisted — never eval'd.
  • Publishing a definition is an audited, permissioned action (requires PROCESS_DESIGN_EDIT).

See Services and Security for the identity-forwarding and RBAC model.

Clone this wiki locally