idea of flow renderer

This commit is contained in:
2026-06-12 22:28:29 +02:00
parent 25ff97d953
commit f87d41223e
4 changed files with 442 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# Interactive HTML Generator: Architecture & Flow Definition Format
Following the success of the interactive SVG PoC, this document outlines the proposed architecture for adding an **HTML Generator** to the State Machine Exporter, allowing users to define and visualize specific business flows dynamically.
## 1. The Flow Definition Format (JSON & JSON Schema)
To make the interactive HTML generator useful and intuitive, users need a way to define their business flows externally. Rather than reinventing the wheel, we looked at industry standards for defining graph paths, workflows, and execution traces:
* **Workflow Definitions:** Tools like *Amazon States Language (ASL)*, *SCXML*, and *XState* are highly structured but define the *entire* machine's logic. They are too heavy for simply defining a "path" through an existing machine.
* **Trace Formats:** *OpenTelemetry (OTel)* uses "Spans" to represent individual steps forming a "Trace". This is closer to what we want (a history of states).
* **Graph Formats:** The *JSON Graph Format (JGF)* standardizes graph nodes and edges representation but is geared towards raw data modeling rather than human-readable business flows.
**Conclusion on Format:** We will use a lightweight custom JSON format inspired by execution traces. It is designed to be highly readable for humans defining business flows, relying simply on ordered arrays of State names.
### JSON Schema for IDE Autocomplete
To make the format truly intuitive and prevent typos, we will publish a **JSON Schema** (`flow-schema.json`).
By referencing this schema, developers writing `flows.json` in IDEs like VS Code or IntelliJ will get **instant autocomplete, hover tooltips, and real-time validation** for fields like `id`, `title`, and `color`.
* *Future Enhancement:* The Java exporter could dynamically generate a project-specific JSON Schema on the fly that hardcodes the allowed `enum` values for the `path` array (e.g., restricted exactly to `"SUBMITTED"`, `"PAID"`, etc.), providing perfect autocomplete for state names!
### Proposed Schema (`flows.json`)
```json
{
"$schema": "./flow-schema.json",
"stateMachine": "click.kamil.examples.statemachine.simple.SimpleEnumStateMachineConfiguration",
"flows": [
{
"id": "flow-happy-path",
"title": "Happy Path (Success)",
"description": "The standard flow for a successful order: Submitted -> Paid -> Fulfilled.",
"color": "#32CD32",
"path": [
".start.",
"SUBMITTED",
"PAID",
"FULFILLED",
".end."
]
},
{
"id": "flow-cancel-post-pay",
"title": "Cancel Post-Payment",
"description": "Order is canceled after payment.",
"color": "#FF6347",
"path": [
".start.",
"SUBMITTED",
"PAID",
"CANCELED",
".end."
]
}
]
}
```
### Format Details:
* **`$schema`**: Points to the JSON schema for IDE tooling.
* **`stateMachine`**: Links these flows to a specific state machine configuration in the codebase.
* **`id`**: A unique identifier for the flow (used for DOM elements).
* **`title` & `description`**: Rendered in the HTML sidebar for the user.
* **`color` (Optional)**: Allows overriding the default highlight color (e.g., green for success, red for errors).
* **`path`**: An ordered array of **State Names** (acting like an OpenTelemetry trace).
* *Note on Edges:* The user only needs to define the *states* they pass through. The generator will automatically calculate the edges (transitions) between these states to highlight the lines. Special tokens like `.start.` and `.end.` map to the initial and terminal pseudo-states.
## 2. Robust Error Handling & Input Validation
Before generating the HTML, the exporter **must validate** that the provided `flows.json` is logically sound against the actual codebase AST. If a user defines a flow that isn't physically possible in the state machine, the tool must fail fast with highly descriptive errors.
### Validation Rules & Error Handling Strategy:
1. **Schema Validation (Syntax):**
* *Check:* Is the JSON structurally correct (required fields present, correct types)?
* *Error:* "Invalid format in flows.json: 'title' is required for flow 'flow-cancel-post-pay'."
2. **State Existence (Dangling Pointers):**
* *Check:* Every state listed in the `path` array must exist in the parsed `StateMachineModel`.
* *Error:* "Validation Error in 'Happy Path': State 'FULLFILLED' does not exist in StateMachine 'SimpleEnum'. Did you mean 'FULFILLED'?" (implementing a Levenshtein distance check for typos would be excellent here).
3. **Transition Validity (Impossible Paths):**
* *Check:* For every adjacent pair in the `path` array (e.g., `["SUBMITTED", "PAID"]`), the validator must check the `StateMachineModel.getTransitions()` to ensure a valid transition exists where `source == "SUBMITTED"` and `target == "PAID"`.
* *Error:* "Validation Error in 'Happy Path': No transition exists from 'PAID' to 'SUBMITTED'. Please check your state machine configuration."
4. **Ambiguous Transitions (Multiple Edges):**
* *Check:* If multiple transitions exist between the same two states (e.g., two different events trigger a move from A -> B).
* *Handling:* By default, highlight *all* edges between A and B to represent the general path.
* *Future Enhancement:* Expand the JSON format to allow specifying the exact `event` name in the path array (e.g., `["SUBMITTED", {"state": "PAID", "event": "PAY_CREDIT_CARD"}]`) to disambiguate.
## 3. HTML Generation Strategy
The HTML Exporter will be a new class implementing the `StateMachineExporter` interface (or a specialized decorator around the `PlantUml` exporter).
### Steps for Generation:
1. **Generate PlantUML SVG:** First, use the existing PlantUML engine to generate the raw SVG string in memory (using the modern CSS `<style>` approach we finalized).
2. **Parse `flows.json`:** Load the user-provided flow definitions.
3. **Validate Flows:** Run the "Flow Validator" against the AST `StateMachineModel`.
4. **Calculate Edges:** For each valid flow, convert the `path` array (States) into an `edges` array (Transitions) so the Javascript knows exactly which SVG arrows to highlight.
5. **Inject Template:** Load a base HTML template (similar to `interactive_poc2.html`).
6. **Data Binding:**
* Inject the raw `<svg>` string directly into the template (avoiding CORS issues).
* Inject the validated flow data into a `<script>` tag as a JSON object, or directly render the sidebar HTML elements with `data-*` attributes.
7. **Output:** Write the final standalone `.html` file to the output directory.
## 4. Advantages of this Architecture
* **Standalone Output:** The resulting HTML file is 100% self-contained. No server, no external CSS/JS, and no CORS issues. It can be directly emailed, attached to Jira tickets, or hosted on GitHub Pages.
* **CI/CD Integration:** Because validation is strictly tied to the AST, if a developer refactors the Java State Machine and breaks a documented "Flow", the CI pipeline will fail, ensuring interactive documentation never goes out of date.

View File

@@ -0,0 +1,66 @@
# Thought Experiment: Interactive SVG Flow Highlighting
## Overview
The goal of this thought experiment is to explore the feasibility and usefulness of animating or interactively highlighting specific "flows" (paths composed of states and transitions) within a generated state machine diagram. For example, hovering over a label like "Successful Payment Flow" would highlight the `SUBMITTED -> PAID -> FULFILLED` path, dimming the rest of the diagram.
## Usefulness
This feature would be **highly useful** and would significantly elevate the value of the State Machine Exporter:
1. **Comprehension of Complex Machines:** In large diagrams (like `ComplexStateMachineConfig`), a static image becomes a "spaghetti" of crossing lines. Interactive highlighting allows developers to visually isolate a single business transaction (e.g., "Cancellation Flow", "Refund Flow") without losing the context of the whole machine.
2. **Documentation & Onboarding:** It turns a static architectural diagram into an interactive documentation tool, making it much easier for new team members to trace how an entity moves through its lifecycle.
3. **Debugging:** By defining flows based on logged events, a developer could "replay" a sequence of events and see exactly what path the state machine took.
## Feasibility
This is **entirely feasible** using the tools currently available in the project.
### The Technical Reality of PlantUML SVGs
When PlantUML generates an SVG (`plantuml -tsvg`), it embeds highly predictable metadata into the DOM structure:
- **States (Nodes):** PlantUML creates `<g>` elements for states. They typically have an `id` matching the state name (e.g., `id="FULFILLED"`) or prefixed with entity (e.g., `id="entity_SUBMITTED"`).
- **Transitions (Links):** PlantUML creates `<g>` elements for arrows with predictable IDs and data attributes. For example: `<g class="link" data-entity-1="SUBMITTED" data-entity-2="PAID" id="link_SUBMITTED_PAID">`.
Because these identifiers match the semantic names of the states in the Java/JSON model, mapping a logical "Flow" to visual SVG elements is straightforward.
## Proposed Technical Approach
To realize this, we would build a lightweight **Interactive HTML Exporter** (or augment the JSON exporter to generate a companion HTML file).
### 1. Data Structure (The "Flow" Definition)
We need a way to define flows. This could be passed via configuration or inferred from the model. A flow is essentially an array of IDs:
```json
{
"name": "Successful Payment",
"elements": ["SUBMITTED", "link_SUBMITTED_PAID", "PAID", "link_PAID_FULFILLED", "FULFILLED"]
}
```
### 2. HTML/JS Wrapper
Instead of just outputting an `.svg` or `.png`, the exporter would output an `.html` file that contains:
1. **The inline SVG code** (generated via PlantUML).
2. **A Sidebar UI** containing a list of defined flows.
3. **Vanilla JavaScript** to handle the interactivity.
### 3. The JavaScript Logic (Proof of Concept)
The JS logic is remarkably simple. It listens for `mouseenter` events on the flow list:
1. **Dim Everything:** Add a class to the root SVG wrapper (e.g., `.flow-active`) that sets `opacity: 0.2` on all `<g>` tags.
2. **Highlight Flow:** Iterate through the `elements` array of the active flow. Find the SVG elements by their `id` (e.g., `document.getElementById('link_SUBMITTED_PAID')`) and add a `.highlighted` class.
3. **CSS Magic:**
```css
.diagram-container.flow-active svg g.highlighted {
opacity: 1 !important;
}
.diagram-container.flow-active svg g.highlighted path {
stroke: red !important;
stroke-width: 3px !important;
}
```
## Limitations & Challenges
While feasible, there are a few edge cases to handle:
1. **Multiple Transitions Between Same States:** If there are two distinct transitions from `A` to `B` (e.g., triggered by different events), PlantUML might generate IDs like `link_A_B` and `link_A_B_1`. The JS logic would need to rely on the event stereotypes we added recently (e.g., `<<e_PAY>>`) to distinguish them. We could parse the SVG text nodes to find the event label, or rely on the custom CSS classes if PlantUML supports passing them through to SVG (currently, PlantUML inlines styles rather than keeping CSS classes for transitions).
2. **Layout Shifts:** We must rely purely on CSS styling (colors, opacities, stroke widths). We cannot animate the *position* or layout of the arrows because PlantUML hardcodes the exact SVG path coordinates.
3. **Standalone vs. Hosted:** Generating a single `.html` file is easy and portable. However, if this is meant to be embedded in a documentation site (like Confluence or Backstage), injecting custom JS/CSS along with raw SVG can sometimes run into sanitization or iframe limitations.
4. **Maintenance:** Any changes to how PlantUML generates its internal SVG IDs in future versions would break the JS highlighting logic.
## Conclusion
The idea is brilliant. It transforms the output from a static image into an interactive, exploratory tool. Since the `StateMachineModel` is already cleanly exported as JSON, a standalone frontend script could easily marry that JSON data with the PlantUML SVG output to create dynamic, highlightable documentation flows without requiring heavy backend changes.

View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; display: flex; }
#sidebar { width: 300px; padding: 20px; background: #f4f4f4; }
#diagram { flex-grow: 1; padding: 20px; }
.flow-btn { padding: 10px; margin-bottom: 5px; cursor: pointer; background: #ddd; border: 1px solid #ccc; border-radius: 4px;}
.flow-btn:hover { background: #bdf; }
/* Dim everything by default when a flow is active */
.diagram-container.flow-active svg g {
opacity: 0.2;
transition: opacity 0.3s;
}
/* Highlight active elements */
.diagram-container.flow-active svg g.highlighted {
opacity: 1 !important;
}
/* Make highlighted paths thicker/red */
.diagram-container.flow-active svg g.highlighted path,
.diagram-container.flow-active svg g.highlighted polygon {
stroke: red !important;
stroke-width: 3px !important;
}
.diagram-container.flow-active svg g.highlighted rect,
.diagram-container.flow-active svg g.highlighted ellipse {
stroke: red !important;
stroke-width: 3px !important;
}
</style>
</head>
<body>
<div id="sidebar">
<h3>Flows</h3>
<div class="flow-btn" data-flow='["SUBMITTED", "link_SUBMITTED_PAID", "PAID", "link_PAID_FULFILLED", "FULFILLED"]'>Flow: Successful Payment</div>
<div class="flow-btn" data-flow='["SUBMITTED", "link_SUBMITTED_CANCELED", "CANCELED"]'>Flow: Cancel Before Pay</div>
</div>
<div id="diagram" class="diagram-container">
<!-- SVG will be injected here -->
</div>
<script>
// Load SVG
fetch('SimpleEnumStateMachineConfiguration.svg')
.then(r => r.text())
.then(svg => {
document.getElementById('diagram').innerHTML = svg;
setupInteractivity();
});
function setupInteractivity() {
const container = document.getElementById('diagram');
document.querySelectorAll('.flow-btn').forEach(btn => {
btn.addEventListener('mouseenter', (e) => {
container.classList.add('flow-active');
const flowIds = JSON.parse(e.target.getAttribute('data-flow'));
flowIds.forEach(id => {
// PlantUML sometimes prepends entity_
let el = document.getElementById(id) || document.getElementById('entity_' + id);
if(el) {
el.classList.add('highlighted');
} else {
// For links, fallback to data-attributes if id parsing fails
if (id.startsWith('link_')) {
const parts = id.split('_');
let link = document.querySelector(`g[data-entity-1="${parts[1]}"][data-entity-2="${parts[2]}"]`);
if (link) link.classList.add('highlighted');
}
}
});
});
btn.addEventListener('mouseleave', () => {
container.classList.remove('flow-active');
document.querySelectorAll('.highlighted').forEach(el => el.classList.remove('highlighted'));
});
});
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long