Feet-and-inches entry: type 2' 7" instead of 2.6 #59

Closed
opened 2026-07-21 04:27:22 +00:00 by steve · 0 comments
Owner

Design: DESIGN.md

Context

Imperial dimension fields take decimal feet rounded to 0.1 ft (displayFromCm, units.ts:38-44). 0.1 ft is 1.2 inches, so of the whole-inch marks in a foot only 0″, 6″ and 12″ are exactly representable. Everything else is off by up to 0.6″:

You want Decimal feet Field shows You get
6″ 0.5 0.5 6″ ✓
3″ 0.25 0.3 3.6″
8″ 0.667 0.7 8.4″
2′ 7″ 2.583 2.6 2′ 7.2″

Type 2.58 and the field redisplays 2.6 — the value stores fine, but what you typed and what you see disagree.

Two things make this read as an oversight rather than a decision:

  • The app already speaks feet-and-inches, just not where you edit. formatCm (units.ts:47) renders 24′ 0″, which is why the garden card says 24′ 0″ × 24′ 0″ while the edit dialog beside it says Width (ft) 24.
  • The conversion helper already takes inches. cmFromFtIn(feet, inches = 0) — that parameter exists and no caller has ever passed it.

Note this is not fixed by #47. That issue makes entry exact but deliberately keeps display-side rounding (which is what stops 7.99 ft showing through). Different layer.

Scope

  • parseDimension(input, unit): number | null in units.ts. Imperial accepts 2' 7", 2′ 7″ (typographic and straight quotes), 2ft 7in, 31", 2', and a bare number still meaning feet so nothing anyone types today breaks. Metric keeps taking decimal meters. Unparseable → null → no commit.
  • formatDimensionInput(cm, unit): string for prefill — 2′ 7″ under imperial, decimal meters under metric. Render inches to one decimal (7′ 10.5″) rather than whole: 0.1″ is ~0.25 cm, so what's displayed stays honest about what's stored.
  • Swap the affected inputs from type="number" — it cannot hold 2' 7" — to text. Affects the inspector's W/H/X/Y and the garden form's width/height.
  • Metric behaviour unchanged throughout.
  • Tests: the table above round-trips; both quote styles parse; -2' 6" is −30″ not −2′ + 6″ (see below); garbage is rejected without mutating anything.

Sharp edges

Negative compound values. X/Y may be negative (Inspector.tsx:79-83 says so explicitly). -2' 6" must parse as −(2ft + 6in) = −30″. The naive implementation parses the sign onto the feet term only and yields −18″. This is the bug this issue will produce if nobody looks for it.

The no-op guard has to change. commitDim currently skips a write when the parsed value equals displayFromCm(current) — comparing at display precision, which is what stops a blur-without-edit from drifting the value. With compound entry there's no single display number to compare against, and a numeric epsilon has a nastier failure: a bed dragged on canvas to some arbitrary cm renders as, say, 2′ 7″, and a mere blur would then snap it to a whole inch. Silent mutation on blur is exactly what that guard exists to prevent.

Cheapest robust fix: compare the field's current text against the string it was rendered with, and skip if unchanged. That handles both concerns and doesn't depend on picking a tolerance.

Open question — mobile keyboards

type="number" currently gets you a numeric keypad; text input gets a full keyboard, and '/" aren't on the decimal keypad anyway. Since a bare number still means feet, the numeric path stays usable — but it's a real trade and worth deciding deliberately rather than discovering. inputMode="decimal" on a text input is worth trying first; check it on a phone before settling.

Out of scope

  • Fractional inches (2' 6 1/2"). Decimal inches cover it; revisit only if it grates.
  • Changing metric entry.
  • The read-only formatCm display, which is already correct.

Key files

web/src/lib/units.ts · web/src/lib/units.test.ts · web/src/editor/Inspector.tsx (commitDim) · web/src/components/gardens/GardenFormModal.tsx

Dependencies

Do after #47 — that lands exact storage, and this is the ergonomics layer on top. No point doing them in the other order.

Acceptance criteria

  • 2' 7" can be typed into any imperial dimension field and comes back as 2′ 7″.
  • Every whole-inch value from 0″ to 12″ round-trips exactly.
  • A bare 2.5 still means 2½ feet.
  • Blurring an unedited field never writes — verified against a value that isn't on a whole inch.
  • -2' 6" is −30 inches.
  • Metric entry is untouched; npm test green.
Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md) ## Context Imperial dimension fields take **decimal feet rounded to 0.1 ft** (`displayFromCm`, units.ts:38-44). 0.1 ft is **1.2 inches**, so of the whole-inch marks in a foot only **0″, 6″ and 12″ are exactly representable**. Everything else is off by up to 0.6″: | You want | Decimal feet | Field shows | You get | |---|---|---|---| | 6″ | 0.5 | 0.5 | 6″ ✓ | | 3″ | 0.25 | 0.3 | 3.6″ | | 8″ | 0.667 | 0.7 | 8.4″ | | 2′ 7″ | 2.583 | 2.6 | 2′ 7.2″ | Type `2.58` and the field redisplays `2.6` — the value stores fine, but what you typed and what you see disagree. Two things make this read as an oversight rather than a decision: - **The app already speaks feet-and-inches, just not where you edit.** `formatCm` (units.ts:47) renders `24′ 0″`, which is why the garden card says *24′ 0″ × 24′ 0″* while the edit dialog beside it says *Width (ft) 24*. - **The conversion helper already takes inches.** `cmFromFtIn(feet, inches = 0)` — that parameter exists and no caller has ever passed it. Note this is **not** fixed by #47. That issue makes *entry* exact but deliberately keeps display-side rounding (which is what stops `7.99 ft` showing through). Different layer. ## Scope - [ ] `parseDimension(input, unit): number | null` in `units.ts`. Imperial accepts `2' 7"`, `2′ 7″` (typographic and straight quotes), `2ft 7in`, `31"`, `2'`, and a **bare number still meaning feet** so nothing anyone types today breaks. Metric keeps taking decimal meters. Unparseable → `null` → no commit. - [ ] `formatDimensionInput(cm, unit): string` for prefill — `2′ 7″` under imperial, decimal meters under metric. Render inches to **one decimal** (`7′ 10.5″`) rather than whole: 0.1″ is ~0.25 cm, so what's displayed stays honest about what's stored. - [ ] Swap the affected inputs from `type="number"` — it cannot hold `2' 7"` — to text. Affects the inspector's W/H/X/Y and the garden form's width/height. - [ ] Metric behaviour unchanged throughout. - [ ] Tests: the table above round-trips; both quote styles parse; `-2' 6"` is −30″ **not** −2′ + 6″ (see below); garbage is rejected without mutating anything. ## Sharp edges **Negative compound values.** X/Y may be negative (`Inspector.tsx:79-83` says so explicitly). `-2' 6"` must parse as −(2ft + 6in) = −30″. The naive implementation parses the sign onto the feet term only and yields −18″. This is the bug this issue will produce if nobody looks for it. **The no-op guard has to change.** `commitDim` currently skips a write when the parsed value equals `displayFromCm(current)` — comparing at display precision, which is what stops a blur-without-edit from drifting the value. With compound entry there's no single display number to compare against, and a numeric epsilon has a nastier failure: a bed dragged on canvas to some arbitrary cm renders as, say, `2′ 7″`, and a mere blur would then snap it to a whole inch. Silent mutation on blur is exactly what that guard exists to prevent. Cheapest robust fix: **compare the field's current text against the string it was rendered with, and skip if unchanged.** That handles both concerns and doesn't depend on picking a tolerance. ## Open question — mobile keyboards `type="number"` currently gets you a numeric keypad; text input gets a full keyboard, and `'`/`"` aren't on the decimal keypad anyway. Since a bare number still means feet, the numeric path stays usable — but it's a real trade and worth deciding deliberately rather than discovering. `inputMode="decimal"` on a text input is worth trying first; check it on a phone before settling. ## Out of scope - Fractional inches (`2' 6 1/2"`). Decimal inches cover it; revisit only if it grates. - Changing metric entry. - The read-only `formatCm` display, which is already correct. ## Key files `web/src/lib/units.ts` · `web/src/lib/units.test.ts` · `web/src/editor/Inspector.tsx` (`commitDim`) · `web/src/components/gardens/GardenFormModal.tsx` ## Dependencies Do after #47 — that lands exact storage, and this is the ergonomics layer on top. No point doing them in the other order. ## Acceptance criteria - `2' 7"` can be typed into any imperial dimension field and comes back as `2′ 7″`. - Every whole-inch value from 0″ to 12″ round-trips exactly. - A bare `2.5` still means 2½ feet. - Blurring an unedited field never writes — verified against a value that isn't on a whole inch. - `-2' 6"` is −30 inches. - Metric entry is untouched; `npm test` green.
steve added the frontendpolish labels 2026-07-21 04:27:22 +00:00
steve closed this issue 2026-07-21 05:08:57 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#59