Core and adapter integration
Start with a real identity space
Section titled “Start with a real identity space”Import entityId, createState, reduceState, createController and the data types from entity-viz-kit/core. Entity IDs are opaque strings, unique in the host application’s declared identity space. EVK preserves them exactly, rejects invalid IDs, and never infers identity from a name. Applications combining namespaces provide their own mapping.
Metadata lives in an EntityRegistry. Equal labels do not merge. An unresolved ID renders safely as that ID with an unknown type. The registry is bounded (default 20,000 entries) and parsed updates are atomic. A host-provided registry is not cleared by EVK; the host must scope/clear it when permissions or identity spaces change. Controller-owned metadata is cleared on context changes/disposal.
Adapter contract
Section titled “Adapter contract”SearchAdapter.search(request, signal) returns a promise of unknown data on purpose. The controller parses the response before acceptance. The response must echo requestId and numeric revision unchanged and supply status, structured documents, entities and recommendations. Example payloads and a functioning synthetic adapter are in integration/src/ts/fixture-adapter.ts and fixtures.ts.
Query intent contains exact free text plus selected entity IDs. The host decides what that intent means for ranking/filtering. Context is a separate ID/revision/label plus optional source document and context entity references; it is never secretly appended as a user tag. The synthetic example uses case-insensitive substring AND all selected IDs; that is not a production query language or an EVK requirement.
Optional adapter methods profile, relation, evidence and source take a typed key plus request identity and context, and return { requestId, revision, status, value }. ok requires a valid value with the requested ID; unavailable requires null. Absent methods produce an explicit unavailable resource state. Slots do not cache an unbounded history.
The adapter owns URLs, authorization, credentials, backend timeouts, endpoint payload translation and any network caching. No such configuration is serialized into EVK state. Raw HTML or executable data is not accepted. Source/image URLs must be absolute HTTP(S) without embedded credentials; supplying a URL does not automatically load it in this milestone.
Controller and controlled state
Section titled “Controller and controlled state”createController(adapter, { state?, registry?, debounceMs? }) is idle until start or refresh. Subscribe to getSnapshot() for state, query revision, current search state and fixed lazy-resource channels. An unchanged store returns the same snapshot reference. dispatch applies the pure reducer; setState lets a host replace validated controlled state. An application that already has a store can use the reducer/validation independently.
Use query.add/query.remove/query.text/query.clear, context.set, inspect, presentation.order and presentation.set. Addition is idempotent and absent removal harmless. Inspection does not alter query membership. Query clear preserves context and inspection. Presentation tag order must be a permutation of query membership; reordering does not retrieve.
Semantic query revisions change for text, membership or meaningful context updates. Signatures use structured arrays and a sorted membership copy. A context display label alone does not change semantics; hosts must increment the context revision for other contextual changes. Request IDs distinguish retry attempts at the same semantic revision. A returned response can update the controller only if its slot epoch and echoed identity remain current, even if the adapter ignores abort.
Text state changes immediately. Only dispatch is debounced. Call setComposing(true/false) from native composition handlers; tagged selection bypasses a pending text timer, while composition defers retrieval until completion. flush sends pending current intent. refresh retries current intent. The controller has no default network timeout: hosts implement bounded retrieval appropriate to their backend. Lazy views must verify that a resource slot key matches the currently inspected ID before rendering it; inspection alone does not initiate a lazy call. A disposed controller throws on further mutation and must be replaced.
Search phases are initial, loading, refreshing, ready, empty, partial, failed, unavailable and disposed. dataRevision belongs to retained content, not the current query. The view must label old data when these revisions differ. Context transitions clear owned old content atomically before subscriber notification. Error messages are generic and do not echo private adapter details.
Plain-data and offset limits
Section titled “Plain-data and offset limits”Identifiers: 1–1024 UTF-16 code units, no C0/DEL characters, no trimming/normalization. Nested data: at most 24 levels, 250,000 values, 8,000,000 combined key/string units. Unknown schema fields, accessors, symbols, cycles, class instances, sparse arrays, functions, undefined and nonfinite numbers are rejected. In-process hostile Proxies can execute traps before validation; untrusted remote input must enter as parsed JSON/plain data, not a hostile object with executable behavior.
Search pages: at most 1,000 documents, 10,000 display entities and 1,000 recommendations. Each snippet has at most 500,000 UTF-16 units; maps/ranges and membership arrays have explicit caps in schemas.ts. These are resource-safety maxima, not rendering-performance claims.
Anchors declare canonicalVersion 1, document/version, exact quotation, prefix/suffix and a range whose unit is utf16 or codepoint. UTF-16 ranges cannot split surrogate pairs. Evidence passage text is the cited exact quotation; its mention/relationship intervals are local to that passage, while the anchor range addresses the source. 0.3 defines canonical DOM mapping and re-anchoring algorithms; 0.1 validates contracts but does not pretend to implement those algorithms.
Graph and later-surface contracts
Section titled “Graph and later-surface contracts”Pair IDs encode a sorted unordered pair without delimiter collisions. aggregateRelations preserves distinct directional assertions; repeated assertion IDs with conflicting meaning fail. Evidence de-duplication uses evidence ID plus stance, retaining contradictory links. Self assertions are returned separately from graph pairs.
GraphView positions/pins are world coordinates with graph/context identity, not screen coordinates. ExtensionEnvelope and AudienceSnapshot have strict versioned data schemas now, but no transport, privileged sender validation or authorization is claimed. The host authorizes any audience projection before publication in 0.6.
Contract freeze for 0.2
Section titled “Contract freeze for 0.2”Preserve identifier brands, action vocabulary, four state domains, query/revision/echo semantics, range units, pair encoding and reserved import boundaries. Additive API extensions are normal; any incompatible change needs an architecture note plus migration of all examples/tests in the same snapshot. Use the existing controller from React rather than implementing a second race-handling loop.