Skip to content

feat: render a workflow from JSON passed in from outside (claude) - #14

Merged
timurbazhirov merged 5 commits into
mainfrom
claude/workflow-json-visualization-kw34v4
Aug 27, 2026
Merged

feat: render a workflow from JSON passed in from outside (claude)#14
timurbazhirov merged 5 commits into
mainfrom
claude/workflow-json-visualization-kw34v4

Conversation

@VsevolodX

@VsevolodX VsevolodX commented Aug 23, 2026

Copy link
Copy Markdown
Member

Add WorkflowViewer: hand it workflow JSON and it shows the workflow's units as cards plus the selected subworkflow's units as a flowchart, with no store, router or dependency injection to wire up first.

The types are the ecosystem's own, with nothing in between: JSON going in is @mat3ra/esse's WorkflowSchema or JobSchema (as the object, or a JSON string of one), and what comes out of createWorkflowFromConfig() is a @mat3ra/wode Workflow — the entity that gives the cards their subworkflows, model instances, properties and statuses. A host app that already holds a Workflow can pass it straight through. The constructor's required entity lists (units, subworkflows, workflows) are defaulted when a config omits the empty ones, as Subworkflow.toJSON()-derived payloads do.

The entity validates against the ESSE schemas on the way in, so a config that is not valid ESSE is reported in place, naming the schema and what it requires, rather than half-rendering JSON nobody can vouch for:

wove: not a valid workflow config: REQUIRED_PROPERTY_MISSING — Subworkflow Schema requires: application, model, name, properties, units

The standalone bundle exposes window.renderWorkflow(workflowConfig, container, options), the same contract as wave.js' window.renderThreeDEditor(materialConfig, container), so a notebook or any other page can embed a workflow view through the api-examples viewer helpers with render_function="renderWorkflow". Stylesheets are emitted as main.css to keep that URL stable next to main.js, and the demo page mounts into its own container so importing the bundle only registers schemas and defines the global rather than rendering anything.

Checked in a browser on both entry points — the demo page and window.renderWorkflow() from the built bundle — plus tsc, both builds, and 43 tests, one of which constructs every one of the 57 standata workflows.

Add `WorkflowViewer`: hand it workflow JSON (a config object, a JSON string of
one, or a payload carrying one under `workflow`) and it shows the workflow's
units as cards plus the selected subworkflow's units as a flowchart, with no
store, router or dependency injection to wire up first.

`createWorkflowFromConfig()` turns that JSON into the instances the components
read by building a wode `Workflow`, filling in the entity lists its constructor
cannot do without (`workflows` is a required prop that external configs
routinely omit). A config the entity rejects — partial, or written against
another schema version — falls back to a read-only plain-JSON view instead of
leaving the container blank; the JSON comes from outside, so it is not ours to
assume complete.

The standalone bundle now exposes `window.renderWorkflow(workflowConfig,
container, options)`, the same contract as wave.js'
`window.renderThreeDEditor(materialConfig, container)`, so a notebook or any
other page can embed a workflow view through the api-examples viewer helpers
with `render_function="renderWorkflow"`. Stylesheets are emitted as `main.css`
to keep that URL stable next to `main.js`, and the demo page mounts into its
own container so importing the bundle only registers schemas and defines the
global rather than rendering anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXqtpnFoKB8rHjkqUpAmM
Comment thread src/components/workflows/WorkflowViewer.tsx Outdated
claude added 3 commits August 27, 2026 03:07
Review feedback: use the schemas from ESSE rather than hand-rolled types.

`WorkflowConfig` is now `Partial<WorkflowSchema>` (partial because JSON from
outside is not ours to assume complete), units are ESSE's `WorkflowUnitSchema`
and the subworkflow-level union wode composes from ESSE unit schemas, a
container config is keyed off `JobSchema`, and the subworkflow stand-in extends
`SubworkflowSchema` with only what the entities add on top — `id`,
`unitsInstances`, `modelInstance`, `setIsDraft`, `toJSON`. `WorkflowViewer`
follows: its units, subworkflows and `onUnitSelect` are typed, not `any`.

The two casts that remain are the boundary where external JSON becomes an
entity config, and both are commented: past them a config the entity rejects
lands in the read-only fallback rather than as a type error at the call site.
Behaviour is unchanged — the same 44 tests pass, and both the demo page and a
page calling `window.renderWorkflow` from the built bundle render as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXqtpnFoKB8rHjkqUpAmM
…E schemas

Review feedback: WorkflowLike/SubworkflowLike were a third description of a
workflow, alongside the ESSE schemas (the JSON) and the wode entities (the
instances). They existed only to type the plain-JSON stand-ins that
adaptWorkflowConfig/adaptSubworkflowConfig built when the entity constructor
rejected a config — and those stand-ins only reached WorkflowUnitCard,
OverviewAccordion and Properties, all of which declare `subworkflow:
WodeSubworkflow`, because WorkflowUnitsFlowchart typed `workflow` as `any`.
The `any` was laundering a type the components would otherwise have refused.

So: JSON in is ESSE's WorkflowSchema/JobSchema, what comes out is a wode
Workflow, and nothing describes a workflow in between. The stand-ins, both
Like interfaces, the UnitInstance wrappers and isWorkflowLike are gone, along
with ~140 lines of adapter code.

The fallback they served turns out to be dead weight: the entity rejects a
config only when it is not valid ESSE (an execution unit without
`application`, which the schema requires), and all 57 standata workflows
construct cleanly. Rather than half-render JSON nobody can vouch for, the
viewer now reports the problem in place, naming the schema and what it
requires. WorkflowUnitsFlowchart is typed to Workflow/AnyWorkflowUnit, and the
header chips come from the entity's own `usedApplications` roll-up, which
dedupes and covers nested workflows.

Verified in a browser on both entry points: the demo page renders 57 standata
workflows (cards, chips, flowchart, switching), window.renderWorkflow() from
the built bundle renders with no page errors, and invalid-ESSE JSON shows the
error in place instead of a blank container.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3qj13tVSuzTFc3qsFzBbE
WorkflowUnitsFlowchart enabled the card's Delete action off `editable` alone,
while defaulting `onRemove` to a no-op. WorkflowViewer sets `editable` but
wires no `onUnitRemove`, so `editable: true` rendered an enabled Delete button
whose click did nothing. Gate the action on a handler actually being there;
callers that pass one are unaffected.

`editable` on the read-only viewer therefore means the Copy action, which
works on its own — say so in the prop docs and the README instead of promising
'editing affordances'.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3qj13tVSuzTFc3qsFzBbE

@timurbazhirov timurbazhirov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review pass over the whole PR. The ESSE-schemas point is addressed in the thread above; the rest of what I found, with the fixes pushed:

1. Parallel type hierarchy (fixed, 7ed3cbb). Covered in the thread — the two *Like interfaces and the JSON stand-ins are gone, any no longer launders stand-ins past components that declare subworkflow: WodeSubworkflow, and the fallback they existed for was only reachable with configs that aren't valid ESSE.

2. editable enabled a Delete button that did nothing (fixed, a05d08f). WorkflowUnitsFlowchart gated the card's Delete on editable alone while defaulting onRemove to a no-op. WorkflowViewer sets editable but wires no onUnitRemove, so editable: true rendered an enabled Delete whose click went nowhere. It is now gated on a handler actually being present; callers that pass one are unaffected. On the read-only viewer editable means the Copy action, which does work on its own — the prop doc and the README say that now instead of promising "editing affordances".

3. Header chips were hand-rolled (folded into 7ed3cbb). The viewer walked subworkflowInstances and deduped labels itself. Workflow.usedApplications already does exactly this, dedupes, and covers nested workflows, which the hand-rolled version missed.

Verified rather than assumed: both entry points driven in a real browser (demo page and window.renderWorkflow() from the built bundle), tsc, both builds, and 43 tests — one of which constructs every one of the 57 standata workflows, so the entity path is exercised against real configs rather than a hand-written fixture.

Two things I left alone, flagging rather than changing:

  • assetFileNames: "main.[ext]" in vite.config.ts is right for the stable main.css URL, but it is a collision risk the moment the bundle emits a second asset of any one type (a font, an image) — they would all want the same filename. Fine today: the build emits only main.css.
  • npm run lint cannot run here — @exabyte-io/eslint-config is not in devDependencies, so eslint exits before doing anything. Pre-existing and CI skips eslint (skip-eslint: 'true'), so nothing in this PR is affected, but the lint script is currently decorative. Prettier I checked file by file: my changes add no formatting drift. (The repo-wide prettier --check failures come from a prettier 2 → 3 JSX >-placement change and hit untouched files too.)

Generated by Claude Code

`assetFileNames: "main.[ext]"` gave the stylesheet the stable URL a host page
needs — the way wave.js' main.css has one — but it named every asset by its
extension alone, so a second asset of any one type (a font, an image) would
collide on the same filename. Raised in review; fine today only because the
bundle emits nothing but main.css.

Keep the stable name for the stylesheet, hash the rest. Verified against the
built bundle: main.css and main.js still serve at their documented URLs, and a
page loading the stylesheet by URL gets React Flow's styles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXqtpnFoKB8rHjkqUpAmM

Copy link
Copy Markdown
Member Author

Picked up the first of the two flagged items — the asset-name collision risk was mine, and it's fixed in 3383e92.

assetFileNames is now a function: the stylesheet keeps the stable main.css name a host page loads by URL, everything else goes back to [name]-[hash].[ext]. So the wave.js-style contract holds without every asset of a given type wanting the same filename the day the bundle emits a font or an image.

Verified against a fresh build: main.js and main.css both serve 200 at their documented URLs, a page that loads the stylesheet by URL gets React Flow's styles, and with editable set the card offers an enabled Copy and a disabled Delete — i.e. a05d08f's gating behaves as intended through the built bundle.

The second item I'm leaving alone deliberately: npm run lint needs @exabyte-io/eslint-config added to devDependencies, which is a repo-wide fix rather than this PR's, and CI already skips eslint. Worth its own change.


Generated by Claude Code

@timurbazhirov
timurbazhirov merged commit f69dbd2 into main Aug 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants