Skip to content

Chat bubble

In review
Live · web

Roles: agent leads (left), user trails (right), system centers

Routing every session through the credential store for credentials.

Perfect. Ship it.

Grouping: a same-sender run keeps the tail only on the edge bubbles

Reading the reconnect loop.

Backoff caps at 30s with jitter.

Re-running the SSE tests now.

Nice.

Add a metric for reconnect count too.

Delivery states: own-message lane (status is warm + icon + text, never color alone)

Cap the retry count at 3.

Sending10:26 AM

Use exponential backoff.

Sent10:25 AM
FailedCouldn’t send: no connection to the gateway.
Not delivered

Selected + timestamp: tap a bubble to select (cyan ring + faint wash, not a glow)

10:21 AM
10:22 AM

Retry fired 0 times · selected: sel · tab to a bubble or Retry; the island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the intended @dizyx/nockerl-react API (not yet exported; promotion tracked),
// modeled on the shipped Compose bubble container.) The bubble is the PRIMITIVE
// that holds one message; AgentMessage composes its header + body ON it.
export function Thread() {
return (
<>
{/* agent → leading/left soft plane; user → trailing/right cyan jewel */}
<ChatBubble role="agent">Routing through the credential store.</ChatBubble>
<ChatBubble role="user">Perfect, ship it.</ChatBubble>
{/* a same-sender run: tail only on the edge bubbles, squared seams between */}
<ChatBubble role="agent" group="first">Reading the reconnect loop.</ChatBubble>
<ChatBubble role="agent" group="last">Re-running the SSE tests.</ChatBubble>
{/* own-message delivery lane + a centered system bubble */}
<ChatBubble role="user" delivery="sent" timestamp="10:25 AM">Use backoff.</ChatBubble>
<ChatBubble role="user" delivery="failed" onRetry={resend}>Couldn’t send.</ChatBubble>
<ChatBubble role="system">Context compacted</ChatBubble>
</>
);
}
PropTypeDefaultDescription
role *'user' | 'agent' | 'system'Speaker. user → trailing/right (cyan jewel card); agent → leading/left (soft lifted plane); system → centered, muted divider bubble.
childrenReactNodeThe message body. The bubble is a pure container. It does not render a header, markdown, or tool panels (that is AgentMessage).
group'single' | 'first' | 'middle' | 'last''single'Position in a consecutive same-sender run. The tail stays on the edge bubbles (first/last); interior speaker-side corners square off and spacing tightens.
delivery'sending' | 'sent' | 'failed'Own-message delivery state. Adds a warm receipt (clock · check · alert) under the bubble; failed restyles the bubble to the banner inline-alert grammar (inset status disc + whisper-red border + lifted neutral surface) with title, message and Retry. Never color-alone.
timestampstringShort time string shown in the meta line under the bubble (e.g. "10:23 AM").
selectedbooleanfalseSelected / long-pressed: a cyan focus ring + a faint cyan wash (the single brand accent), never a glow.
onSelect() => voidTap-to-select handler for selection (a toggle + soft cyan wash), distinct from the long-press pop menu (a menu of actions); the two compose. When set, the whole bubble becomes one focusable button (one accessible name).
onRetry() => voidRetry handler for a failed bubble; renders a real, focusable Retry button.