---
phase: 07-admin-forms-lists
plan: 02
type: execute
wave: 2
depends_on: ["07-01"]
files_modified:
  - summercms/src/admin/forms/widgets/TextWidget.scala
  - summercms/src/admin/forms/widgets/TextareaWidget.scala
  - summercms/src/admin/forms/widgets/DropdownWidget.scala
  - summercms/src/admin/forms/widgets/CheckboxWidget.scala
  - summercms/src/admin/forms/widgets/DatePickerWidget.scala
  - summercms/src/admin/forms/widgets/RepeaterWidget.scala
  - summercms/src/admin/forms/widgets/RelationWidget.scala
  - summercms/src/admin/forms/WidgetBootstrap.scala
autonomous: true

must_haves:
  truths:
    - "Text input renders with proper attributes (placeholder, required, disabled)"
    - "Textarea renders with rows/cols and comment help text"
    - "Dropdown renders select element with options from config or method"
    - "Checkbox renders with label alignment and checked state"
    - "DatePicker includes Flatpickr initialization attributes"
    - "Repeater renders item list with add/remove/reorder controls"
    - "Relation widget renders modal trigger with list display"
  artifacts:
    - path: "summercms/src/admin/forms/widgets/TextWidget.scala"
      provides: "Text input widget (text, email, password, number, url types)"
      exports: ["TextWidget"]
    - path: "summercms/src/admin/forms/widgets/TextareaWidget.scala"
      provides: "Textarea widget with size variants"
      exports: ["TextareaWidget"]
    - path: "summercms/src/admin/forms/widgets/DropdownWidget.scala"
      provides: "Select dropdown widget with options"
      exports: ["DropdownWidget"]
    - path: "summercms/src/admin/forms/widgets/CheckboxWidget.scala"
      provides: "Checkbox and switch widgets"
      exports: ["CheckboxWidget", "SwitchWidget"]
    - path: "summercms/src/admin/forms/widgets/DatePickerWidget.scala"
      provides: "Date/time picker with Flatpickr integration"
      exports: ["DatePickerWidget"]
    - path: "summercms/src/admin/forms/widgets/RepeaterWidget.scala"
      provides: "Repeater widget with nested form fields"
      exports: ["RepeaterWidget"]
    - path: "summercms/src/admin/forms/widgets/RelationWidget.scala"
      provides: "Relation widget with modal management"
      exports: ["RelationWidget"]
    - path: "summercms/src/admin/forms/WidgetBootstrap.scala"
      provides: "Registers all standard widgets at startup"
      exports: ["WidgetBootstrap"]
  key_links:
    - from: "summercms/src/admin/forms/widgets/*.scala"
      to: "summercms/src/admin/forms/WidgetRegistry.scala"
      via: "implements FormWidget trait"
      pattern: "extends FormWidget"
    - from: "summercms/src/admin/forms/WidgetBootstrap.scala"
      to: "summercms/src/admin/forms/WidgetRegistry.scala"
      via: "registers widgets on boot"
      pattern: "registry\\.register"
---

<objective>
Implement all standard form widgets for the admin backend.

Purpose: This plan creates the widget implementations that render actual form inputs. Each widget type handles its specific UI requirements, from simple text inputs to complex repeaters and relation modals.

Output: Complete set of form widgets (text, textarea, dropdown, checkbox, datepicker, repeater, relation) that integrate with the WidgetRegistry from Plan 07-01.
</objective>

<execution_context>
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
@/home/jin/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07-admin-forms-lists/07-CONTEXT.md
@.planning/phases/07-admin-forms-lists/07-RESEARCH.md
@.planning/phases/07-admin-forms-lists/07-01-SUMMARY.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement basic input widgets (Text, Textarea, Dropdown, Checkbox)</name>
  <files>
    summercms/src/admin/forms/widgets/TextWidget.scala
    summercms/src/admin/forms/widgets/TextareaWidget.scala
    summercms/src/admin/forms/widgets/DropdownWidget.scala
    summercms/src/admin/forms/widgets/CheckboxWidget.scala
  </files>
  <action>
Create TextWidget.scala:
- Implements FormWidget trait
- Handles multiple input types via field.type: text, email, password, number, url
- Renders input element with:
  - type attribute based on field type
  - name attribute for form submission
  - value from context data
  - placeholder from field.placeholder
  - required attribute if field.required
  - disabled attribute if field.disabled
  - readonly attribute if field.readOnly
  - Custom attributes from field.attributes map
  - CSS classes: "form-control" base + field.cssClass

```scala
object TextWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val inputType = field.fieldType match
      case "password" => "password"
      case "email" => "email"
      case "url" => "url"
      case "number" => "number"
      case _ => "text"

    input(
      cls := s"form-control ${field.cssClass.getOrElse("")}",
      tpe := inputType,
      attr("name") := name,
      attr("value") := Option(value).map(_.toString).getOrElse(""),
      // ... additional attributes
    )
```

Create TextareaWidget.scala:
- Renders textarea element
- Supports size variant: tiny (2 rows), small (4), default (6), large (10), huge (15), giant (20)
- Size determined by field.size property
- Content goes inside textarea tags, not value attribute

Create DropdownWidget.scala:
- Renders select element with option children
- Options from field.options (Map[String, String] from YAML)
- Support emptyOption for "-- Select --" placeholder
- Mark selected option based on current value
- Handle multiple selection if field indicates (future enhancement)

```scala
object DropdownWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val currentValue = Option(value).map(_.toString).getOrElse("")

    select(
      cls := s"form-control ${field.cssClass.getOrElse("")}",
      attr("name") := name,
      field.required.filter(identity).map(_ => attr("required") := "required"),

      field.emptyOption.map(empty => option(attr("value") := "", empty)),

      field.options.map(_.toSeq.map { case (k, v) =>
        option(
          attr("value") := k,
          if k == currentValue then Some(attr("selected") := "selected") else None,
          v
        )
      }).getOrElse(Nil)
    )
```

Create CheckboxWidget.scala:
- Renders checkbox input with label wrapper
- Also supports "switch" type for toggle-style checkbox
- Checkbox value handling: hidden input with "0" + checkbox with "1" (standard pattern)
- Checked state based on truthy value (true, 1, "1", "yes", "on")

```scala
object CheckboxWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val isChecked = value match
      case b: Boolean => b
      case n: Number => n.intValue() != 0
      case s: String => Set("true", "1", "yes", "on").contains(s.toLowerCase)
      case _ => false

    div(cls := "checkbox-wrapper",
      input(tpe := "hidden", attr("name") := name, attr("value") := "0"),
      label(cls := "checkbox-label",
        input(
          tpe := "checkbox",
          attr("name") := name,
          attr("value") := "1",
          if isChecked then Some(attr("checked") := "checked") else None
        ),
        span(field.label.getOrElse(name))
      )
    )
```
  </action>
  <verify>
Run `mill summercms.compile` - should compile without errors.
Verify each widget produces expected HTML structure.
  </verify>
  <done>
Basic input widgets (Text, Textarea, Dropdown, Checkbox) implemented with proper attribute handling, value binding, and CSS classes.
  </done>
</task>

<task type="auto">
  <name>Task 2: Implement DatePicker and Repeater widgets</name>
  <files>
    summercms/src/admin/forms/widgets/DatePickerWidget.scala
    summercms/src/admin/forms/widgets/RepeaterWidget.scala
  </files>
  <action>
Create DatePickerWidget.scala:
- Renders text input with Flatpickr data attributes for JS initialization
- Support modes: date, datetime, time (via field.mode property)
- Data attributes for Flatpickr configuration:
  - data-flatpickr: marker for JS initialization
  - data-enable-time: for datetime/time modes
  - data-no-calendar: for time-only mode
  - data-date-format: based on mode (Y-m-d, Y-m-d H:i, H:i)
  - data-min-date, data-max-date: if specified in field config

```scala
object DatePickerWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val mode = field.mode.getOrElse("date")
    val (enableTime, noCalendar, format) = mode match
      case "datetime" => (true, false, "Y-m-d H:i")
      case "time" => (true, true, "H:i")
      case _ => (false, false, "Y-m-d")

    input(
      cls := s"form-control flatpickr-input ${field.cssClass.getOrElse("")}",
      tpe := "text",
      attr("name") := name,
      attr("value") := Option(value).map(_.toString).getOrElse(""),
      data("flatpickr") := "true",
      if enableTime then Some(data("enable-time") := "true") else None,
      if noCalendar then Some(data("no-calendar") := "true") else None,
      data("date-format") := format
    )
```

Create RepeaterWidget.scala (complex widget per CONTEXT.md):
- Renders list of repeater items with nested form fields
- Each item has:
  - Collapse/expand toggle (items collapsible per CONTEXT.md)
  - Title from field.titleFrom (shows value of specified field as item title)
  - Drag handle for reordering (Sortable.js data attributes)
  - Remove button
  - Nested form fields from field.form.fields
- Below items:
  - Add button with prompt text from field.prompt
- Item wrapper with unique UUID-based keys (not sequential indexes per RESEARCH.md)
- Respects minItems/maxItems constraints (disable add/remove accordingly)
- Data attributes for Sortable.js: data-sortable, data-handle

```scala
object RepeaterWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val items = value match
      case list: List[_] => list.asInstanceOf[List[Map[String, Any]]]
      case _ => List.empty[Map[String, Any]]

    val nestedFields = field.form.map(_.fields).getOrElse(Map.empty)
    val minItems = field.minItems.getOrElse(0)
    val maxItems = field.maxItems.getOrElse(Int.MaxValue)

    div(cls := "repeater-widget",
      data("repeater") := "true",
      data("min-items") := minItems.toString,
      data("max-items") := maxItems.toString,

      div(cls := "repeater-items", data("sortable") := "true",
        items.zipWithIndex.map { case (itemData, idx) =>
          val itemKey = itemData.getOrElse("_key", java.util.UUID.randomUUID().toString)
          renderRepeaterItem(field, name, itemKey.toString, itemData, nestedFields, idx, ctx)
        }
      ),

      div(cls := "repeater-add",
        button(
          tpe := "button",
          cls := "btn btn-secondary btn-add-item",
          attr("hx-get") := s"/admin/repeater/add-item?field=$name",
          attr("hx-target") := ".repeater-items",
          attr("hx-swap") := "beforeend",
          if items.size >= maxItems then Some(attr("disabled") := "disabled") else None,
          field.prompt.getOrElse("Add Item")
        )
      )
    )

  private def renderRepeaterItem(
    field: FormField,
    baseName: String,
    itemKey: String,
    itemData: Map[String, Any],
    nestedFields: Map[String, FormFieldConfig],
    index: Int,
    ctx: FormContext
  ): Frag =
    div(cls := "repeater-item", data("item-key") := itemKey,
      div(cls := "repeater-item-header",
        span(cls := "drag-handle", data("handle") := "true", "::"),
        span(cls := "item-title",
          field.titleFrom.flatMap(tf => itemData.get(tf).map(_.toString)).getOrElse(s"Item ${index + 1}")
        ),
        button(tpe := "button", cls := "btn-collapse", data("toggle") := "collapse", "-"),
        button(tpe := "button", cls := "btn-remove", data("action") := "remove", "x")
      ),
      div(cls := "repeater-item-body",
        nestedFields.map { case (fieldName, fieldConfig) =>
          val nestedName = s"${baseName}[$itemKey][$fieldName]"
          val nestedValue = itemData.getOrElse(fieldName, null)
          // Render nested field (simplified - would use FormRenderer in real impl)
          renderNestedField(fieldConfig, nestedName, nestedValue, ctx)
        }
      ),
      input(tpe := "hidden", attr("name") := s"${baseName}[$itemKey][_key]", attr("value") := itemKey)
    )
```

Note: Repeater is complex - the implementation should include:
- HTMX endpoint for adding new items dynamically
- JavaScript for Sortable.js initialization
- Proper name indexing for form submission
  </action>
  <verify>
Run `mill summercms.compile` - should compile without errors.
Verify DatePicker produces input with Flatpickr data attributes.
Verify Repeater produces item list structure with controls.
  </verify>
  <done>
DatePicker widget renders with Flatpickr integration attributes. Repeater widget renders item list with collapse, drag-reorder, add/remove controls and nested field support.
  </done>
</task>

<task type="auto">
  <name>Task 3: Implement Relation widget and Widget bootstrap</name>
  <files>
    summercms/src/admin/forms/widgets/RelationWidget.scala
    summercms/src/admin/forms/WidgetBootstrap.scala
  </files>
  <action>
Create RelationWidget.scala (RelationController-style per CONTEXT.md):
- Modal-based relation management (not simple dropdown)
- Renders:
  - Display area showing currently selected related records
  - Buttons: "Link", "Create" (if allowed), "Remove" for selected
  - Hidden inputs with selected relation IDs
- Modal loaded via HTMX on button click
- Supports relation types: belongsTo, hasOne, hasMany, belongsToMany

```scala
object RelationWidget extends FormWidget:
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val selectedIds = value match
      case list: List[_] => list.map(_.toString)
      case single if single != null => List(single.toString)
      case _ => List.empty

    val relationName = field.nameFrom.getOrElse("name")
    val sessionKey = ctx.sessionKey

    div(cls := "relation-widget",
      data("relation") := "true",
      data("session-key") := sessionKey,

      // Display selected relations
      div(cls := "relation-items",
        selectedIds.map(id => renderRelationItem(name, id, relationName))
      ),

      // Hidden inputs for form submission
      selectedIds.map(id =>
        input(tpe := "hidden", attr("name") := s"${name}[]", attr("value") := id)
      ),

      // Action buttons
      div(cls := "relation-actions",
        button(
          tpe := "button",
          cls := "btn btn-secondary btn-link-relation",
          attr("hx-get") := s"/admin/relation/modal?field=$name&session=$sessionKey",
          attr("hx-target") := "#relation-modal .modal-content",
          attr("hx-trigger") := "click",
          "Link Existing"
        ),
        button(
          tpe := "button",
          cls := "btn btn-secondary btn-create-relation",
          attr("hx-get") := s"/admin/relation/create-modal?field=$name&session=$sessionKey",
          attr("hx-target") := "#relation-modal .modal-content",
          attr("hx-trigger") := "click",
          "Create New"
        )
      ),

      // Modal placeholder
      div(id := "relation-modal", cls := "modal",
        div(cls := "modal-content")
      )
    )

  private def renderRelationItem(name: String, id: String, displayAttr: String): Frag =
    div(cls := "relation-item", data("id") := id,
      span(cls := "item-display", s"Loading..."),  // Would be populated via HTMX or initial load
      button(
        tpe := "button",
        cls := "btn-remove-relation",
        data("action") := "remove",
        data("id") := id,
        "x"
      )
    )
```

Create WidgetBootstrap.scala:
- Registers all standard widgets with the WidgetRegistry on application boot
- Can be called from app initialization or plugin boot lifecycle

```scala
object WidgetBootstrap:
  def register: ZIO[WidgetRegistry, Nothing, Unit] =
    for
      registry <- ZIO.service[WidgetRegistry]
      _ <- registry.register("text", TextWidget)
      _ <- registry.register("password", TextWidget)  // Same widget, different type attr
      _ <- registry.register("email", TextWidget)
      _ <- registry.register("url", TextWidget)
      _ <- registry.register("number", TextWidget)
      _ <- registry.register("textarea", TextareaWidget)
      _ <- registry.register("dropdown", DropdownWidget)
      _ <- registry.register("select", DropdownWidget)  // Alias
      _ <- registry.register("checkbox", CheckboxWidget)
      _ <- registry.register("switch", SwitchWidget)
      _ <- registry.register("datepicker", DatePickerWidget)
      _ <- registry.register("date", DatePickerWidget)  // Alias
      _ <- registry.register("datetime", DatePickerWidget)
      _ <- registry.register("time", DatePickerWidget)
      _ <- registry.register("repeater", RepeaterWidget)
      _ <- registry.register("relation", RelationWidget)
    yield ()

  val layer: ZLayer[WidgetRegistry, Nothing, Unit] =
    ZLayer.fromZIO(register)
```

Also create SwitchWidget in CheckboxWidget.scala file (variant of checkbox with different styling):
```scala
object SwitchWidget extends FormWidget:
  // Similar to CheckboxWidget but with switch CSS classes
  def render(field: FormField, name: String, value: Any, ctx: FormContext): Frag =
    val isChecked = CheckboxWidget.isValueChecked(value)
    div(cls := "switch-wrapper",
      label(cls := "switch",
        input(tpe := "hidden", attr("name") := name, attr("value") := "0"),
        input(
          tpe := "checkbox",
          attr("name") := name,
          attr("value") := "1",
          if isChecked then Some(attr("checked") := "checked") else None
        ),
        span(cls := "slider")
      )
    )
```
  </action>
  <verify>
Run `mill summercms.compile` - should compile without errors.
Verify RelationWidget produces modal trigger structure.
Verify WidgetBootstrap registers all widgets when called.
  </verify>
  <done>
RelationWidget implements modal-based relation management with link/create actions. WidgetBootstrap registers all standard widgets (text, textarea, dropdown, checkbox, switch, datepicker, repeater, relation) with the registry.
  </done>
</task>

</tasks>

<verification>
After completing all tasks:

1. **Compile check:**
   ```bash
   mill summercms.compile
   ```
   Must succeed with no errors.

2. **Widget output verification:**
   For each widget, verify rendered HTML contains:
   - TextWidget: input[type="text"] with form-control class
   - TextareaWidget: textarea element with rows attribute
   - DropdownWidget: select element with option children
   - CheckboxWidget: hidden input + checkbox input with label
   - DatePickerWidget: input with data-flatpickr attribute
   - RepeaterWidget: .repeater-items container with add button
   - RelationWidget: .relation-items with modal trigger buttons

3. **Widget registration verification:**
   After WidgetBootstrap.register runs, verify:
   - registry.resolve("text") returns Some(TextWidget)
   - registry.resolve("textarea") returns Some(TextareaWidget)
   - registry.resolve("dropdown") returns Some(DropdownWidget)
   - All registered types are resolvable

4. **Integration verification:**
   Use FormRenderer from 07-01 with sample fields.yaml containing various field types.
   Verify form renders with appropriate widgets for each field type.
</verification>

<success_criteria>
- [ ] TextWidget handles text, email, password, number, url input types
- [ ] TextareaWidget supports size variants (tiny, small, default, large, huge, giant)
- [ ] DropdownWidget renders select with options from config, handles emptyOption
- [ ] CheckboxWidget renders checkbox with hidden field pattern for proper form submission
- [ ] SwitchWidget provides toggle-style checkbox variant
- [ ] DatePickerWidget includes Flatpickr data attributes for JS initialization
- [ ] RepeaterWidget renders item list with collapse, drag handle, add/remove
- [ ] Repeater uses UUID keys (not sequential indexes) for items
- [ ] RelationWidget renders modal triggers for link/create actions
- [ ] WidgetBootstrap registers all widgets with proper type mappings
- [ ] All widgets implement FormWidget trait consistently
- [ ] Project compiles successfully with `mill summercms.compile`
</success_criteria>

<output>
After completion, create `.planning/phases/07-admin-forms-lists/07-02-SUMMARY.md`
</output>
