From e78ffe341944109a8f4dc86794d2134193830257 Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Fri, 22 May 2026 14:38:37 -0700 Subject: [PATCH] =?UTF-8?q?refactor:=20remove=20world=20smuggling=20?= =?UTF-8?q?=E2=80=94=20type=20styles,=20fragments,=20topologySubjectIds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit registry.ts: Add optional styles and fragments table types to the solver's GeometryWorld. Previously these were attached via (world as any).styles / .fragments, bypassing the type system. extraction.ts: - remapTopologyIds: accept topologySubjectIds as explicit parameter instead of reading (world as any)._topologySubjectIds and deleting it post-remap. Eliminates 3 smuggling accesses (read, set, delete) - All worldAny.styles / worldAny.fragments / worldAny.strings / worldAny.transforms / worldAny.matrices assignments now use typed world.xxx access. worldAny variable removed entirely. - attachMeasuredChWidths: use world.styles directly. - (result.world as any).env mutation replaced with typed result.world.env assignment. - (sizeWorld as any).styles replaced with typed access. predicates.ts: Replace all 3 (world as any).styles inline type-assert reads (getSubjectFontSizePx, getRootFontSizePx, getSubjectChWidthPx) with direct world.styles?.xxx access. No runtime behavior change. Zero (world as any) casts remain in extraction.ts or predicates.ts. 658 tests pass. --- packages/imhotep-playwright/src/extraction.ts | 58 +++++++------------ packages/imhotep-solver/src/predicates.ts | 26 ++++----- packages/imhotep-solver/src/registry.ts | 27 +++++++++ 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/packages/imhotep-playwright/src/extraction.ts b/packages/imhotep-playwright/src/extraction.ts index 905bd1d..c04f933 100644 --- a/packages/imhotep-playwright/src/extraction.ts +++ b/packages/imhotep-playwright/src/extraction.ts @@ -342,12 +342,7 @@ export function attachMeasuredChWidths( selectorToIds: Map, chWidthsBySelector: Map, ): void { - const worldAny = world as any - const styles = worldAny.styles as { - subjectId?: number[] - fontSize?: number[] - chWidth?: number[] - } | undefined + const styles = world.styles if (!styles) return const bySubject = new Map() for (const [key, ids] of selectorToIds) { @@ -543,7 +538,6 @@ export async function extractWorldFastGeometry( }) as FastExtractedPayload const world = buildGeometryWorld(extracted.elements) as GeometryWorld - const worldAny = world as any if (requiredFacts?.fragments) { const fragmentId: number[] = [] @@ -581,20 +575,15 @@ export async function extractWorldFastGeometry( } } - worldAny.fragments = { - fragmentId, + world.fragments = { subjectId, + boxId: fragmentId, fragmentKind, - boxLeft, - boxTop, - boxRight, - boxBottom, - lineIndex, - flowIndex, - parentFragmentId, + firstTextRunId: [], + textRunCount: [], } - worldAny.subjects.firstFragmentId = firstFragmentIds - worldAny.subjects.fragmentCount = fragmentCounts + world.subjects.firstFragmentId = firstFragmentIds + world.subjects.fragmentCount = fragmentCounts } if (requiredFacts?.geometry) { @@ -619,7 +608,7 @@ export async function extractWorldFastGeometry( originY.push(t.originY) } - worldAny.transforms = { + world.transforms = { transformId, subjectId: transformSubjectId, matrixStart, @@ -627,11 +616,11 @@ export async function extractWorldFastGeometry( originX, originY, } - worldAny.matrices = { values: matrices } + world.matrices = { values: matrices } } if (requiredFacts?.styles) { - const strings = worldAny.strings?.values ?? [] + const strings = world.strings?.values ?? [] const stringToId = new Map() for (let i = 0; i < strings.length; i++) stringToId.set(strings[i], i) const intern = (value: string): number => { @@ -696,8 +685,8 @@ export async function extractWorldFastGeometry( chWidth.push(s.chWidth) } - worldAny.strings = { values: strings } - worldAny.styles = { + world.strings = { values: strings } + world.styles = { subjectId: styleSubjectId, display, position, @@ -757,7 +746,7 @@ export async function extractWorldFastGeometry( // CDP Extraction // --------------------------------------------------------------------------- -function remapTopologyIds(world: GeometryWorld): void { +function remapTopologyIds(world: GeometryWorld, topologySubjectIds?: number[]): void { const nSubjects = world.subjects.ids.length if (nSubjects === 0) return @@ -766,7 +755,7 @@ function remapTopologyIds(world: GeometryWorld): void { backendToSolver.set(world.subjects.domNodeId[i], world.subjects.ids[i]) } - const rawSubjectIds = (world as any)._topologySubjectIds as number[] | undefined + const rawSubjectIds = topologySubjectIds function reorderAndRemap(rawValues: ArrayLike, targetLength: number): number[] { if (!rawSubjectIds || rawSubjectIds.length === 0) { @@ -810,8 +799,6 @@ function remapTopologyIds(world: GeometryWorld): void { if (world.scroll) { world.scroll.containerId = remapSimple(world.scroll.containerId) } - - delete (world as any)._topologySubjectIds } export async function extractWorldCdp( @@ -901,7 +888,8 @@ export async function extractWorldCdp( const snapshot = cdpResponse.snapshots[0] const canonical = adaptSnapshotToCanonical(snapshot) const world = adaptCanonicalWorldToSolver(canonical as any) as GeometryWorld - ;(world as any).styles = { + const topologySubjectIds = (canonical as any).topology?.subjectIds as number[] | undefined + world.styles = { subjectId: Array.from(canonical.styles.subjectId), lineHeight: Array.from(canonical.styles.lineHeight), fontFamilyStringId: Array.from(canonical.styles.fontFamilyStringId), @@ -909,9 +897,7 @@ export async function extractWorldCdp( fontWeight: Array.from(canonical.styles.fontWeight), } - ;(world as any)._topologySubjectIds = (canonical as any).topology?.subjectIds - - remapTopologyIds(world) + remapTopologyIds(world, topologySubjectIds) const selectorToIds = new Map() for (const [selectorKey, nodeIds] of selectorToNodeIds) { @@ -1010,10 +996,10 @@ export async function extractWorld( ? await extractWorldFastGeometry(playwrightPage, filteredSelectors, requiredFacts) : await extractWorldCdp(playwrightPage, filteredSelectors, requiredFacts) - ;(result.world as any).env = { - ...(result.world as any).env, - viewportWidth: env.viewportWidth, - viewportHeight: env.viewportHeight, + result.world.env = { + ...result.world.env, + viewportWidth: env.viewportWidth as number, + viewportHeight: env.viewportHeight as number, } if (requiredFacts?.styles) { @@ -1959,7 +1945,7 @@ export function buildCompatibilityReport(ui: ImhotepUi): CompatibilityReport { viewportWidth: 1280, viewportHeight: 720, } - ;(sizeWorld as any).styles = { + sizeWorld.styles = { subjectId: [1], fontSize: [16], chWidth: [9], diff --git a/packages/imhotep-solver/src/predicates.ts b/packages/imhotep-solver/src/predicates.ts index d402d38..1940233 100644 --- a/packages/imhotep-solver/src/predicates.ts +++ b/packages/imhotep-solver/src/predicates.ts @@ -314,8 +314,7 @@ function parseLengthOption(raw: unknown): { value: number; unit: string } | null } function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number { - const styles = (world as any).styles as { fontSize?: ArrayLike } | undefined; - const fontSize = styles?.fontSize; + const fontSize = world.styles?.fontSize; if (!fontSize) return 16; const ids = world.subjects?.ids; if (!ids) return 16; @@ -326,28 +325,23 @@ function getSubjectFontSizePx(world: GeometryWorld, subjectId: number): number { } function getRootFontSizePx(world: GeometryWorld): number { - const styles = (world as any).styles as { fontSize?: ArrayLike } | undefined; - const fontSize = styles?.fontSize; + const fontSize = world.styles?.fontSize; if (!fontSize || fontSize.length === 0) return 16; const fs = Number(fontSize[0] ?? 16); return Number.isFinite(fs) && fs > 0 ? fs : 16; } function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number { - const styles = (world as any).styles as { - subjectId?: ArrayLike - chWidth?: ArrayLike - } | undefined; - const chWidth = styles?.chWidth; - if (!chWidth || chWidth.length === 0) { + const subjectIdArr = world.styles?.subjectId; + const chWidthArr = world.styles?.chWidth; + if (!chWidthArr || chWidthArr.length === 0) { return getSubjectFontSizePx(world, subjectId) * 0.5; } - const styleSubjectIds = styles?.subjectId; - if (styleSubjectIds && styleSubjectIds.length > 0) { - for (let i = 0; i < styleSubjectIds.length; i++) { - if (Number(styleSubjectIds[i]) !== subjectId) continue; - const w = Number(chWidth[i]); + if (subjectIdArr && subjectIdArr.length > 0) { + for (let i = 0; i < subjectIdArr.length; i++) { + if (Number(subjectIdArr[i]) !== subjectId) continue; + const w = Number(chWidthArr[i]); if (Number.isFinite(w) && w > 0) return w; break; } @@ -357,7 +351,7 @@ function getSubjectChWidthPx(world: GeometryWorld, subjectId: number): number { if (ids) { const idx = ids.indexOf(subjectId); if (idx >= 0) { - const w = Number(chWidth[idx]); + const w = Number(chWidthArr[idx]); if (Number.isFinite(w) && w > 0) return w; } } diff --git a/packages/imhotep-solver/src/registry.ts b/packages/imhotep-solver/src/registry.ts index dc0adc2..ebd254f 100644 --- a/packages/imhotep-solver/src/registry.ts +++ b/packages/imhotep-solver/src/registry.ts @@ -139,6 +139,33 @@ export interface GeometryWorld { visibleArea: number[]; clippedArea: number[]; }; + /** Computed style data attached by extraction (optional, populated when requiredFacts.styles=true). */ + styles?: { + subjectId: number[]; + display?: number[]; + position?: number[]; + zIndexKind?: number[]; + zIndexValue?: number[]; + overflowX?: number[]; + overflowY?: number[]; + opacity?: number[]; + visibility?: number[]; + containFlags?: number[]; + pointerEvents?: number[]; + lineHeight?: number[]; + fontFamilyStringId?: number[]; + fontSize?: number[]; + fontWeight?: number[]; + chWidth?: number[]; + }; + /** Fragment data attached by extraction (optional, populated when requiredFacts.fragments=true). */ + fragments?: { + subjectId: number[]; + boxId: number[]; + fragmentKind: number[]; + firstTextRunId: number[]; + textRunCount: number[]; + }; } // --- Clause descriptor -------------------------------------------------------