feat(genui): server-defined components, and buttons that say who they're for - #35
Merged
Merged
Conversation
…'re for
Two halves of mekik PROTOCOL.md §10, on the client side.
Server-defined components: the backend announces a catalog on connect and
`@chativa/genui` turns each definition into a custom element, so a
`{ type: "ui", component: "order-card" }` chunk mounts a widget nobody compiled
into the page. The catalog is cached in localStorage under the server URL and
validated by a hash riding along on the `hello` frame, so a returning user's
widgets render before the server answers.
The other half is what a button on one of those widgets does. `<chativa-html>`
bound exactly one attribute — `data-event` — and the backend had to guess who the
click was for. Now the attribute is the answer:
component-event="<name>" → scope "component" the widget's own conversation
with the node that mounted it
mekik-event="<name>" → scope "graph" the application
data-event="<name>" → no scope the backend decides
Most specific wins; an empty value is not a trigger; forms carry the scope too.
`scope` and the originating `component` ride to the backend as optional fields on
the `genui_event` frame, threaded as one optional `GenUIEventOptions` argument
from `sendEvent` through `GenUIMessage`, `ChatWidget`/`AgentPanel`, `ChatEngine`
and `IConnector` to `createGenUIEventFrame`. Optional at every hop on purpose:
`data-event` still makes the two-argument `sendEvent` call it always made, and a
frame with no routing metadata is byte-for-byte the frame backends see today.
The component name is stamped by `GenUIMessage`, not by the component — only the
chunk knows which widget an interaction came from, and asking components to
report it themselves is asking for it to be wrong.
MekikConnector: an `interrupt` frame carrying `data.event` renders nothing. That
pause is waiting for a widget already on screen, so default Approve/Cancel chips
would be a dead end — tapping Approve would send a `resume` the node is not
waiting for.
Verified against the real backend, not by inspection: chativa's
`createGenUIEventFrame` output fed straight into a live mekik app resolves the
node parked on `onEvent` for a component-event, reaches the graph handler with
the component name for a mekik-event, and is rejected as `bad_request` for an
unknown scope. 570 tests pass; 13 packages typecheck.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ilds
`website/docs/genui/streaming.md` opened with `---` and never closed the block,
so docusaurus parsed the whole page as YAML front matter and the build failed
outright ("end of the stream or a document separator is expected"). The page also
had none of the metadata its siblings carry, so it would have sorted and titled
itself by filename.
Verified with `docusaurus build` on both sites.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The client half of mekik
PROTOCOL.md§10. Backend counterpart: AimTune/mekik#10.Server-defined components. The backend announces a catalog on connect and
@chativa/genuiturns each definition into a custom element, so a{ type: "ui", component: "order-card" }chunk mounts a widget nobody compiled into the page. Cached inlocalStorageunder the server URL and validated by a hash riding along on thehelloframe the connector already sends — so a returning user's widgets render before the server answers, at no extra round trip.What its buttons do.
<chativa-html>bound exactly one attribute,data-event, and the backend had to guess who the click was for. Now the attribute is the answer:scopesentcomponent-event="rate_delivery""component"mekik-event="track_order""graph"data-event="…"Most specific wins, an empty value is not a trigger, and forms carry the scope too.
Threading it through
scopeand the originatingcomponentreach the backend as optional fields on thegenui_eventframe, carried as one optionalGenUIEventOptionsargument:Optional at every hop on purpose. A
data-eventstill makes the two-argumentsendEventcall it always made, and a frame with no routing metadata is byte-for-byte the frame backends see today. All five connectors passoptsthrough; ones whose protocol doesn't model routing simply never receive it.The component name is stamped by
GenUIMessage, not by the component — only the chunk knows which widget an interaction came from, and asking components to report it themselves is asking for it to be wrong.MekikConnector
An
interruptframe carryingdata.eventnow renders nothing. That pause is waiting for a widget already on screen, so default Approve/Cancel chips would be a dead end: tapping Approve sends aresumethe node isn't waiting for.Verified against the real backend
Not by inspection — I fed chativa's
createGenUIEventFrameoutput straight into a live mekik app:Plus 570 tests passing and 13 packages typechecking.
Reviewer notes
GenUIComponentAPI.sendEventgained an optional third parameter — source-compatible for every existing component.ChatEnginetest was updated: it assertedreceiveComponentEventarity, which changed by one optional argument. A new test covers the forwarding.scope; that's feat(genui): the backend defines the widget, and its buttons route mekik#10.🤖 Generated with Claude Code