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).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- **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.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-10 22:51:32 -05:00
1 parent 7c8419fabd
commit 55e6e5cabb
4 files changed
+9 -7

No files matched your search

@@ -1129,7 +1129,10 @@ export const useSceneStore = create<SceneState>((set, get, store) => ({
const base64ToFile = (base64: string, filename: string): File => {
const arr = base64.split(",");
const mime = arr[0].match(/:([^;]*);/)?.[1] || "image/png";
// data:<mime>;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);
@@ -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 || "",
@@ -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.
@@ -1587,6 +1587,7 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
role="textbox"
aria-multiline="true"
aria-label={placeholder || "Prompt"}
tabIndex={disabled ? -1 : 0}
ref={editorRef}
contentEditable={!disabled}
onInput={handleInput}