Questions & answers
The questions a careful user, reviewer or security lead should ask before trusting Clone with their Speckle data, answered from what the code does today. Where the honest answer is "no" or "not yet", it says so.
23 questions in 5 topics.
Trust and security
Is Clone an official Speckle product?
No. Clone is a third-party client. It is not a fork of Speckle's web app and does not use Speckle's private hosted viewer code. It talks to the same public GraphQL and REST APIs as any registered Speckle application, and renders with the published @speckle/viewer package. See the overview.
Does Clone ever see my Speckle password?
No. Sign-in is delegated to Speckle over OAuth 2.0 with PKCE. You enter your credentials on Speckle; Clone only receives an access code, which its server exchanges for tokens at /api/speckle-connect. The full sequence is in Signing in.
Can JavaScript in the browser read my access token?
Yes, currently. The tokens are stored in httpOnly cookies, which stops scripts reading the cookie. But the dashboard sends most GraphQL queries from the browser with a Bearer header, so the token value is read on the server and handed to client components, including through the getToken server action the viewer calls. The httpOnly flag protects the cookie, not the credential.
The exception is /api/model-data. It reads the cookie on the server, never returns the token, and never forwards it to the pre-signed storage URLs that bundle artifacts are downloaded from.
What does signing out actually do?
It revokes your tokens with Speckle, then clears the session. Clone calls Speckle's POST /auth/logout with your access and refresh tokens, which deletes both on the Speckle server. It then deletes the speckle_access_token and speckle_refresh_token cookies and sends you to /login. A copy of the token taken before sign-out stops working as well.
Revocation is best effort. If Speckle does not answer within five seconds, the local session is still cleared and the server logs a warning, but the tokens stay valid until Speckle expires them. To be certain, revoke Clone's access in your Speckle account settings under Profile → Developer, as described in Building with OAuth2. The mechanics are in Signing out.
Does the route guard check that my token is valid?
Not on every navigation, but a dead token now ends the session automatically. proxy.ts only checks that the speckle_access_token cookie exists, so no navigation waits on a network call. Validity is checked by requests the app makes anyway: every dashboard page asks Speckle for the active user as it loads.
Speckle refuses expired, revoked and made-up tokens with 403 Your token is not valid. When a request gets that answer, Clone sends the browser to /api/session-expired. That route asks Speckle itself, clears the cookies only if the token really is invalid, and lands you on /login with a note that your session ended, ready to sign in again. If Speckle cannot be reached to confirm, nothing is cleared: the login page says so, and signing in replaces the session. The details are in When a session ends.
What happens when my session expires?
You are signed out automatically and can sign in again straight away. Speckle revokes an expired token the first time it is used and refuses the request. The next Clone request that meets that refusal ends the session, and the login page explains why you are there. Speckle also returns a refresh token and Clone stores it, but nothing uses it yet, so an expiry means signing in again rather than a silent renewal. The cookies are set without an expiry, so browsers treat them as session cookies.
Does Clone store my model data on its server?
No. There is no database and no cross-user server cache. /api/model-data loads data from Speckle on each request and marks every response Cache-Control: private, no-store. Results are cached client-side by TanStack Query, keyed by user, project, model and version.
Two small things do outlive the page, in your browser's localStorage rather than on a server: the camera bookmarks you save in the viewer, and the URL of the last model loaded. Neither holds property data or your token.
Is my data sent to analytics or other third parties?
The codebase includes no analytics, tracking or error-reporting packages. Requests go to the Speckle server for GraphQL, REST and OAuth, and, for bundles, to the pre-signed artifact URLs Speckle hands out, fetched without your token. Your hosting provider's own request logs are outside what the code controls.
Data accuracy
Can I trust the totals?
Within limits that are stated rather than hidden. Totals are grouped by unit and never added across different units, and values without a unit are grouped as unit unspecified. When properties come from the viewer fallback, records can include collections and other non-geometric objects, so a count is a count of records, not of building elements. Try it in the rule demo.
Does Clone convert units?
No. Units are carried alongside values exactly as the export provides them. A missing unit stays missing, nothing is inferred from a property's name, and nothing is converted.
Is the geometry complete enough for spatial analysis?
Not always. For bundles, Clone decodes SGEO v1 meshes, lines, polylines, point clouds and the display polylines of NURBS curves and spirals. Instance placements and definitions, other analytic primitives and model reference-point transforms are not implemented. A partially unsupported model shows a visible warning and a fully unsupported one returns an error, but partial geometry should not be used for spatial analysis. See Supported geometry and Speckle's geometry schema.
Does version comparison tell me what changed?
Visually, not quantitatively. Comparison mode puts two versions side by side with synchronised cameras and camera bookmarks. It does not yet diff properties or quantities against stable authoring identifiers, so a question like "how did this wall's volume change?" is not one it answers. See Comparing versions.
Does the dashboard load everything in my account?
Not everything. Projects and workspaces follow pagination cursors until none remain. Other lists stop at explicit limits, such as up to 100 models with one version each in project details and up to 25 automations with 10 runs each, or at the server's default page size. The query reference lists every limit.
Scale and performance
How large a model can Clone load?
Bundles are read in Node under enforced limits:
128MB
of artifacts
256files
per bundle
1Mrows
per table
120s
deadline, also aborted if the client disconnects
Anything beyond them fails with an explicit error instead of hanging. Larger models would need a streamed or worker-based pipeline, which is not built. Geometry and properties are sent together, so your deployment's response-size limit can also apply.
Does every filter change query the server?
No. Filtering runs in the browser against the dataset that has already been downloaded and normalised, so adding or changing a rule costs no network request. The trade-off is that the property export returns whole tables: Clone downloads the full dataset before you can filter it.
What happens if Speckle is slow or down?
/api/model-data fails rather than hangs. It gives GraphQL calls 30 seconds, the property export 60 seconds and bundle reads 120 seconds, and a bundle read is also cancelled if the client disconnects. Upstream failures come back as 502 with the error message, a missing session as 401, bad identifiers as 400 and a missing version as 404. The dashboard's browser GraphQL client has no timeout of its own.
Setup and deployment
Can I use Clone with a self-hosted Speckle server?
Yes, with two changes. Set SERVER_URL for sign-in and model data, and update the hard-coded endpoint in context/graphql-fetcher.ts, which the dashboard calls from the browser:
- Browser GraphQL client
context/graphql-fetcher.tshttps://app.speckle.systems/graphqlHard-coded - OAuth and model data
/api/speckle-connect, /api/model-dataSERVER_URLConfigurableDefaults to https://app.speckle.systems.
Pointing Clone at another server means changing both. Setting only SERVER_URL signs you in to one server while the dashboard still queries the other.
For the server itself, see Hosting your own Speckle server.
What do I need before sign-in works locally?
A Speckle OAuth application with your local callback, /api/speckle-connect on the origin your dev server runs on, registered as its redirect URL. Then three values in .env.local: SPECKLE_APP_ID, SPECKLE_APP_SECRET, and NEXT_PUBLIC_BASE_URL set to that same origin. Restart the dev server after changing them. Full steps in Development.
Why does sign-in say it is not configured?
SPECKLE_APP_ID or SPECKLE_APP_SECRET is missing or blank. The sign-in action checks both before building Speckle's authorisation URL, and returns a configuration error instead of a broken redirect. Add them to .env.local and restart the dev server, because environment variables are read at startup.
Why did sign-in finish on a different site, signed out?
NEXT_PUBLIC_BASE_URL is probably unset. The OAuth callback builds its redirects from that variable and falls back to the production deployment, https://clone-opal.vercel.app, when it is missing. The session cookies are set on your local origin, but the browser is sent to the live site, which has no session and shows its login page. Set the variable to your local origin and restart the dev server.
Sign-in returned auth_failed. What went wrong?
The token exchange did not produce a token. Either the PKCE challenge cookie was missing when Speckle redirected back, for example because sign-in started on a different origin from the callback, or Speckle rejected the exchange, which usually means the app ID, secret or redirect URL do not match the registered application. A callback without an access code returns missing_access_code instead.
Automations
Why does a run show as Canceled when Speckle reported something else?
Clone maps Speckle Automate's eight run statuses onto four outcomes, and a status it does not recognise falls through to Canceled. A status Speckle adds later therefore shows as a neutral row instead of breaking the table, but it is mislabelled until the mapping in lib/automations.ts is updated.
PENDINGQueuedINITIALIZINGStartingRUNNINGRunning
SUCCEEDEDSucceeded
FAILEDFailedEXCEPTIONErroredTIMEOUTTimed out
CANCELEDCanceled
lib/automations.ts. A status Clone does not recognise falls through to Canceled, so a new upstream state shows as a neutral row instead of breaking the table.Not answered here? The documentation covers how each part works in more depth.