Skip to content

Add Workflow Export/Import#555

Merged
leszko merged 56 commits intomainfrom
ryanontheinside/feat/shareable-workflows/front-end
Mar 5, 2026
Merged

Add Workflow Export/Import#555
leszko merged 56 commits intomainfrom
ryanontheinside/feat/shareable-workflows/front-end

Conversation

@ryanontheinside
Copy link
Copy Markdown
Collaborator

@ryanontheinside ryanontheinside commented Feb 28, 2026

Summary

Adds workflow export and import, allowing users to share and apply complete pipeline configurations.

Backend:

  • /resolve endpoint that checks environment requirements (plugins, LoRAs) and reports what needs to be installed before a workflow can be applied

Frontend:

  • Workflow export dialog with JSON download
  • Workflow import dialog with step-by-step dependency resolution UI — shows required plugins, missing LoRAs, and pipeline compatibility before applying
  • Extracted plugins and server info into shared React contexts
  • Frontend refactoring: simplified prompt timeline, deduplicated pipeline logic, inlined workflow settings state

Not included (follow-up PRs):

  • Images (reference image, first image, last image)
  • Plugin resolution with versions
  • Cloud support

🤖 Generated with Claude Code

Introduces the .scope-workflow.json Pydantic schema (ScopeWorkflow) and a
build_workflow() function that snapshots loaded pipelines into a shareable
format. Adds POST /api/v1/workflow/export endpoint.

Key design decisions:
- All schema models use extra="ignore" for forward compatibility
- WorkflowLoRAProvenance subclasses LoRAProvenance with explicit extra="ignore"
- Pipeline source type is "builtin" | "pypi" | "git" | "local" (actionable)
- Per-pipeline frontend_params keyed by pipeline_id
- PipelineManager.get_load_snapshot() decouples export from PM internals
- PluginManager._list_plugins_sync renamed to list_plugins_sync (public API)

Signed-off-by: RyanOnTheInside <[email protected]>
… LoRA extraction

When no pipelines are loaded yet, seed the snapshot from frontend_params
keys so export still captures the user's selected pipeline. Merge
frontend_params into the params dict before extracting LoRAs, fixing
an order-of-operations bug where LoRAs from the frontend were missed.

Signed-off-by: RyanOnTheInside <[email protected]>
Deduplicate the lazy-cache-then-iterate pattern for plugin lookups
that was duplicated between export.py and resolve.py.

Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
- Delete _utils.py (unused in this PR; consumers live in #525)
- Simplify validate endpoint: use FastAPI parameter parsing
  instead of manual json.loads + ValidationError handling
- Add negative validation tests (invalid source type, missing name)

Signed-off-by: RyanOnTheInside <[email protected]>
@ryanontheinside ryanontheinside force-pushed the ryanontheinside/feat/shareable-workflows/timeline-migration branch from ebfec79 to 0e8b864 Compare March 3, 2026 00:29
@ryanontheinside ryanontheinside force-pushed the ryanontheinside/feat/shareable-workflows/front-end branch from 2925554 to f2b6656 Compare March 3, 2026 00:30
Add resolve.py for side-effect-free dependency checking (pipelines,
plugins, LoRAs, settings validation) and apply.py to load resolved
workflows into the running server. Two new endpoints:
- POST /api/v1/workflow/validate — returns resolution plan for trust gate
- POST /api/v1/workflow/apply — resolves, loads pipelines, returns runtime params

Key behaviors: missing LoRAs don't block (graceful degradation), plugin
install is opt-in, params classified as load vs runtime via is_load_param,
all pipelines' params merged with primary (last) winning on conflict.

Signed-off-by: RyanOnTheInside <[email protected]>
Parameters not in the pipeline config schema are frontend runtime params
returned via runtime_params on apply, not errors. Remove the false
"Unknown parameter" warnings from settings validation.

Signed-off-by: RyanOnTheInside <[email protected]>
Replace _make_workflow in test_workflow_schema.py with shared
make_workflow from workflow_helpers.py. Fix test_unknown_param_warns
to assert no warning (unknown params are frontend runtime params
silently passed through, not errors).

Signed-off-by: RyanOnTheInside <[email protected]>
resolve.py now uses find_plugin_info/get_plugin_list from _utils.py
instead of its own _get_plugin_list closure.

SHA256 verification on LoRA files is moved from resolve to apply so
that /workflow/validate stays fast (no hashing multi-GB files).

Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
Add resolve.py for side-effect-free dependency checking (pipelines,
plugins, LoRAs, settings validation) and apply.py to load resolved
workflows into the running server. Two new endpoints:
- POST /api/v1/workflow/validate — returns resolution plan for trust gate
- POST /api/v1/workflow/apply — resolves, loads pipelines, returns runtime params

Key behaviors: missing LoRAs don't block (graceful degradation), plugin
install is opt-in, params classified as load vs runtime via is_load_param,
all pipelines' params merged with primary (last) winning on conflict.

Signed-off-by: RyanOnTheInside <[email protected]>
Replace _make_workflow in test_workflow_schema.py with shared
make_workflow from workflow_helpers.py. Fix test_unknown_param_warns
to assert no warning (unknown params are frontend runtime params
silently passed through, not errors).

Signed-off-by: RyanOnTheInside <[email protected]>
- Add WorkflowPrompt, WorkflowTimelineEntry, WorkflowTimeline schema models
  with forward-compatible extra="ignore" on all models
- Add optional timeline and min_scope_version fields to ScopeWorkflow
- Create migrate.py with version migration scaffolding (rejects future
  versions, passes current through unchanged, uses semver comparison)
- Extend build_workflow() to accept and pass through optional timeline
- Add min_scope_version check in resolve_workflow() that warns when the
  installed Scope version is older than required
- Wire migration into /workflow/validate endpoint
- Add timeline field to WorkflowExportRequest (typed as WorkflowTimeline)

Signed-off-by: RyanOnTheInside <[email protected]>

^ Conflicts:
^	src/scope/core/workflows/export.py
^	src/scope/core/workflows/resolve.py
^	src/scope/server/app.py
Signed-off-by: RyanOnTheInside <[email protected]>
@ryanontheinside ryanontheinside force-pushed the ryanontheinside/feat/shareable-workflows/timeline-migration branch from 0e8b864 to 90a13b3 Compare March 3, 2026 09:59
Signed-off-by: RyanOnTheInside <[email protected]>
Signed-off-by: RyanOnTheInside <[email protected]>
@ryanontheinside ryanontheinside force-pushed the ryanontheinside/feat/shareable-workflows/front-end branch from f2b6656 to b5e4d5d Compare March 3, 2026 10:00
@leszko leszko changed the base branch from ryanontheinside/feat/shareable-workflows/timeline-migration to main March 3, 2026 10:09
@leszko leszko changed the title 05 feat: add workflow sharing UI Add Workflow Export/Import Mar 3, 2026
leszko and others added 15 commits March 3, 2026 14:44
- Delete workflow_helpers.py; inline make_workflow and mock_plugin_manager
  into test_workflow_resolve.py (only consumer)
- Remove unused helpers: mock_pipeline_manager, ok_plan, blocked_plan
- Remove dead mock_registry.get_config_class assignments (6 occurrences)
- Consolidate WorkflowRequest import at module level
- Use tmp_path consistently instead of MagicMock() for models_dir

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
- Inline annotateWorkflowPromptState into buildScopeWorkflow (set fields
  directly in the object literal instead of mutating after construction)
- Remove the now-unused exported function
- Un-export buildWorkflowTimeline (only used internally)
- Deduplicate pre/postprocessor pipeline building with shared helper
- Add explanatory comment on PARAM_MAPPINGS

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
- Extract LoRA download and plugin install logic from WorkflowImportDialog
    into useLoRADownloads/usePluginInstalls hooks
  - Add path traversal guard on LoRA filename resolution (backend)
  - Add Field constraints (max_length) on workflow request models
  - Add frontend validation of workflow structure before resolution
  - Add confirmation dialogs for plugin installs and workflow loading
  - Fix swapped Upload/Download icons on Export/Import buttons
  - Fix version_mismatch not blocking can_apply in resolution plan
  - Resolve LoRA paths at import time using available files list
  - Fix misplaced function definition between import blocks
  - Remove stale console.log and duplicate comment
  - Add tests for path traversal, input validation, and edge cases

Signed-off-by: RyanOnTheInside <[email protected]>
…e after plugin install

- Extract CivitAI version_id from download URLs when not explicitly provided,
  falling back to direct URL download if unresolvable
- Persist version_id in LoRA provenance for reliable future re-downloads
- Re-resolve workflow dependencies after server restart from plugin install
  so the UI reflects newly available pipelines

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafal Leszko <[email protected]>
…iffusers at import time

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Move plugins list and server info fetching into dedicated contexts
(PluginsContext, ServerInfoContext) with corresponding hooks, eliminating
duplicate API calls across PluginsDialog, SettingsDialog, and
WorkflowExportDialog. Also fix workflow import to re-resolve dependencies
after LoRA downloads complete.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
The handleLoad callback captured a stale loraFiles closure from before
the download completed. Now refresh() returns the fresh list directly
so handleLoad always resolves LoRA paths against the latest files.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Prefer the first timeline entry prompt over the top-level prompts
field, since the timeline is what actually plays on start.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
ryanontheinside and others added 12 commits March 5, 2026 10:21
* Harden workflow resolution: fix crash, false-positive version, empty string validation

- Wrap list_plugins_sync() in try/except so a corrupted plugin environment
  degrades gracefully instead of crashing all workflow imports
- Return a detail warning when installed plugin has no version metadata but
  the workflow requires a specific version (was silently returning "ok")
- Add min_length=1 to pipeline_id and plugin_name to reject empty strings
  at the Pydantic validation layer
- Add adversarial/edge-case tests covering plugin manager failures, version
  comparison gaps, degenerate inputs, symlink/path edge cases, and
  unsanitized package_spec (documented as known limitations where applicable)

Signed-off-by: RyanOnTheInside <[email protected]>

* hf civitai url download fix

Signed-off-by: RyanOnTheInside <[email protected]>

---------

Signed-off-by: RyanOnTheInside <[email protected]>
Use a ref to access current loraFiles in the error handler instead of
including loraFiles in the useCallback dependency array, which caused
refresh to be recreated on every fetch, triggering the effect in a loop.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafal Leszko <[email protected]>
The auto-reset useEffect in useStreamState was overriding the workflow's
inputMode with the pipeline's default_mode whenever the pipelineId changed.
Skip the auto-reset when loading a workflow so the imported input_mode is
preserved, and trigger video source reinitialization when needed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafal Leszko <[email protected]>
- Pass LoRA provenance URL through in civitai download requests and
  preserve it in the manifest for all source types, not just "url"
- Refresh PluginsContext after plugin install so exported workflows
  retain the correct git source instead of falling back to "builtin"

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafal Leszko <[email protected]>
The previous check only covered missing LoRAs and plugins. Missing
pipelines (or other dependency kinds) still allowed the button to be
clicked. Replace with a single check across all resolution items.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafal Leszko <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
… and components

- Deduplicate LoRAProvenance type (single definition in api.ts, re-exported from workflowApi.ts)
- Extract useDependencyTracker generic hook to eliminate duplicated state management
- Extract DependencyStatusIndicator component from duplicated LoRA/plugin status UI
- Extract toPromptItems helper to replace 4 identical prompt mapping expressions
- Simplify provenance construction with spread pattern
- Fix stale closure deps in handleFileSelect and handleClose
- Remove redundant setValidating(false) calls covered by finally block
- Add useMemo for plugin mapping in PluginsDialog

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
…internal details

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
- Add make_pipeline() and items_by_kind() helpers to reduce boilerplate
- Merge duplicate version_mismatch test into single test with both assertions
- Parametrize package spec injection tests
- Fix null byte test to assert ValueError instead of silently swallowing it

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
Previously the button was enabled when a plugin install completed locally,
even though the server still reported the dependency as missing (red X).
Now the button checks the resolution plan status directly, so it stays
disabled until re-resolution confirms all items are ok.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Rafał Leszko <[email protected]>
@leszko leszko merged commit b000adb into main Mar 5, 2026
7 of 9 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