From 55e6e5cabbff1466153bb6ce59a1424ed5747eb4 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 10 Nov 2026 22:51:32 -0500 Subject: [PATCH] frontend: Sonar follow-ups (S8786, S6852, S7059) (#2824) Two issues SonarCloud raised on main after #2821/#2822 merged: - **typescript:S8786** `SceneState.ts` `base64ToFile`: #2821's `/:([^;]*);/` is still flagged. The mime type is now read with `indexOf`: the text between the first `:` and the `;` after it, else `image/png`. Checked equal to the regex on `data:image/png;base64`, `data:;base64`, a header with no `:` or `;`, and several `:`. - **typescript:S6852** `MentionTextarea`: the `role="textbox"` editor gets `tabIndex={disabled ? -1 : 0}`. A `contentEditable` element was focusable already, so nothing changes for users. Typechecked locally against main (no new errors). ## Summary by CodeRabbit * **Bug Fixes** * Improved image import handling so MIME types are identified more reliably, with PNG used as a fallback when the type cannot be determined. * Disabled text editors are no longer reachable through sequential keyboard navigation; enabled editors remain tabbable. * Physics support is initialized during editor setup after the required canvas and container elements are available. - **typescript:S7059** `editor.ts`: `ensureAmmoLoaded()` (an async load) moves out of the `Editor` constructor to `EngineProvider`, the only place an `Editor` is built, right before `new Editor(...)`. The loader is a singleton, so the script is still appended once. --- .../libs/components/pagedraw/src/lib/stores/SceneState.ts | 5 ++++- .../src/lib/contexts/EngineContext/EngineProvider.tsx | 4 ++++ frontend/libs/components/pagescene/src/lib/engine/editor.ts | 6 ------ .../libs/components/promptbox/src/lib/MentionTextarea.tsx | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts b/frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts index 12db75a0c0..b586152312 100644 --- a/frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts +++ b/frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts @@ -1129,7 +1129,10 @@ export const useSceneStore = create((set, get, store) => ({ const base64ToFile = (base64: string, filename: string): File => { const arr = base64.split(","); - const mime = arr[0].match(/:([^;]*);/)?.[1] || "image/png"; + // data:;base64: the text between the first ":" and the ";" after it. + const start = arr[0].indexOf(":") + 1; + const end = arr[0].indexOf(";", start); + const mime = (start > 0 && end !== -1 && arr[0].slice(start, end)) || "image/png"; const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); diff --git a/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx b/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx index c2a3cf718c..deec2df039 100644 --- a/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx +++ b/frontend/libs/components/pagescene/src/lib/contexts/EngineContext/EngineProvider.tsx @@ -2,6 +2,7 @@ import { ReactNode, useEffect, useRef, useState } from "react"; import { EngineContext, setActiveEditor } from "./EngineContext"; import Editor from "../../engine/editor"; +import { ensureAmmoLoaded } from "../../engine/ammoLoader"; import type { PageSceneAdapter } from "../../adapter"; import { getSceneGenerationMetaData } from "../../sceneMetadata"; import { usePageSceneStore } from "../../PageSceneStore"; @@ -61,6 +62,9 @@ export const EngineProvider = ({ // them to null on unmount). if (!sceneContainer || !editorCanvas || !camViewCanvas) return undefined; + // Ammo.js WASM is a global side-effect script the physics path depends on; the singleton + // appends it to document.body once, however often the Editor is constructed. + void ensureAmmoLoaded(); const newEditor = new Editor(adapterRef.current); newEditor.initialize({ sceneToken: sceneToken || "", diff --git a/frontend/libs/components/pagescene/src/lib/engine/editor.ts b/frontend/libs/components/pagescene/src/lib/engine/editor.ts index 98d127cee6..a980c1d459 100644 --- a/frontend/libs/components/pagescene/src/lib/engine/editor.ts +++ b/frontend/libs/components/pagescene/src/lib/engine/editor.ts @@ -31,7 +31,6 @@ import { ModalTransformController } from "./editor/ModalTransformController"; import Stats from "three/examples/jsm/libs/stats.module.js"; import { SparkRenderer } from "@sparkjsdev/spark"; import { usePageSceneStore } from "../PageSceneStore"; -import { ensureAmmoLoaded } from "./ammoLoader"; import { EngineEventBus } from "./events/EngineEventBus"; import { EngineStoreBridge } from "./EngineStoreBridge"; import { @@ -187,11 +186,6 @@ class Editor { this.adapter = adapter; this.can_initialize = true; this.stats = new Stats(); - // Ammo.js WASM is a global side-effect script the physics path - // depends on. The singleton ensures it's appended to document.body - // exactly once, even across Editor re-construction. - void ensureAmmoLoaded(); - // Version and name. this.version = 2.0; // Clock, scene and camera essentials. diff --git a/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx b/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx index 23196ab9b0..174562c1fe 100644 --- a/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx +++ b/frontend/libs/components/promptbox/src/lib/MentionTextarea.tsx @@ -1587,6 +1587,7 @@ export const MentionTextarea = forwardRef( role="textbox" aria-multiline="true" aria-label={placeholder || "Prompt"} + tabIndex={disabled ? -1 : 0} ref={editorRef} contentEditable={!disabled} onInput={handleInput}