| 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 |
|
|
| 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