# Viewer URL State Contract Mechanica share links are part of the viewer’s public API: they need to survive refreshes, copy/paste, documentation links, and older URLs created before the viewer store grows new fields. The canonical query-string format is intentionally human-readable, while the optional `vs` payload provides a compact versioned form for very large states. ## Canonical parameters | Parameter | Meaning | Example | | --- | --- | --- | | `machine` / legacy `m` | Active machine or procedural asset id. | `machine=four-stroke-engine` | | `mode` | Render mode. Known aliases are normalised, e.g. `x-ray` becomes `xray`. | `mode=wireframe` | | `cam` | Camera position tuple as `x,y,z`. | `cam=8,3,-6` | | `target` | Orbit target / look-at tuple as `x,y,z`. | `target=0,1,0` | | `zoom` | Orthographic zoom or camera zoom-like scalar. Clamped to `0.05..100`. | `zoom=1.4` | | `fov` | Perspective camera field of view. Clamped to `10..90`. | `fov=42` | | `preset` | Active saved camera preset id. | `preset=iso` | | `selected` | Selected component id for drawer/tooltip restoration. | `selected=crankshaft` | | `hidden` | Comma-separated component ids hidden by the parts panel. | `hidden=piston,valves` | | `isolated` | Comma-separated component ids isolated by the parts panel. | `isolated=input-shaft,output-shaft` | | `opacity` | Comma-separated `partId:opacity` pairs. Opacity is clamped to `0..1`. | `opacity=block:0.25,crankshaft:0.8` | | `exploded` | Exploded-view toggle as `1` or `0`. | `exploded=1` | | `explode` | Exploded-view separation amount, clamped to `0..3`. | `explode=1.25` | | `annotations` | Annotation visibility override. Defaults are omitted. | `annotations=0` | | `labels` | Part-label visibility override. Defaults are omitted. | `labels=0` | | `section` | Cross-section state, usually `axis:offset` or a boolean. | `section=x:0.2` | ## Compatibility and safety rules - The parser accepts legacy aliases such as `px/py/pz`, `tx/ty/tz`, `wireframe=1`, `cameraPosition`, `selectedPartId`, and `hiddenPartIds`. - Malformed numeric tuples are ignored rather than propagated as `NaN`. - Camera coordinates are capped to a large finite range to avoid accidental or malicious URLs creating unusable camera positions. - Lists are de-duplicated and sorted so repeated serialisation does not create query-string churn. - Opacity values are clamped, and default opacity `1` is omitted from newly generated public URLs unless `includeDefaults` is requested. ## Compact payloads Passing `preferCompact: true` to `serializeViewStateToSearchParams` emits a single `vs` parameter. The payload is base64url-encoded JSON with a schema version and short keys, for example: ```ts const params = serializeViewStateToSearchParams(viewState, { preferCompact: true }); const decoded = deserializeViewStateFromSearchParams(params); ``` The parser treats explicit query parameters as the stable public format and the compact payload as an implementation detail. Keep the readable parameters in documentation and tutorials; use `vs` only where URL length is a concern. ## Automated coverage `src/utils/viewStateUrl.test.ts` covers canonical round-tripping, corrupt parameter handling, legacy camera formats, URL preservation, and compact payload decoding. These tests are intentionally pure unit tests so the share-link contract can be validated quickly without booting WebGL or loading Three.js assets.