Phase 07: Admin Forms & Lists - 3 plan(s) in 2 wave(s) - Wave 1: 07-01 (YAML parsing, WidgetRegistry, FormRenderer) - Wave 2: 07-02 (form widgets), 07-03 (list rendering) - parallel - Ready for execution
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-admin-forms-lists | 01 | execute | 1 |
|
true |
|
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.
<execution_context> @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md </execution_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 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.
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:
def render(field: FormField, name: String, value: Any, ctx: FormContext): scalatags.Text.Frag - WidgetRegistry as ZIO service with Ref-based mutable registry:
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.
trait FormRenderer:
def render(config: FieldsYamlConfig, data: Map[String, Any], errors: Map[String, String], mode: FormMode): Task[scalatags.Text.Frag]
Implementation details:
- Parse FieldsYamlConfig and group fields by tab (using field.tab property)
- If multiple tabs exist, render horizontal tab navigation (per CONTEXT.md)
- Within each tab, group fields by section if cssClass indicates sections
- 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:
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:
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:
field.trigger.map(t => Seq(
data("trigger-field") := t.field,
data("trigger-action") := t.action,
data("trigger-condition") := t.condition
))
-
Compile check:
mill summercms.compileMust succeed with no errors.
-
YAML parsing verification: Create a test that parses sample fields.yaml:
fields: title: label: Title span: left required: true content: type: textarea tab: Content comment: Enter the main content tabs: fields: description: type: textareaVerify it produces FieldsYamlConfig with correct field structure.
-
Widget registry verification: Register a test widget and verify resolution works.
-
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
<success_criteria>
- 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</success_criteria>