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:
- Reads
documents()(the existing public-only content collection). - Renders each document with Astro's existing Markdown renderer, including the configured document-link transform and syntax highlighting.
- Renders the existing
RootPageandDirectoryIndexcomponents for/,/bookand any real public directory indexes. It does not copy their authored text. - Passes rendered bodies to
createWorldspaceDatainsrc/lib/worldspace-data.ts, which usesbookTreeand 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.
/playgroundkeeps/::document.read()and also exposes/::collection:create(name: String) -> Unitcreates a zero-valued counter;remove(name: String) -> Unitremoves an existing counter.- Children start as
apple = 3andorange = 0, ordered lexically. Each exposes only/::counter:get() -> Integer,increment(amount: Integer) -> Integer, andreset() -> 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 areResourceLimitExceeded; domain rejections areInternalwith 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
descriptionis the current decimal integer and whosenameremains 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.