Skip to content

feat(widget): embeddable, brandable chat widget — one script tag from pipeline to production chat#1586

Open
nihalnihalani wants to merge 3 commits into
rocketride-org:developfrom
nihalnihalani:feat/RR-1582-chat-widget
Open

feat(widget): embeddable, brandable chat widget — one script tag from pipeline to production chat#1586
nihalnihalani wants to merge 3 commits into
rocketride-org:developfrom
nihalnihalani:feat/RR-1582-chat-widget

Conversation

@nihalnihalani

@nihalnihalani nihalnihalani commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

#Hire-Me-RocketRide

Contribution Type

Featurerocketride-chat-widget: an embeddable, brandable chat widget. One script tag from a deployed pipeline to production chat on any website.

Closes #1582

Summary

The engine already generates a public chat surface per pipeline ({host}/chat?auth={public_auth}, with the public key deliberately separated from the private token) — but its only consumer was the standalone chat-ui app. A finished pipeline couldn't become something an end user touches on your site. This PR packages that existing surface the way the category leaders do (Flowise flowise-embed, Langflow's Share pane component, Dify's app widget — and OpenAI's ChatKit, the piece that survived the Agent Builder shutdown):

<!-- floating bubble: the classic embed -->
<script src=".../rocketride-chat.js"
        data-engine-url="https://engine.example.com:5565"
        data-auth="YOUR_PUBLIC_AUTH_KEY"
        data-title="Ask our docs" data-accent="#5f2167"
        data-position="bottom-right" defer></script>

<!-- or inline, anywhere in a page -->
<rocketride-chat engine-url="https://engine.example.com:5565" auth="YOUR_PUBLIC_AUTH_KEY"></rocketride-chat>
  • One ~72 KB bundle (21 KB gzip), zero frameworks — vanilla web component with shadow DOM (host CSS stays out, widget CSS stays in), ESM + IIFE from one build, React-free (verified by bundle inspection).
  • Brandable: observed live attributes (title, accent, welcome, placeholder, theme=light|dark|auto) + --rr-* CSS custom properties that pierce the shadow boundary; RocketRide violet #5f2167 default.
  • Security-first: authenticates with the pipeline's public key only — the exact mechanism the chat node's published /chat?auth= link uses. JSDoc, README, and the demo all state that the engine API key and private token must never appear in a page. Assistant output goes through an escape-first safe renderer (allowlist: paragraphs, code, bold, http(s)-only links with rel="noopener noreferrer") — raw model HTML never reaches the DOM.
  • Production UX: connecting/connected/error-with-Retry states, live "thinking" status from the SSE events the protocol already delivers, autoscroll that respects user scroll-up, Enter/Shift+Enter, disabled input while pending; a11y with role=log aria-live=polite, labeled controls, aria-expanded launcher, Escape-to-close, reduced-motion awareness.
  • SDK untouched: the connection layer reuses the browser-compatible rocketride TS SDK (bundled from source with the Node-only ws path stubbed); zero diff to the SDK, proven by its own full test suite staying green.

Validation

Unit / static (no server): 112/112 jsdom tests, clean typecheck.

  • XSS corpus at two levels: the implementation suite (script tags, img onerror, javascript:/data:/vbscript: and scheme-obfuscated URLs, attribute breakouts, fenced-code-language injection, markdown-label HTML) plus the devil's-advocate pass ran 35+ fresh adversarial fixtures against the built bundle, and the independent verifier added 5 more DOM-parsed payloads — everything inert; only p/br/pre/code/strong/a survive.
  • Component behavior (states, keyboard, aria, autoscroll guard, attribute reactivity, double-init guards), loader config parsing, theming, connection protocol against a mocked client.
  • gitleaks clean; MIT headers on all sources; frozen-lockfile install clean.

Live end-to-end — twice, on real engines:

  1. Published production image (ghcr.io/rocketride-org/rocketride-engine:latest): an admin deploys a chat-source pipeline via the SDK; the task exposes its public pk_ key; the widget's built bundle — using only that public key — connects (connecting → connected), sends a question, and receives the pipeline's answer array over the real protocol.
  2. Engine built from current develop: the identical script returns the real (mock-LLM) answer text end to end.

Visual check: the demo page was loaded in a browser — inline widget and bubble mode both render correctly, including the branded panel and the clean error + Retry state when no engine is reachable.

Review notes

  • One internal devil's-advocate major was found and fixed before this PR: the safe renderer's inline-token restoration was single-pass, so a stashed fragment containing another token sentinel (inline code inside a markdown link label) could mis-restore — replaced with a recursive restore, with a regression test.
  • The bundle inlines the SDK from packages/client-typescript/src (it has no standalone npm build; single-file embeds must inline it regardless). Build is wired into the repo builder (chat-widget:build/test) ordered after the SDK's type generation.
  • Scope: one new package + a one-line pnpm-workspace.yaml registration + docs. No engine, server, or existing-app changes.
  • Pairs with the deployments subsystem: deploy a pipeline server-side, drop the script tag on a site — that's the full publish story competitors sell.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the RocketRide Chat Widget package for framework-free browser embedding.
    • Supports inline <rocketride-chat> web components and a floating launcher bubble.
    • Includes theming/branding, connection state + retry, emitted rr-message/rr-error events, and safely rendered assistant output.
  • Documentation
    • Added/expanded client docs plus dedicated widget documentation (installation, security guidance, loader/component attributes, troubleshooting, and demo usage).
  • Tests
    • Added comprehensive Jest coverage for the widget UI, loader behavior, theming, connection/protocol logic, and safe rendering/XSS rules.
  • Chores
    • Added build/test scaffolding and package configuration for distribution.

nihalnihalani and others added 2 commits July 15, 2026 00:45
…get (rocketride-org#1582)

New workspace package packaging RocketRide's existing public chat surface
as something teams can put on their own sites. One ~72 KB (21 KB gzip)
React-free bundle, two consumption modes:

- <rocketride-chat engine-url=... auth=...> web component (shadow DOM,
  style isolation both ways) rendered inline anywhere; attributes are
  observed live (title, accent, welcome, placeholder, theme=light|dark|
  auto), theming via --rr-* CSS custom properties, default accent
  #5f2167.
- One-script-tag floating bubble: data-* attributes on the script tag
  configure a launcher button that opens a panel hosting the same
  element (position, Escape-to-close, aria-expanded).

Auth uses ONLY the pipeline's public authorization key — the same
{host}/chat?auth={public_auth} mechanism the chat source node already
publishes; JSDoc and docs state explicitly that the engine API key and
private token must never appear in a page. The connection layer reuses
the browser-compatible 'rocketride' TS SDK (bundled from source, Node
'ws' path stubbed; SDK itself untouched), maps live SSE status to a
'thinking' indicator, and keeps a 6-turn history per question.

Assistant output goes through an escape-first safe renderer (allowlist:
paragraphs, line breaks, inline/fenced code, bold, http(s)-only links
with rel="noopener noreferrer") — no raw model HTML ever reaches the
DOM. A11y: role=log with aria-live=polite, labeled controls, Enter/
Shift+Enter, focus-visible outlines, reduced-motion aware.

112 jsdom tests (XSS corpus, component states, theming, loader config,
connection protocol) plus a demo page for both modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Embedding guide for both consumption modes with copy-paste snippets,
the full attribute / CSS-custom-property / events reference, and a
security section: authenticate with the pipeline's public key only —
never the engine API key or private token — plus CORS/TLS notes for
exposing an engine to browsers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.

@nihalnihalani nihalnihalani left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review from the author: inline notes on the security and design decisions most worth a reviewer's attention. Full evidence chain (112 jsdom tests, three independent XSS audits against the built bundle, two live-engine e2e runs on the public-key-only path, browser visual check) is in the PR description.

*/

/** Escapes the five HTML-significant characters. Applied before any formatting. */
export function escapeHtml(text: string): string {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The renderer's contract is escape-first: ALL input is HTML-escaped before any formatting is applied, and formatting only ever emits an allowlist (p/br/pre/code/strong/a with http(s)-only hrefs, rel="noopener noreferrer"). No external markdown dependency, no raw innerHTML of model output anywhere.

The recursive token-restore at line ~114 is the fix for a genuine major our internal devil's advocate caught: the original single-pass String.replace restore could mis-restore when one stashed fragment contained another token sentinel (inline code inside a markdown link label). Three separate audits ran attack corpora against the built bundle — the implementation suite, the DA's 35+ fresh fixtures (including attribute breakouts and CSS-injection attempts through the theming attributes), and the independent verifier's 5 DOM-parsed payloads.

// The SDK owns reconnection: linear backoff, retries forever.
persist: true,
// Never fall back to ambient env credentials (e.g. ROCKETRIDE_APIKEY).
env: {},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env: {} is deliberate and load-bearing: it prevents the SDK from picking up any ambient ROCKETRIDE_APIKEY fallback, so the widget can only ever authenticate with the value explicitly passed as auth — which the docs, JSDoc, and demo all define as the pipeline's public key (the same credential the chat node's published /chat?auth={public_auth} link uses). Verified live twice: on the published ghcr engine image and on an engine built from develop, the built bundle connected, chatted, and received answers using only the public pk_ key.

* Turns a single `<script>` tag into a floating chat "bubble": a launcher
* button fixed to a corner of the page which opens a panel hosting the
* `<rocketride-chat>` web component. Configuration is read from the script
* tag's `data-*` attributes via `document.currentScript`:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one-script-tag mode reads its config from the script tag's own data-* attributes via document.currentScript — no global config object, no init call, matching the embed ergonomics non-developers expect from Flowise/Dify-style widgets. The loader guards double-initialization (script included twice, element already defined), renders the launcher as a real <button> with aria-expanded, and closes on Escape. Everything mounts the same <rocketride-chat> element the inline mode uses, so there is exactly one code path for the chat surface itself.

@@ -0,0 +1,66 @@
{

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging decisions worth confirming: (1) the bundle inlines the rocketride SDK from packages/client-typescript/src via esbuild alias — the SDK has no standalone npm build step in-repo, and a single-file embed must inline it regardless; the SDK itself is untouched (zero diff; its full test suite ran green as part of verification). (2) The SDK's optional Node-only ws import is aliased to a stub so esbuild doesn't drag Node built-ins into a browser bundle. (3) unpkg/jsdelivr fields point at the IIFE so the script-tag snippet works from a CDN the moment this publishes. Bundle: ~72 KB raw / ~21 KB gzip, React-free (verified by dist inspection).

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c48af89a-804a-405d-bcee-95486ff118af

📥 Commits

Reviewing files that changed from the base of the PR and between 06d8e57 and a2892ba.

📒 Files selected for processing (6)
  • docs/README-chat-widget.md
  • packages/chat-widget/src/loader.ts
  • packages/chat-widget/src/styles.ts
  • packages/chat-widget/tests/loader.test.ts
  • packages/chat-widget/tsconfig.json
  • packages/chat-widget/tsconfig.test.json
💤 Files with no reviewable changes (1)
  • packages/chat-widget/tsconfig.json

📝 Walkthrough

Walkthrough

Introduces the rocketride-chat-widget workspace package, providing an inline <rocketride-chat> web component and script-tag floating bubble. It adds SDK connection handling, safe rendering, theming, accessibility behavior, browser bundles, tests, demos, packaging, and integration documentation.

Changes

Chat widget implementation

Layer / File(s) Summary
Connection protocol and safe rendering
packages/chat-widget/src/types.ts, packages/chat-widget/src/connection.ts, packages/chat-widget/src/render.ts, packages/chat-widget/tests/*
Defines widget contracts, persistent SDK connections, history replay, status updates, answer extraction, and allowlisted HTML rendering with protocol and security tests.
Inline chat component and presentation
packages/chat-widget/src/component.ts, packages/chat-widget/src/styles.ts, packages/chat-widget/tests/component.test.ts, packages/chat-widget/tests/theming.test.ts
Adds the shadow-DOM custom element with message flow, connection states, keyboard handling, accessibility, autoscroll, theming, retry behavior, and public methods.
Script loader and bubble integration
packages/chat-widget/src/loader.ts, packages/chat-widget/src/entry-iife.ts, packages/chat-widget/src/index.ts, packages/chat-widget/demo/index.html, packages/chat-widget/tests/loader.test.ts
Adds script configuration parsing, floating bubble mounting, focus and Escape handling, idempotent initialization, module registration, and a live demo.
Package delivery and documentation
packages/chat-widget/package.json, packages/chat-widget/esbuild.js, packages/chat-widget/scripts/tasks.js, packages/chat-widget/tsconfig*.json, docs/README-chat-widget.md, docs/README-clients.md, pnpm-workspace.yaml
Adds ESM/IIFE builds, declaration generation, Jest setup, workspace tasks, package metadata, workspace registration, and integration/API/security documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested labels: module:ui

Suggested reviewers: jmaionchi, stepmikhaylov, rod-christensen

Sequence Diagram(s)

sequenceDiagram
  participant HostPage
  participant ChatWidget
  participant WidgetConnection
  participant RocketRideEngine
  HostPage->>ChatWidget: configure web component or script tag
  ChatWidget->>WidgetConnection: connect with engine URL and public auth
  WidgetConnection->>RocketRideEngine: establish persistent chat connection
  HostPage->>ChatWidget: submit message
  ChatWidget->>WidgetConnection: send question and replay history
  WidgetConnection->>RocketRideEngine: submit pipeline question
  RocketRideEngine-->>WidgetConnection: status updates and answers
  WidgetConnection-->>ChatWidget: update transcript and state
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The widget, docs, demo, theming, security, and workspace-registration requirements are covered, but no protocol-level test against a real engine is shown. Add a real-engine protocol test that exercises connect, send, and render behavior end-to-end in addition to the jsdom and mocked-client tests.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names the new embeddable chat widget and its one-script browser embed focus.
Out of Scope Changes check ✅ Passed The changes stay focused on the new chat-widget package, docs, tests, and workspace wiring, with no unrelated feature areas introduced.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/README-chat-widget.md`:
- Line 90: Update the RocketRide description in the README to hyphenate
“open-source” when it directly modifies “developer-native AI pipeline platform,”
preserving the rest of the sentence unchanged.

In `@packages/chat-widget/esbuild.js`:
- Line 82: Update the esbuild configuration’s target option from ES2020 to
ES2022, preserving the existing build configuration around it.

In `@packages/chat-widget/src/loader.ts`:
- Around line 462-487: Update initFromScript to add a page-level deduplication
guard before mountChatBubble, in addition to the existing per-script
MOUNTED_FLAG check. Store and check the guard on the relevant page document so
separate loader script elements cannot create multiple bubbles or SDK
connections, while preserving the current null return and first-mount behavior.

In `@packages/chat-widget/src/styles.ts`:
- Around line 363-373: Update the dark-theme focus-visible selector to include
.rr-retry alongside .rr-input and .rr-send, so all three controls use
var(--_accent-text) for their outline color in dark mode.

In `@packages/chat-widget/tsconfig.json`:
- Around line 13-18: Remove the redundant noImplicitAny, noImplicitThis,
strictNullChecks, and strictPropertyInitialization entries from the TypeScript
configuration. Keep noImplicitReturns and noUnusedLocals unchanged because they
are independent of the strict setting.

In `@packages/chat-widget/tsconfig.test.json`:
- Around line 10-15: Remove the redundant baseUrl and paths entries from
tsconfig.test.json, relying on the inherited configuration from tsconfig.json.
Preserve the existing rocketride resolution through the base configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f2fe83cc-ce1c-4091-8cd1-260b9057fee7

📥 Commits

Reviewing files that changed from the base of the PR and between 377cbe1 and 06d8e57.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (28)
  • docs/README-chat-widget.md
  • docs/README-clients.md
  • packages/chat-widget/.gitignore
  • packages/chat-widget/README.md
  • packages/chat-widget/demo/index.html
  • packages/chat-widget/esbuild.js
  • packages/chat-widget/jest.config.js
  • packages/chat-widget/package.json
  • packages/chat-widget/scripts/tasks.js
  • packages/chat-widget/src/component.ts
  • packages/chat-widget/src/connection.ts
  • packages/chat-widget/src/entry-iife.ts
  • packages/chat-widget/src/index.ts
  • packages/chat-widget/src/loader.ts
  • packages/chat-widget/src/render.ts
  • packages/chat-widget/src/stubs/ws.ts
  • packages/chat-widget/src/styles.ts
  • packages/chat-widget/src/types.ts
  • packages/chat-widget/tests/component.test.ts
  • packages/chat-widget/tests/connection.test.ts
  • packages/chat-widget/tests/helpers.ts
  • packages/chat-widget/tests/loader.test.ts
  • packages/chat-widget/tests/render.test.ts
  • packages/chat-widget/tests/theming.test.ts
  • packages/chat-widget/tsconfig.json
  • packages/chat-widget/tsconfig.test.json
  • packages/chat-widget/tsconfig.types.json
  • pnpm-workspace.yaml

Comment thread docs/README-chat-widget.md Outdated
Comment thread packages/chat-widget/esbuild.js
Comment thread packages/chat-widget/src/loader.ts
Comment thread packages/chat-widget/src/styles.ts
Comment thread packages/chat-widget/tsconfig.json Outdated
Comment thread packages/chat-widget/tsconfig.test.json Outdated
…cleanups

CodeRabbit review round on rocketride-org#1586:
- initFromScript now guards at page level (existing bubble host in the
  document) — two separate script-tag includes load two module scopes,
  so the per-script flag alone could double-mount; regression test added.
- Dark-theme focus-visible override now covers .rr-retry, matching
  .rr-input/.rr-send.
- tsconfig cleanups (redundant strict-family flags; inherited
  baseUrl/paths in the test config) and a README hyphenation fix.

esbuild target stays ES2020 deliberately — the workspace convention
(client-typescript, chat-ui tsconfigs) and wider embed reach.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

builder docs Documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(widget): embeddable, brandable chat widget — one script tag from pipeline to production chat on any website

1 participant