Skip to content

feat(a2ui): publish preview payloads#2743

Merged
Sherry-hue merged 1 commit into
lynx-family:mainfrom
Sherry-hue:feat/a2ui-cloudflare-payload
May 28, 2026
Merged

feat(a2ui): publish preview payloads#2743
Sherry-hue merged 1 commit into
lynx-family:mainfrom
Sherry-hue:feat/a2ui-cloudflare-payload

Conversation

@Sherry-hue
Copy link
Copy Markdown
Collaborator

@Sherry-hue Sherry-hue commented May 28, 2026

Summary by CodeRabbit

  • New Features

    • External preview payload publishing (Supabase Storage) and a new publish endpoint.
    • Playground can reference preview messages/action-mocks via external URLs and persists/forwards preview payload URLs through chat and action flows.
    • Optional client-side payload-store mode for local/dev workflows.
  • Improvements

    • Documentation updated with setup, URL format, CORS and disablement instructions.
    • QR URL display styling refined.

Review Change Stack

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 28, 2026

⚠️ No Changeset found

Latest commit: 8b02a26

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dee0e445-70f3-4dbd-a902-0697862339a5

📥 Commits

Reviewing files that changed from the base of the PR and between 9a7c399 and 8b02a26.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/components/PreviewPanel.tsx
  • packages/genui/a2ui-playground/src/hooks/useConversation.ts
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/render.tsx
  • packages/genui/a2ui-playground/src/storage/types.ts
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/server/app/a2ui/action/stream/route.ts
  • packages/genui/server/app/a2ui/payload-publisher.ts
  • packages/genui/server/app/a2ui/payload/route.ts
  • packages/genui/server/app/a2ui/stream/route.ts
  • packages/genui/server/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/genui/a2ui-playground/examples/README.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/genui/server/app/a2ui/action/stream/route.ts
  • packages/genui/server/package.json
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/server/app/a2ui/stream/route.ts
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/hooks/useConversation.ts
  • packages/genui/server/app/a2ui/payload-publisher.ts
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/storage/types.ts

📝 Walkthrough

Walkthrough

Server uploads validated A2UI messages to Supabase Storage and returns preview URLs in SSE done events; the playground records and persists those URLs, supports URL-based render/init flows, can publish payloads from the client when enabled, and updates preview UI/QR/styling and docs.

Changes

A2UI Payload Publishing and Preview URLs

Layer / File(s) Summary
Type contracts and build configuration
packages/genui/a2ui-playground/src/storage/types.ts, packages/genui/a2ui-playground/rsbuild.config.ts, packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
PreviewPayloadUrls interface (messagesUrl + optional actionMocksUrl) added; persisted message/snapshot types updated; build config injects __A2UI_PLAYGROUND_CLIENT_PAYLOAD_STORE__.
Server-side payload publisher & route
packages/genui/server/app/a2ui/payload-publisher.ts, packages/genui/server/app/a2ui/payload/route.ts, packages/genui/server/package.json
Adds publishA2UIPayload (Supabase S3 uploads, public URL construction) and POST /a2ui/payload route with CORS, rate limiting, validation, and structured JSON responses; adds @aws-sdk/client-s3 dependency.
Stream routes publish previews
packages/genui/server/app/a2ui/stream/route.ts, packages/genui/server/app/a2ui/action/stream/route.ts
Stream and action-stream routes call publishA2UIPayload when validation.ok and include preview URLs in the final SSE done payload.
Conversation hook state management
packages/genui/a2ui-playground/src/hooks/useConversation.ts
useConversation extended to track/persist previewPayloadUrls, propagate through hot state, cloning, syncHotState, and recordTurn snapshot logic; hook returns previewPayloadUrls.
Chat page streaming and preview URL capture
packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
readA2UIResponse supports onPreviewPayload; AIChatPage keeps preview URL state/ref, clears on new generation, wires callback into streaming requests, and persists preview URLs with recorded turns.
Render init & URL generation
packages/genui/a2ui-playground/src/utils/renderUrl.ts, packages/genui/a2ui-playground/src/render.tsx
Introduces initData query payload support, RENDER_INIT_DATA_QUERY_PARAM, extends RenderInit with messagesUrl/actionMocksUrl, and updates build/parse logic to pass through external URLs when present.
PreviewPanel: URL handling and client payload-store
packages/genui/a2ui-playground/src/components/PreviewPanel.tsx
PreviewPanel accepts messagesUrl/actionMocksUrl, builds render/share URLs using them, and (when client store enabled) can POST { messages, actionMocks } to /__a2ui_payload to obtain published URLs; QR/share UI uses full URLs and truncation helper removed.
Demos page payload publishing for custom edits
packages/genui/a2ui-playground/src/pages/DemosPage.tsx
DemosPage publishes custom edited JSON to /a2ui/payload when rendering non-demo content (unless client flag set), updates preview input with returned URLs, and shows a publishing state on Render.
UI styling and docs
packages/genui/a2ui-playground/src/styles.css, packages/genui/a2ui-playground/examples/README.md
QR URL row switches to inline-flex with max-width:100%, URL text flex adjusted; examples README documents Supabase Storage payload publishing and local dev toggle.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

platform:Web, framework:React

Suggested reviewers

  • gaoachao
  • HuJean
  • PupilTong

Poem

🐇 I hopped your payloads into Supabase streams,
Validated messages now live in dreams.
From server to QR and preview iframe bright,
URLs carry stories through day and night.
A tiny rabbit cheers the publish flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "feat(a2ui): publish preview payloads" clearly and accurately summarizes the main change: implementing preview payload publishing functionality for A2UI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
packages/genui/a2ui-playground/src/hooks/useConversation.ts (1)

453-540: 💤 Low value

Consider whether snapshotPreviewPayloadUrls parameter is necessary.

The RecordTurnInput interface accepts both previewPayloadUrls (for the assistant message) and snapshotPreviewPayloadUrls (for the conversation snapshot). This allows the caller to specify different URLs for the message vs. the snapshot, but the actual downstream usage in AIChatPage (line 1237-1244 context) only passes previewPayloadUrls, never snapshotPreviewPayloadUrls.

Unless there's a planned use case for independently controlling snapshot URLs, consider simplifying by removing snapshotPreviewPayloadUrls and using previewPayloadUrls for both the message and the snapshot (lines 475-477 would become input.previewPayloadUrls ?? null).

🤖 Prompt for 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.

In `@packages/genui/a2ui-playground/src/hooks/useConversation.ts` around lines 453
- 540, RecordTurnInput's separate snapshotPreviewPayloadUrls parameter appears
unused; remove snapshotPreviewPayloadUrls from the RecordTurnInput type and
callers and unify logic in recordTurn (function recordTurn in
useConversation.ts) to set nextPreviewPayloadUrls = input.previewPayloadUrls ??
null (replace the current input.snapshotPreviewPayloadUrls ??
input.previewPayloadUrls ?? null), then update any call sites (e.g., where
recordTurn is invoked from AIChatPage) to stop passing
snapshotPreviewPayloadUrls and only provide previewPayloadUrls so message and
snapshot share the same URLs.
packages/genui/a2ui-playground/src/pages/AIChatPage.tsx (1)

63-66: ⚡ Quick win

Reuse the shared PreviewPayloadUrls type instead of redefining it locally.

This duplicates a persisted contract and can drift over time; import the existing type from src/storage/types.ts.

♻️ Proposed refactor
 import { useConversation } from '../hooks/useConversation.js';
 import type { ModelChatMessage } from '../hooks/useConversation.js';
+import type { PreviewPayloadUrls } from '../storage/types.js';
 import { useResizablePanels } from '../hooks/useResizablePanels.js';
@@
-interface PreviewPayloadUrls {
-  messagesUrl: string;
-  actionMocksUrl?: string;
-}
-
 interface ProviderSettings {
🤖 Prompt for 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.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx` around lines 63 -
66, The local interface PreviewPayloadUrls defined in AIChatPage.tsx duplicates
the shared persisted contract; remove the local declaration and import the
existing PreviewPayloadUrls type from the shared module (src/storage/types.ts),
then update any usages in AIChatPage.tsx to reference the imported type (e.g.,
PreviewPayloadUrls) so the file relies on the single source of truth.
🤖 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 `@packages/genui/a2ui-playground/src/components/PreviewPanel.tsx`:
- Around line 319-328: The current guard clears render state and returns when
previewSource.demoId, previewSource.messagesUrl, and useClientPayloadStore are
all falsy, which also discards inline preview messages; change the logic so that
lack of URL publishing only clears sharing/QR state (call setRenderShareUrl('')
and setLynxDevUrl('')) but does not clear/return if inline messages exist—check
previewSource.messages (or messages.length === 0) and only setRenderUrl('') and
return when there are truly no inline messages and no
demoId/useClientPayloadStore/messagesUrl; update the conditional around
setRenderUrl, setRenderShareUrl, setLynxDevUrl to preserve inline rendering
while degrading only sharing behavior.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 342-357: The async callback from publishA2UIPayloadForPreview can
apply stale UI state; to fix, add a request-sequencing guard: capture a local
sequence id (from a useRef counter like publishSeqRef) or use an AbortController
before calling publishA2UIPayloadForPreview, include the signal if supported,
then in the .then/.catch/.finally handlers check that the sequence id still
matches the latest (or that the controller was not aborted) before calling
setPreviewInput, setPreviewRenderKey, setError, or setIsPublishingPayload;
reference the existing publishA2UIPayloadForPreview call and the state setters
(setPreviewInput, setPreviewRenderKey, setError, setIsPublishingPayload) and the
inputs (committed.parsed, currentScenario) when implementing the guard.

In `@packages/genui/a2ui-playground/src/utils/renderUrl.ts`:
- Around line 43-53: The current branch that handles init.messagesUrl skips
building the canonical base64 init payload and only sets individual query params
(messagesUrl/actionMocksUrl), breaking the renderUrl contract; update the
renderUrl flow so that even when init.messagesUrl is present you still include
the base64-encoded init payload (use encodeBase64Url(JSON.stringify(...)) and
url.searchParams.set with the same init payload query param used elsewhere)
while optionally also setting messagesUrl/actionMocksUrl as metadata; ensure you
reference the same encodeBase64Url, init object and url.searchParams.set calls
so the preview iframe always receives the base64 initData.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/src/hooks/useConversation.ts`:
- Around line 453-540: RecordTurnInput's separate snapshotPreviewPayloadUrls
parameter appears unused; remove snapshotPreviewPayloadUrls from the
RecordTurnInput type and callers and unify logic in recordTurn (function
recordTurn in useConversation.ts) to set nextPreviewPayloadUrls =
input.previewPayloadUrls ?? null (replace the current
input.snapshotPreviewPayloadUrls ?? input.previewPayloadUrls ?? null), then
update any call sites (e.g., where recordTurn is invoked from AIChatPage) to
stop passing snapshotPreviewPayloadUrls and only provide previewPayloadUrls so
message and snapshot share the same URLs.

In `@packages/genui/a2ui-playground/src/pages/AIChatPage.tsx`:
- Around line 63-66: The local interface PreviewPayloadUrls defined in
AIChatPage.tsx duplicates the shared persisted contract; remove the local
declaration and import the existing PreviewPayloadUrls type from the shared
module (src/storage/types.ts), then update any usages in AIChatPage.tsx to
reference the imported type (e.g., PreviewPayloadUrls) so the file relies on the
single source of truth.
🪄 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: CHILL

Plan: Pro

Run ID: bba65d5b-16e4-4164-8abb-c8252e980513

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5c7b0 and 9a7c399.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/genui/a2ui-playground/examples/README.md
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/components/PreviewPanel.tsx
  • packages/genui/a2ui-playground/src/hooks/useConversation.ts
  • packages/genui/a2ui-playground/src/pages/AIChatPage.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/storage/types.ts
  • packages/genui/a2ui-playground/src/styles.css
  • packages/genui/a2ui-playground/src/utils/renderUrl.ts
  • packages/genui/server/app/a2ui/action/stream/route.ts
  • packages/genui/server/app/a2ui/payload-publisher.ts
  • packages/genui/server/app/a2ui/payload/route.ts
  • packages/genui/server/app/a2ui/stream/route.ts
  • packages/genui/server/package.json

Comment thread packages/genui/a2ui-playground/src/components/PreviewPanel.tsx Outdated
Comment thread packages/genui/a2ui-playground/src/pages/DemosPage.tsx
Comment thread packages/genui/a2ui-playground/src/utils/renderUrl.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 28, 2026

Merging this PR will not alter performance

✅ 81 untouched benchmarks
⏩ 26 skipped benchmarks1


Comparing Sherry-hue:feat/a2ui-cloudflare-payload (8b02a26) with main (7a5c7b0)

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Sherry-hue Sherry-hue force-pushed the feat/a2ui-cloudflare-payload branch from 9a7c399 to 8b02a26 Compare May 28, 2026 08:08
@PupilTong PupilTong self-requested a review May 28, 2026 09:33
@Sherry-hue Sherry-hue merged commit 076fc63 into lynx-family:main May 28, 2026
45 checks passed
PupilTong added a commit that referenced this pull request Jun 3, 2026
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a per-conversation "Share" action that builds and copies a
durable preview URL, shows success/failure toasts, and is disabled for
disabled or renaming items.
* Sharing reuses or persists preview payloads so links remain valid when
in-memory data is unavailable.

* **Refactor**
* Centralized preview-payload publishing into a shared utility used by
playback and sharing flows.

* **Documentation**
  * Added playground README with setup and preview-sharing instructions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

## What
Adds a **Share** action to every conversation in the GenUI playground's
Create page. Clicking it copies a durable, Supabase-backed `render.html`
link for that conversation's generated UI to the clipboard, with the
existing copy toast — the same artifact `PreviewPanel` already produces
via "Copy render URL", now available per conversation.

## Why
Conversations could be renamed and deleted, but not shared. The server
already publishes each turn's A2UI payload to Supabase Storage (#2743)
and the playground persists the resulting `previewPayloadUrls` on each
conversation snapshot, so a durable shareable link is essentially
already available — it just wasn't surfaced.

## How it works
`handleShareConversation(id)` resolves a durable `messagesUrl`:
1. Active conversation → freshest published URLs already in component
state.
2. Otherwise → `previewPayloadUrls` persisted on the conversation
snapshot (or the most recent message that has them).
3. Fallback → if no stored URL yet, publish the raw A2UI messages on
demand via `POST /a2ui/payload`.

`buildRenderUrl()` then turns the `messagesUrl` into the shareable link.

## Changes
- **New** `src/utils/publishPayload.ts` — shared client→Supabase publish
helper, extracted from `DemosPage`.
- `ConversationListPanel.tsx` — `onShare` prop + a **Share** pill
(reuses existing styling).
- `AIChatPage.tsx` — `handleShareConversation` + wiring.
- `DemosPage.tsx` — refactored to import the shared helper.

## Checklist
- [x] Tests updated (or not required). _(not required)_
- [x] Documentation updated (or not required). _(not required)_
- [x] Changeset added... (or not required). _(not required —
`a2ui-playground` is private; changesets skip private packages)_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants