@props([ /** * Livewire wire:model-style property path. The Alpine * bridge writes the editor's rendered HTML back to this * property via `$wire.set(path, html, false)` — landing * the value in Livewire state without firing a network * request. Save reads the up-to-date value on click. */ 'model' => null, /** * Initial HTML content for the editor. Passed as a Blade * prop instead of being read via `$wire.get()` at init * time — server-side rendering puts existing body content * on the editor surface from the very first paint. */ 'initial' => '', /** Placeholder text shown when the editor is empty. */ 'placeholder' => null, /** * data-* marker for tests + the placeholder picker. */ 'testId' => 'rich-editor', ]) {{-- Native contenteditable mini-editor. Replaces the previous TipTap-based implementation. Pros: - Zero JS deps. - document.execCommand is widely supported for the simple commands we need (bold/italic/h2/h3/lists/ link). - HTML output is exactly what the sanitizer and self-help `.sh-prose` renderer already expect. - Easy to debug: the editor's state IS the contenteditable's innerHTML. Architecture: 1. `wire:ignore` on the outer wrapper. Livewire never morphs the editor subtree after first render. 2. `x-data="miniEditor(...)"` on an inner element so wire:ignore + Alpine bindings aren't on the same node (Livewire 3 has subtle bugs there). 3. Initial content arrives via the `:initial` prop. First paint shows the right body content. 4. Write-back via `$wire.set(model, html, false)` — no network request per keystroke. 5. Step swap via `wire:key` on the wrapper (operator's responsibility at the call site). Different key → Livewire replaces the subtree → Alpine reinits with the new step's content. Same key → wrapper survives. --}}