Skip to content

Add /import?src=<url>: a hand-off point for scanning apps - #720

Open
alxbouchard wants to merge 5 commits into
pascalorg:mainfrom
alxbouchard:import-from-url
Open

Add /import?src=<url>: a hand-off point for scanning apps#720
alxbouchard wants to merge 5 commits into
pascalorg:mainfrom
alxbouchard:import-from-url

Conversation

@alxbouchard

@alxbouchard alxbouchard commented Aug 25, 2026

Copy link
Copy Markdown

What

A new /import?src=<https-url>[&name=<scene name>] page: an external tool — in our case an iOS LiDAR scanning app — hosts a build JSON at a URL and opens this page; the visitor reviews what the file contains and imports it as a new scene with one click.

Until now the only way to get a generated scene into the editor was dragging a file onto Load Build, which does not exist on mobile. With this page, any scan app can end its export flow with "Open in Pascal Editor".

How it works

  • The fetch happens client-side in the visitor's browser — the same trust model as dropping a file on Load Build. The host must allow CORS; no server ever fetches the URL, so there is no SSRF surface.
  • The file runs through the same validateBuildJson pre-flight as Load Build, and the page shows the node counts, floor area, warnings and errors before anything happens.
  • Only an explicit click creates the scene, through the regular POST /api/scenes route — so auth, origin checks and apiGraphSchema validation (including the AssetUrl allowlist) all apply unchanged.
  • src accepts https only (http for localhost during development), rejects embedded credentials, and caps the document at 25 MB. URL validation lives in lib/import-src.ts with unit tests.

Tested

  • bun test lib: 41 pass (6 new)
  • bun run check-types, biome check: clean
  • End to end against a real scan: a RoomPlan-captured apartment (31 walls, 27 items, slab, scene materials) served from a CORS-enabled URL → review page → one click → scene opens in the editor with furniture and per-item slot materials rendering correctly.

Why we built it

We build A3 Atlas Scanner, an iOS field tool that captures homes with RoomPlan and already exports your {nodes, rootNodeIds, materials} graph (catalog items scaled to measured dimensions, measured colors as scene materials, IFC alongside). This page is the missing link that turns every scan into a one-tap Pascal scene. Happy to adjust anything to fit the project's conventions.

🤖 Generated with Claude Code


Note

Medium Risk
New import surface and client-fetched third-party JSON, but no server-side URL fetch; scene persistence still goes through the authenticated scenes API and existing graph validation.

Overview
Adds /import?src=<url>[&name=…] so external tools (e.g. mobile scan apps) can hand off a CORS-hosted build JSON: the browser fetches and runs validateBuildJson, shows stats/warnings/errors and an editable scene name, then creates a scene only after Import as a new scene via existing POST /api/scenes (auth and server validation unchanged).

parseImportSrc in import-src.ts gates the src query param (https only, http on localhost, no credentials, 10 MB cap aligned with the scene store) with unit tests.

validateBuildJson now parses optional top-level materials into ParsedBuildJson (invalid entries warned and skipped). Load Build in the settings panel passes those materials into setScene so scene:<id> slot refs keep custom finishes on file import.

Reviewed by Cursor Bugbot for commit eae2ccc. Bugbot is set up for automated code reviews on this repo. Configure here.

A scanning app (or any external tool) can now open
editor.pascal.app/import?src=<https-url> to hand a build JSON to the
editor. The fetch happens client-side in the visitor's browser (same
trust model as dropping a file on Load Build; the host must allow
CORS), the file runs through the same validateBuildJson pre-flight,
the visitor reviews the contents, and only an explicit click creates
the scene through the regular POST /api/scenes route — so auth,
origin checks and apiGraphSchema validation all apply unchanged.

src accepts https only (http for localhost during development), no
embedded credentials, 25 MB cap. Unit tests for the URL validation.
Comment thread apps/editor/app/import/import-client.tsx Outdated
Comment thread apps/editor/app/import/import-client.tsx
Comment thread apps/editor/app/import/import-client.tsx
validateBuildJson dropped the top-level materials table: every
scene:<id> slot ref in an imported file pointed at a material that no
longer existed, so custom finishes silently reverted to defaults on
both Load Build and /import. ParsedBuildJson now carries materials —
each entry SceneMaterial-validated individually, invalid ones skipped
with a warning so a bad material never takes the import down — and
handleConfirmImport hands them to setScene, whose extra.materials
support already existed. Unit tests for valid, partially-invalid and
non-object materials.
@alxbouchard

Copy link
Copy Markdown
Author

Follow-up commit: while testing the import end to end I found that validateBuildJson drops the top-level materials table, so every scene:<id> slot ref in an imported file pointed at a material that no longer existed — custom finishes silently reverted to defaults on the existing Load Build path too, not just on this new page. The second commit carries materials through ParsedBuildJson (each entry validated individually, invalid ones skipped with a warning) and hands them to setScene, which already supported them. Covered by unit tests; verified visually with a scan whose furniture uses per-item scene materials.

Comment thread apps/editor/lib/import-src.ts Outdated
Review feedback (Bugbot): a superseded or aborted fetch could
overwrite a newer state — including surfacing the cleanup abort as a
CORS error — and a src change left the previous review (and its
Import button) live against the old file. The effect now resets to
'fetching' on every src change and every state update from a
cancelled run is ignored.
@alxbouchard

Copy link
Copy Markdown
Author

Addressed the Bugbot review (it ran against the first commit, 53b688c):

  • Import drops scene materials — this was real, and deeper than the new page: validateBuildJson dropped the top-level materials table for the existing Load Build path too. Fixed in 81715bc (materials carried through ParsedBuildJson, entries validated individually, handed to setScene).
  • Aborted fetch shown as CORS / stale review across src changes — both fixed in the latest commit: the effect resets to fetching on every src change, and a cancelled run can no longer overwrite newer state or surface its abort as an error.

Review feedback (Bugbot): MAX_IMPORT_BYTES was 25 MB while the sqlite
scene store rejects graphs over DEFAULT_MAX_SCENE_BYTES (10 MB) — a
file could pass review then fail POST /api/scenes with a 413 shown as
a generic error. The cap now matches the store's limit, and a 413 gets
its own explanation.
@alxbouchard

Copy link
Copy Markdown
Author

Third Bugbot point addressed: MAX_IMPORT_BYTES now matches the scene store's 10 MB limit (DEFAULT_MAX_SCENE_BYTES) so a file can't pass review and then 413 on create — and a 413 now gets its own message instead of the generic failure.

Comment thread apps/editor/app/import/import-client.tsx
Review feedback (Bugbot): a second tap on Import could fire before
React re-rendered into 'creating', creating two scenes and racing the
redirect. A synchronous useRef guard now blocks re-entry; it is
released in a finally so a failed create can be retried.
@alxbouchard

Copy link
Copy Markdown
Author

Double-tap point addressed: a synchronous useRef guard now blocks re-entry into the create call (released in a finally so a failed create can be retried) — the state-based check alone could indeed race the re-render, especially on the mobile hand-off this page exists for.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit eae2ccc. Configure here.

<div className="rounded-xl border border-border/60 bg-background p-6">
<p className="text-destructive text-sm">{phase.message}</p>
</div>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Create errors discard the review

Medium Severity

A failed POST /api/scenes switches the page to the error phase, which unmounts the review UI and the already-validated graph. The creating ref is released so a retry can run, but nothing remains to click. On this mobile hand-off a refresh re-fetches src, and a short-lived scan URL may already be gone.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eae2ccc. Configure here.

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.

1 participant