urban-workflow-playground
English · Français
Web components to write, visualize and test urban-workflow workflows, and the server that provides them with real analysis and simulation (ANTLR parser, visitor, in-memory engine).
The package serves two purposes:
- the playground: a ready-to-use page (
urban-workflow-playground, port 8777) that assembles all the components; - a client application: it mounts the components it wants, where it wants them (the editor alone, the diagram next to a form, tabs of its choosing…), and serves the API from its own FastAPI application.
Running the playground
pip install urban-workflow-playground
urban-workflow-playground # http://127.0.0.1:8777
or python -m urban_workflow_playground. Variables: PLAYGROUND_HOST
(default 127.0.0.1), PLAYGROUND_PORT (default 8777),
PLAYGROUND_LAYOUT_DIR (layout directory, see below).
From a clone of the repository: pip install -e ./playground.
The components
Each component renders into the element it is given and communicates with the others only through a shared workspace. None is mandatory, and none assumes another one is present.
| Name | What it displays |
|---|---|
editor |
workflow text: highlighting, contextual intellisense (Ctrl+Space or automatic), errors underlined and listed below (option diagnostics: false to hide the list) |
status |
badge with the result of the last analysis (« script valide » (valid script), « 2 erreur(s) » (2 errors)) |
diagram |
editable graphical view (states, transitions, initializers ⚡, expirations ⏰), with its toolbar; « Générer le DSL » (Generate the DSL) writes the canonical script back into the workspace |
events |
list of events; for each one, the common base (read-only) and the visual editor of its business schema |
states |
the same for states |
scripts |
rule scripts (AS @name, expression language) and artifact scripts (USING @name, jslt language), with completion, errors inside the script and a preview of the produced JSON; server-side Python functions shown read-only |
rules |
simulated business rules (code rules do not exist in the browser: name, typed parameters, result) |
schemas |
JSON schemas (YAML) of events, states and artifacts, with what uses them |
structure |
compiled configuration: initializers, states, transitions |
simulation |
JSON events replayed on the real in-memory engine: states reached, actions and their messages, log |
help |
language cheat sheet |
examples |
drop-down list of examples, which loads the chosen example into the workspace |
tabs |
tabbed container of components (optional) |
UrbanWorkflow.components() lists the loaded components, with their
label and description.
Mounting components
A host page loads the dependencies (CodeMirror, X6, the schema editor),
then a single components file, static/urban-workflow.js, and its
stylesheet:
<link rel="stylesheet" href="/workflow/static/vendor/codemirror.css">
<link rel="stylesheet" href="/workflow/static/vendor/show-hint.css">
<link rel="stylesheet" href="/workflow/static/vendor/lint.css">
<link rel="stylesheet" href="/workflow/static/vendor/schema-editor.css"> <!-- events, states -->
<link rel="stylesheet" href="/workflow/static/urban-workflow.css">
<div id="texte"></div>
<div id="schema"></div>
<span id="etat"></span>
<script src="/workflow/static/vendor/codemirror.js"></script>
<script src="/workflow/static/vendor/simple.js"></script>
<script src="/workflow/static/vendor/show-hint.js"></script>
<script src="/workflow/static/vendor/lint.js"></script>
<script src="/workflow/static/vendor/x6.min.js"></script> <!-- diagram -->
<script src="/workflow/static/vendor/schema-editor.js"></script> <!-- events, states -->
<script src="/workflow/static/urban-workflow.js"></script>
<script>
const ws = UrbanWorkflow.createWorkspace({
storage: false, // the application provides the data
script: monWorkflow,
schemas: [{name: "schema_Demande", yaml: "type: object\n…"}],
});
UrbanWorkflow.mount("editor", "#texte", {workspace: ws});
const diagramme = UrbanWorkflow.mount("diagram", "#schema", {workspace: ws});
diagramme.activate(); // first display: imports the script
UrbanWorkflow.mount("status", "#etat", {workspace: ws});
ws.on("change", (c) => { if (c.what === "script") enregistrer(ws.getScript()); });
</script>
Dependencies are only needed for the components that use them: X6 for
diagram, the schema editor for events and states, CodeMirror for
editor and scripts.
UrbanWorkflow.mount(name, element | selector, options) returns the
component instance. All instances have activate() (to call when the
component becomes visible again after being hidden: CodeMirror and X6
re-measure, the diagram imports the script on its first display) and
destroy(). Some have more:
| Component | Methods |
|---|---|
editor |
codemirror, focus(), refresh(); option readOnly |
diagram |
reload(), generate(), getModel(), getPositions(), setLayoutStore(store); option layoutStore |
simulation |
run() (promise of the result) |
scripts |
select(name), runPreview(), editor() |
events, states |
select(name) |
tabs |
show(index), current(), component(name) |
Several instances of the same component can coexist in a page, on the
same workspace or on different ones: no component uses a fixed id.
The workspace
const ws = UrbanWorkflow.createWorkspace({
apiBase: "https://exemple.fr/workflow/", // default: derived from the URL of urban-workflow.js
storage: "local", // default; false = nothing is kept;
// or {load(nom), save(nom, valeur)}
storageKey: "uw-playground", // prefix of the localStorage keys
script: "", rules: [], schemas: [], scripts: [], events: [],
analyzeDelay: 450, // ms between a change and the analysis
});
| Data | Read | Write |
|---|---|---|
| workflow script | getScript() |
setScript(text), insertText(index, text) |
simulated rules {name, params: [{name, type}], result} |
getRules() |
setRules(list), setRule(rule), removeRule(name) |
schemas {name, yaml} |
getSchemas(), findSchema(name) |
setSchemas(list), setSchema(name, yaml), removeSchema(name) |
scripts {name, language, source} |
getScripts(), findScript(name) |
setScripts(list), setScriptSource(name, source, language), removeScript(name) |
| sample events | getEvents() |
setEvents(list) |
| last analysis | getAnalysis(), getErrors(), getSymbols() |
analyze() (immediate, promise) |
Any change to the script, rules, schemas or scripts triggers a new
analysis after analyzeDelay. Rules, schemas and scripts are persisted by
storage; the script and the events are not (it is up to the application
to keep the workflow wherever it stores it).
Events (ws.on(name, fn) returns the unsubscribe function):
| Event | Detail |
|---|---|
change |
{what: "script" | "rules" | "schemas" | "scripts" | "events", origin} |
analyze |
the new analysis {ok, errors, symbols, summary} |
analyze-error |
the server did not respond |
reveal |
{component, detail}: display request (see below) |
Other methods: simulate(events?) (promise of the /api/simulate
result), loadExample(example), api(path, body?, method?) (raw API
call), payload() (the common request body).
Display requests. A component does not know where the others are.
When it needs to show one (click on an error located in a script,
« Générer le DSL » producing a new text), it calls
ws.reveal("scripts", {script, line, column}) or ws.reveal("editor").
The target component reacts to the detail, and the tabs container
activates the tab that holds it. An application that lays out the
components differently listens to ws.on("reveal", …) to do the same.
Tabs
const onglets = UrbanWorkflow.mount("tabs", "#zone", {
workspace: ws,
tabs: [
{label: "Texte", component: "editor"},
{label: "Diagramme", component: "diagram", options: {layoutStore: monStockage}},
{label: "Paramétrage", components: ["rules", "schemas"]},
],
active: 0, // index, or name of a component
onChange: (index, onglet) => {},
});
onglets.component("diagram").reload();
The playground page is nothing more than a composition of this kind
(static/page.js).
Appearance
Styles are scoped under the .uw class (set by mount on each element)
and do not leak into the host page. Colors are CSS variables you can
redefine: --uw-bg, --uw-panel, --uw-border, --uw-text,
--uw-muted, --uw-accent, --uw-ok, --uw-ko, --uw-mono.
:root { --uw-accent: #e0a030; }
The server in a client application
create_app builds the FastAPI application (API, page and static files).
Mount it under the prefix of your choice:
from fastapi import FastAPI
from urban_workflow_playground.app import create_app
from mon_appli.regles import plafond_ok, client_connu
app = FastAPI()
app.mount("/workflow", create_app(
python_functions={"plafond_ok": plafond_ok, "client_connu": client_connu},
layout_dir="/var/lib/mon-appli/layouts",
))
python_functions:(ctx, **params)functions that workflows reference withDEFINE RULE … AS @name; their names are reserved, and thescriptscomponent shows them read-only. Defaults to the playground examples;{}to publish none. The browser, for its part, can only writeexpressionandjsltscripts: no Python code coming from the browser is ever executed;layout_dir: directory of the diagram layout files (defaults toPLAYGROUND_LAYOUT_DIR; otherwise positions stay in the browser).
The page and the components only use relative URLs: mounted under
/workflow, the playground opens at /workflow/ (with the trailing
slash) and components loaded from /workflow/static/… talk to
/workflow/api/… without configuration. A host page served elsewhere
passes apiBase to createWorkspace.
HTTP API
| Route | Purpose |
|---|---|
POST /api/analyze |
{script, rules, schemas, scripts} → {ok, is_fragment, errors, symbols, summary}; positioned errors (line, column, script if the error is inside a script) |
POST /api/simulate |
same + events → steps (impacted workflows, states, actions and messages, log) and final storage state |
POST /api/diagram |
{script} → surface model of the diagram |
POST /api/generate |
{model, rules, schemas, scripts} → canonical script + analysis |
POST /api/artifact-preview |
{script, …, artifact, event, state} → JSON produced by the artifact, or the violations of its schema |
GET /api/base-event-schema, GET /api/base-state-schema |
common bases (JSON Schema) |
GET/PUT /api/layout/{workflow} |
diagram layout (below) |
GET /healthz |
liveness probe |
GET /static/urban-workflow.js, /static/urban-workflow.css |
components and styles in one file |
Diagram layout
Like schemas and scripts, state positions are not in the workflow
document: they live in a file next to it, <workflow>.layout.json,
indexed by state name:
{"version": 1, "workflow": "Reclamation",
"states": {"Ouvert": {"x": 280, "y": 60}, "Clos": {"x": 600, "y": 100}}}
A state missing from the file (added in the text editor, for example) is placed automatically, on its own: the others keep their place. « Réorganiser » (Rearrange) repositions everything.
Three ways to store it:
-
files on the server — with
layout_dir(orPLAYGROUND_LAYOUT_DIR=/path), the server reads and writes/path/<workflow>.layout.jsonthroughGET /api/layout/{workflow}({"enabled": true, "layout": …|null}) andPUT /api/layout/{workflow}(the document above, or the short form{"State": {"x": …, "y": …}}). Workflow name and positions are validated, writes are atomic.urban_workflow_playground.layout.LayoutDirectorydoes the same from Python; -
browser — without a directory (
enabled: false), positions stay inlocalStorage; -
application storage — the application plugs its own into the component:
const diagramme = UrbanWorkflow.mount("diagram", "#schema", { workspace: ws, layoutStore: { load: (workflow) => fetch(`/mon-api/${workflow}/layout`).then(r => r.json()), save: (workflow, positions) => fetch(`/mon-api/${workflow}/layout`, {method: "PUT", body: JSON.stringify(positions)}), }, }); // or later: diagramme.setLayoutStore(store); diagramme.reload();
loadreturns{State: {x, y}}(or a promise, ornull);setLayoutStore(null)reverts to the default storage.
Notes
- A development tool: the server has no authentication. An application that mounts it puts it behind its own; do not expose it publicly as is.
- Simulated rules return the configured boolean (the engine and the validation are real; only the internal logic of the rules is replaced).
- The visitor stops at the first semantic error: errors are therefore fixed one at a time (syntax errors, however, are reported all at once).
- The diagram works on the script's surface model
(
urban_workflow.dsl.surface): guards and execution blocks remain raw DSL text there; generation rewrites the script in canonical form (--comments are not preserved).FRAGMENTscripts or scripts withINCLUDEcannot be represented as a diagram. urban_workflow_playground/static/vendor/: CodeMirror 5.65.18 and AntV X6 3.1.8 (MIT licenses), bundled to work offline; theschema-editor.jsschema editor is built fromplayground/schema-editor/.
Metadata
Release files for urban-workflow-playground 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| urban_workflow_playground-0.3.0.tar.gz | 532.4 kB | With provenance | Details for urban_workflow_playground-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| urban_workflow_playground-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for urban_workflow_playground-0.3.0-py3-none-any.whl |
Total release size: 1.1 MB
Release files / urban_workflow_playground-0.3.0.tar.gz
| Download URL | urban_workflow_playground-0.3.0.tar.gz |
|---|---|
| Size | 532.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9739258c87a0dcfb3c3ed1ea5740703ee9d9b24c126e5fe7beab6c8c2a42b770
|
|
BLAKE2b-256 checksum How to use checksums |
a3fb799ee7cf1c20fde0567ef5f377f3490090fa96a54958488aa2fd6bcb958e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logRelease files / urban_workflow_playground-0.3.0-py3-none-any.whl
| Download URL | urban_workflow_playground-0.3.0-py3-none-any.whl |
|---|---|
| Size | 536.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
987e0caf524ed7eb845bd1bd715e943984ff505e05b1f922525af5b79be547c5
|
|
BLAKE2b-256 checksum How to use checksums |
8f0fc16589ee000f144073d55691161d323c24e072e77f6e9de162494618a09a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logSecurity for urban-workflow-playground 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for urban_workflow_playground-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.