feat(greeting): let the first impression be a card, not only a paragraph - #8
Merged
Conversation
The greeting could carry exactly one thing — a string, mapped to one `text`
frame. Everything the message layer added in 0.3.0 was unreachable from it, so
the one message an app is guaranteed to send was its least expressive.
It now takes a string (unchanged), a **described** rich message, or a list
mixing both, delivered in order. Each item lands as its own persistent frame, so
all of them replay on reconnect and the conversation is still never greeted
twice.
Emitting needs a node's `ctx`, and the greeting fires on connect, outside any
run — so this adds the value form of a message: `{type, data, id?}`. Built with
`mekik.messageSpec` / `Messages.Spec`, or typed per kind:
`messages.card.spec(data)` and `Messages.CardSpec(…)`, the same parameters as the
emitter minus the ctx. The .NET emitters are now one-liners over their own Spec
builders, so each type keeps one parameter list rather than two.
GenUI components are deliberately not accepted there: a chunk belongs to a turn's
stream, and on connect there is no stream to attach it to. The distinction is
documented where a reader would look for it.
Reserved frame types are refused by the builders (they throw) and dropped by the
engine if a hand-built spec names one — the same rule the mapper applies, so a
greeting cannot collide with the protocol's own frames.
Source-compatible: `greeting: () => "hi"` still typechecks in both languages
(.NET's `Func<…, string?>` is assignable to `Func<…, object?>` by covariance).
Covered by unit tests for the spec builders and engine scenarios in both
languages — ordering, ids, gap-free seq, the empty-string skip, and the
reserved-type drop. `examples/storefront.ts` now greets with prose plus a button
message and asserts both frames.
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 greeting could carry exactly one thing — a string, mapped to one
textframe. Everything the message layer added in 0.3.0 was unreachable from it, so the one message an app is guaranteed to send was its least expressive.What changes
A greeting now takes a string (unchanged), a described rich message, or a list mixing both — delivered in order, each landing as its own persistent frame:
The one new concept: a described message
Emitting needs a node's
ctx, and the greeting fires on connect, outside any run. So this adds the value form of a message —{ type, data, id? }— built withmekik.messageSpec/Messages.Spec, or typed per kind:messages.card.spec(data)andMessages.CardSpec(…), the same parameters as the emitter minus thectx. In .NET the emitters are now one-liners over their own…Specbuilders, so each type keeps one parameter list rather than two.GenUI components are deliberately not accepted there: a chunk belongs to a turn's stream, and on connect there is no stream to attach it to. That's called out where a reader would look for it (messages page, getting-started, PROTOCOL.md §4.5).
Safety and compatibility
greeting: () => "hi"still typechecks in both languages (.NET'sFunc<…, string?>is assignable toFunc<…, object?>by covariance).PROTOCOL_VERSIONchange — the greeting simply emits frames that already existed.Verification
Unit tests for the spec builders plus engine scenarios in both languages: ordering, the spec's id reaching the wire, gap-free
seqacross greeting frames, replay-not-re-greet on reconnect, the empty-string skip, and the reserved-type drop.pnpm checkgreen (53 TS tests + every example probe);dotnet test49/49; Docusaurus build clean.examples/storefront.tsnow greets with prose plus a button message and asserts both frames arrive before any turn.🤖 Generated with Claude Code