Skip to content

update perf monitor#836

Closed
Devin T. Currie (DTCurrie) wants to merge 1 commit into
mainfrom
potential-perf-monitor-fix
Closed

update perf monitor#836
Devin T. Currie (DTCurrie) wants to merge 1 commit into
mainfrom
potential-perf-monitor-fix

Conversation

@DTCurrie

Copy link
Copy Markdown
Member

Fixes the three-perf performance monitor when the visualizer is embedded in a host page that serves a strict Content-Security-Policy (for example app.viam.com). Previously the monitor anchored to the corner of the whole viewport instead of the visualizer container, and its numeric counters (GPU/CPU/FPS plus the geometry/texture/shader stats) were blank while only the line graphs drew.

Frontend

  • PerfMonitor.svelte (new): a local replacement for @threlte/extras's PerfMonitor. It mounts three-perf into Threlte's canvas wrapper (useThrelte().dom) instead of document.body, overrides three-perf's hardcoded position: fixed to absolute so the monitor anchors to the visualizer container, and sets z-index: 10 so it sits above the overlay panels (Details and FloatingPanel at z-4/z-5) that share its top-right corner but below the fullscreen and file-drop layers.
  • Scene.svelte: render the local PerfMonitor instead of the one from @threlte/extras.
  • App.svelte: call configureTextBuilder({ useWorker: false }) from troika-three-text in the component's module script so troika typesets text on the main thread instead of in a blob: worker. It runs on import, before any troika Text mounts, which is required because troika ignores the setting after its first font request. Note this switches every troika Text on the page to main-thread typesetting, which is intended.
  • vite-env.d.ts: ambient module declaration for troika-three-text, which ships no types, covering the single export we import.
  • package.json: add three-perf and troika-three-text as direct dependencies. Both were already present transitively (three-perf via @threlte/extras, troika via both), declaring them matches the new direct imports, and they dedupe to a single troika-three-text@0.52.4.

Two changes outside src/: a drive-by fix to the Makefile up target, which ran pnpm up (pnpm resolves bare up to its built-in update alias, so it mutated package.json/pnpm-lock.yaml instead of running the up script) and now runs pnpm run up, and a new .claude/skills/verify guide documenting how to build and drive the app locally.

Why?

Why disable troika's worker instead of allowing it in the host CSP?

troika-worker-utils loads its worker in two stages: it creates the worker from a small bootstrap blob:, then the worker calls importScripts() on a second blob: URL to pull in the real typesetting code. A strict host CSP allows the first step (worker creation, governed by worker-src) but blocks the second (importScripts of a blob:, governed by the worker's script-src), so the worker starts, fails to load its code, and every troika Text renders blank. This is also why troika's own supportsWorkers() fallback does not save it: it only checks whether new Worker() throws synchronously, which succeeds, so troika commits to the worker path and only fails later in stage two. Relaxing the host CSP is the wrong fix. We do not own that policy, it would have to be repeated for every host that embeds the visualizer, and adding blob: to script-src weakens the host's XSS posture just so a debug overlay can typeset numbers off the main thread. Disabling the worker makes the library self-sufficient on any host, and the cost is negligible here: we render only short labels, and the expensive part (SDF glyph generation) already runs on the main thread via WebGL regardless of this setting.

Why do our other blob workers, like the PCD loader, keep working under the same CSP?

They are self-contained. The PCD loader inlines its entire parser into one worker string and never calls importScripts, so it only needs worker creation, which the CSP permits. troika is the only worker that fetches its code in a second importScripts(blob:) step, which is the step the CSP blocks. troika-worker-utils exposes no option to emit a self-contained worker, so configureTextBuilder({ useWorker: false }) is the supported escape hatch.

Why fork the Threlte component instead of passing domElement plus a CSS rule?

Threlte's PerfMonitor does accept a domElement prop, but three-perf hardcodes position: fixed on its wrapper, so correcting the anchor still requires overriding that style. Doing it with a global CSS rule fails in the exact embedded case this PR targets: the standalone app.css does not ship with the packaged library (svelte-package builds from src/lib, which imports no CSS), and having a library leak a global #three-perf-ui rule into a host page is a worse smell than the self-contained JS override. The fork duplicates a small amount of stable stage-wiring, which is the accepted tradeoff.

Testing

Ran pnpm check (svelte-check + go vet) and pnpm lint (prettier + eslint), both clean.

Verified behavior by driving the running app headless with Playwright against the snapshot scene, since the bug is only observable at runtime. Toggled Settings > Debug > Render stats and confirmed the monitor anchors to the container's top-right corner rather than the viewport, with all numeric counters rendering. Intercepted every Worker construction and confirmed none originate from troika (only the PCD, GLTF, and Sentry blob workers appear). With an entity selected, confirmed the monitor paints above the Details panel that shares its corner. No automated tests were added; this is a UI and debug-overlay behavior change verified by hand.

@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 90e8f10

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@viamrobotics/motion-tools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-10 20:18 UTC

@DTCurrie

Copy link
Copy Markdown
Member Author

I do not think this is a good solution, and I think this may be worth pushing an update to @threlte/extras for.


let perf: ThreePerf

$effect.pre(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird that claude is still $effect.pre happy, neither of these should be pre.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah can't seem to get it off them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants