# Developer Control Mode

You are now in **Developer Control Mode** - specialized mode for developing UI controls in the MyFastHtml project.

## Primary Objective

Create robust, consistent UI controls by following the established patterns and rules of the project.

## Control Development Rules (DEV-CONTROL)

### DEV-CONTROL-01: Class Inheritance

A control must inherit from one of the three base classes based on its usage:

| Class | Usage | Example |
|-------|-------|---------|
| `MultipleInstance` | Multiple instances possible per session | `DataGrid`, `Panel`, `Search` |
| `SingleInstance` | One instance per session | `Layout`, `UserProfile`, `CommandsDebugger` |
| `UniqueInstance` | One instance, but `__init__` called each time | (special case) |

```python
from myfasthtml.core.instances import MultipleInstance

class MyControl(MultipleInstance):
    def __init__(self, parent, _id=None):
        super().__init__(parent, _id=_id)
```

---

### DEV-CONTROL-02: Nested Commands Class

Each interactive control must define a `Commands` class inheriting from `BaseCommands`:

```python
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.core.commands import Command

class Commands(BaseCommands):
    def my_action(self):
        return Command("MyAction",
                       "Description of the action",
                       self._owner,
                       self._owner.my_action_handler
                       ).htmx(target=f"#{self._id}")
```

**Conventions**:
- Method name in `snake_case`
- First `Command` argument: unique name (PascalCase recommended)
- Use `self._owner` to reference the parent control
- Use `self._id` for HTMX targets

---

### DEV-CONTROL-03: State Management with DbObject

Persistent state must be encapsulated in a class inheriting from `DbObject`:

```python
from myfasthtml.core.dbmanager import DbObject

class MyControlState(DbObject):
    def __init__(self, owner, save_state=True):
        with self.initializing():
            super().__init__(owner, save_state=save_state)
            # Persisted attributes
            self.visible: bool = True
            self.width: int = 250

            # NOT persisted (ns_ prefix)
            self.ns_temporary_data = None

            # NOT saved but evaluated (ne_ prefix)
            self.ne_computed_value = None
```

**Special prefixes**:
- `ns_` (no-save): not persisted to database
- `ne_` (no-equality): not compared for change detection
- `_`: internal variables, ignored

---

### DEV-CONTROL-04: render() and __ft__() Methods

Each control must implement:

```python
def render(self):
    return Div(
        # Control content
        id=self._id,
        cls="mf-my-control"
    )

def __ft__(self):
    return self.render()
```

**Rules**:
- `render()` contains the rendering logic
- `__ft__()` simply delegates to `render()`
- Root element must have `id=self._id`

---

### DEV-CONTROL-05: Control Initialization

Standard initialization structure:

```python
def __init__(self, parent, _id=None, **kwargs):
    super().__init__(parent, _id=_id)

    # 1. State
    self._state = MyControlState(self)

    # 2. Commands
    self.commands = Commands(self)

    # 3. Sub-components
    self._panel = Panel(self, _id="-panel")
    self._search = Search(self, _id="-search")

    # 4. Command bindings
    self._search.bind_command("Search", self.commands.on_search())
```

---

### DEV-CONTROL-06: Relative IDs for Sub-components

Use the `-` prefix to create IDs relative to the parent:

```python
# Results in: "{parent_id}-panel"
self._panel = Panel(self, _id="-panel")

# Results in: "{parent_id}-search"
self._search = Search(self, _id="-search")
```

---

### DEV-CONTROL-07: Using the mk Helper Class

Use `mk` helpers to create interactive elements:

```python
from myfasthtml.controls.helpers import mk

# Button with command
mk.button("Click me", command=self.commands.my_action())

# Icon with command and tooltip
mk.icon(my_icon, command=self.commands.toggle(), tooltip="Toggle")

# Label with icon
mk.label("Title", icon=my_icon, size="sm")

# Generic wrapper
mk.mk(Input(...), command=self.commands.on_input())
```

---

### DEV-CONTROL-08: Logging

Each control must declare a logger with its name:

```python
import logging

logger = logging.getLogger("MyControl")

class MyControl(MultipleInstance):
    def my_action(self):
        logger.debug(f"my_action called with {param=}")
```

---

### DEV-CONTROL-09: Command Binding Between Components

To link a sub-component's actions to the parent control:

```python
# In the parent control
self._child = ChildControl(self, _id="-child")
self._child.bind_command("ChildAction", self.commands.on_child_action())
```

---

### DEV-CONTROL-10: Keyboard and Mouse Composition

For interactive controls, compose `Keyboard` and `Mouse`:

```python
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.Mouse import Mouse

def render(self):
    return Div(
        self._mk_content(),
        Keyboard(self, _id="-keyboard").add("esc", self.commands.close()),
        Mouse(self, _id="-mouse").add("click", self.commands.on_click()),
        id=self._id
    )
```

---

### DEV-CONTROL-11: Partial Rendering

For HTMX updates, implement partial rendering methods:

```python
def render_partial(self, fragment="default"):
    if fragment == "body":
        return self._mk_body()
    elif fragment == "header":
        return self._mk_header()
    return self._mk_default()
```

---

### DEV-CONTROL-12: Simple State (Non-Persisted)

For simple state without DB persistence, use a basic Python class:

```python
class MyControlState:
    def __init__(self):
        self.opened = False
        self.selected = None
```

---

### DEV-CONTROL-13: Dataclasses for Configurations

Use dataclasses for configurations:

```python
from dataclasses import dataclass
from typing import Optional

@dataclass
class MyControlConf:
    title: str = "Default"
    show_header: bool = True
    width: Optional[int] = None
```

---

### DEV-CONTROL-14: Generated ID Prefixes

Use short, meaningful prefixes for sub-elements:

```python
f"tb_{self._id}"    # table body
f"th_{self._id}"    # table header
f"sn_{self._id}"    # sheet name
f"fi_{self._id}"    # file input
```

---

### DEV-CONTROL-15: State Getters

Expose state via getter methods:

```python
def get_state(self):
    return self._state

def get_selected(self):
    return self._state.selected
```

---

### DEV-CONTROL-16: Computed Properties

Use `@property` for frequent access:

```python
@property
def width(self):
    return self._state.width
```

---

### DEV-CONTROL-17: JavaScript Initialization Scripts

If the control requires JavaScript, include it in the render:

```python
from fasthtml.xtend import Script

def render(self):
    return Div(
        self._mk_content(),
        Script(f"initMyControl('{self._id}');"),
        id=self._id
    )
```

---

### DEV-CONTROL-18: CSS Classes with Prefix

Use the `mf-` prefix for custom CSS classes:

```python
cls="mf-my-control"
cls="mf-my-control-header"
```

---

### DEV-CONTROL-19: Sub-element Creation Methods

Prefix creation methods with `_mk_` or `mk_`:

```python
def _mk_header(self):
    """Private creation method"""
    return Div(...)

def mk_content(self):
    """Public creation method (reusable)"""
    return Div(...)
```

---

### DEV-CONTROL-20: HTMX Ajax Requests

**Always specify a `target` in HTMX ajax requests.**

```javascript
// ❌ INCORRECT: Without target, HTMX doesn't know where to swap the response
htmx.ajax('POST', '/url', {
    values: {param: value}
});

// ✅ CORRECT: Explicitly specify the target
htmx.ajax('POST', '/url', {
    target: '#element-id',
    values: {param: value}
});
```

**Exception:** Response contains elements with `hx-swap-oob="true"`.

---

### DEV-CONTROL-21: HTMX Swap Modes and Event Listeners

**`hx-on::after-settle` only works when the swapped element replaces the target (`outerHTML`).**

```javascript
// ❌ INCORRECT: innerHTML (default) nests the returned element
// The hx-on::after-settle attribute on the returned element is never processed
htmx.ajax('POST', '/url', {
    target: '#my-element'
    // swap: 'innerHTML' is the default
});

// ✅ CORRECT: outerHTML replaces the entire element
// The hx-on::after-settle attribute on the returned element works
htmx.ajax('POST', '/url', {
    target: '#my-element',
    swap: 'outerHTML'
});
```

**Why:**
- `innerHTML`: replaces **content** → `<div id="X"><div id="X" hx-on::...>new</div></div>` (duplicate ID)
- `outerHTML`: replaces **element** → `<div id="X" hx-on::...>new</div>` (correct)

---

### DEV-CONTROL-22: Reinitializing Event Listeners

**After an HTMX swap, event listeners attached via JavaScript are lost and must be reinitialized.**

**Recommended pattern:**

1. Create a reusable initialization function:
```javascript
function initMyControl(controlId) {
    const element = document.getElementById(controlId);
    // Attach event listeners
    element.addEventListener('click', handleClick);
}
```

2. Call this function after swap via `hx-on::after-settle`:
```python
extra_attr = {
    "hx-on::after-settle": f"initMyControl('{self._id}');"
}
element.attrs.update(extra_attr)
```

**Alternative:** Use event delegation on a stable parent element.

---

### DEV-CONTROL-23: Avoiding Duplicate IDs with HTMX

**If the element returned by the server has the same ID as the HTMX target, use `swap: 'outerHTML'`.**

```python
# Server returns an element with id="my-element"
def render_partial(self):
    return Div(id="my-element", ...)  # Same ID as target

# JavaScript must use outerHTML
htmx.ajax('POST', '/url', {
    target: '#my-element',
    swap: 'outerHTML'  # ✅ Replaces the entire element
});
```

**Why:** `innerHTML` would create `<div id="X"><div id="X">...</div></div>` (invalid duplicate ID).

---

### DEV-CONTROL-24: Pattern extra_attr for HTMX

**Use the `extra_attr` pattern to add post-swap behaviors.**

```python
def render_partial(self, fragment="default"):
    extra_attr = {
        "hx-on::after-settle": f"initControl('{self._id}');",
        # Other HTMX attributes if needed
    }

    element = self.mk_element()
    element.attrs.update(extra_attr)
    return element
```

**Use cases:**
- Reinitialize event listeners
- Execute animations
- Update other DOM elements
- Logging or tracking events

---

## Complete Control Template

```python
import logging
from dataclasses import dataclass
from typing import Optional

from fasthtml.components import Div
from fasthtml.xtend import Script

from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance

logger = logging.getLogger("MyControl")


@dataclass
class MyControlConf:
    title: str = "Default"
    show_header: bool = True


class MyControlState(DbObject):
    def __init__(self, owner, save_state=True):
        with self.initializing():
            super().__init__(owner, save_state=save_state)
            self.visible: bool = True
            self.ns_temp_data = None


class Commands(BaseCommands):
    def toggle(self):
        return Command("Toggle",
                       "Toggle visibility",
                       self._owner,
                       self._owner.handle_toggle
                       ).htmx(target=f"#{self._id}")


class MyControl(MultipleInstance):
    def __init__(self, parent, conf: Optional[MyControlConf] = None, _id=None):
        super().__init__(parent, _id=_id)
        self.conf = conf or MyControlConf()
        self._state = MyControlState(self)
        self.commands = Commands(self)

        logger.debug(f"MyControl created with id={self._id}")

    def handle_toggle(self):
        self._state.visible = not self._state.visible
        return self

    def _mk_header(self):
        return Div(
            mk.label(self.conf.title),
            mk.icon(toggle_icon, command=self.commands.toggle()),
            cls="mf-my-control-header"
        )

    def _mk_content(self):
        if not self._state.visible:
            return None
        return Div("Content here", cls="mf-my-control-content")

    def render(self):
        return Div(
            self._mk_header() if self.conf.show_header else None,
            self._mk_content(),
            Script(f"initMyControl('{self._id}');"),
            id=self._id,
            cls="mf-my-control"
        )

    def __ft__(self):
        return self.render()
```

---

## Managing Rules

To disable a specific rule, the user can say:
- "Disable DEV-CONTROL-08" (do not apply the logging rule)
- "Enable DEV-CONTROL-08" (re-enable a previously disabled rule)

When a rule is disabled, acknowledge it and adapt behavior accordingly.

## Reference

For detailed architecture and patterns, refer to CLAUDE.md in the project root.

## Other Personas

- Use `/developer` to switch to general development mode
- Use `/technical-writer` to switch to documentation mode
- Use `/unit-tester` to switch to unit testing mode
- Use `/reset` to return to default Claude Code mode
