diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c0e94ba --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,425 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +MyFastHtml is a Python utility library that simplifies FastHTML application development by providing: +- Command management system for client-server interactions +- Bidirectional data binding system +- Predefined authentication pages and routes +- Interactive control helpers +- Session-based instance management + +**Tech Stack**: Python 3.12+, FastHTML, HTMX, DaisyUI 5, Tailwind CSS 4 + +## Development Workflow and Guidelines + +### Development Process + +**Code must always be testable**. Before writing any code: + +1. **Explain available options first** - Present different approaches to solve the problem +2. **Wait for validation** - Ensure mutual understanding of requirements before implementation +3. **No code without approval** - Only proceed after explicit validation + +### Collaboration Style + +**Ask questions to clarify understanding or suggest alternative approaches:** +- Ask questions **one at a time** +- Wait for complete answer before asking the next question +- Indicate progress: "Question 1/5" if multiple questions are needed +- Never assume - always clarify ambiguities + +### Communication + +**Conversations**: French or English +**Code, documentation, comments**: English only + +### Code Standards + +**Follow PEP 8** conventions strictly: +- Variable and function names: `snake_case` +- Explicit, descriptive naming +- **No emojis in code** + +**Documentation**: +- Use Google or NumPy docstring format +- Document all public functions and classes +- Include type hints where applicable + +### Dependency Management + +**When introducing new dependencies:** +- List all external dependencies explicitly +- Propose alternatives using Python standard library when possible +- Explain why each dependency is needed + +### Unit Testing with pytest + +**Test naming patterns:** +- Passing tests: `test_i_can_xxx` - Tests that should succeed +- Failing tests: `test_i_cannot_xxx` - Edge cases that should raise errors/exceptions + +**Test structure:** +- Use **functions**, not classes (unless inheritance is required) +- Before writing tests, **list all planned tests with explanations** +- Wait for validation before implementing tests + +**Example:** +```python +def test_i_can_create_command_with_valid_name(): + """Test that a command can be created with a valid name.""" + cmd = Command("valid_name", "description", lambda: None) + assert cmd.name == "valid_name" + +def test_i_cannot_create_command_with_empty_name(): + """Test that creating a command with empty name raises ValueError.""" + with pytest.raises(ValueError): + Command("", "description", lambda: None) +``` + +### File Management + +**Always specify the full file path** when adding or modifying files: +``` +✅ Modifying: src/myfasthtml/core/commands.py +✅ Creating: tests/core/test_new_feature.py +``` + +### Error Handling + +**When errors occur:** +1. **Explain the problem clearly first** +2. **Do not propose a fix immediately** +3. **Wait for validation** that the diagnosis is correct +4. Only then propose solutions + +## Development Commands + +### Testing +```bash +# Run all tests +pytest + +# Run specific test file +pytest tests/core/test_bindings.py + +# Run specific test +pytest tests/core/test_bindings.py::test_function_name + +# Run tests with verbose output +pytest -v +``` + +### Cleaning +```bash +# Clean build artifacts and cache +make clean + +# Clean package distribution files +make clean-package + +# Clean test artifacts (.sesskey, test databases) +make clean-tests + +# Clean everything including source artifacts +make clean-all +``` + +### Package Building +```bash +# Build distribution +python -m build + +# Install in development mode +pip install -e . +``` + +## Architecture Overview + +### Core System: Commands + +Commands abstract HTMX interactions by encapsulating server-side actions. Located in `src/myfasthtml/core/commands.py`. + +**Key classes:** +- `BaseCommand`: Base class for all commands with HTMX integration +- `Command`: Standard command that executes a Python callable +- `LambdaCommand`: Inline command for simple operations +- `CommandsManager`: Global registry for command execution + +**How commands work:** +1. Create command with action: `cmd = Command("name", "description", callable)` +2. Command auto-registers with `CommandsManager` +3. `cmd.get_htmx_params()` generates HTMX attributes (`hx-post`, `hx-vals`) +4. HTMX posts to `/myfasthtml/commands` route with `c_id` +5. `CommandsManager` routes to correct command's `execute()` method + +**Command customization:** +```python +# Change HTMX target and swap +cmd.htmx(target="#result", swap="innerHTML") + +# Bind to observable data (disables swap by default) +cmd.bind(data_object) +``` + +### Core System: Bindings + +Bidirectional data binding system connects UI components with Python data objects. Located in `src/myfasthtml/core/bindings.py`. + +**Key concepts:** +- **Observable objects**: Use `make_observable()` from myutils to enable change detection +- **Three-phase lifecycle**: Create → Activate (bind_ft) → Deactivate +- **Detection modes**: How changes are detected (ValueChange, AttributePresence, SelectValueChange) +- **Update modes**: How UI updates (ValueChange, AttributePresence, SelectValueChange) +- **Data converters**: Transform data between UI and Python representations + +**Binding flow:** +1. User changes input → HTMX posts to `/myfasthtml/bindings` +2. `Binding.update()` receives form data, updates observable object +3. Observable triggers change event → `Binding.notify()` +4. All bound UI elements update via HTMX swap-oob + +**Helper usage:** +```python +from myfasthtml.controls.helpers import mk + +# Bind input and label to same data +input_elt = Input(name="field") +label_elt = Label() + +mk.manage_binding(input_elt, Binding(data, "attr")) +mk.manage_binding(label_elt, Binding(data, "attr")) +``` + +**Important binding notes:** +- Elements MUST have a `name` attribute to trigger updates +- Multiple elements can bind to same data attribute +- First binding call uses `init_binding=True` to set initial value +- Bindings route through `/myfasthtml/bindings` endpoint + +### Core System: Instances + +Session-scoped instance management system. Located in `src/myfasthtml/core/instances.py`. + +**Key classes:** +- `BaseInstance`: Base for all managed instances +- `SingleInstance`: One instance per parent per session +- `UniqueInstance`: One instance ever per session (singleton-like) +- `RootInstance`: Top-level singleton for application +- `InstancesManager`: Global registry with session-based isolation + +**Instance creation pattern:** +```python +# __new__ checks registry before creating +# If instance exists with same (session_id, _id), returns existing +instance = MyInstance(parent, session, _id="optional") +``` + +**Automatic ID generation:** +- SingleInstance: `snake_case_class_name` +- UniqueInstance: `snake_case_class_name` +- Regular BaseInstance: `parent_prefix-uuid` + +### Application Setup + +**Main entry point**: `create_app()` in `src/myfasthtml/myfastapp.py` + +```python +from myfasthtml.myfastapp import create_app + +app, rt = create_app( + daisyui=True, # Include DaisyUI CSS + vis=True, # Include vis-network.js + protect_routes=True, # Enable auth beforeware + mount_auth_app=False, # Mount auth routes + base_url=None # Base URL for auth +) +``` + +**What create_app does:** +1. Adds MyFastHtml CSS/JS assets via custom static route +2. Optionally adds DaisyUI 5 + Tailwind CSS 4 +3. Optionally adds vis-network.js +4. Mounts `/myfasthtml` app for commands and bindings routes +5. Optionally sets up auth routes and beforeware +6. Creates AuthProxy instance if auth enabled + +### Authentication System + +Located in `src/myfasthtml/auth/`. Integrates with FastAPI backend (myauth package). + +**Key components:** +- `auth/utils.py`: JWT helpers, beforeware for route protection +- `auth/routes.py`: Login, register, logout routes +- `auth/pages/`: LoginPage, RegisterPage, WelcomePage components + +**How auth works:** +1. Beforeware checks `access_token` in session before each route +2. Auto-refreshes token if < 5 minutes to expiry +3. Redirects to `/login` if token invalid/missing +4. Protected routes receive `auth` parameter with user info + +**Session structure:** +```python +{ + 'access_token': 'jwt_token', + 'refresh_token': 'refresh_token', + 'user_info': { + 'email': 'user@example.com', + 'username': 'user', + 'roles': ['admin'], + 'id': 'uuid', + 'created_at': 'timestamp', + 'updated_at': 'timestamp' + } +} +``` + +## Project Structure + +``` +src/myfasthtml/ +├── myfastapp.py # Main app factory (create_app) +├── core/ +│ ├── commands.py # Command system +│ ├── bindings.py # Binding system +│ ├── instances.py # Instance management +│ ├── utils.py # Utilities, routes app +│ ├── constants.py # Routes, constants +│ ├── dbmanager.py # Database helpers +│ ├── AuthProxy.py # Auth proxy instance +│ └── network_utils.py # Network utilities +├── controls/ +│ ├── helpers.py # mk class with UI helpers +│ ├── BaseCommands.py # Base command implementations +│ ├── Search.py # Search control +│ └── Keyboard.py # Keyboard shortcuts +├── auth/ +│ ├── utils.py # JWT, beforeware +│ ├── routes.py # Auth routes +│ └── pages/ # Login, Register, Welcome pages +├── icons/ # Icon libraries (fluent, material, etc.) +├── assets/ # CSS/JS files +└── test/ # Test utilities + +tests/ +├── core/ # Core system tests +├── testclient/ # TestClient and TestableElement tests +├── auth/ # Authentication tests +├── controls/ # Control tests +└── html/ # HTML component tests +``` + +## Testing System + +**Custom test client**: `myfasthtml.test.TestClient` extends FastHTML test client + +**Key features:** +- `user.open(path)`: Navigate to route +- `user.find_element(selector)`: Find element by CSS selector +- `user.should_see(text)`: Assert text in response +- Returns `TestableElement` objects with component-specific methods + +**Testable element types:** +- `TestableInput`: `.send(value)` +- `TestableCheckbox`: `.check()`, `.uncheck()`, `.toggle()` +- `TestableTextarea`: `.send(value)`, `.append(text)`, `.clear()` +- `TestableSelect`: `.select(value)`, `.select_by_text(text)`, `.deselect(value)` +- `TestableRange`: `.set(value)`, `.increase()`, `.decrease()` +- `TestableRadio`: `.select()` +- `TestableButton`: `.click()` +- `TestableDatalist`: `.send(value)`, `.select_suggestion(value)` + +**Test pattern:** +```python +def test_component(user, rt): + @rt("/") + def index(): + data = Data("initial") + component = Component(name="field") + label = Label() + + mk.manage_binding(component, Binding(data)) + mk.manage_binding(label, Binding(data)) + + return component, label + + user.open("/") + user.should_see("initial") + + elem = user.find_element("selector") + elem.method("new_value") + user.should_see("new_value") +``` + +## Important Patterns + +### Creating interactive buttons with commands +```python +from myfasthtml.controls.helpers import mk +from myfasthtml.core.commands import Command + +def action(): + return "Result" + +cmd = Command("action", "Description", action) +button = mk.button("Click", command=cmd) +``` + +### Bidirectional binding +```python +from myutils.observable import make_observable +from myfasthtml.core.bindings import Binding +from myfasthtml.controls.helpers import mk + +data = make_observable(Data("value")) +input_elt = Input(name="field") +label_elt = Label() + +mk.manage_binding(input_elt, Binding(data, "value")) +mk.manage_binding(label_elt, Binding(data, "value")) +``` + +### Using helpers (mk class) +```python +# Button with command +mk.button("Text", command=cmd, cls="btn-primary") + +# Icon with command +mk.icon(icon_svg, size=20, command=cmd) + +# Label with icon +mk.label("Text", icon=icon_svg, size="sm") + +# Generic wrapper +mk.mk(element, command=cmd, binding=binding) +``` + +## Common Gotchas + +1. **Bindings require `name` attribute**: Without it, form data won't include the field +2. **Commands auto-register**: Don't manually register with CommandsManager +3. **Instances use __new__ caching**: Same (session, id) returns existing instance +4. **First binding needs init**: Use `init_binding=True` to set initial value +5. **Observable required for bindings**: Use `make_observable()` from myutils +6. **Auth routes need base_url**: Pass `base_url` to `create_app()` for proper auth API calls + +## Dependencies + +**Core:** +- python-fasthtml: Web framework +- myauth: Authentication backend +- mydbengine: Database abstraction +- myutils: Observable pattern, utilities + +**UI:** +- DaisyUI 5: Component library +- Tailwind CSS 4: Styling +- vis-network: Network visualization + +**Development:** +- pytest: Testing framework +- httpx: HTTP client for tests +- python-dotenv: Environment variables