From a7ab55706f046e1f4df3058c57f2889268d58dee Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Mon, 31 Aug 2026 17:45:43 -0500 Subject: [PATCH] fix(ui): the Steps fit typechecks, and reads after the model it reads (#1664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two long-standing complaints from svelte-check, both in the one `fitOptions` block, and neither harmless-looking for the right reason: The per-side padding was widened to `string`, and the canvas types a side as `` `${number}px` `` — so `{ left: '440px' }` silently failed to check against the very option it is written for. It keeps its literal types now. The values were always correct at runtime, which is why the fit looked right and the error looked ignorable. And `fitOptions` read `model` from above its declaration. `$derived` is lazy so it ran, but it was a forward reference all the same; it now sits below the model, where it reads. `ui/` is at 0 errors, 0 warnings across 416 files. Claude-Session: https://claude.ai/code/session_012M9UE2Txyh7w8wyothDPTe Co-authored-by: Claude Opus 5 (1M context) --- ui/src/views/StepsView.svelte | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/ui/src/views/StepsView.svelte b/ui/src/views/StepsView.svelte index b5f7996..dcee6b1 100644 --- a/ui/src/views/StepsView.svelte +++ b/ui/src/views/StepsView.svelte @@ -120,19 +120,6 @@ } }); - /** - * The fit. A picture of a few boxes is centred — and the key, bottom left, - * would sit on its second row; it is fitted to the right of the key instead. - * A picture of many boxes is fitted to the whole stage, as the Screens view's - * — and a picture laid out by region may fit far out: the regions and their - * captions are the overview, and the reader zooms into one, where a 0.4 - * floor on a big screen's picture opened on a window torn out of its middle. - */ - const fitOptions = $derived( - model !== null && model.layout.nodes.length <= 24 && legendOpen - ? { padding: { left: '440px', top: '32px', right: '32px', bottom: '32px' }, maxZoom: 1, minZoom: 0.4 } - : { padding: 0.1, maxZoom: 1, minZoom: model !== null && model.regions !== null ? 0.2 : 0.4 } - ); const nodeTypes = { step: StepNode, region: RegionCaption, fork: ForkPoint, decision: DecisionCaption }; /** Two clicks on one box closer than this are a double-click. */ @@ -218,6 +205,27 @@ /** The order can be asked for and have nothing to read: the view then says so. */ const orderReadable = $derived(payload?.program != null); + /** + * The fit. A picture of a few boxes is centred — and the key, bottom left, + * would sit on its second row; it is fitted to the right of the key instead. + * A picture of many boxes is fitted to the whole stage, as the Screens view's + * — and a picture laid out by region may fit far out: the regions and their + * captions are the overview, and the reader zooms into one, where a 0.4 + * floor on a big screen's picture opened on a window torn out of its middle. + * + * Declared AFTER the model it reads: `$derived` is lazy, so the forward + * reference ran, but it is a forward reference all the same and the checker + * was right to say so. The per-side padding keeps its literal types (`as + * const`), because the canvas types a side as `` `${number}px` `` — widened + * to `string` it silently fails to typecheck against the very option it is + * written for. + */ + const fitOptions = $derived( + model !== null && model.layout.nodes.length <= 24 && legendOpen + ? { padding: { left: '440px', top: '32px', right: '32px', bottom: '32px' } as const, maxZoom: 1, minZoom: 0.4 } + : { padding: 0.1, maxZoom: 1, minZoom: model !== null && model.regions !== null ? 0.2 : 0.4 } + ); + /** The selection with the decision points it touches — what the edge filter and the dimming reason over. */ const reach = $derived(model === null || selected === null ? null : selectionReach(model, selected)); const neighbours = $derived.by(() => {