Files

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:

{"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

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<Worldspace, DataError>. 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:

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.