NARU API
    Preparing search index...

    Module @naru3d/workspace

    NARU workspace

    @naru3d/workspace owns the naru.workspace.2 document (naru.workspace.1 is still read and treated as an empty annotation list): a versioned, non-authoritative record of what an import pointed at and what the user was looking at. It holds the format, a fail-closed parser, a canonical serializer, and the pure decision that a reopen makes. It depends on no renderer, no compiler, and no host API, so the same code runs in the Studio, in Node, and in an embedder.

    The design and its gates are ADR-0022.

    Under ADR-0002 the native CAD/BIM documents remain source of truth. A workspace stores composition and review intent: one package reference with the identities the import already computed, the sources it consumed, and one view. It stores no geometry, no property values, and no copy of anything a package or a source already holds.

    It is also not a cache entry. A whole-package cache hit (ADR-0009) proves an input-to-output identity; it carries no camera, no selection, and no answer to "did the source move".

    {
    "schemaVersion": "naru.workspace.2",
    "label": "Digital Hub review",
    "package": {
    "reference": { "kind": "url", "href": "https://example.test/digital-hub/" },
    "packageDigest": "<64 lowercase hex>",
    "resources": [{ "path": "scene.bin", "byteLength": 1234, "sha256": "<64 hex>" }]
    },
    "sources": [
    { "key": "architecture", "label": "arc.ifc", "byteLength": 1234, "sha256": "<64 hex>" }
    ],
    "view": {
    "camera": { "yaw": 0, "pitch": 0, "panRight": 0, "panUp": 0, "zoom": 1 },
    "section": { "enabled": false, "axis": "x", "direction": 1, "fraction": 0.5 },
    "hiddenOccurrenceIds": [],
    "selectedOccurrenceId": null,
    "annotations": [{ "position": [58.6, 2.4, -6.6], "text": "Check slab thickness here" }]
    }
    }

    Package and resource identity is copied from build-report.json (output.packageDigest, output.resources[]); source identity from adapter-report.json (sources[]). A workspace never asserts an identity the pipeline did not compute.

    Selection and visibility are keyed by occurrenceId, never by node index: node order is an emission detail, so an index silently rebinds to a different element after a recompile. The section plane stores an axis, a direction, and a fraction; its bounds are derived from the scene on reopen, so the document carries no absolute coordinate.

    import { parseWorkspace, serializeWorkspace } from "@naru3d/workspace";

    const workspace = parseWorkspace(text);
    const bytes = serializeWorkspace(workspace); // canonical, ends with one newline

    serializeWorkspace is deterministic: sources are ordered by key, resources by path, hidden occurrence ids deduplicated and sorted, keys emitted in a fixed order, JSON compact, one trailing newline. Two hosts holding the same workspace write the same bytes. The document carries no timestamp, no host name, no absolute path, and no user identity.

    parseWorkspace and normalizeWorkspace are fail-closed and throw WorkspaceError with UNSUPPORTED_SCHEMA, INVALID_WORKSPACE, or LIMIT_EXCEEDED. The parser performs no network access and resolves no filesystem path. It accepts only http and https package hrefs without embedded credentials, and stores a local package as a bare file name rather than a path. schemaVersion is checked first; within a version this repository issued, an unknown key is refused as corruption rather than ignored. A manifest that names no source is refused too: it would otherwise reopen as verified with geometryIsCurrent true while nothing about that geometry's provenance had been checked. Every collection and string is bounded by defaultWorkspaceParseLimits, which an embedder may tighten through the limits option.

    import { evaluateWorkspaceReopen } from "@naru3d/workspace";

    const decision = evaluateWorkspaceReopen(workspace, {
    packagePresent: true,
    packageDigest,
    resources,
    resourcesComplete: true,
    sourceInspection: "unavailable",
    occurrenceIds,
    });

    Each part is judged verified, changed, missing, or unverifiable, and the whole reopen resolves to verified, changed-source, changed-package, unverifiable, or blocked in the precedence blocked > changed-source > changed-package > unverifiable > verified. Source outranks package because the source is authoritative: a package that still matches its digest while its source moved is stale output, not a clean reopen.

    sourceInspection: "unavailable" means the host cannot inspect sources -- a browser tab cannot stat a local IFC file -- and never that no source moved. It yields unverifiable, which never collapses into verified. decision.geometryIsCurrent is true only in the fully verified state, so stale geometry is never labelled current by omission.

    The view is resolved against the occurrence ids the reopened hierarchy actually carries: ids it no longer holds are reported in droppedHiddenOccurrenceIds and droppedSelection rather than silently discarded. Without an occurrenceIds set the view is carried through untouched and resolvedAgainstHierarchy is false.

    pnpm test
    

    test/document.test.ts covers the round trip, the canonical byte equality, and every refusal the trust boundary owes; test/reopen.test.ts covers the per-part states, the precedence, the package-observability combinations, and view resolution.

    What the tests cannot show is the renderer acting on a reopen decision. That is recorded once, in a headed browser, in artifacts/workspace/reopen/.

    WorkspaceError
    ObservedResource
    ObservedSource
    WorkspaceAnnotation
    WorkspaceCamera
    WorkspaceDocument
    WorkspaceObservation
    WorkspacePackage
    WorkspacePackageResource
    WorkspacePackageVerdict
    WorkspaceParseLimits
    WorkspaceParseOptions
    WorkspaceReopenDecision
    WorkspaceResourceVerdict
    WorkspaceSection
    WorkspaceSource
    WorkspaceSourceVerdict
    WorkspaceView
    WorkspaceViewResolution
    WorkspaceErrorCode
    WorkspaceEvidenceState
    WorkspacePackageReference
    WorkspaceReopenState
    WorkspaceSectionAxis
    defaultWorkspaceParseLimits
    previousWorkspaceSchemaVersion
    workspaceSchemaVersion
    evaluateWorkspaceReopen
    normalizeWorkspace
    parseWorkspace
    serializeWorkspace