@own-your-computer/wikimix-react
v0.1.0
Published
Unstyled optional React interaction controls for server-rendered Wikimix hosts
Readme
@own-your-computer/wikimix-react
Optional, unstyled React 19 interaction islands for Wikimix Engine API v1.
Requires the official @own-your-computer/wikimix-client 0.1 client. This local
0.1.0 candidate is packable; public registry release is tracked separately.
The host renders and sanitizes its thread, reply, article and resolution content on the server. These controls never create a React root, hydrate a document, render primary Markdown, import CSS, or choose a route. Mount them in an empty child of an explicit control slot and retain the host's ordinary fallback link.
Install
After public publication:
npm install --save-exact --ignore-scripts --registry=https://registry.npmjs.org/ \
@own-your-computer/[email protected] \
@own-your-computer/[email protected] react@^19.1.1 react-dom@^19.1.1Before publication use the exact local client/control tarballs. The package is ESM, Apache-2.0, includes LICENSE/NOTICE, and requires Node 24+ for builds. It supports API major 1; browser/runtime support and release limitations are recorded in the repository.
Mount an island
import { createRoot } from 'react-dom/client'
import { createWikimixClient } from '@own-your-computer/wikimix-client'
import { Composer, WikimixProvider } from '@own-your-computer/wikimix-react'
const client = createWikimixClient({ credentials: 'same-origin' })
const slot = document.querySelector<HTMLElement>('[data-wikimix-slot="thread-actions"]')!
const mount = document.createElement('div')
slot.append(mount)
createRoot(mount).render(
<WikimixProvider
client={client}
onDocumentChange={() => window.location.assign(slot.dataset.refreshUrl!)}
>
<Composer threadID={Number(slot.dataset.threadId)} />
</WikimixProvider>,
)The slot IDs and refresh URL above are trusted host template data. Authentication,
permissions, routing, fallback authoring links and error-page navigation belong
to the host. Render only controls the current actor should use; API policy is
still authoritative. A provider belongs to an island, not the primary document.
Unmount its root when the host removes that island. Changing a control's target
ID starts fresh form state; initialMarkdown and initialValue seed a target's
first render. Keep the client instance stable and remount the provider on an
authentication identity change.
The reference publication host already listens for wikimix:changed. Its adapter
can dispatch that event instead of selecting a URL:
onDocumentChange={change => {
window.dispatchEvent(new CustomEvent('wikimix:changed', { detail: change }))
}}Use this adapter only where the host's navigation listener is installed. Returning
from onDocumentChange means the host has initiated fresh server navigation.
Refreshing an island's data cache does not refresh the server document. Script
failure must leave the server content and ordinary links readable.
Public surface
| Export | Purpose |
| --- | --- |
| WikimixProvider / useWikimix | Island client, required onDocumentChange({threadID}), messages |
| Composer | Labelled Markdown textarea and reply submission |
| Feedback | Useful, incorrect-information or unnecessary feedback for an agent reply |
| AgentStatus | Announces current agent state; keyboard-operable refresh button |
| useThread(threadID, options?) | One bounded thread page for interaction decisions |
| useFeedback(threadID, options?) | One bounded feedback page |
| useAgentState(threadID, options?) | Agent indicator state |
| useAgentRun(threadID, runID, options?) | Run metadata, never the full trace |
| useMutation() | Mutation state, duplicate-submit protection, committed-change notification |
| defaultMessages | Default English control vocabulary |
Query hooks return { data, loading, error, reload }. Thread and feedback queries
accept the client's limit and cursor. All query hooks accept pollInterval
in milliseconds; default 0 disables automatic polling. Positive intervals have
a 250 ms minimum and schedule after the prior poll settles. Changing a query
identity, manually reloading, or unmounting ignores obsolete responses; it does
not cancel the underlying client request. Agent status polling alone never
refreshes primary content. Hosts decide when later worker output needs a new
server navigation and should handle denied/error states without displaying old
data as current.
Mutation usage for a committed reply is:
const mutation = useMutation()
await mutation.mutate(client => client.reply(threadID, markdown), { threadID })mutate returns the result or undefined on failure/duplicate invocation, and
exposes the caught error in mutation.error. status is idle, pending,
refreshing, committed, error, or refresh-error. locked prevents duplicate
writes in flight and after a committed change. Failed writes preserve control
input and move focus to the error. A failed refresh keeps submission locked;
retryRefresh() retries only the host callback. A successful write still
notifies the host if its island unmounted while the request was pending.
Pass null for work that has not committed document content, such as enqueuing a
compaction. A queued/running/skipped/failed job is not a document change. Observe
its run result, then call the provider's onDocumentChange only for a committed
done/compacted result. The field-journal fixture demonstrates that composition.
History, compaction, rollback, resolution, dialogs and primary document renderers
are deliberately absent from the exported control set.
Optional Lexical composer
Install the optional peers only in hosts using the rich editor:
npm install lexical@^0.49.0 @lexical/code@^0.49.0 @lexical/link@^0.49.0 \
@lexical/list@^0.49.0 @lexical/markdown@^0.49.0 @lexical/react@^0.49.0 \
@lexical/rich-text@^0.49.0import { Composer } from '@own-your-computer/wikimix-react/lexical'It accepts the same props as the base composer, with a labelled contenteditable, Markdown shortcuts, links/lists and undo history. Both send Markdown through the same client and refresh contract. The package supplies no formatting toolbar or Markdown document renderer. Importing the base entry never imports Lexical; a base-only packed consumer installs with no Lexical modules at all.
Presentation and accessibility
Every control accepts a root className and optional slots class names:
label, hint, input, actions, submit, status, and option as applicable.
Semantic markup and input handlers stay owned by the controls. Set provider
messages to a partial replacement of defaultMessages for host vocabulary or
translation. API errors are available to custom hooks; built-in controls use the
configured, generic error messages.
Stable data-wm-part values identify composer, feedback, agent-status,
label, hint, input, actions, submit, option, status, and refresh.
Mutation roots/statuses expose data-wm-state with the mutation status above.
Agent status roots expose loading, error, none, reading, responding,
waiting, or compacting. Form labels, fieldsets, native radio/textarea controls,
buttons and live status/error regions work without any stylesheet. The package
adds no animation, so reduced-motion users receive the same usable controls.
Verification and second host
From the repository root:
npm --prefix client ci --ignore-scripts
npm --prefix client run build
npm --prefix react ci --ignore-scripts
npm --prefix react test
npm --prefix react run lint
npm --prefix react run build
make test-react-integrationThe browser command requires the Candy-managed reference database host to be
running. It creates a separate disposable database and temporary Candy route,
installs exact local tarballs in a clean consumer, and builds
fixtures/react-host. That host uses its own journal template and only public
Go/npm imports. It checks script-blocked reading, unchanged primary DOM identity,
no CSS, axe, reduced motion, keyboard interaction and real reply/feedback/worker
compaction followed by server navigation. It removes its route, processes and
database, retaining package hashes and proof artifacts under the fixture's ignored
.artifacts/ directory. No model call is made.
Unit accessibility checks use jsdom with color contrast disabled because it has no layout; the Chromium fixture runs axe without that exclusion. Browser execution currently covers Chromium; the declared older Chrome/Edge, Firefox and Safari floors and public-registry consumption remain release validation work. The reference UI uses these same public controls in server-owned interaction slots.
