diff --git a/ui/docs/calculator-ux.md b/ui/docs/calculator-ux.md index 7bb8ec4..5797eec 100644 --- a/ui/docs/calculator-ux.md +++ b/ui/docs/calculator-ux.md @@ -111,13 +111,19 @@ negative, the five blocks follow the engine value rules (`pkg/calc/validator.go`, surfaced per-field by `shipClassFieldErrors`), and a custom load may not exceed cargo capacity. -Every displayed number — the derived results and the goal-seek -back-solved input — is rounded **up** to three decimals through the -shared `pkg/calc/number.go.Ceil3` (bridged as `core.ceil3`), so a value -is never shown lower than it is (a speed of 5.0003 reads 5.001). The -engine keeps its own round-to-nearest `util.Fixed*`; `Ceil3` is a -display-only helper that lives in `pkg/calc` so the UI and Go share one -implementation. +Every displayed number — the derived results, the inherited tech / +planet MAT labels, and the goal-seek back-solved input — is rounded +**up** to three decimals through the shared `pkg/calc/number.go.Ceil3` +(bridged as `core.ceil3`) and always padded to three decimals so the +column reads the same on integers and fractions alike (a speed of 20 +shows as `20.000`, of 5.0003 as `5.001`). Labels and inputs use the +monospace stack from the design tokens (`--font-mono`) with +right-aligned, tabular numerals so values line up vertically across +rows. To match the display rule, every number input also refuses a +fourth decimal as the user types: typing `1.2345` clamps the input to +`1.234` on input. The engine keeps its own round-to-nearest +`util.Fixed*`; `Ceil3` is a display-only helper that lives in `pkg/calc` +so the UI and Go share one implementation. ## Create / load / delete diff --git a/ui/frontend/src/lib/calculator/ship-design-area.svelte b/ui/frontend/src/lib/calculator/ship-design-area.svelte index 739f47a..48e9cac 100644 --- a/ui/frontend/src/lib/calculator/ship-design-area.svelte +++ b/ui/frontend/src/lib/calculator/ship-design-area.svelte @@ -139,6 +139,20 @@ calculator math — so the ship-group upgrade flow can reuse it later. const floor = techFloor[key]; techs[key] = next < floor ? floor : next; } + // Refuse a fourth decimal as typing happens: keeps the calculator + // from ever displaying a >3-decimal fraction the user could not + // have intended (the calculator math is `Ceil3`-rounded for display + // anyway). Pairs with `bind:value` — `apply` overwrites the bound + // state when Svelte's own bind handler has already read the + // over-precise number. + function capDecimals(event: Event, apply: (next: number) => void): void { + const el = event.currentTarget as HTMLInputElement; + const txt = el.value; + const dot = txt.indexOf("."); + if (dot < 0 || txt.length - dot - 1 <= 3) return; + el.value = txt.slice(0, dot + 4); + apply(el.valueAsNumber); + } const BLOCK_ROWS: { key: keyof DesignBlocksState; @@ -196,6 +210,7 @@ calculator math — so the ship-group upgrade flow can reuse it later. title={blockError(row.key)} data-testid={`calculator-block-${row.key}`} onkeydown={(e) => onBlockKey(e, row.key, row.smartStep)} + oninput={(e) => capDecimals(e, (v) => (blocks[row.key] = v))} /> {/if} {#if row.tech !== null} @@ -213,6 +228,7 @@ calculator math — so the ship-group upgrade flow can reuse it later. title={techError(techKey)} data-testid={`calculator-tech-${techKey}`} onkeydown={(e) => bumpTech(e, techKey)} + oninput={(e) => capDecimals(e, (v) => (techs[techKey] = v))} />