--- phase: 07-admin-forms-lists plan: 01 type: execute wave: 1 depends_on: [] files_modified: - summercms/build.mill - summercms/src/admin/yaml/FieldsYamlSchema.scala - summercms/src/admin/yaml/ColumnsYamlSchema.scala - summercms/src/admin/yaml/YamlParser.scala - summercms/src/admin/forms/FormField.scala - summercms/src/admin/forms/WidgetRegistry.scala - summercms/src/admin/forms/FormRenderer.scala - summercms/src/admin/forms/FormContext.scala autonomous: true must_haves: truths: - "YAML fields.yaml content parses into typed FormFieldConfig ADT" - "YAML columns.yaml content parses into typed ListColumnConfig ADT" - "Widget registry resolves field type string to widget implementation" - "Form container renders with proper structure (tabs, sections, field groups)" artifacts: - path: "summercms/src/admin/yaml/FieldsYamlSchema.scala" provides: "FormFieldConfig, TriggerConfig, TabsConfig ADTs with circe decoders" exports: ["FormFieldConfig", "FieldsYamlConfig"] - path: "summercms/src/admin/yaml/ColumnsYamlSchema.scala" provides: "ListColumnConfig ADT with circe decoders" exports: ["ListColumnConfig", "ColumnsYamlConfig"] - path: "summercms/src/admin/yaml/YamlParser.scala" provides: "ZIO-wrapped YAML parsing service" exports: ["YamlParser"] - path: "summercms/src/admin/forms/WidgetRegistry.scala" provides: "Widget type to renderer mapping" exports: ["WidgetRegistry", "FormWidget"] - path: "summercms/src/admin/forms/FormRenderer.scala" provides: "Form container HTML generation" exports: ["FormRenderer"] key_links: - from: "summercms/src/admin/yaml/YamlParser.scala" to: "summercms/src/admin/yaml/FieldsYamlSchema.scala" via: "circe-yaml parsing" pattern: "parser\\.parse.*as\\[FieldsYamlConfig\\]" - from: "summercms/src/admin/forms/FormRenderer.scala" to: "summercms/src/admin/forms/WidgetRegistry.scala" via: "widget resolution for field rendering" pattern: "registry\\.resolve" --- Establish YAML parsing infrastructure and form rendering foundation for admin backend. Purpose: This plan creates the core building blocks that all form and list functionality depends on. The YAML ADTs define the configuration language, the parser loads definitions, and the widget registry enables extensible field rendering. Output: Typed YAML parsing for fields.yaml/columns.yaml, widget registry pattern, and basic form container rendering with ScalaTags. @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md @.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 Task 1: Add dependencies and create YAML schema ADTs summercms/build.mill summercms/src/admin/yaml/FieldsYamlSchema.scala summercms/src/admin/yaml/ColumnsYamlSchema.scala Add circe-yaml and scalatags dependencies to build.mill: ```scala ivy"io.circe::circe-yaml:1.0.0", ivy"io.circe::circe-generic:0.14.9", ivy"com.lihaoyi::scalatags:0.13.1" ``` Create FieldsYamlSchema.scala with case classes matching WinterCMS fields.yaml structure: - TriggerConfig(action: String, field: String, condition: String) - PresetConfig(field: String, type: String) - FormFieldConfig with all WinterCMS-compatible properties: - type, label, span, size, tab, placeholder, comment, commentAbove - default (as Json for flexibility), required, disabled, hidden, readOnly - context (String or List[String] via Json), trigger, preset, dependsOn - options (Map or method name via Json), cssClass, attributes - Repeater-specific: form, prompt, titleFrom, minItems, maxItems, sortable - Relation-specific: select, nameFrom, emptyOption - FileUpload-specific: imageWidth, imageHeight - TabsConfig for tab groupings - FieldsYamlConfig as root (fields, tabs, secondaryTabs) Use circe semi-auto derivation (deriveDecoder) for all types. Handle Option fields properly with default None values. Create ColumnsYamlSchema.scala with: - ListColumnConfig: label, type, searchable, sortable, invisible, width, relation, select, format, valueFrom, path - FilterConfig: scope, label, conditions - ColumnsYamlConfig as root (columns, filters, defaultSort) Include implicit decoders in companion objects. Run `mill summercms.compile` - should compile without errors. Check that all case classes have proper circe decoders by reviewing generated code structure. Build compiles with new dependencies. YAML schema ADTs defined with all WinterCMS-compatible fields and circe decoders. Task 2: Create YamlParser service and Widget registry summercms/src/admin/yaml/YamlParser.scala summercms/src/admin/forms/WidgetRegistry.scala summercms/src/admin/forms/FormField.scala summercms/src/admin/forms/FormContext.scala Create YamlParser.scala as a ZIO service: ```scala trait YamlParser: def parseFields(yaml: String): IO[YamlParseError, FieldsYamlConfig] def parseColumns(yaml: String): IO[YamlParseError, ColumnsYamlConfig] def parseFieldsFromFile(path: Path): IO[YamlParseError, FieldsYamlConfig] def parseColumnsFromFile(path: Path): IO[YamlParseError, ColumnsYamlConfig] ``` - Use io.circe.yaml.parser for parsing - Wrap errors in YamlParseError sealed trait (ParseError, FileNotFound, DecodeError) - Live implementation using ZIO.attemptBlocking for file reads Create FormField.scala with processed field representation: - FormField case class with resolved values (not raw config) - Include computed properties: effectiveLabel (from label or field name), effectiveSpan, isRequired, etc. - Method to convert FormFieldConfig to FormField with defaults applied Create FormContext.scala: - FormContext case class holding: model data (Map[String, Any]), session key, form mode (create/update), validation errors - Methods: getValue(fieldName), getError(fieldName), isCreate, isUpdate Create WidgetRegistry.scala: - FormWidget trait with render method signature: ```scala def render(field: FormField, name: String, value: Any, ctx: FormContext): scalatags.Text.Frag ``` - WidgetRegistry as ZIO service with Ref-based mutable registry: ```scala def register(typeName: String, widget: FormWidget): UIO[Unit] def resolve(typeName: String): UIO[Option[FormWidget]] def resolveOrDefault(typeName: String): UIO[FormWidget] // Falls back to TextWidget ``` - Include DefaultTextWidget as fallback implementation Run `mill summercms.compile` - should compile without errors. Write a simple test parsing a fields.yaml string to verify parsing works. YamlParser service parses YAML strings/files into typed configs. WidgetRegistry provides type-to-widget mapping with registration and resolution. FormContext provides form state access. Task 3: Create FormRenderer with tab and section support summercms/src/admin/forms/FormRenderer.scala Create FormRenderer.scala as a ZIO service that generates form HTML: ```scala trait FormRenderer: def render(config: FieldsYamlConfig, data: Map[String, Any], errors: Map[String, String], mode: FormMode): Task[scalatags.Text.Frag] ``` Implementation details: 1. Parse FieldsYamlConfig and group fields by tab (using field.tab property) 2. If multiple tabs exist, render horizontal tab navigation (per CONTEXT.md) 3. Within each tab, group fields by section if cssClass indicates sections 4. For each field: - Resolve widget from WidgetRegistry - Apply span classes (span-left, span-right, span-full based on field.span) - Add trigger data attributes if field.trigger defined - Render label with required asterisk if needed - Render comment/commentAbove help text - Call widget.render for the actual input - Show inline error if present in errors map Use ScalaTags for all HTML generation: ```scala import scalatags.Text.all._ ``` Form structure following CONTEXT.md decisions: - Labels above inputs (stacked layout) - Sections collapsible, expanded by default - Required fields: red asterisk after label - Help text visible below fields - Modern minimal aesthetic via CSS classes (actual CSS in theme, not inline) Include HTMX attributes for form submission: ```scala form( attr("hx-post") := submitUrl, attr("hx-target") := "#form-container", attr("hx-swap") := "innerHTML", // ... fields div(cls := "form-buttons", button(tpe := "submit", cls := "btn btn-primary", "Save"), button(tpe := "submit", cls := "btn btn-primary", attr("hx-post") := s"$submitUrl?close=true", "Save and Close") ) ) ``` Trigger support via data attributes for client-side JavaScript: ```scala field.trigger.map(t => Seq( data("trigger-field") := t.field, data("trigger-action") := t.action, data("trigger-condition") := t.condition )) ``` Run `mill summercms.compile` - should compile without errors. Manually verify FormRenderer produces valid HTML structure by calling render with sample config. FormRenderer generates complete form HTML from FieldsYamlConfig. Supports tabs, sections, field grouping, error display, and HTMX submission. Integrates with WidgetRegistry for field rendering. After completing all tasks: 1. **Compile check:** ```bash mill summercms.compile ``` Must succeed with no errors. 2. **YAML parsing verification:** Create a test that parses sample fields.yaml: ```yaml fields: title: label: Title span: left required: true content: type: textarea tab: Content comment: Enter the main content tabs: fields: description: type: textarea ``` Verify it produces FieldsYamlConfig with correct field structure. 3. **Widget registry verification:** Register a test widget and verify resolution works. 4. **Form renderer verification:** Call FormRenderer.render with sample config and verify: - Returns valid ScalaTags Frag - Contains form element with hx-post attribute - Contains field containers with proper span classes - Required fields have asterisk in label - [ ] circe-yaml, circe-generic, and scalatags dependencies added to build.mill - [ ] FieldsYamlSchema.scala defines FormFieldConfig with all WinterCMS-compatible properties - [ ] ColumnsYamlSchema.scala defines ListColumnConfig with all properties - [ ] YamlParser service parses YAML strings and files with proper error handling - [ ] FormField case class provides processed field representation with defaults - [ ] FormContext provides form state (data, errors, mode) access - [ ] WidgetRegistry trait and live implementation with register/resolve methods - [ ] FormRenderer generates form HTML with tabs, sections, HTMX attributes - [ ] Trigger data attributes included for conditional visibility support - [ ] Project compiles successfully with `mill summercms.compile` After completion, create `.planning/phases/07-admin-forms-lists/07-01-SUMMARY.md`