From cc4727a32e9450ca99149c39527af8dfe193d1d2 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Tue, 26 May 2026 18:43:32 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20F8-06=20calculator=20polish=20?= =?UTF-8?q?=E2=80=94=20always=203-decimal=20display,=20mono=20font,=20inpu?= =?UTF-8?q?t=20cap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner feedback round 2 on PR #61: - Pad every read-only calculator value to three decimals: tech labels, derived results (mass, speed, attack, defence, bombing, cargo capacity), planet MAT, planet build-rate, modernization cost, and the full-cargo capacity label all read as "1.000" instead of "1", matching the goal-seek back-solved input and the report. Drops thousands grouping so the same `fmt()` string also embeds cleanly in the read-only `` cell. - Switch label and input styling onto the existing `--font-mono` token (right-aligned, tabular-nums) so columns line up vertically across rows like a financial table. - Refuse a fourth decimal as the user types in every calculator number input (DWSC blocks, tech, MAT, custom load, lock value, modernization target tech): the `oninput` truncates the input text past three decimal digits and explicitly writes the truncated value back through `bind:value`, so Svelte's later reactive flush cannot undo the cap. - Doc + tests follow the rule (five new vitest cases covering the 3-decimal label format, the input cap on each input class, and the integer-padding rule for derived results). --- ui/docs/calculator-ux.md | 20 ++-- .../lib/calculator/ship-design-area.svelte | 20 +++- .../src/lib/sidebar/calculator-tab.svelte | 40 +++++++- ui/frontend/tests/calculator-tab.test.ts | 93 ++++++++++++++++--- 4 files changed, 150 insertions(+), 23 deletions(-) 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))} />