docs/getting-started/MIGRATION_FROM_DIY.md

# Migration Guide: DIY to Raxol

Already built your own terminal rendering? This guide shows how to integrate or migrate to Raxol.

**New to Raxol?** See the [Package Guide](./PACKAGES.md) to choose the right package for your needs.

## Table of Contents

- [Why Migrate?](#why-migrate)
- [Migration Strategies](#migration-strategies)
- [Adapting Your Buffer Format](#adapting-your-buffer-format)
- [Incremental Migration](#incremental-migration)
- [Feature Parity Checklist](#feature-parity-checklist)
- [Case Study: droodotfoo](#case-study-droodotfoo)

---

## Why Migrate?

You've built a working terminal renderer. Why consider Raxol?

### What Raxol Adds

**1. Phoenix LiveView Integration**

If you're rendering terminals in web apps, Raxol.LiveView handles:
- Buffer → HTML conversion
- CSS theming (5 built-in themes)
- Event handling (keyboard, mouse, paste)
- 60fps rendering optimizations

**2. Testing Utilities**

```elixir
# Your current testing (probably):
output = render_buffer(buffer)
assert output =~ "expected text"  # Fragile string matching

# With Raxol:
buffer = Buffer.write_at(buffer, 5, 3, "expected text")
cell = Buffer.get_cell(buffer, 5, 3)
assert cell.char == "e"  # Test actual data structure
```

**3. Performance Optimizations**

- Diff rendering (50x faster updates)
- Benchmarking suite
- Memory profiling
- Automated performance regression detection

**4. Documentation & Examples**

- Comprehensive API docs
- Cookbook recipes
- Working examples
- Active community

### What You Keep

**Your code.** Raxol is designed for incremental adoption:

```elixir
# Keep your existing code
MyApp.TerminalRenderer.render(your_buffer)

# Add Raxol for specific features
html = Raxol.LiveView.TerminalBridge.buffer_to_html(your_buffer)
```

---

## Migration Strategies

Choose the approach that fits your needs.

### Strategy 1: Side-by-Side (Lowest Risk)

Run both implementations, compare outputs:

```elixir
defmodule MyApp.Renderer do
  def render(data) do
    # Your implementation
    your_buffer = YourRenderer.create_buffer(data)
    your_output = YourRenderer.render(your_buffer)

    # Raxol implementation (parallel)
    raxol_buffer = RaxolAdapter.from_your_format(your_buffer)
    raxol_output = Raxol.Core.Buffer.to_string(raxol_buffer)

    # Compare (in dev/test)
    if Mix.env() != :prod do
      compare_outputs(your_output, raxol_output)
    end

    # Use your implementation (for now)
    your_output
  end
end
```

**Pros:**
- Zero risk to production
- Validate Raxol behavior
- Identify edge cases

**Cons:**
- Doubled rendering cost (dev/test only)
- Maintenance overhead

**When to use:** Validating migration, finding gaps

### Strategy 2: Feature Flagging

Gradually roll out Raxol to users:

```elixir
defmodule MyApp.Renderer do
  def render(data, opts \\ []) do
    use_raxol? = Keyword.get(opts, :use_raxol, false) ||
                 Application.get_env(:my_app, :raxol_enabled, false)

    if use_raxol? do
      render_with_raxol(data)
    else
      render_with_your_code(data)
    end
  end
end

# config/config.exs
config :my_app,
  raxol_enabled: System.get_env("RAXOL_ENABLED") == "true"
```

**Pros:**
- Gradual rollout (1% → 10% → 100%)
- Easy rollback
- A/B testing possible

**Cons:**
- Maintain both paths
- Feature flag complexity

**When to use:** Production migration with safety net

### Strategy 3: Module Replacement

Replace your module with Raxol adapter:

```elixir
# Before:
defmodule MyApp.Buffer do
  def create(width, height), do: # your code
  def write_at(buffer, x, y, text), do: # your code
end

# After:
defmodule MyApp.Buffer do
  # Delegate to Raxol
  defdelegate create(width, height), to: Raxol.Core.Buffer, as: :create_blank_buffer
  defdelegate write_at(buffer, x, y, text), to: Raxol.Core.Buffer
  defdelegate write_at(buffer, x, y, text, style), to: Raxol.Core.Buffer

  # Keep custom functions
  def your_special_function(buffer), do: # your code
end
```

**Pros:**
- Minimal code changes
- Keep existing API
- Gradual internal migration

**Cons:**
- API compatibility required
- May hide Raxol features

**When to use:** Drop-in replacement, minimal changes

### Strategy 4: Clean Break

Rewrite using Raxol from scratch:

```elixir
# Old code (delete):
defmodule MyApp.OldRenderer do
  # 500 lines of custom buffer code
end

# New code:
defmodule MyApp.Renderer do
  alias Raxol.Core.{Buffer, Box, Renderer}

  def render(data) do
    Buffer.create_blank_buffer(80, 24)
    |> Box.draw_box(0, 0, 80, 24, :single)
    |> Buffer.write_at(10, 5, data.title)
  end
end
```

**Pros:**
- Simplest long-term
- Full Raxol feature access
- No legacy code

**Cons:**
- Highest risk
- Requires comprehensive testing
- All-or-nothing

**When to use:** Small codebases, greenfield projects

---

## Adapting Your Buffer Format

Most DIY implementations use similar structures. Here's how to adapt.

### Common DIY Format

```elixir
# Your buffer (typical structure)
%{
  width: 80,
  height: 24,
  cells: [
    # Flat array of cells
    %{x: 0, y: 0, char: "H", fg: :cyan},
    %{x: 1, y: 0, char: "e", fg: :cyan},
    # ...
  ]
}
```

### Raxol Format

```elixir
# Raxol buffer (nested structure)
%{
  width: 80,
  height: 24,
  lines: [
    # Array of lines
    %{cells: [
      # Each line has cells
      %{char: "H", style: %{fg_color: :cyan}},
      %{char: "e", style: %{fg_color: :cyan}},
      # ...
    ]},
    # ...
  ]
}
```

### Adapter: Your Format → Raxol

```elixir
defmodule MyApp.BufferAdapter do
  @doc "Convert your buffer format to Raxol format"
  def to_raxol(your_buffer) do
    # Create blank Raxol buffer
    raxol_buffer = Raxol.Core.Buffer.create_blank_buffer(
      your_buffer.width,
      your_buffer.height
    )

    # Transfer cells
    Enum.reduce(your_buffer.cells, raxol_buffer, fn cell, buf ->
      style = convert_style(cell)
      Raxol.Core.Buffer.set_cell(buf, cell.x, cell.y, cell.char, style)
    end)
  end

  @doc "Convert your style format to Raxol style"
  defp convert_style(cell) do
    %{
      fg_color: cell.fg,
      bg_color: Map.get(cell, :bg),
      bold: Map.get(cell, :bold, false),
      italic: Map.get(cell, :italic, false),
      underline: Map.get(cell, :underline, false)
    }
  end
end
```

### Adapter: Raxol → Your Format

```elixir
defmodule MyApp.BufferAdapter do
  @doc "Convert Raxol buffer to your format (if needed)"
  def from_raxol(raxol_buffer) do
    cells =
      for {line, y} <- Enum.with_index(raxol_buffer.lines),
          {cell, x} <- Enum.with_index(line.cells) do
        %{
          x: x,
          y: y,
          char: cell.char,
          fg: cell.style[:fg_color],
          bg: cell.style[:bg_color],
          bold: cell.style[:bold] || false
        }
      end

    %{
      width: raxol_buffer.width,
      height: raxol_buffer.height,
      cells: cells
    }
  end
end
```

### Performance Consideration

**Adapters add overhead.** Benchmark both approaches:

```elixir
# Benchmark: Direct Raxol
{time_raxol, _} = :timer.tc(fn ->
  Raxol.Core.Buffer.create_blank_buffer(80, 24)
  |> Raxol.Core.Buffer.write_at(10, 5, "Test")
end)

# Benchmark: Adapter path
{time_adapter, _} = :timer.tc(fn ->
  your_buffer = YourRenderer.create_buffer(80, 24)
  raxol_buffer = BufferAdapter.to_raxol(your_buffer)
end)

IO.puts("Direct: #{time_raxol}μs, Adapter: #{time_adapter}μs")
# If adapter is > 2x slower, consider Strategy 3 or 4
```

---

## Incremental Migration

Step-by-step migration plan.

### Phase 1: Add Raxol Dependency (Week 1)

```elixir
# mix.exs
def deps do
  [
    {:raxol_core, "~> 2.0"},  # Start with just core
    # ... your other deps
  ]
end
```

Run tests, ensure no conflicts.

### Phase 2: Create Adapters (Week 1-2)

```elixir
# test/support/buffer_adapter_test.exs
defmodule MyApp.BufferAdapterTest do
  use ExUnit.Case

  test "converts your buffer to Raxol" do
    your_buffer = YourRenderer.create_buffer(10, 5)
    your_buffer = YourRenderer.write_at(your_buffer, 2, 3, "Hi")

    raxol_buffer = BufferAdapter.to_raxol(your_buffer)

    cell = Raxol.Core.Buffer.get_cell(raxol_buffer, 2, 3)
    assert cell.char == "H"
  end

  test "round-trip conversion preserves data" do
    original = YourRenderer.create_buffer(10, 5)
    original = YourRenderer.write_at(original, 2, 3, "Test")

    raxol = BufferAdapter.to_raxol(original)
    back = BufferAdapter.from_raxol(raxol)

    assert buffer_equal?(original, back)
  end
end
```

### Phase 3: Add LiveView Support (Week 2-3)

If you're using Phoenix:

```elixir
# mix.exs
def deps do
  [
    {:raxol_core, "~> 2.0"},
    {:raxol_liveview, "~> 2.0"},  # Add LiveView support
    # ...
  ]
end
```

```elixir
# lib/my_app_web/live/terminal_live.ex
defmodule MyAppWeb.TerminalLive do
  use MyAppWeb, :live_view

  def render(assigns) do
    ~H"""
    <.live_component
      module={Raxol.LiveView.TerminalComponent}
      id="terminal"
      buffer={@raxol_buffer}
      theme={:nord}
    />
    """
  end

  def handle_info(:update, socket) do
    # Your existing logic
    your_buffer = YourRenderer.update(socket.assigns.your_buffer)

    # Convert to Raxol for rendering
    raxol_buffer = BufferAdapter.to_raxol(your_buffer)

    {:noreply, assign(socket,
      your_buffer: your_buffer,
      raxol_buffer: raxol_buffer
    )}
  end
end
```

### Phase 4: Replace Components (Week 3-6)

Gradually replace custom components:

```elixir
# Week 3: Replace box drawing
# Before:
your_buffer = YourRenderer.draw_box(buffer, 0, 0, 10, 5)

# After:
raxol_buffer = Raxol.Core.Box.draw_box(buffer, 0, 0, 10, 5, :single)

# Week 4: Replace text rendering
# Before:
your_buffer = YourRenderer.write_colored(buffer, 5, 3, "Text", :cyan)

# After:
raxol_buffer = Raxol.Core.Buffer.write_at(buffer, 5, 3, "Text", %{fg_color: :cyan})

# Week 5: Replace diffing
# Before:
diff = YourRenderer.calculate_diff(old, new)

# After:
diff = Raxol.Core.Renderer.render_diff(old, new)
```

### Phase 5: Remove Old Code (Week 6+)

Once confidence is high:

```bash
# Archive old code (don't delete yet)
git mv lib/my_app/old_renderer.ex lib/my_app/archived/

# Update imports throughout codebase
# YourRenderer -> Raxol.Core.Buffer
# YourRenderer.Box -> Raxol.Core.Box

# Remove adapters (no longer needed)
git rm lib/my_app/buffer_adapter.ex
```

### Phase 6: Monitor & Optimize (Ongoing)

```elixir
# Add performance tracking
defmodule MyApp.RenderMetrics do
  def track_render(fun) do
    {time, result} = :timer.tc(fun)

    MyApp.Metrics.histogram("terminal.render_time_us", time)

    if time > 16_000 do
      Logger.warn("Slow render: #{time}μs")
    end

    result
  end
end
```

---

## Feature Parity Checklist

Ensure Raxol can do everything your code does.

### Buffer Operations

- [ ] Create buffer with dimensions
- [ ] Write text at coordinates
- [ ] Read cell at coordinates
- [ ] Clear buffer
- [ ] Resize buffer
- [ ] Fill rectangular area
- [ ] Copy region to another buffer

### Styling

- [ ] Foreground colors (16 basic)
- [ ] Background colors (16 basic)
- [ ] 256-color palette support
- [ ] RGB true color support
- [ ] Bold text
- [ ] Italic text
- [ ] Underline
- [ ] Strikethrough
- [ ] Reverse video
- [ ] Custom attributes

### Box Drawing

- [ ] Single-line boxes
- [ ] Double-line boxes
- [ ] Rounded corners
- [ ] Horizontal lines
- [ ] Vertical lines
- [ ] Custom line characters

### Rendering

- [ ] Full buffer render to string
- [ ] Diff rendering (only changed cells)
- [ ] ANSI escape code generation
- [ ] HTML output (for web)
- [ ] Cursor positioning

### Advanced Features

- [ ] Unicode support (grapheme clusters)
- [ ] Wide characters (CJK)
- [ ] Zero-width characters (combining diacritics)
- [ ] Emoji support
- [ ] Sixel graphics (if applicable)
- [ ] Custom rendering backends

### Performance

- [ ] < 1ms buffer operations
- [ ] < 16ms full renders (60fps)
- [ ] Memory efficient (< 100KB per buffer)
- [ ] Diff calculation < 2ms

**If any features are missing:** Open a GitHub issue! We'll add them or help you extend Raxol.

---

## Case Study: droodotfoo

Real-world example of DIY → Raxol migration.

### Their Setup (Before)

```elixir
# lib/droodotfoo/terminal_bridge.ex
defmodule Droodotfoo.TerminalBridge do
  use GenServer

  # Custom buffer format
  defstruct [:width, :height, :cells, :cache]

  # Custom HTML rendering
  def buffer_to_html(buffer) do
    # ~300 lines of conversion logic
  end

  # Custom diffing
  def calculate_diff(old, new) do
    # ~100 lines of diff algorithm
  end
end
```

### Pain Points

1. **Maintenance burden** - 500+ lines of buffer code to maintain
2. **No testing utilities** - Hard to test rendering logic
3. **Performance unknowns** - No benchmarking infrastructure
4. **Missing features** - Wanted more themes, better diffing

### Migration Approach (Recommended)

**Phase 1: LiveView Integration Only**

```elixir
# mix.exs
def deps do
  [
    {:raxol_liveview, "~> 2.0"},  # Just for HTML rendering
    # Keep their buffer code for now
  ]
end
```

**Phase 2: Adapter**

```elixir
defmodule Droodotfoo.RaxolAdapter do
  def to_raxol(droodotfoo_buffer) do
    # Convert their format to Raxol
    Raxol.Core.Buffer.create_blank_buffer(
      droodotfoo_buffer.width,
      droodotfoo_buffer.height
    )
    |> populate_from_droodotfoo(droodotfoo_buffer)
  end

  defp populate_from_droodotfoo(raxol_buffer, droodotfoo_buffer) do
    Enum.reduce(droodotfoo_buffer.cells, raxol_buffer, fn {coord, cell}, buf ->
      {x, y} = coord
      style = %{
        fg_color: cell.fg_color,
        bg_color: cell.bg_color,
        bold: cell.bold
      }
      Raxol.Core.Buffer.set_cell(buf, x, y, cell.char, style)
    end)
  end
end
```

**Phase 3: Replace HTML Rendering**

```elixir
# Before: 300 lines of custom code
Droodotfoo.TerminalBridge.buffer_to_html(buffer)

# After: One line
buffer
|> Droodotfoo.RaxolAdapter.to_raxol()
|> Raxol.LiveView.TerminalBridge.buffer_to_html()
```

**Result:**
- 300 lines deleted
- 5 built-in themes (vs 1 custom)
- Better performance (1.2ms vs 8ms avg)
- Easier testing

**Phase 4: (Optional) Full Migration**

Replace buffer implementation:

```elixir
# Delete custom buffer code (~200 lines)
# Use Raxol.Core.Buffer directly
buffer = Raxol.Core.Buffer.create_blank_buffer(80, 24)
```

### Lessons Learned

1. **Start with LiveView** - Biggest immediate value
2. **Keep adapters simple** - Don't optimize prematurely
3. **Measure everything** - Benchmark before/after
4. **Gradual rollout** - Feature flags in production
5. **Document differences** - Note any behavior changes

---

## Getting Help

### Common Questions

**Q: Will this break my existing code?**

A: No. Raxol can run alongside your code. Use adapters for gradual migration.

**Q: What if Raxol is missing a feature I need?**

A: Three options:
1. Keep that part of your code (use Raxol for other parts)
2. Extend Raxol with a plugin
3. Open a GitHub issue (we'll help add it)

**Q: How long does migration typically take?**

A: Depends on strategy:
- LiveView only: 1-2 days
- Partial migration: 2-4 weeks
- Full migration: 4-8 weeks

**Q: Can I contribute my adapter back to Raxol?**

A: Yes! We'd love to see it. Open a PR with your adapter in `lib/raxol/adapters/`.

### Resources

- **[Quickstart](./QUICKSTART.md)** - Get started quickly
- **[Core Concepts](./CORE_CONCEPTS.md)** - Understand architecture
- **[API Reference](../core/BUFFER_API.md)** - Complete function docs
- **[Cookbook](../cookbook/README.md)** - Practical recipes
- **GitHub Issues** - Ask questions, request features

### Community Support

- Post in GitHub Discussions
- Join our Discord (coming soon)
- Tag @Hydepwns on Twitter/X

---

**Ready to migrate?** Start with Strategy 1 (side-by-side) to validate, then choose your path.

Good luck! We're here to help.