{{-- Flow Builder v2 — tree canvas + drawer. ────────────────────────────────────── Primary editor surface: • Left = tree canvas. The first step is the root; child edges follow `next` / `options[].next`. Each node is a clickable card; clicking opens the right drawer. • Right = drawer. Detail editor for the selected step (step_key / type / title / body / options / next / future placeholders). • Below the canvas: an "unreachable steps" section listing any steps not visited by the DFS-from-root. Operators rewire them in via the drawer until the list is empty. Compatibility data-test markers kept on the tree nodes so the pre-existing FlowBuilderTest / FlowsCrudTest assertions (`flows-edit-step-cards`, `flows-edit-step-card`, `flows-edit-step-choice-block`) continue to match. --}}
{{-- ─── Header ──────────────────────────────────────────────── --}}
{{ $flowId === null ? __('New flow') : __('Edit flow') }}

{{ __('Pick any node on the tree to edit it. Choice options branch visually to the step you set on each option.') }}

{{-- ─── Audit banner (imported flows only) ──────────────────── --}} @if ($importKey !== null)
{{ __('Imported flow') }} @if ($needsReview) {{ __('Needs review') }} @endif
{{ __('Import key') }}
{{ $importKey }}
@if ($importSource)
{{ __('Import source') }}
{{ $importSource }}
@endif @if ($sourceDoc)
{{ __('Original document') }}
{{ $sourceDoc }}
@endif @if (! empty($importMetadata['priority']))
{{ __('Pack priority') }}
{{ $importMetadata['priority'] }}
@endif
@if (! empty($importMetadata['cleardesk_target']))
{{ __('ClearDesk target:') }} {{ $importMetadata['cleardesk_target'] }}
@endif
@endif
{{-- ─── Global validation summary ───────────────────────── Catch-net for any validation errors that don't have a visible per-field error block (e.g. per-step `steps.{i}.step_key` errors when the offending step's drawer isn't open, or any future field that's added without its own `` companion). Without this, save can silently fail and the operator sees nothing — the original "I spent ages and it won't save" report. --}} @if ($errors->any())
{{ __('Save blocked — fix the following before saving:') }}
    @foreach ($errors->all() as $errorMessage)
  • {{ $errorMessage }}
  • @endforeach
@endif {{-- ─── Flow metadata ────────────────────────────────────── --}}
{{ __('Name') }} {{ __('Slug') }} {{ __('Customer-facing URL segment.') }}
{{-- ─── Scope: default vs store-bound ──────────────────── Editable on CREATE; locked + shown read-only on EDIT (changing scope post-create is a copy operation, not an edit). --}}
{{ __('Scope') }} @if ($this->storeSelectorIsEditable()) @foreach ($this->availableStores() as $store) @endforeach {{ __('Default flows ship to every store. Store-bound flows only surface on the chosen store and override same-slug defaults there.') }} @else @php // Store lookup MUST go through the component // so AccountScope gets the current tenant // rebound. Querying Store directly from // Blade crashes on Livewire round-trips // because /livewire/update bypasses the // tenant middleware. $boundStore = $this->resolvedBoundStore(); @endphp
@if ($boundStore !== null) {{ __('Store-bound:') }} {{ $boundStore->name }} @else {{ __('Default flow') }} {{ __('(visible on every store)') }} @endif
{{ __('Scope is fixed after creation. Promote between default and store-bound by using "Copy" on the flow list.') }} @endif
{{ __('Category') }} {{ __('Status') }} @foreach ($this->statusOptions() as $value => $label) @endforeach {{ __('Entry label') }}
{{ __('Description (optional)') }}
{{-- ─── Steps section header ─────────────────────────────── --}}
{{ __('Flow steps') }}

{{ __('Click any node to edit. Branch labels sit on the connector lines so you can read the journey at a glance.') }}

{{ __('Add step') }}
@error('steps')
{{ $message }}
@enderror {{-- ─── Available order data (context preview) ────────────── Operator-facing inventory of every placeholder + context field the engine can populate when a customer goes through this flow. Visible at the top of the editor so Jake can SEE what data is available without having to dig into docs. Example values are illustrative — the actual values come from the customer's selected order at runtime. --}}
{{ __('Available order data') }} {{ __('What you can use in step titles, bodies, options, and conditions.') }}
@foreach ($this->placeholderCategories() as $category)
{{ $category['label'] }}
@foreach ($category['placeholders'] as $placeholder)
{{ $placeholder['key'] }}
{{ $placeholder['label'] }} @if (! empty($placeholder['example'])) {{ $placeholder['example'] }} @endif @if (! empty($placeholder['note']))
{{ $placeholder['note'] }}
@endif
@endforeach
@endforeach
{{-- ─── Tree canvas + Drawer two-column layout ──────────── Side-by-side at desktop. Width: 560px drawer column (in the middle of the spec'd 480-640px range). CRITICAL — DO NOT switch to a Tailwind arbitrary value (e.g. `lg:grid-cols-[minmax(0,1fr)_560px]`). The Tailwind JIT only compiles arbitrary values present in scanned source AT BUILD TIME. Editing this view DOES NOT trigger a rebuild unless `npm run dev` is already running, so the class silently fails to compile and the grid falls back to `grid-cols-1` — which stacks the drawer below the tree, exactly the layout the slice brief is trying to fix. We therefore enforce the grid THREE ways: 1. `data-test="flows-edit-canvas-layout"` marker the regression suite reads. 2. `grid grid-cols-1` Tailwind class (always compiled, stock single-column default). 3. Inline `style="grid-template-columns: ..."` wrapped in a `@media (min-width: 1024px)` so the desktop two-column shape works regardless of Tailwind state. The inline style ships in the rendered HTML and cannot be tree-shaken. Mobile (<1024px): single column, drawer stacks below canvas. Desktop (>=1024px): inline grid template kicks in. --}} {{-- ─── Canvas layout + scroll styles ───────────────────── The desktop grid template lives here because Tailwind JIT only compiles arbitrary values it sees at build time (see CRITICAL note above). The `.flows-canvas-*` classes implement the pannable- graph scroll pattern. Splitting clip / scroll / inner into three layers is non-negotiable: • Shell — sits in the grid track. `min-width: 0` lets the track shrink under content, `overflow: hidden` prevents the cell from being pushed wider than the track when the inner overflows. • Scroll — owns `overflow: auto`. THIS is the only element with a scroll bar. `height: 100%` + `max-height` keeps the viewport pannable rather than blowing the whole page tall on big trees. • Inner — `min-width: max-content` so the inner grows to whatever the tree needs. `display: flex; justify-content: center` centres SMALL trees visually inside the scroll viewport (the inner expands to 100% of scroll when content is narrow); for LARGE trees the inner is content-wide and the flex centring becomes a no-op, leaving the scroll container to expose horizontal pan starting from origin 0. This avoids the historical bug where `justify-center` on a width-100% wrapper pushed the tree's left edge to NEGATIVE x and made the far-left nodes unreachable. The shell + scroll + inner pattern also keeps the drawer's overflow concerns isolated — the drawer has its own `overflow-y-auto` for its body, which never touches the canvas's scroll layer. --}}
{{-- ─── Tree canvas (left) ────────────────────────────── Three-layer pannable-canvas pattern (see