# Backend Storage CAD Viewer uses a small backend interface so the same React app can run against local files during development and hosted object storage in deployment. Client code talks to HTTP routes and catalog URLs; it does not read filesystem paths directly. ## Interface Backend implementations expose this core shape: ```js { kind, readCatalog({ rootDir, fileRef }), refreshCatalog({ rootDir, fileRef }), resolveFileAssetAccess({ fileRef, asset, catalog }), } ``` Trusted upload tooling for hosted storage serializes catalog JSON and uploads it as `catalog.json`. Writable backends may expose `writeAsset({ fileRef, body, contentType })` and `generateStepArtifact({ fileRef, force, catalog })`. Local filesystem backends also expose helpers used by Vite and the local production server: ```js { resolveRoot(rootDir), openFileAsset({ fileRef, asset, catalog }), assetPathForFileRef(fileRef, { resolvedRoot }), entryForSourcePath(catalog, resolvedRoot, sourcePath), contentTypeForPath(filePath), } ``` `readCatalog()` returns catalog JSON from the backend's source of truth. `refreshCatalog()` lets an adapter update or regenerate that in-memory view. Writable helpers may write servable CAD assets such as hidden STEP GLBs or run local CAD generation. ## Local Filesystem `src/server/localAssetBackend.mjs` is the development and local deployment implementation. `readCatalog()` and `refreshCatalog()` scan the absolute `?dir=` root for the current request, keep the catalog as an in-memory object, and return schema v4 entries whose `file` values are absolute paths plus `rootRelativeFile` values for URL navigation. The local backend does not write `catalog.json` or any hidden catalog cache file. Local filesystem deployments are intentionally URL-driven. `?dir=` may be absolute or relative to the directory where the Viewer was started; when omitted it defaults to the startup `--dir`, or to the startup directory if `--dir` was not passed. That default directory is also the first active directory. `?file=` values are always relative to the active `?dir=` directory. `VIEWER_LOCAL_ROOT_DIR`, `VIEWER_LOCAL_WORKSPACE_ROOT`, and the old fixed-root startup flag have been removed and now fail at startup. The local backend serves asset bytes from the active root and writes regenerated artifacts back into it. It rejects path traversal and only serves or writes supported CAD Viewer asset types. Local STEP GLB/topology regeneration calls the Python `cadpy` package. The root viewer carries a generated, installable copy under `viewer/packages/cadpy`; run `scripts/bundle/bundle-skill.sh cad-viewer` after changing `packages/cadpy`, then install `viewer/requirements.txt` into the Python runtime used by the viewer. The generated cad-viewer skill runtime bundles that same installable package under `scripts/viewer/packages/cadpy` and does not need the repository root. Vite dev mounts this backend for: - `GET /__cad/server` - `GET /__cad/catalog` - `GET /__cad/asset?file=...` - `GET /__cad/download?file=...&asset=output|source` - `POST /__cad/reveal?file=...&asset=output|source` - `POST /__cad/step-artifact` `download` streams the requested asset bytes and works for both local and hosted deployments. `reveal` opens the asset in Finder or the platform file manager and is only available for the local filesystem backend. `asset=output` resolves the catalog entry file itself; `asset=source` resolves optional source code, such as a same-stem Python generator for Python-backed STEP files. The local production server uses the same backend: ```bash npm run build npm run serve ``` Then open the printed server URL with `?dir=/absolute/root&file=model.step`. Pass `--port ` to `npm run serve --` only when the default production port is already in use. ## Vercel Blob `src/server/vercelBlobAssetBackend.mjs` is the hosted storage adapter. Vercel deployments construct it in read-only mode: the hosted API reads the catalog and serves public Blob assets, but it does not write Blob objects or regenerate STEP artifacts. Hosted catalog reads are deliberately conservative about Blob traffic. Sustained per-request fetches of the public catalog URL from shared serverless egress IPs trip Vercel's abuse mitigation with intermittent `403 Forbidden` responses, so the hosted backend caches the parsed catalog in-function for 60 seconds (serving the last good catalog if a refresh fails), hosted `/__cad/catalog` responses carry `s-maxage`/`stale-while-revalidate` cache-control so the Vercel CDN absorbs client polling, and hosted viewer builds poll the catalog every 60 seconds instead of the local 2-second development cadence. Expected deployment shape: - Build the frontend normally. - Run a trusted local upload/maintenance script to publish supported assets and a schema v4 `catalog.json` containing public Blob URLs. - Deploy the viewer with `VIEWER_ASSET_BACKEND=vercel-blob` and a read-only catalog configuration. Catalog warnings such as stale STEP artifacts are shown in the UI instead of being fixed by the hosted app. - Hosted Vercel routes ignore local filesystem `?dir=` query values and read the configured hosted `catalog.json` instead. The Vercel deployment entrypoints are intentionally thin: ```text api/cad/server.js api/cad/catalog.js api/cad/download.js api/cad/reveal.js vercel.json ``` `vercel.json` rewrites the client-facing routes below to those API functions: ```text /__cad/server /__cad/catalog /__cad/download /__cad/reveal ``` The local production server can mount the Blob catalog path by environment: ```bash VIEWER_ASSET_BACKEND=vercel-blob \ VIEWER_VERCEL_BLOB_PREFIX=https://.public.blob.vercel-storage.com/models2 \ npm run serve ``` Upload a local directory catalog and supported viewer assets with: ```bash VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN=... \ VIEWER_VERCEL_BLOB_PREFIX=models2 \ npm --prefix viewer run upload:blob -- models ``` `BLOB_READ_WRITE_TOKEN` may be used instead of `VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN`, matching the environment variable Vercel Blob integrations create. The uploader also reads `.vieweruploadignore` from the uploaded directory and accepts repeated `--ignore-file` options for gitignore-style exclude patterns. It excludes `mechbench/`, `mechbench2/`, `7dof_arm/`, and Python source files by default, and public Blob catalogs omit Python source paths and URLs. Add `--skip-existing --fetch-missing-lfs` for publish-style runs that compare against the remote catalog, skip matching assets, and fetch only the Git LFS objects needed for new or changed uploads. The repository publish wrapper, `scripts/viewer/upload-viewer-models-catalog.sh`, owns the branch-defined Blob path prefix for model catalog uploads. Catalog uploads use Vercel Blob's minimum supported cache TTL so in-place `catalog.json` overwrites are visible quickly, while large immutable model assets keep the default Blob cache policy. Hosted catalog reads also append a catalog cache-busting query parameter so the viewer does not inherit stale edge entries from earlier catalog overwrites. For token-free read-only deployments, `VIEWER_VERCEL_BLOB_PREFIX` should be the public Blob URL for the prefix directory. The hosted backend always reads `catalog.json` at the root of that prefix. Trusted local upload or maintenance scripts may use the path prefix, such as `models2`, with a read/write token. The writable Blob helper methods remain available to local scripts that import the backend directly and pass a Blob read/write token. They are not exposed by the hosted Vercel API.