xtablo-source/.planning/phases/13-design-system-foundation/13-03-SUMMARY.md
Arthur Belleville 569c6c7853
docs(13-03): complete form input components plan summary
- Input, Textarea, Select, FormField components ported from go-backend
- TDD gates: 4 RED/GREEN commits, all 13 new tests passing
- Full go test ./... is green (all packages)
2026-05-16 14:01:53 +02:00

7.9 KiB

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
13-design-system-foundation 03 backend/internal/web/ui
form-components
input
textarea
select
form-field
design-system
tdd
requires provides affects
13-01
13-02
input-component
textarea-component
select-component
form-field-component
backend/internal/web/ui/input.css
backend/internal/web/ui/input.templ
backend/internal/web/ui/textarea.css
backend/internal/web/ui/textarea.templ
backend/internal/web/ui/select.css
backend/internal/web/ui/select.templ
backend/internal/web/ui/select_helpers.go
backend/internal/web/ui/form-field.css
backend/internal/web/ui/form_field.templ
backend/internal/web/ui/ui_test.go
backend/tailwind.input.css
added patterns
verbatim-port
tdd-red-green
inline-js-htmx-reinit
nil-guard-optional-regions
created modified
backend/internal/web/ui/input.css
backend/internal/web/ui/input.templ
backend/internal/web/ui/textarea.css
backend/internal/web/ui/textarea.templ
backend/internal/web/ui/select.css
backend/internal/web/ui/select.templ
backend/internal/web/ui/select_helpers.go
backend/internal/web/ui/form-field.css
backend/internal/web/ui/form_field.templ
backend/internal/web/ui/ui_test.go
backend/tailwind.input.css
InputProps and TextareaProps include Disabled and Required as explicit bool fields (D-CA03) — conditional attribute rendering via templ if blocks rather than Attrs pass-through
select_helpers.go ported verbatim from go-backend with no naming changes (package-private helpers, not exported)
tailwind.input.css updated to include all four new CSS imports in order: input, textarea, select, form-field
duration completed_date tasks files
~12 minutes 2026-05-16 2 11

Phase 13 Plan 03: Form Input Components — Input, Textarea, Select, FormField Summary

Four form-input component types ported from go-backend with matching CSS, typed Props structs, and full test coverage: Input (class ui-input, inputType/inputID helpers), Textarea (class ui-textarea, textareaRows default 4, resize vertical), Select (ui-select-control, inline JS with __uiSelectInitAll and htmx:afterSwap re-init), FormField (conditional label/hint/error regions).

Tasks Completed

Task Name Commit Key Files
1 (RED) Failing tests for Input and Textarea ace9f5b ui_test.go (7 tests)
1 (GREEN) Port input.templ/input.css and textarea.templ/textarea.css 9556b20 input.css, input.templ, textarea.css, textarea.templ, tailwind.input.css
2 (RED) Failing tests for Select and FormField 50e3fb0 ui_test.go (6 tests)
2 (GREEN) Port select + form-field components with CSS and helpers 52fb77d select.css, select.templ, select_helpers.go, form-field.css, form_field.templ, tailwind.input.css

What Was Built

Task 1 — Input and Textarea (TDD):

  • input.css: .ui-input with appearance: none, background: var(--color-surface-default), border-radius: 0.75rem, min-height: 44px, padding: 0.75rem 0.95rem, width: 100%, plus placeholder and focus rules.
  • input.templ: InputProps struct with ID, Name, Value, Placeholder, Type, Disabled bool, Required bool, Attrs templ.Attributes. Uses inputID(props.ID, props.Name) and inputType(props.Type) helpers. Conditional disabled/required attributes.
  • textarea.css: .ui-textarea with same base styling as input plus min-height: 7rem and resize: vertical.
  • textarea.templ: TextareaProps struct with ID, Name, Value, Placeholder, Rows int, Disabled bool, Required bool, Attrs templ.Attributes. Uses inputID() and textareaRows() helpers (defaults to 4 rows).
  • tailwind.input.css: Added @import for input.css and textarea.css.
  • 7 tests passing: TestInput_DefaultType, TestInput_EmailType, TestInput_IDFallback, TestInput_ExplicitID, TestTextarea_RendersClass, TestTextarea_DefaultRows, TestTextarea_ExplicitRows.

Task 2 — Select and FormField (TDD):

  • select_helpers.go: 9 helper functions ported verbatim from go-backend: selectPlaceholder, selectNativeID, selectMenuID, selectBoolData, selectSelectedValues, selectOptionSelected, selectSelectedLabels, selectSelectedLabel, selectMenuOptionClass, selectIsDisabled. Imports "strings" and "github.com/a-h/templ".
  • select.templ: SelectOption struct (Value, Label, Disabled bool) and SelectProps struct (ID, Name, Placeholder, Value, Values []string, Multiple bool, Options []SelectOption, Attrs). Full custom dropdown with hidden native <select>, visible ui-select-control button, and dropdown ui-select-menu. Inline <script> block with window.__uiSelectInitAll and document.addEventListener("htmx:afterSwap", ...) re-init listener (Pitfall 6 compliance).
  • select.css: .ui-select (relative positioned wrapper), .ui-select-control (min-height 44px, border-radius 0.75rem), .ui-select-menu (absolute positioned, max-height 16rem, overflow-y auto), option states (hover, selected, disabled).
  • form_field.templ: FormFieldProps struct with Label string, For string, Field templ.Component, Error string, Hint string. Conditional label, field, hint, and error rendering with nil/empty guards.
  • form-field.css: .ui-form-field (grid layout), .ui-form-label (font-weight 600), .ui-form-hint (muted color), .ui-form-error (danger foreground color).
  • tailwind.input.css: Added @import for select.css and form-field.css.
  • 6 tests passing: TestSelect_RendersControl, TestSelect_HasInlineScript, TestSelect_HasHtmxListener, TestFormField_RendersLabel, TestFormField_RendersError, TestFormField_NoErrorWhenEmpty.

Verification Results

  • just generate: succeeds (no templ compile errors)
  • go test ./internal/web/ui/... -count=1: all 29 tests pass
  • go test ./... -count=1: all packages pass (auth, db, files, jobs, web, web/ui, templates)
  • grep -l 'ui-input\|ui-textarea\|ui-select\|ui-form-field' backend/internal/web/ui/*.css: lists 4 files
  • grep '__uiSelectInitAll' backend/internal/web/ui/select.templ: matches (inline JS present)
  • grep 'htmx:afterSwap' backend/internal/web/ui/select.templ: matches (re-init listener present)

Deviations from Plan

Auto-fixed Issues

None — plan executed exactly as written. Disabled and Required bool fields were added to InputProps and TextareaProps as specified by D-CA03 (plan action step 3 and 5). This was within the plan's explicit instructions.

Known Stubs

None — all components are fully implemented with correct CSS selectors and templ rendering logic. No data wiring required for these presentational components.

Threat Flags

No new network endpoints, auth paths, file access patterns, or schema changes introduced.

The threat model entries T-13-03-01 (inline script read-only DOM), T-13-03-02 (templ auto-escapes FormField Error), and T-13-03-03 (static Disabled/Required attributes) are all satisfied:

  • The inline select script reads only DOM data attributes — no user-controlled string is eval'd
  • FormFieldProps strings (Label, Hint, Error) are server-controlled and templ auto-escapes them
  • Disabled/Required are Go bool fields rendered as static HTML attributes

TDD Gate Compliance

  • Task 1 RED gate: commit ace9f5b — 7 failing tests (undefined: Input, undefined: InputProps, undefined: Textarea, undefined: TextareaProps)
  • Task 1 GREEN gate: commit 9556b20 — all 7 TestInput/TestTextarea tests pass; full suite green
  • Task 2 RED gate: commit 50e3fb0 — 6 failing tests (undefined: Select, undefined: SelectProps, undefined: FormField, undefined: FormFieldProps)
  • Task 2 GREEN gate: commit 52fb77d — all 6 TestSelect/TestFormField tests pass; full suite green
  • REFACTOR gate: Not needed — implementation was clean on first pass

Self-Check: PASSED