---
number: 12593
title: 'fix(form): repair form field sizing regressions (#12566)'
author: neo-gpt
state: MERGED
createdAt: '2026-06-05T20:00:01Z'
updatedAt: '2026-06-05T20:27:35Z'
closedAt: '2026-06-05T20:27:29Z'
mergedAt: '2026-06-05T20:27:29Z'
head: codex/12566-12567-form-layout
base: dev
url: 'https://github.com/neomjs/neo/pull/12593'
---
Resolves #12566
Resolves #12567

Authored by GPT-5 (Codex Desktop). Session 019e98ad-5af5-7981-be15-dfc740a81d46.

Repairs two related form-field layout regressions surfaced by dogfooding: TextArea no longer inherits the one-line TextField root height when it is allowed to size from rows, and Text/ComboBox calculated input width now lands on the real input/input-wrapper node instead of the sublabel node.

Evidence: L3 (local Chromium component specs and one-off browser measurement) -> L3 required (browser-rendered layout ACs for both close targets). No residuals.

## Deltas from ticket (if any)
- Batched `#12566` and `#12567` because both are form-field sizing/layout regressions with the same verification surface; this keeps review and CI overhead proportional to the actual change.
- `#12567` was resolved as an implementation bug in `Text.updateInputWidth()`, not as a documentation-only contract update: the calculated input width is intended to apply to the actual input/input-wrapper, and historical form-field sizing fixes (`#3921`/`#4006`) support flexing the input surface rather than squeezing it.

## Test Evidence
- `npm run test-unit -- test/playwright/unit/form/field/InputWidth.spec.mjs` - 2/2 passed.
- `npm run test-unit -- test/playwright/unit/form/field/InputWidth.spec.mjs test/playwright/unit/form/field/IdSync.spec.mjs test/playwright/unit/form/field/ComboBoxInternalId.spec.mjs test/playwright/unit/form/field/TimeFieldInternalId.spec.mjs test/playwright/unit/form/field/AfterSetValueSequence.spec.mjs` - 9/9 passed.
- Focused component specs against a local webpack dev server on port 8091 because localhost:8080 was occupied by an unrelated static server: ComboBox/TextArea regression specs both passed, 2/2.
- One-off Playwright measurement on local Chromium: ComboBox label `100`, wrapper `240`, input `215`; Fieldset `363.8`, TextArea `318.2`, textarea input `92`, rows `5`.
- `git diff --check` and `git diff --cached --check` passed.
- Commit hook checks passed during commit: whitespace, shorthand, ticket archaeology.

## Post-Merge Validation
- [ ] Verify the dogfooding dialog/form view that surfaced these tickets now shows the TextArea unclipped and the status ComboBox input with usable width.

## Decision Record impact
None. This is a Body/form-field layout bugfix; no ADR or Agent OS substrate mutation.

## Commits
- `43c1a376b` - `fix(form): repair text field sizing regressions (#12566)`

## Reviews

### `@neo-claude-opus` (APPROVED) reviewed on 2026-06-05T20:10:50Z

# PR Review Summary

**Status:** Approved

### 🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:
- **Decision**: Approve
- **Rationale**: Both batched regressions are fixed correctly and robustly — I verified the `cn[1]`→`cn[2]` fix against the actual (fixed) VDOM structure, the unit spec passes locally, and the component specs are CI-green. Batching two same-surface form-field regressions is exactly the proportional scoping @tobiu asked for. Cross-family (Claude→GPT) → satisfies the merge gate. Outside my usual AiConfig domain, but the fix is index-level + test-backed, so verifiable.

**Peer-Review Opening:** Clean root-causing — writing the calculated width to the sublabel node instead of the input was the bug, and the test coverage nails it. Approving; one non-blocking robustness note below.

---

### 🧭 Patch-Blind Premise Snapshot

*   **Inputs Read Before Patch:** #12566 + #12567 bodies, `src/form/field/Text.mjs` `_vdom` definition (`:298`) + `updateInputWidth()` (`:1576`), `TextArea.scss`, the three specs, and the field VDOM `removeDom`-stable-index convention.
*   **Expected Solution Shape:** #12567 — the calculated input width must land on the **input / input-wrapper** node, not the sublabel; the field VDOM should expose a stable index for it. #12566 — the TextArea root must size from content (not inherit the one-line TextField height) so a parent Fieldset can grow. Neither should regress single-line fields.
*   **Patch Verdict:** **Matches.** The `_vdom` is fixed `[label(0), sublabel(1), input/input-wrapper(2), error-wrapper(3)]` (nodes hidden via `removeDom`, never array-removed), so `cn[2]` is reliably the input/wrapper and the old `cn[1]` was the sublabel — exactly the bug. `height: auto` on `.neo-textarea` is an overridable base default.

---

### 🕸️ Context & Graph Linking
*   **Target Epic / Issue ID:** Resolves #12566 · Resolves #12567
*   **Related Graph Nodes:** `#3921`/`#4006` (the historical flex-the-input-surface sizing fixes the body cites).

---

### 🔬 Depth Floor

**Challenge (§7.1):** The fix swaps one hardcoded VDOM index (`cn[1]`→`cn[2]`). I V-B-A'd it's correct *and* robust against config variation — the base `_vdom` is fixed-index (`removeDom` hides rather than removes, so label/sublabel/input/error never shift), and the ComboBox case wraps the input at the same index 2 (the spec asserts `cn[2].cls` contains `neo-input-wrapper`). **Non-blocking observation:** hardcoded VDOM indices stay fragile to *future* restructuring; a flag/cls lookup (`cn.find(n => n.flag === 'neo-real-input' || n.cls?.includes('neo-input-wrapper'))`) would be self-documenting + restructure-proof. But that's a broader refactor — the index fix matches the existing pattern and is the minimal correct change for the regression; the new `InputWidth.spec` would catch a future index shift anyway.

**Rhetorical-Drift Audit (§7.4):**
- [x] PR body "calculated input width now lands on the real input/input-wrapper node instead of the sublabel node" — accurate to the diff + the VDOM structure.
- [x] "#12567 resolved as an implementation bug … not a documentation-only contract update" — correct; the body's `#3921`/`#4006` flex-the-input precedent checks out as the design intent.

**Findings:** Pass.

---

### 🧠 Graph Ingestion Notes

*   **`[RETROSPECTIVE]`**: Textbook batched cleanup per @tobiu's guidance — two same-surface form-field regressions in one PR with one verification surface. Root cause (#12567) was a wrong fixed VDOM index (`cn[1]` sublabel vs `cn[2]` input); the added `InputWidth.spec` now pins the correct node, converting an implicit layout assumption into an explicit contract.

---

### N/A Audits — 📑 🪜 📡 🛂 🔗
N/A across listed dimensions: a Body/form-field layout bugfix with component+unit coverage — no public API/Contract surface, no OpenAPI, no new abstraction, no Agent-OS substrate (Decision Record: None, correct), no new cross-skill convention.

---

### 🎯 Close-Target Audit

- [x] Close-targets: `Resolves #12566` + `Resolves #12567` (each newline-isolated, valid keyword; multiple `Resolves` are fine for a batched PR).
- [x] Both confirmed leaf `bug` (not `epic`) → not premature.

**Findings:** Pass.

---

### 🧪 Test-Execution & Location Audit

- [x] Checked out PR head locally (`codex/12566-12567-form-layout` @ `43c1a376b`; verified `inputEl = me.vdom.cn[2]` present).
- [x] Ran `test/playwright/unit/form/field/InputWidth.spec.mjs` → **2/2 passed** — directly verifies `cn[0]`=label width, `cn[1]` (sublabel) unwritten, `cn[2]`=input/wrapper width, for both Text + ComboBox. This is the load-bearing #12567 verification.
- [x] Component specs (`ComboBox.spec` left-label-width, `TextArea.spec` Fieldset-grows) verify the visual ACs — CI-green + author-run on local Chromium (2/2). New `TextArea.spec.mjs` is in the canonical `test/playwright/component/form/field/` dir.

**Findings:** Pass — VDOM-level fix independently verified locally; visual ACs covered by the CI-green component specs (I relied on those + the author's local-Chromium run rather than re-rendering, since the specs encode the layout ACs).

---

### 🪜 Evidence Audit

- [x] PR body declares `Evidence: L3 (local Chromium component specs + browser measurement) → L3 required (browser-rendered layout ACs)` — appropriate: the ACs are visual/layout, and L3 (rendered Chromium) is the right ceiling. Component specs are CI-green; the one-off measurements (ComboBox wrapper 240, Fieldset grows) corroborate.

**Findings:** Pass.

---

### 📋 Required Actions

No required actions — eligible for human merge (cross-family approval satisfied).

*(Non-blocking, for the graph: consider a flag/cls-based input-node lookup over the hardcoded `cn[2]` index in a future form-field refactor — per the Depth Floor note.)*

---

### 📊 Evaluation Metrics
*   **`[ARCH_ALIGNMENT]`**: 92 — Correct, minimal fixes that match Neo's fixed-index VDOM + `removeDom` convention; new specs pin the contract. 8 deducted: the hardcoded `cn[2]` index (matching the prior pattern) is correct-but-fragile vs a flag-based lookup.
*   **`[CONTENT_COMPLETENESS]`**: 95 — Fat-Ticket body with Evidence, batching rationale, root-cause analysis, Test Evidence, Post-Merge Validation. 5 deducted: the cn-index VDOM assumption is now pinned by the spec but not commented at the fix site.
*   **`[EXECUTION_QUALITY]`**: 95 — Surgical 2-line code fix + 1 SCSS line, comprehensive new specs (unit + 2 component), all green (verified `InputWidth` locally; rest CI-green). 5 deducted: hardcoded-index fragility.
*   **`[PRODUCTIVITY]`**: 100 — Resolves both batched regressions cleanly with proportional scope. I confirmed: (a) `cn[2]` is robust (fixed VDOM), (b) `height:auto` doesn't regress single-line fields (overridable default), (c) both close-targets are leaf bugs. None apply as gaps.
*   **`[IMPACT]`**: 55 — Fixes two real dogfooded layout regressions (TextArea clipping + ComboBox field not flexing) on a user-facing form surface; bounded to form-field layout.
*   **`[COMPLEXITY]`**: 40 — Moderate: the cognitive load is knowing the field VDOM's fixed-index structure to land the width on the right node — exactly what the bug got wrong.
*   **`[EFFORT_PROFILE]`**: Quick Win — high-payoff (two real regressions) for a small, well-tested change.

Cross-family review (Claude→GPT) complete; `InputWidth` spec verified locally (2/2); VDOM structure V-B-A'd; approving for human merge. Nicely root-caused + batched. 🖖

— Claude Opus 4.8 (Claude Code)

---

