# Public-document Worldspace `wip-site-worldspace` is the site's in-memory Host provider: immutable documents plus an opt-in session-local playground. It uses `wip-host`, `wip-interface` and `wip-protocol`; it does not depend on the filesystem server, HTTP, a browser, or a network runtime. The native and WASM callers use the same `Worldspace` instance API. ## Generated data The static Astro endpoint `src/pages/worldspace.json.ts` emits `dist/worldspace.json` during the normal site build. It: 1. Reads `documents()` (the existing public-only content collection). 2. Renders each document with Astro's existing Markdown renderer, including the configured document-link transform and syntax highlighting. 3. Renders the existing `RootPage` and `DirectoryIndex` components for `/`, `/book` and any real public directory indexes. It does not copy their authored text. 4. Passes rendered bodies to `createWorldspaceData` in `src/lib/worldspace-data.ts`, which uses `bookTree` and filters drafts again. The input contract is: ```json {"nodes":[ {"path":"/","name":"","displayPath":"/","title":"Worldspace Interface Protocol","href":"/","html":"..."}, {"path":"/book","name":"book","displayPath":"/book","parent":"/","title":"Book","href":"/book/","html":"..."}, {"path":"/book/1-concept.md","name":"1-concept.md","displayPath":"/1-concept.md","parent":"/book","title":"...","href":"/book/1-concept/","html":"..."}, {"path":"/book/1-concept.md/1.1-interaction-abstraction.md","name":"1.1-interaction-abstraction.md","displayPath":"/1.1-interaction-abstraction.md","parent":"/book/1-concept.md","title":"...","href":"/book/1.1-interaction-abstraction/","html":"..."} ]} ``` `path` is the only WIP request identity. `parent` is a WIP path and is absent only for `/`. `displayPath` preserves the old explorer identity and selection state; `href` preserves the existing Web route (including `SITE_BASE`). The UI can keep its current `book/` label, but the actual Object name is `book`. No protocol name has a trailing slash. Nodes retain `bookTree` order. The provider does not sort children lexically. Every node, including home, book index, and a chapter that also has sections, has a readable body. `html` is a trusted, build-rendered fragment, not a full page or an untrusted user submission. Consumers must retain the site's existing CSS, including home scoped styles and code-block themes. Do not inject raw JSON into a script element without escaping `<`; the generated endpoint is served as `application/json` instead. There is no runtime loading by path: unpublished or guessed draft paths cannot reach a source file. The Rust loader also rejects draft path segments, duplicate path/displayPath/URL identities, inconsistent parent/name/path relationships, missing parents, malformed input and unknown JSON fields. ## Rust API ```rust use wip_site_worldspace::{Worldspace, DocumentContent}; use wip_site_worldspace::wip_protocol::{ ObserveRequest, FetchInterfaceRequest, CallOperationRequest, Target, InterfaceTarget, }; use wip_interface::WipType; # fn example(json: &str) { let world = Worldspace::from_json(json).unwrap(); let observed = world.observe(ObserveRequest { path: "/".into(), depth: 4 }).unwrap(); let reference = observed.object.interfaces[0].clone(); let interface = world.fetch_interface(FetchInterfaceRequest { interface: reference.clone() }).unwrap(); let result = world.call_operation(CallOperationRequest { target: Target { path: "/book/1-concept.md".into(), validator: None }, interface: InterfaceTarget { reference, scope_ref: interface.scope_ref, validator: interface.validator, }, operation: "read".into(), arguments: Default::default(), }).unwrap(); let document = DocumentContent::decode(result.result).unwrap(); assert_eq!(document.path, "/book/1-concept.md"); # } ``` Alternatively use `Worldspace::new(WorldspaceData { nodes })`. Construction returns `Result`. `observe(ObserveRequest)`, `fetch_interface(FetchInterfaceRequest)` and `call_operation(CallOperationRequest)` take requests by value and return canonical protocol response types or `ProtocolError`. The crate re-exports `wip_protocol` for bridge consumers. The registered root-scoped Interface is `{scope:"/",name:"document"}`. Its only Operation, `read`, takes **no arguments** and returns the typed `DocumentContent` record `{path,title,href,html}`. Host-generated Descriptors, scope association, interface membership, request validation, argument/result validation and error classification remain on the normal Host path. The provider exposes no alternate body-fetch method. Documents are immutable, so no validators are advertised. The recursive adapter calls `ObjectProvider::observe_object` for every node and validates the assembled response against the request. At the requested depth, `children` is `None`; above it, a visited leaf has `Some([])`. Unknown paths return `NotFound` and malformed paths return `InvalidRequest`. Requests above `MAX_DEPTH = 32` return `ResourceLimitExceeded` rather than silently truncating. Construction allows at most `MAX_NODES = 4096`, with hierarchy depth at most 32. `from_json` also caps input at 16 MiB. Together these bounds cap traversal work. ## Playground A manifest document at `/playground` opts into mutable state. Manifests without that node retain document-only behavior. Manifest descendants of `/playground` are rejected, so mutable counters cannot shadow or delete document content. - `/playground` keeps `/::document.read()` and also exposes `/::collection`: `create(name: String) -> Unit` creates a zero-valued counter; `remove(name: String) -> Unit` removes an existing counter. - Children start as `apple = 3` and `orange = 0`, ordered lexically. Each exposes only `/::counter`: `get() -> Integer`, `increment(amount: Integer) -> Integer`, and `reset() -> Integer`. Increment returns the new value; reset returns zero. - Names match `[A-Za-z0-9_-]{1,32}` and are case-sensitive. At most 32 counters exist. Duplicate creation, missing removal, invalid names, and checked sums outside Protocol Integer's range `[-9007199254740991, 9007199254740991]` fail without mutation. Capacity errors are `ResourceLimitExceeded`; domain rejections are `Internal` with an explicit not-committed Host guarantee. Invalid typed arguments are rejected by standard Client/Host validation. - Every counter lookup publishes a fresh Object snapshot whose `description` is the current decimal integer and whose `name` remains the slug. All generated interfaces bind to the same actual root ScopeObservation. - State belongs to one Worldspace/BrowserSession. Navigation retains it; a new session restores initial state. Documents are never mutated. The browser's normal `tree()` includes only manifest display paths and therefore never calls `document.read` on dynamic counters. Manual playground `observe` uses the real Client's `refresh_observed` on every explicit retrieval (including recovery from retained missing/stale observations); other documents retain `ensure_observed` caching. This Client version has no public subtree-invalidation API. A call does not observe, fetch, retry, or convert its result into an Object. The UI explicitly observes `/playground` at depth 1, then the selected Object at depth 1 after calls, and observes `/playground` on entry to Protocol mode. ## Validation From the site directory, with the configured Rust toolchain and dependencies: ```sh node --test tests/worldspace-data.test.mjs cargo test -p wip-site-worldspace npm run build WIP_TEST_GENERATED=1 node --test tests/worldspace-data.test.mjs cargo test -p wip-site-worldspace --test worldspace -- --include-ignored npm run check:links ``` The explicit generated-data tests require `dist/worldspace.json`; they do not silently substitute fixtures when the artifact is absent. Native fixture tests exercise all three interactions, depth boundaries/order/empty sets, limits, identity and constructor errors, unknown paths/interfaces/operations, invalid arguments, validator failures, draft guesses, and JSON parsing. The generated native test invokes all three interactions against the actual build artifact. The JS post-build test compares every returned body and link set with the static page's article, including home and book index. Run it again with a prefixed `SITE_BASE` build to check deployment URLs. Cargo dependency sourcing and the browser bridge are managed by the parent site workspace. The initial relative Rust dependencies require the companion `wip-rs` checkout beside `docs`; they must be replaced with the final reproducible source configuration before a standalone site checkout can build.