AI-First Starting UX: Build from scratch#4918
Draft
lmac-1 wants to merge 11 commits into
Draft
Conversation
Base automatically changed from
4868-build-with-ai
to
4848-ai-first-starting-ux-parent
July 6, 2026 08:17
Adds the "Build from scratch" entry point on the new-workflow landing screen: Workflows.create_webhook_workflow/2 builds a workflow with a webhook trigger, one job, and an always-on edge; the build_from_scratch LiveView event authorizes via the role-scoped ProjectUsers :create_workflow check so viewers can't trigger it.
0110e36 to
290612b
Compare
…flow_name/2 Clicking "Build from scratch" twice in one project failed on the workflow name unique constraint, since create_webhook_workflow/2 hardcoded its name. Extract the collision-avoidance logic duplicated in workflow_channel.ex and new_workflow_component.ex into a public Workflows.unique_workflow_name/2 (names-only query, includes soft-deleted rows since the unique index is not partial) and use it in create_webhook_workflow/2, which now defaults to "Untitled workflow" with " 1", " 2"... suffixes on collision. The channel delegates to the shared function. The legacy component keeps its private copy because that path is being deleted in this epic.
Rapid double-clicks on the "Build from scratch" card sent multiple build_from_scratch events, each creating a workflow. Wrap the push in useActionLock so only one event goes out, disable the card while pending, and guard the LiveView handler with a creating_workflow? assign as a server-side backstop for events that arrive anyway. Also normalize the new-workflow placeholder to "Untitled workflow" to match the rest of the codebase, and add regression tests: repeated clicks create suffixed workflows, creation after deletion reuses the freed name, and a failed save shows an error flash.
Build-from-scratch now redirects with ?trigger_view=picker so the newly created webhook trigger opens on the "What triggers this workflow?" picker instead of the read-only show panel, inviting the user to actively choose a trigger type. TriggerInspector captures the one-shot signal via a lazy useState initializer, then clears it from both React state (so a later Edit click lands on Choose, not the picker) and the URL via updateSearchParams (so a page refresh doesn't reopen the picker).
updateSearchParams always pushed a history entry, so stripping the one-shot ?trigger_view=picker signal after build-from-scratch left a phantom Back-button stop pointing right back at the picker. Add an optional { replace: true } to patch the current entry in place instead, and use it in TriggerInspector.
…nvas The build_from_scratch redirect pushed a new history entry on top of /new, so Back from the canvas landed on the now-pointless landing screen instead of wherever the user was before it. Pass replace: true to push_navigate so the canvas URL takes over /new's slot.
…limit create_webhook_workflow/2 was calling save_workflow/2 with enabled: true unconditionally, bypassing the WorkflowUsageLimiter. A project at its active-workflow quota could use the Build from scratch entry point to create a live webhook trigger regardless of quota. Now calls limit_workflow_creation/1 before building attrs and sets trigger enabled: false if the limit is exceeded, mirroring the auto-disable behaviour the collab-editor session path already applies to new workflows at the limit.
…toast Wraps the build_from_scratch pushEventTo call in a 10s timeout so the card doesn't stay permanently disabled if the server never acknowledges the push (socket disconnect or server-side error). Shows an error toast on failure so the user knows to retry. Also fixes disabled card hover styles bleeding through, renames the default job to "Untitled job", and removes a stale comment.
save_workflow's @SPEC only listed changeset and :workflow_deleted as error shapes, but handle_save_result can also return {:error, false} (snapshot failure) or other Multi step reasons. create_webhook_workflow inherited the same too-narrow spec. Widen both specs to match what the code actually returns, and rename the bound error variables from `changeset` to `reason` since they aren't always changesets.
The build_from_scratch handler is fully synchronous, so a second event on the same socket can never observe creating_workflow?: true — the flag is always reset or the socket has already navigated away by the time the process could dequeue another event. The client-side useActionLock is the guard that actually matters. Drop the dead assign, guard clause, and the test that only exercised it via a hand-built socket.
useAIWorkflowApplications.globalStep.test.ts was missing the streamingApply/streamingApplyActions props that its sibling test files (autoApply, jobCode, workflow) already pass. The hook calls streamingApplyActions.clear() unconditionally in handleApplyWorkflow, so leaving it undefined threw a TypeError as soon as that path ran.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the "Build from scratch" path from the new-workflow landing screen (part of the #4848 AI-first starting UX epic).
Clicking the "Build from scratch" card now:
Workflows.create_webhook_workflow/2: a webhook trigger, one "Transform Data" job on@openfn/language-common@latest, and an always-enabled edge — the same shape as the old "Event-based template".?trigger=<id>in the URL, so the trigger inspector ("What triggers this workflow?") is open on load.The
build_from_scratchLiveView event is authorized with the role-scopedProjectUsers:create_workflowpolicy, so viewers get a permission error flash instead of a new workflow.Workflows are named "Untitled workflow", "Untitled workflow 1", etc. via a new shared
Workflows.unique_workflow_name/2— extracted from the identical private copies inworkflow_channel.exandnew_workflow_component.ex, and now used by both this flow and the channel'svalidate_workflow_name. (The legacy component keeps its copy since that path is being deleted in this epic.) This also fixes a bug where clicking the card a second time in the same project failed on the name unique constraint.The card is also protected against double-clicks:
useActionLockensures onebuild_from_scratchevent per click-burst, the card is disabled while creation is pending, and the LiveView handler has acreating_workflow?guard as a server-side backstop.Closes #4895
Additional changes
Workflow activation limiter:
create_webhook_workflow/2now callsWorkflowUsageLimiter.limit_workflow_creation/1before saving. If a project is at its active-workflow quota, the trigger is created withenabled: false— matching the auto-disable behavior the collab-editor session path already applies to new workflows at the limit. Without this, a project at quota could bypass it by using the "Build from scratch" entry point.Browser history fix: The redirect from the landing screen to the canvas now uses
replace: trueonpush_navigate, so the canvas URL replaces the/newentry in the browser history. Previously, hitting Back from the canvas would return to the landing screen rather than wherever the user navigated from before. The one-shot?trigger_view=pickerparam is also cleaned up withreplace: trueinupdateSearchParams, for the same reason.Validation steps
<name>_del).Additional notes for the reviewer
create_webhook_workflow/2goes through the existingsave_workflow/2pipeline, so snapshots,lock_version, and audit behave like any other workflow save.:ok). On SaaS, a project at its quota now gets a disabled trigger instead of an enabled one.AI Usage
Please disclose whether you've used AI anywhere in this PR (it's cool, we just
want to know!):
You can read more details in our
Responsible AI Policy
Pre-submission checklist
/reviewwith Claude Code)
(e.g.,
:owner,:admin,:editor,:viewer)