asgeirtj/system_prompts_leaks · error · Error

three-d-stage: not ready — await stage.ready first

Error message

three-d-stage: not ready — await stage.ready first

What it means

The <three-d-stage> custom element throws this from setObject() when this._THREE is undefined. _THREE is only assigned inside the async _boot() (line 213), which dynamically imports three.js and its addons through the page import map. The ready Promise (created in the constructor at line 179, resolved at the end of _boot() at line 291) is the synchronization point — it yields { THREE } once the scene is live. Calling setObject before that promise settles dereferences a null THREE namespace, so the guard throws rather than producing a less obvious TypeError deeper in.

Source

Thrown at Anthropic/Claude Design/Starter components/three-d-stage.js:307

      }

      this._readyResolve({ THREE });
    }

    disconnectedCallback() {
      // Stop rendering and observing while detached; connectedCallback
      // resumes both. (The renderer itself is kept — a move within the
      // document must not rebuild the scene.)
      if (this._renderer) this._renderer.setAnimationLoop(null);
      if (this._ro) this._ro.disconnect();
    }

    /** Show (and own) the object. Replaces any previous object, enables
     *  shadows on every mesh, rests it on the ground plane, and frames
     *  the camera to its bounds. */
    setObject(object) {
      const THREE = this._THREE;
      if (!THREE) throw new Error('three-d-stage: not ready — await stage.ready first');
      if (this._object) this._scene.remove(this._object);
      this._object = object;
      object.traverse((o) => {
        if (o.isMesh) {
          o.castShadow = true;
          o.receiveShadow = true;
        }
      });
      const box = new THREE.Box3().setFromObject(object);
      if (!box.isEmpty()) {
        // Rest the object on the ground without moving its origin.
        this._ground.position.y = box.min.y;
        const sphere = box.getBoundingSphere(new THREE.Sphere());
        const dist =
          (sphere.radius / Math.tan((this._camera.fov * Math.PI) / 360)) * 1.35;
        const dir = new THREE.Vector3(1, 0.55, 1.25).normalize();
        this._camera.position
          .copy(sphere.center)

View on GitHub (pinned to 93c999115b)

Solutions

  1. Await stage.ready before calling setObject — `const { THREE } = await stage.ready;` resolves once three.js is loaded and the scene is built.
  2. Verify the <script type="importmap"> block is in <head> before any module script, with the exact pinned three@0.184.0 URLs and integrity hashes from the header docs.
  3. If _boot() rejected, read the red .err overlay on the stage element — it shows the specific import failure (CDN, version, or integrity) that prevented _THREE from being set.
  4. If calling from a non-async context, restructure so the model build and setObject happen inside an async function that can await stage.ready.

Example fix

// before
const stage = document.querySelector('three-d-stage');
const model = new THREE.Group(); // THREE is not in scope / _THREE not set
stage.setObject(model); // throws: not ready

// after
const stage = document.querySelector('three-d-stage');
const { THREE } = await stage.ready;
const model = new THREE.Group();
stage.setObject(model);
Defensive patterns

Strategy: validation

Validate before calling

// stage.ready is a Promise<{ THREE }> created in the constructor
// and resolved at the end of _boot(). Awaiting it is the only
// correct precondition for setObject — there is no sync flag.
const { THREE } = await stage.ready; // resolves, or rejects on _boot failure
// Now safe to build the model and call stage.setObject(...)

Type guard

// There is no public sync readiness flag; _THREE is the internal
// signal set inside _boot(). Use it only if you must check without
// awaiting (it can be undefined during the initial boot race):
function isStageReady(stage) {
  return Boolean(stage._THREE) && Boolean(stage._scene);
}
// Prefer awaiting stage.ready — this guard is for diagnostics only.

Try / catch

try {
  stage.setObject(model);
} catch (e) {
  if (e.message.startsWith('three-d-stage: not ready')) {
    const { THREE } = await stage.ready; // may itself reject if _boot failed
    stage.setObject(model);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling stage.setObject(model) synchronously after document.querySelector('three-d-stage') without awaiting stage.ready. Also fires when _boot() rejected (import map missing/broken, CDN unreachable, version mismatch) so _THREE was never assigned — the ready Promise rejects but if the caller never awaited it, the rejection is swallowed and the next setObject call hits the null guard.

Common situations: Copy-pasting usage code that omits the `const { THREE } = await stage.ready` line shown in the header docs. Placing the <script type="importmap"> after the module script, or omitting it entirely, so import('three') fails. A wrong/unpinned three.js version in the import map where the addons path layout differs. Calling setObject from a non-async callback that cannot await the promise.


AI-assisted analysis of asgeirtj/system_prompts_leaks@93c999115b (2026-08-13). Data as JSON: /api/errors/f8ea464b7c59f8e9. Report an issue: GitHub.