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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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″:Type
2.58and the field redisplays2.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:
formatCm(units.ts:47) renders24′ 0″, which is why the garden card says 24′ 0″ × 24′ 0″ while the edit dialog beside it says Width (ft) 24.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 ftshowing through). Different layer.Scope
parseDimension(input, unit): number | nullinunits.ts. Imperial accepts2' 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): stringfor 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.type="number"— it cannot hold2' 7"— to text. Affects the inspector's W/H/X/Y and the garden form's width/height.-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-83says 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.
commitDimcurrently skips a write when the parsed value equalsdisplayFromCm(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
2' 6 1/2"). Decimal inches cover it; revisit only if it grates.formatCmdisplay, 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.tsxDependencies
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 as2′ 7″.2.5still means 2½ feet.-2' 6"is −30 inches.npm testgreen.