Added CLAUDE.md
This commit is contained in:
425
CLAUDE.md
Normal file
425
CLAUDE.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user