# textual-wtf v0.9-dev1 - Changelog

## Major Architectural Changes

### 1. Callable BoundFields (WTForms-style API)

**File: `bound_fields.py`**

- Added `__call__()` method to BoundField class
  - Returns widget configured with keyword arguments
  - Tracks field rendering to prevent duplicates
  - Example: `form.name(placeholder="Enter name", disabled=True)`
  
- Updated `create_widget()` method
  - Now accepts `**kwargs` for runtime configuration
  - Merges runtime kwargs with field's widget_kwargs
  - Syncs initial value to widget on creation

**File: `fields.py`**

- Updated `Field.create_widget()` method
  - Added optional `widget_kwargs` parameter
  - Merges base configuration with runtime overrides

### 2. FormLayout Architecture

**File: `layouts.py` (NEW)**

Created new layout system with two classes:

**FormLayout (base class)**
- Inherits from `VerticalScroll`
- Tracks rendered fields via `_rendered_fields` set
- Provides `_track_field_render()` to prevent duplicate field rendering
- Abstract `compose_form()` method for subclasses to implement
- Delegates to form for data operations (get_data, set_data, validate)

**DefaultFormLayout**
- Implements default vertical form layout
- Replicates previous RenderedForm behavior
- Renders: title → subform titles → fields with labels → buttons
- Handles submit/cancel button events
- Posts Form.Submitted and Form.Cancelled messages

### 3. Form Class Updates

**File: `forms.py`**

**Removed:**
- `RenderedForm` class (replaced by FormLayout system)
- Unused imports (on, Vertical, Center, Horizontal, VerticalScroll, Button, Static, Label)

**Modified BaseForm:**
- Added `layout_class` class attribute (defaults to DefaultFormLayout)
- Updated `__init__()`:
  - Added `layout_class` parameter (overrides class-level default)
  - Removed `render_type` parameter
  - Layout selection logic: instance param → class attribute → DefaultFormLayout
  
- Updated `render()` method:
  - Creates layout instance using `self._layout_class`
  - Sets `self._current_layout` for field rendering tracking
  - Returns layout instance (not RenderedForm)

**Modified Form:**
- Updated `Submitted` and `Cancelled` message classes:
  - Accept `layout` parameter instead of `r_form`
  - Store as `self.layout`
  - Provide `self.form` for backward compatibility

### 4. Public API Updates

**File: `__init__.py`**

Added exports:
- `FormLayout`
- `DefaultFormLayout`

Fixed typo in `__all__` (was `"__version__,"` now `"__version__"`)

## Benefits of These Changes

1. **Cleaner API**: Fields are now callable, making layout code more intuitive
   ```python
   yield self.form.name(placeholder="Your name")
   ```

2. **Flexible Layouts**: Easy to create custom form layouts by subclassing FormLayout
   ```python
   class TwoColumnLayout(FormLayout):
       def compose_form(self):
           with Horizontal():
               yield self.form.name()
               yield self.form.email()
   ```

3. **Safety**: Prevents duplicate field rendering (1:1 field-to-widget mapping)
   - Raises `FormError` if same field rendered twice

4. **Backward Compatible**: Existing code continues to work with DefaultFormLayout

## Migration Guide

### Before (v0.8)
```python
class MyForm(Form):
    name = StringField(label="Name")
    
form = MyForm()
rendered = form.render()
```

### After (v0.9-dev1)
```python
# Still works exactly the same!
class MyForm(Form):
    name = StringField(label="Name")
    
form = MyForm()
rendered = form.render()

# But now you can also do custom layouts:
class MyLayout(FormLayout):
    def compose_form(self):
        yield Label("Custom Layout")
        yield self.form.name(placeholder="Enter name")
        yield Button("Submit", id="submit")

form = MyForm(layout_class=MyLayout)
rendered = form.render()
```

## Breaking Changes

⚠️ **Potential Breaking Changes:**

1. **RenderedForm class removed**
   - If code directly instantiated `RenderedForm`, use `DefaultFormLayout` instead
   - Message handlers that checked `isinstance(x, RenderedForm)` need updating

2. **Message structure changed**
   - `Form.Submitted` and `Form.Cancelled` now receive `layout` instead of `r_form`
   - `message.form` now refers to the layout (backward compatible)
   - To access actual form: `message.layout.form`

3. **render_type parameter removed**
   - Use `layout_class` parameter instead
   - Applies to both `BaseForm.__init__()` and subclasses

## Testing Requirements

The following areas need test updates:

1. **Test files referencing RenderedForm:**
   - Replace `RenderedForm` imports with `DefaultFormLayout`
   - Update assertions checking widget type

2. **Test message handling:**
   - Update to use `message.layout` instead of `message.form` for layout
   - Or use `message.form` (backward compatible alias)

3. **New tests needed:**
   - BoundField `__call__()` with various kwargs
   - Duplicate field rendering raises FormError
   - Custom layout classes
   - Field rendering tracking

## Files Modified

- `src/textual_wtf/bound_fields.py` - Added __call__ method
- `src/textual_wtf/fields.py` - Updated create_widget()
- `src/textual_wtf/forms.py` - Removed RenderedForm, updated BaseForm/Form
- `src/textual_wtf/layouts.py` - NEW FILE
- `src/textual_wtf/__init__.py` - Updated exports

## Files Needing Updates

- All test files that import or reference `RenderedForm`
- Demo applications in `src/textual_wtf/demo/` (likely need updates)
- Documentation files

## Next Steps

1. Update test suite to use new architecture
2. Update demo applications
3. Update documentation
4. Add new tests for layout system
5. Consider whether to add validation that all fields were rendered
