443 lines
9.9 KiB
Markdown
443 lines
9.9 KiB
Markdown
# 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(...)
|
|
```
|
|
|
|
---
|
|
|
|
## 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.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 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
|