Skip to content

finding(fields): CurrencyField/PercentField lean entirely on browser number-input sanitization, which happy-dom does not implement #6765

Description

@os-sales

Filed unassigned by the dev seat implementing objectui#6715, as an out-of-scope finding. Not fixed there — that card is fenced to LocationField. Recording, not choosing.

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-15 (90 issues), grepped for CurrencyField / PercentField / type="number" / number input / sanitiz. Only objectui#6452 and objectui#6318 matched, neither about this. A control word (LocationField) hit three cards in the same sweep (objectui#6715, objectui#6744, objectui#6755), so the read was sound rather than empty.

What

Two sibling widgets hand parseFloat(e.target.value) straight to onChange with no whole-string guard of their own, measured on b76ca6764:

  • packages/fields/src/widgets/CurrencyField.tsx:89 and :118
  • packages/fields/src/widgets/PercentField.tsx:45

That is the same reading objectui#6715 has just removed from LocationField, where it truncated 12abc into a plausible wrong coordinate. Here the assumed guard is different: both widgets render type="number" inputs, and the HTML value-sanitization algorithm is supposed to make the value an empty string when the text is not a valid floating-point number, so parseFloat should never see residue.

The measurement

happy-dom — this package's test environment — does not implement that sanitization. Measured directly:

type=text   set="12abc" -> value="12abc"  parseFloat=12
type=number set="12abc" -> value="12abc"  parseFloat=12
type=number set="1.2.3" -> value="1.2.3"  parseFloat=1.2
type=number set="0x10"  -> value="0x10"   parseFloat=0

Two consequences, and the second is the one worth recording:

  1. The widgets' correctness rests on a browser behaviour that nothing in this repo asserts. If it holds, the residue case degrades to a silent drop (the value becomes an empty string, onChange receives null, and nothing is said) — which is the objectui#6716 defect class rather than the objectui#6715 one.
  2. Any test written for these two widgets exercises the unsanitized path, not the shipped one. A suite can go green on a code path no browser takes, and a pin can fix in place a truncating behaviour that never actually reaches a user. The oracle and the product disagree, and nothing says so.

What is NOT measured

  • Real-browser behaviour of either widget. No dogfood or Playwright run was made against them; the reasoning above about sanitization is from the HTML spec, not from an observation.
  • Whether a silent drop on a currency or percent field is worth announcing, the way objectui#6716 decided it was for a coordinate.
  • Whether either widget has a user-visible defect at all. This is an observation about an unasserted dependency and a misleading test environment, not a reproduced failure.

Related

objectui#6715 (the same parseFloat reading in LocationField, where the input is type="text" so there is no sanitization to lean on) · objectui#6716 (the silent-refusal class) · objectui#6272 (the plausible-wrong-value precedent)

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpriority:p2

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions