feat: add visual builder + workspace mode to Burr UI#687
Conversation
|
@andreahlert I can't get this to run locally -- did you commit everything? otherwise see the CI failures. |
|
Also need some example to load to help someone get started. |
|
Thanks for reviewing. Didn't have the time to check it properly, I'll do it as soon as possible. Target: 03-30-2026 |
|
Thanks for trying it out. Two things:
Working on both now. |
JiwaniZakir
left a comment
There was a problem hiding this comment.
In run.py, the shutdown sequence in lifespan calls await workspace.cleanup_processes() directly before await backend.lifespan(app).__anext__(), but there's no try/finally wrapping these calls. If cleanup_processes() raises an exception, the backend's own cleanup will be silently skipped, potentially leaking database connections or other resources. The backend shutdown should be guarded with a try/finally block regardless of what happens in workspace cleanup.
Additionally, supports_workspace=True is hardcoded unconditionally in _get_app_spec() — this means the UI will always advertise workspace support even if the feature is disabled or broken at runtime. This should be derived from a config flag or at least check that the workspace module initialized successfully, similar to how is_snapshotting_backend and is_annotations_backend are computed from the actual backend capabilities.
The _validate_workspace allowlist approach in workspace.py is a reasonable security boundary, but _read_links() is called on every file-browsing request with no caching. If the links file is read from disk on each API call under load, this could become a bottleneck — a short-lived in-process cache or memoization with invalidation on write would be worth considering.
elijahbenizzy
left a comment
There was a problem hiding this comment.
Played around some -- looking great! Node types should probably have specific implementations but I think this is a good start to put out there.
Adds a low-code visual builder with split-view Monaco editor, workspace file browsing, and project persistence. Builder features: - 9 node types (Action, Input, Result, LLM Call, API Call, Code, Streaming, Loop, Router) with specialized property editors - Inline + buttons on edges for inserting nodes (Activepieces-style) - Recursive layout algorithm supporting loop arcs and router fan-out - Monaco Editor with multi-file code generation (actions.py, app.py, run.py, requirements.txt) - Bidirectional sync between graph and code - Searchable node type picker with categories - File menu with save/load projects, Ctrl+S support - Undo/redo (50-state history), copy/paste, delete shortcuts - Code generation with sanitized identifiers and proper imports Workspace features: - Backend router for file browsing, code viewing, script execution (SSE streaming), process management, and workspace-project linking - Builder project persistence in ~/.burr/builder_projects/ - Path traversal protection with workspace validation New dependencies (all MIT, ASF Category A): - @monaco-editor/react - @tanstack/react-query - @xyflow/react - react-joyride
- Remove unused imports/variables flagged by ESLint - Replace regex literal with RegExp constructor to avoid no-control-regex - Format workspace.py with black and isort
- Replace `as any` with proper types where possible - Add eslint-disable comments for icon type maps (heroicons ForwardRef) - Remove unused `id` from edge component destructures - Remove unused `langMap` variable from BuilderView
This takes work from @jaeyow and apache#572. Adds a drag-and-drop graph editor for designing Burr application graphs visually and exporting as Python code or JSON. Key changes: - New /graph-builder route with full visual editor (ReactFlow v12) - Migrate existing GraphView from reactflow v11 to @xyflow/react v12 - Remove reactflow and @tisoap/react-flow-smart-edge dependencies - Per-node async/streaming toggles matching Burr's 4 action variants - Python code generation with correct decorators and signatures - 3 pre-built example graphs (MultiModal Chatbot, CRAG, Streaming) - localStorage auto-save/restore of graph state - Empty-state onboarding overlay and structured help sidebar - Fix appcontainer layout for full-height content
Python (workspace.py): - Module-level docstring describing all capabilities - Docstrings on all Pydantic models, endpoints, and helper functions - Documented security functions with Args/Returns/Raises TypeScript: - JSDoc on all exported types in builderTypes, codeGenerator, codeParser - JSDoc on all tree operation functions in treeUtils - JSDoc on layout algorithm functions in flowLayout - JSDoc on useBuilderState hook
Remove the legacy react-query v3 package and standardize on @tanstack/react-query v5 across all components. This fixes the "No QueryClient set" error in BuilderView which was caused by two separate QueryClient providers from different packages.
Wrap workspace.cleanup_processes() in try/finally so backend shutdown always runs even if process cleanup fails. Derive supports_workspace from workspace.is_available() instead of hardcoding True. Add in-process cache to _read_links() to avoid disk reads on every file-browsing request.
898f6be to
0fff68f
Compare
|
All review feedback addressed. Rebased clean on main, no conflicts. @JiwaniZakir your three points: @skrawcz docstrings on every function/class in workspace.py, including the inner ones. Examples were already in from the graph-builder merge. @elijahbenizzy opened #735 to track the node type implementations as a follow-up. Agreed it's fine for the initial merge. Ready for re-review whenever you get a chance. |
|
This PR has been inactive for 17 days after receiving review feedback. Please mark it as ready for review when you have addressed the comments. |
Combine event consumer cancellation from main with workspace cleanup from this branch in the lifespan shutdown path.
|
|
||
| # In-process cache for workspace links to avoid disk reads on every request. | ||
| _links_cache: Optional[dict] = None | ||
| _links_cache_lock = threading.Lock() |
There was a problem hiding this comment.
we don't need this. we run fastapi in an asyncio context.
| global _links_cache | ||
| with _links_cache_lock: | ||
| if _links_cache is not None: | ||
| return _links_cache | ||
| if os.path.exists(_LINKS_PATH): | ||
| with open(_LINKS_PATH, "r") as f: | ||
| data = json_module.load(f) | ||
| else: | ||
| data = {} | ||
| with _links_cache_lock: | ||
| _links_cache = data | ||
| return data |
There was a problem hiding this comment.
due to the way python runs, this should be atomic in an asyncio context.
| with _links_cache_lock: | ||
| _links_cache = data |
There was a problem hiding this comment.
again we don't need the lock.
There was a problem hiding this comment.
yeah fair, my bad. threw the lock in out of habit (check-then-act on a global), but ofc it's all async + no awaits so it's already serialized. ripping out the lock + threading import.
skrawcz
left a comment
There was a problem hiding this comment.
just one nit around threading.lock
All workspace endpoints are async and the cache helpers never await, so they are already serialized on the event loop. Addresses review feedback on apache#687. Signed-off-by: André Ahlert <andre@aex.partners>
…r-lowcode Signed-off-by: André Ahlert <andre@aex.partners> # Conflicts: # telemetry/ui/package-lock.json
TypeScript 4.9 misresolves @tanstack/react-query v5's legacy .d.ts under CRA's enforced moduleResolution: node, widening every useQuery `data` to any/unknown. After the react-query -> @tanstack/react-query migration this surfaced as TS7006/TS18046 noImplicitAny build failures (20 sites across AdminView, AnnotationsView, AppView, TrackingSidePanel and the examples views), breaking the Node.js CI and Release Validation build jobs. TypeScript 5 infers the query generics correctly, clearing all 20 errors with no component changes (verified with a full `npm run build`). CRA still forces moduleResolution: node, so no tsconfig change is needed. react-scripts@5.0.1 caps its optional typescript peer at ^4, so add telemetry/ui/.npmrc with legacy-peer-deps=true to let `npm install` resolve TS 5 (the build itself is unaffected by the peer range). Exclude the new .npmrc from Apache RAT, matching the other header-less tool config files. Signed-off-by: André Ahlert <andre@aex.partners>
3612768 to
93880a1
Compare


Building on #667 (Graph Builder by @skrawcz), this adds a workspace mode and significantly enhanced builder that turns Burr's UI into a development environment.
What's new
Visual Builder (enhanced)
Monaco Editor (split view)
Project save/load
Workspace integration
Other improvements
Screenshots
Builder with nodes and generated code

Node editor with LLM Call configuration

Searchable node type picker

Technical details
Backend (Python)
burr/tracking/server/workspace.py: new APIRouter with endpoints for file tree, file content, script execution (SSE), process management, workspace links, and builder project persistencerun.pyto mount workspace router,schema.pyto addsupports_workspaceFrontend (~6,700 new lines)
utils/: builderTypes, codeGenerator (9 node types + multi-file), codeParser (bidirectional sync), flowLayout (recursive layout), treeUtils (immutable tree ops), stateFlowhooks/useBuilderState.ts: tree-based state with undo/redo, keyboard shortcuts, save/loadbuilder/: BuilderView (split layout), BuilderGraph (ReactFlow with custom node/edge types), NodeEditor, NodeTypePicker (portal-based), 4 node components, 5 edge components with inline + buttonsworkspace/: FileExplorer, CodeViewer, RunTerminal, WorkspaceSelectorproject/: ProjectWorkspaceView, ActivityBar, SidePanel, TabBar, TrackingSidePanelDependencies added
@monaco-editor/react(MIT)@tanstack/react-query(MIT)@xyflow/react(MIT)react-joyride(MIT)All MIT license, ASF Category A compatible.
How I tested
npx tsc --noEmitzero errorsChecklist