In our last deep dive, we talked about the logic and data structures that drive Vyshyvanka. But software isn't just about clean backends; it's about how the user interacts with those systems. Today, we're stepping into the browser to talk about the Vyshyvanka Designer - our Blazor WebAssembly canvas.
Why Blazor WebAssembly?
When we decided to build a visual workflow designer, we had a few options: React, Vue, or maybe a native desktop app. We chose Blazor WebAssembly for a few critical reasons:
- Shared Logic: By staying within the .NET ecosystem, we can share domain models and validation logic between our Engine and the Designer. When we update a node definition in the Core, the Designer understands those changes instantly without rewriting types in TypeScript.
- Performance: Blazor WASM gives us the performance of a compiled language inside the browser, which is crucial when you're dragging and dropping dozens of nodes or panning around a large workflow.
- Developer Velocity: Our team works best in C#. Having a single language for both our ASP.NET Core API and our frontend allows us to move fast without the context switching of a JavaScript-heavy stack.
The Designer Architecture
The Designer isn't one big component; it's a system of decomposed services designed to keep the UI snappy and maintainable:
- WorkflowStore: The single source of truth. It holds your workflow data, node definitions, and the 'dirty' flag that tells the UI when you have unsaved changes.
- WorkflowEditService: This is the brains of the operation. Whenever you drag a node, add a connection, or delete a step, the EditService handles the business logic. It doesn't worry about rendering; it just worries about state.
- WorkflowValidationService: We run validation in the browser as you edit. If you try to connect an 'Object' output to a 'Boolean' input, the UI turns red before you even let go of your mouse.
- CanvasStateService: Handles the 'feel' of the application. Panning, zooming, node selection, and undo/redo stacks are managed here.
Communicating with the Engine
We made a hard architectural choice early on: The Designer communicates exclusively via HTTP.
There are no direct WebSocket connections or shared memory paths to the Engine. By forcing all interaction through the WorkflowApiClient, we ensure the Designer remains a pure client. This means we can treat the entire Designer as a standalone application that could be hosted anywhere. It doesn't care if the engine is running on your machine, in a Docker container, or in the cloud—as long as it can hit the API, it works.
Component Design Rules
To keep our UI code clean, we enforce strict rules:
- No
@codeblocks in .razor files: All logic lives inName.razor.cspartial classes. - Injection over
@inject: We use[Inject]attributes in our code-behind to keep the markup clean and the dependencies explicit. - One Component, Three Files: Every visual piece follows a
Name.razor,Name.razor.cs, andName.razor.csspattern. It keeps our styles isolated and our logic readable.
The Goal: Real-Time Clarity
The goal of the Designer isn't just to look good; it's to provide real-time clarity. You should know exactly what your workflow is doing at every moment. By integrating our validation service directly into the canvas, we turn the design phase into an active conversation between you and the system.
Building a visual editor in Blazor was an adventure, but the results—a type-safe, performant, and deeply integrated experience—made it all worth it.
In the next part, we'll look at the execution engine internals: The Pipeline. We'll explain how we resolve the graph, manage node execution order, and handle the state machine from 'Pending' to 'Completed'. Stay tuned!
Check out the project source code here: https://github.com/homolibere/Vyshyvanka
Top comments (0)