9.9 KiB
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) |
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:
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
Commandargument: unique name (PascalCase recommended) - Use
self._ownerto reference the parent control - Use
self._idfor HTMX targets
DEV-CONTROL-03: State Management with DbObject
Persistent state must be encapsulated in a class inheriting from DbObject:
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 databasene_(no-equality): not compared for change detection_: internal variables, ignored
DEV-CONTROL-04: render() and ft() Methods
Each control must implement:
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 torender()- Root element must have
id=self._id
DEV-CONTROL-05: Control Initialization
Standard initialization structure:
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:
# 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:
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:
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:
# 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:
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:
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:
class MyControlState:
def __init__(self):
self.opened = False
self.selected = None
DEV-CONTROL-13: Dataclasses for Configurations
Use dataclasses for configurations:
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:
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:
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:
@property
def width(self):
return self._state.width
DEV-CONTROL-17: JavaScript Initialization Scripts
If the control requires JavaScript, include it in the render:
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:
cls="mf-my-control"
cls="mf-my-control-header"
DEV-CONTROL-19: Sub-element Creation Methods
Prefix creation methods with _mk_ or mk_:
def _mk_header(self):
"""Private creation method"""
return Div(...)
def mk_content(self):
"""Public creation method (reusable)"""
return Div(...)
Complete Control Template
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
/developerto switch to general development mode - Use
/technical-writerto switch to documentation mode - Use
/unit-testerto switch to unit testing mode - Use
/resetto return to default Claude Code mode