# 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