Skip to content

feat(a2ui-playground): fullscreen preview for mobile#2557

Merged
Huxpro merged 5 commits into
lynx-family:mainfrom
Huxpro:Huxpro/a2ui-mobile-preview
May 4, 2026
Merged

feat(a2ui-playground): fullscreen preview for mobile#2557
Huxpro merged 5 commits into
lynx-family:mainfrom
Huxpro:Huxpro/a2ui-mobile-preview

Conversation

@Huxpro
Copy link
Copy Markdown
Collaborator

@Huxpro Huxpro commented May 3, 2026

Summary

Depends on #2556.

On mobile viewports, the Lynx Preview panel has severely limited height due to the stacked column layout (sidebar + code panel + preview all compete for space). This makes it impossible to actually enjoy the rendered output.

Solution: Fullscreen preview overlay

  • Add an expand button in the preview panel header to toggle fullscreen mode
  • Fullscreen overlay covers the entire viewport with z-index above everything
  • Auto-expand on mobile: When rendering starts on a viewport <=980px, the preview automatically enters fullscreen
  • Close via the close button in the header to return to the normal layout

Test plan

  • On desktop: click the expand button in preview header — verify fullscreen, click to close
  • On mobile: select a demo — verify preview auto-enters fullscreen
  • Verify simulation bar and QR section work in fullscreen
  • Verify Phone/Full toggle works in fullscreen

Summary by CodeRabbit

New Features

  • Added preview mode selector to switch between phone and full-width layouts.
  • Introduced independent fullscreen toggle for expanded preview viewing.
  • Added live component stack display showing components rendered over time.
  • Preview panel now intelligently initializes based on viewport size (phone layout for narrower screens).

Style

  • Updated dark theme styling implementation.
  • Enhanced preview control UI with improved buttons and responsive header layout.
  • Refined phone preview frame styling.

Copilot AI review requested due to automatic review settings May 3, 2026 23:42
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 3, 2026

⚠️ No Changeset found

Latest commit: 4748494

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 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9606e48a-dc32-47cc-ac2f-f78434caae69

📥 Commits

Reviewing files that changed from the base of the PR and between d5553e7 and 4748494.

📒 Files selected for processing (3)
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/styles.css
✅ Files skipped from review due to trivial changes (2)
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx

📝 Walkthrough

Walkthrough

The PR adds preview mode selection (phone/full) with fullscreen expand capability to DemosPage, introduces a live component stack that displays components as they render with timed progression, and updates supporting styles and utilities. A new componentsByMessage helper extracts component names per message.

Changes

Preview Mode & Live Component Stack

Layer / File(s) Summary
Helpers & State
packages/genui/a2ui-playground/src/demos.ts, packages/genui/a2ui-playground/src/pages/DemosPage.tsx (lines 100–105)
Added componentsByMessage(messages) to extract component names per message; introduced previewMode, fullscreen, liveComponents state, and liveTimersRef ref.
Live Rendering Logic
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (lines 162–183)
doRender builds live component stack from parsed messages, clears prior timers, and schedules incremental setLiveComponents updates based on rendering speed; forces fullscreen for mobile viewports.
Preview Container & Header
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (lines 396–433)
Preview panel applies previewPanelFullscreen class when fullscreen is enabled; header removes scenario metadata chip and adds expand/collapse button alongside Phone/Full mode controls.
Preview Body & Live Stack Display
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (lines 495–517)
Body renders MobilePreview for phone mode or iframe otherwise, with full-width styling applied when in full mode; new live component stack section displays tagged component names with animation.
Styling & Animations
packages/genui/a2ui-playground/src/styles.css
Added fullscreen overlay (.previewPanelFullscreen, .previewExpandBtn), theme toggle, preview mode controls (.previewModeSwitch, .previewModeBtn), live component stack UI (.liveComponentStack, .liveComponentTag with tagAppear animation), scenario tags, and refactored phone frame styling; dark theme now uses [data-theme="dark"] attribute selector.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • gaoachao
  • HuJean
  • PupilTong

Poem

🐰 In modes both full and phone so small,
Live components shimmer, answering the call,
With fullscreen glory and stacked-up grace,
Each render dances at measured pace.
The preview blooms—a rabbit's delight! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 accurately describes the main change: adding a fullscreen preview feature for mobile viewports in the a2ui-playground component.
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
Review rate limit: 4/8 reviews remaining, refill in 28 minutes and 28 seconds.

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a fullscreen-capable Lynx preview experience in a2ui-playground, primarily to make mobile viewports usable by expanding the preview into a viewport-covering overlay (with auto-expand on small screens).

Changes:

  • Add fullscreen overlay behavior for the preview panel (including auto-fullscreen on render when window.innerWidth <= 980).
  • Add “Phone / Full” preview mode switch, including a new full-panel iframe mode.
  • Update theming/title UI plumbing (dark theme via data-theme, theme toggle button, and HTML title).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/genui/a2ui-playground/src/styles.css Adds styling for fullscreen preview overlay, preview mode switch, and theme toggle; refactors phone frame styles.
packages/genui/a2ui-playground/src/pages/DemosPage.tsx Implements preview fullscreen toggle + mobile auto-fullscreen + “Phone/Full” mode rendering.
packages/genui/a2ui-playground/src/components/MobilePreview.tsx Simplifies phone preview markup to a single framed iframe.
packages/genui/a2ui-playground/src/App.tsx Introduces data-theme dark-mode toggle and updates the top-bar brand text.
packages/genui/a2ui-playground/rsbuild.config.ts Sets the generated HTML document title.
Comments suppressed due to low confidence (1)

packages/genui/a2ui-playground/src/styles.css:388

  • .previewMetaTags also appears to be unused (no references in TS/TSX under src/). Consider removing it (or restoring the associated markup) to keep styles in sync with the DOM.
.previewMetaTags {
  display: flex;
  gap: 4px;
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +400 to +407
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('full')}
title='Full panel'
>
Full
</button>
</div>
Comment on lines 375 to +399
</div>

{/* Preview Panel */}
<div className='previewPanel'>
<div
className={fullscreen
? 'previewPanel previewPanelFullscreen'
: 'previewPanel'}
>
<div className='previewPanelHeader'>
<span className='previewPanelTitle'>Lynx Preview</span>
{currentScenario
? (
<div className='previewPanelMeta'>
<div className='previewMetaTags'>
{currentScenario.tags.map((t) => <Chip key={t}>{t}</Chip>)}
</div>
</div>
)
: null}
<div className='spacer' />
<div className='previewModeSwitch'>
<button
type='button'
className={previewMode === 'phone'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('phone')}
title='Phone frame'
>
Phone
</button>
<button
type='button'
className={previewMode === 'full'
Comment on lines +349 to +353
.previewPanelMeta {
flex: 1;
min-width: 0;
}

@codecov
Copy link
Copy Markdown

codecov Bot commented May 3, 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!

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 386-415: In DemosPage.tsx update the preview control buttons to
include explicit ARIA semantics: for the Phone and Full buttons inside the
previewModeSwitch (the buttons that call setPreviewMode and read previewMode)
add an aria-pressed attribute that evaluates to true when previewMode ===
'phone' or previewMode === 'full' respectively; and for the fullscreen toggle
button with class previewExpandBtn (the one that calls setFullscreen and reads
fullscreen) add an aria-label that reflects the action (e.g., "Exit fullscreen"
when fullscreen is true, otherwise "Expand preview") so screen readers get a
meaningful description.
🪄 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: 7ba0bef6-07da-4698-9059-b932e228df73

📥 Commits

Reviewing files that changed from the base of the PR and between 4fc29ee and 94d536b.

📒 Files selected for processing (5)
  • packages/genui/a2ui-playground/rsbuild.config.ts
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/components/MobilePreview.tsx
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/styles.css

Comment on lines +386 to +415
<div className='previewModeSwitch'>
<button
type='button'
className={previewMode === 'phone'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('phone')}
title='Phone frame'
>
Phone
</button>
<button
type='button'
className={previewMode === 'full'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('full')}
title='Full panel'
>
Full
</button>
</div>
<button
type='button'
className='previewExpandBtn'
onClick={() => setFullscreen((v) => !v)}
title={fullscreen ? 'Exit fullscreen' : 'Expand preview'}
>
{fullscreen ? '\u2715' : '\u2922'}
</button>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit accessibility semantics to preview controls.

At Line 408, the fullscreen icon button should have an explicit aria-label.
At Lines 387-403, expose selected state on Phone/Full buttons via aria-pressed.

Suggested patch
           <div className='previewModeSwitch'>
             <button
               type='button'
               className={previewMode === 'phone'
                 ? 'previewModeBtn active'
                 : 'previewModeBtn'}
               onClick={() => setPreviewMode('phone')}
               title='Phone frame'
+              aria-pressed={previewMode === 'phone'}
             >
               Phone
             </button>
             <button
               type='button'
               className={previewMode === 'full'
                 ? 'previewModeBtn active'
                 : 'previewModeBtn'}
               onClick={() => setPreviewMode('full')}
               title='Full panel'
+              aria-pressed={previewMode === 'full'}
             >
               Full
             </button>
           </div>
           <button
             type='button'
             className='previewExpandBtn'
             onClick={() => setFullscreen((v) => !v)}
             title={fullscreen ? 'Exit fullscreen' : 'Expand preview'}
+            aria-label={fullscreen ? 'Exit fullscreen preview' : 'Expand preview to fullscreen'}
           >
             {fullscreen ? '\u2715' : '\u2922'}
           </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className='previewModeSwitch'>
<button
type='button'
className={previewMode === 'phone'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('phone')}
title='Phone frame'
>
Phone
</button>
<button
type='button'
className={previewMode === 'full'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('full')}
title='Full panel'
>
Full
</button>
</div>
<button
type='button'
className='previewExpandBtn'
onClick={() => setFullscreen((v) => !v)}
title={fullscreen ? 'Exit fullscreen' : 'Expand preview'}
>
{fullscreen ? '\u2715' : '\u2922'}
</button>
<div className='previewModeSwitch'>
<button
type='button'
className={previewMode === 'phone'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('phone')}
title='Phone frame'
aria-pressed={previewMode === 'phone'}
>
Phone
</button>
<button
type='button'
className={previewMode === 'full'
? 'previewModeBtn active'
: 'previewModeBtn'}
onClick={() => setPreviewMode('full')}
title='Full panel'
aria-pressed={previewMode === 'full'}
>
Full
</button>
</div>
<button
type='button'
className='previewExpandBtn'
onClick={() => setFullscreen((v) => !v)}
title={fullscreen ? 'Exit fullscreen' : 'Expand preview'}
aria-label={fullscreen ? 'Exit fullscreen preview' : 'Expand preview to fullscreen'}
>
{fullscreen ? '\u2715' : '\u2922'}
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 386 -
415, In DemosPage.tsx update the preview control buttons to include explicit
ARIA semantics: for the Phone and Full buttons inside the previewModeSwitch (the
buttons that call setPreviewMode and read previewMode) add an aria-pressed
attribute that evaluates to true when previewMode === 'phone' or previewMode ===
'full' respectively; and for the fullscreen toggle button with class
previewExpandBtn (the one that calls setFullscreen and reads fullscreen) add an
aria-label that reflects the action (e.g., "Exit fullscreen" when fullscreen is
true, otherwise "Expand preview") so screen readers get a meaningful
description.

@Huxpro Huxpro force-pushed the Huxpro/a2ui-mobile-preview branch from b27f091 to d5553e7 Compare May 3, 2026 23:56
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

133-177: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pending replay timers survive failed renders and Clear.

Timeouts are only cancelled after JSON.parse succeeds. If the user clicks Render with invalid JSON—or hits Clear mid-replay—the previous callbacks keep firing and can repopulate liveComponents against an empty/stale preview.

Suggested patch
   const doRender = useCallback(
     (json: string, scenario: Scenario | undefined) => {
       setError('');
       setRenderQrError('');
+      for (const t of liveTimersRef.current) clearTimeout(t);
+      liveTimersRef.current = [];
+      setLiveComponents([]);
       let parsed: unknown;
       try {
         parsed = JSON.parse(json);
       } catch (e) {
         setError(`Invalid JSON: ${String(e)}`);
@@
-      for (const t of liveTimersRef.current) clearTimeout(t);
-      liveTimersRef.current = [];
-      setLiveComponents([]);
       const perMsg = componentsByMessage(parsed);
       const delayMs = 800 / (speed || 1);
       let accumulated: string[] = [];
@@
   const handleClear = useCallback(() => {
+    for (const t of liveTimersRef.current) clearTimeout(t);
+    liveTimersRef.current = [];
+    setLiveComponents([]);
     setCustomJson('[]');
     setRenderUrl('');
     setLynxDevUrl('');
     setRenderQrError('');
     setError('');

Also applies to: 309-316

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 133 -
177, In doRender, existing replay timers in liveTimersRef.current must be
cleared before attempting JSON.parse and also when clearing the preview; move
the for/clearTimeout loop and liveTimersRef.current = [] plus
setLiveComponents([]) to run at the start of doRender (before parsing) and
ensure the Clear handler (the function that empties preview/state) also performs
the same cleanup; on JSON.parse failure return after this cleanup so no stale
timeouts can later repopulate liveComponents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 440-452: The keyframes name tagAppear violates the
keyframes-name-pattern; rename the `@keyframes` rule to a kebab-case name (e.g.,
tag-appear) and update any references to it (the animation property on the
selector that currently uses "tagAppear 300ms ease-out") to use the new
kebab-case identifier so Stylelint passes.

---

Outside diff comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 133-177: In doRender, existing replay timers in
liveTimersRef.current must be cleared before attempting JSON.parse and also when
clearing the preview; move the for/clearTimeout loop and liveTimersRef.current =
[] plus setLiveComponents([]) to run at the start of doRender (before parsing)
and ensure the Clear handler (the function that empties preview/state) also
performs the same cleanup; on JSON.parse failure return after this cleanup so no
stale timeouts can later repopulate liveComponents.
🪄 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: f56763bb-6b5f-4287-8518-c545bd8c307f

📥 Commits

Reviewing files that changed from the base of the PR and between 94d536b and b27f091.

📒 Files selected for processing (3)
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/styles.css

Comment on lines +440 to +452
animation: tagAppear 300ms ease-out;
}

@keyframes tagAppear {
from {
opacity: 0;
transform: translateY(4px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename tagAppear to kebab-case so Stylelint passes.

The current keyframe name violates the configured keyframes-name-pattern, so this stylesheet will fail lint as-is.

Suggested patch
 .liveComponentTag {
   padding: 2px 8px;
   border-radius: 4px;
   background: var(--geist-surface);
   border: 1px solid var(--geist-border);
   font-size: 11px;
   font-weight: 500;
   color: var(--geist-foreground);
   white-space: nowrap;
-  animation: tagAppear 300ms ease-out;
+  animation: tag-appear 300ms ease-out;
 }
 
-@keyframes tagAppear {
+@keyframes tag-appear {
   from {
     opacity: 0;
     transform: translateY(4px) scale(0.9);
   }
🧰 Tools
🪛 Stylelint (17.9.0)

[error] 443-443: Expected keyframe name "tagAppear" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 440 - 452, The
keyframes name tagAppear violates the keyframes-name-pattern; rename the
`@keyframes` rule to a kebab-case name (e.g., tag-appear) and update any
references to it (the animation property on the selector that currently uses
"tagAppear 300ms ease-out") to use the new kebab-case identifier so Stylelint
passes.

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: 1

♻️ Duplicate comments (2)
packages/genui/a2ui-playground/src/styles.css (1)

430-442: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename tagAppear to kebab-case so Stylelint passes.

The keyframe name tagAppear violates the keyframes-name-pattern rule. Rename to tag-appear for consistency with the project's linting configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/styles.css` around lines 430 - 442, The
keyframes name tagAppear violates the project's keyframes-name-pattern; rename
the keyframe to tag-appear and update any uses accordingly (e.g., the animation
property that currently references tagAppear) so `@keyframes` tag-appear { ... }
matches the animation: tag-appear 300ms ease-out; in the CSS, preserving the
same animation timing and transforms.
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

404-433: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit accessibility semantics to preview controls.

The fullscreen toggle button and Phone/Full mode buttons lack ARIA attributes for assistive technology users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 404 -
433, Add explicit ARIA semantics to the preview controls in DemosPage.tsx: for
the Phone and Full mode buttons (rendered inside the previewModeSwitch that call
setPreviewMode and read previewMode) add aria-pressed set to true when
active/false otherwise and an aria-label like "Phone preview" / "Full preview";
also wrap them in a container with role="tablist" or add aria-controls pointing
to the preview panel id if one exists. For the fullscreen toggle button (class
previewExpandBtn using setFullscreen and reading fullscreen) add an aria-pressed
attribute reflecting fullscreen state and a descriptive aria-label such as
"Enter fullscreen" / "Exit fullscreen", and include aria-controls pointing to
the preview panel if applicable so screen readers can associate the control with
the preview.
🧹 Nitpick comments (1)
packages/genui/a2ui-playground/src/pages/DemosPage.tsx (1)

168-175: 💤 Low value

Potential stale closure issue with accumulated variable.

The accumulated variable is declared with let outside the forEach callback and reassigned inside the setTimeout callback. Because timers fire asynchronously and all closures share the same accumulated binding, each timer will see the mutations from previously-fired timers—which appears to be the intended behavior here. However, this pattern can be fragile if timer ordering ever changes.

Consider using functional state updates for clarity and robustness:

♻️ Alternative using functional updates
-      let accumulated: string[] = [];
       perMsg.forEach((newNames, i) => {
         if (newNames.length === 0) return;
         const timer = setTimeout(() => {
-          accumulated = [...accumulated, ...newNames];
-          setLiveComponents([...accumulated]);
+          setLiveComponents((prev) => [...prev, ...newNames]);
         }, delayMs * (i + 1));
         liveTimersRef.current.push(timer);
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 168 -
175, The code uses an outer mutable variable `accumulated` captured by multiple
`setTimeout` callbacks which can lead to fragile ordering; replace that pattern
by removing `accumulated` and using a functional state update inside each
timeout: in the `perMsg.forEach` callback, call setTimeout(() =>
setLiveComponents(prev => [...prev, ...newNames]), delayMs * (i + 1)) so each
timer appends `newNames` based on the latest state (use `perMsg`, `delayMs`,
`newNames`, and `setLiveComponents` from the diff).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 162-177: The liveTimersRef timers are only cleared when doRender
runs, so on unmount pending timeouts can still call setLiveComponents; add a
React cleanup effect that on unmount iterates liveTimersRef.current and calls
clearTimeout for each to prevent stale updates. Place this useEffect (with an
empty deps array) near the component's state/hooks initialization (after the
existing state declarations) and reference liveTimersRef and setLiveComponents
so any outstanding timers are cleared when the component unmounts.

---

Duplicate comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 404-433: Add explicit ARIA semantics to the preview controls in
DemosPage.tsx: for the Phone and Full mode buttons (rendered inside the
previewModeSwitch that call setPreviewMode and read previewMode) add
aria-pressed set to true when active/false otherwise and an aria-label like
"Phone preview" / "Full preview"; also wrap them in a container with
role="tablist" or add aria-controls pointing to the preview panel id if one
exists. For the fullscreen toggle button (class previewExpandBtn using
setFullscreen and reading fullscreen) add an aria-pressed attribute reflecting
fullscreen state and a descriptive aria-label such as "Enter fullscreen" / "Exit
fullscreen", and include aria-controls pointing to the preview panel if
applicable so screen readers can associate the control with the preview.

In `@packages/genui/a2ui-playground/src/styles.css`:
- Around line 430-442: The keyframes name tagAppear violates the project's
keyframes-name-pattern; rename the keyframe to tag-appear and update any uses
accordingly (e.g., the animation property that currently references tagAppear)
so `@keyframes` tag-appear { ... } matches the animation: tag-appear 300ms
ease-out; in the CSS, preserving the same animation timing and transforms.

---

Nitpick comments:
In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx`:
- Around line 168-175: The code uses an outer mutable variable `accumulated`
captured by multiple `setTimeout` callbacks which can lead to fragile ordering;
replace that pattern by removing `accumulated` and using a functional state
update inside each timeout: in the `perMsg.forEach` callback, call setTimeout(()
=> setLiveComponents(prev => [...prev, ...newNames]), delayMs * (i + 1)) so each
timer appends `newNames` based on the latest state (use `perMsg`, `delayMs`,
`newNames`, and `setLiveComponents` from the diff).
🪄 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: f18c8268-56fc-4cf9-b959-31b4e0ab8438

📥 Commits

Reviewing files that changed from the base of the PR and between b27f091 and d5553e7.

📒 Files selected for processing (4)
  • packages/genui/a2ui-playground/src/App.tsx
  • packages/genui/a2ui-playground/src/demos.ts
  • packages/genui/a2ui-playground/src/pages/DemosPage.tsx
  • packages/genui/a2ui-playground/src/styles.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/genui/a2ui-playground/src/App.tsx

Comment on lines +162 to +177
// Live component stack: reveal component names as they would appear
// during streaming, synced with the replay speed.
for (const t of liveTimersRef.current) clearTimeout(t);
liveTimersRef.current = [];
setLiveComponents([]);
const perMsg = componentsByMessage(parsed);
const delayMs = 800 / (speed || 1);
let accumulated: string[] = [];
perMsg.forEach((newNames, i) => {
if (newNames.length === 0) return;
const timer = setTimeout(() => {
accumulated = [...accumulated, ...newNames];
setLiveComponents([...accumulated]);
}, delayMs * (i + 1));
liveTimersRef.current.push(timer);
});
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear timers on component unmount to prevent memory leaks and stale state updates.

The timers stored in liveTimersRef.current are cleared when doRender is called again, but they are not cleared when the component unmounts. This can cause setLiveComponents to be called on an unmounted component.

🛡️ Proposed fix: Add cleanup effect

Add a cleanup effect after the existing state declarations (around line 106):

// Clean up live component timers on unmount
useEffect(() => {
  return () => {
    for (const t of liveTimersRef.current) clearTimeout(t);
  };
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-playground/src/pages/DemosPage.tsx` around lines 162 -
177, The liveTimersRef timers are only cleared when doRender runs, so on unmount
pending timeouts can still call setLiveComponents; add a React cleanup effect
that on unmount iterates liveTimersRef.current and calls clearTimeout for each
to prevent stale updates. Place this useEffect (with an empty deps array) near
the component's state/hooks initialization (after the existing state
declarations) and reference liveTimersRef and setLiveComponents so any
outstanding timers are cleared when the component unmounts.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 4, 2026

Merging this PR will improve performance by 18.84%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
008-many-use-state-destroyBackground 9.5 ms 8 ms +18.84%
transform 1000 view elements 47 ms 40 ms +17.58%

Comparing Huxpro:Huxpro/a2ui-mobile-preview (4748494) with main (f4f2d13)

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.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

Web Explorer

#9340 Bundle Size — 900.03KiB (0%).

4748494(current) vs f4f2d13 main#9339(baseline)

Bundle metrics  Change 2 changes
                 Current
#9340
     Baseline
#9339
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 9 9
No change  Assets 11 11
Change  Modules 230(+0.88%) 228
No change  Duplicate Modules 11 11
Change  Duplicate Code 27.28%(-0.04%) 27.29%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9340
     Baseline
#9339
No change  JS 495.9KiB 495.9KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

React MTF Example

#899 Bundle Size — 196.68KiB (0%).

4748494(current) vs f4f2d13 main#898(baseline)

Bundle metrics  no changes
                 Current
#899
     Baseline
#898
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 174 174
No change  Duplicate Modules 66 66
No change  Duplicate Code 44.05% 44.05%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#899
     Baseline
#898
No change  IMG 111.23KiB 111.23KiB
No change  Other 85.45KiB 85.45KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

React External

#882 Bundle Size — 680.82KiB (0%).

4748494(current) vs f4f2d13 main#881(baseline)

Bundle metrics  no changes
                 Current
#882
     Baseline
#881
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#882
     Baseline
#881
No change  Other 680.82KiB 680.82KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

React Example

#7767 Bundle Size — 225.52KiB (0%).

4748494(current) vs f4f2d13 main#7766(baseline)

Bundle metrics  no changes
                 Current
#7767
     Baseline
#7766
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 180 180
No change  Duplicate Modules 69 69
No change  Duplicate Code 44.54% 44.54%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7767
     Baseline
#7766
No change  IMG 145.76KiB 145.76KiB
No change  Other 79.77KiB 79.77KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

React Example (Element Template)

#32 Bundle Size — 198.61KiB (0%).

d5553e7(current) vs 4fc29ee main#31(baseline)

Bundle metrics  Change 2 changes
                 Current
#32
     Baseline
#31
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 80(+2.56%) 78
No change  Duplicate Modules 23 23
Change  Duplicate Code 40.48%(-0.1%) 40.52%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#32
     Baseline
#31
No change  IMG 145.76KiB 145.76KiB
No change  Other 52.85KiB 52.85KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

Huxpro added 5 commits May 4, 2026 02:16
- Add expand button in preview panel header to toggle fullscreen
- Fullscreen overlay covers entire viewport with close button
- Auto-enter fullscreen on mobile (<=980px) when rendering starts
- Improves mobile UX where preview panel had limited height
Move the component chips (Card, Column, Image, etc.) from the preview
panel header into the sidebar under each scenario name, so users can
see which A2UI components each demo showcases before selecting it.
Show a live "Components" bar at the bottom of the preview panel that
reveals component names (Card, Column, Image, etc.) as they appear
during streaming simulation. Each tag animates in, synced with the
replay speed. Uses componentsByMessage() to extract per-message
component introductions from the demo JSON data.
@Huxpro Huxpro force-pushed the Huxpro/a2ui-mobile-preview branch from d5553e7 to 4748494 Compare May 4, 2026 00:16
@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 4, 2026

React Example (Element Template)

#35 Bundle Size — 198.61KiB (0%).

4748494(current) vs f4f2d13 main#34(baseline)

Bundle metrics  Change 2 changes
                 Current
#35
     Baseline
#34
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 80(+1.27%) 79
No change  Duplicate Modules 23 23
Change  Duplicate Code 40.48%(-0.05%) 40.5%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#35
     Baseline
#34
No change  IMG 145.76KiB 145.76KiB
No change  Other 52.85KiB 52.85KiB

Bundle analysis reportBranch Huxpro:Huxpro/a2ui-mobile-previe...Project dashboard


Generated by RelativeCIDocumentationReport issue

@Huxpro Huxpro merged commit 4311aed into lynx-family:main May 4, 2026
74 of 76 checks passed
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